Web Audio API๋ฅผ ์ฌ์ฉํ์ฌ WebXR์์ 3D ๊ณต๊ฐ ์ํฅ์ ๊ณ์ฐํ๊ณ ๊ตฌํํ๋ ๊ฐ๋ฐ์๋ฅผ ์ํ ์ข ํฉ ๊ฐ์ด๋. ํต์ฌ ๊ฐ๋ ๋ถํฐ ๊ณ ๊ธ ๊ธฐ์ ๊น์ง ๋ชจ๋ ๊ฒ์ ๋ค๋ฃน๋๋ค.
์กด์ฌ๊ฐ์ ์ฌ์ด๋: WebXR ๊ณต๊ฐ ์ํฅ๊ณผ 3D ์์น ๊ณ์ฐ ์ฌ์ธต ๋ถ์
๋น ๋ฅด๊ฒ ๋ฐ์ ํ๋ ๋ชฐ์ ํ ๊ธฐ์ ํ๊ฒฝ์์ ์๊ฐ์ ์ถฉ์ค๋๊ฐ ์ข ์ข ์คํฌํธ๋ผ์ดํธ๋ฅผ ๋ฐ์ต๋๋ค. ์ฐ๋ฆฌ๋ ๊ณ ํด์๋ ๋์คํ๋ ์ด, ์ฌ์ค์ ์ธ ์ ฐ์ด๋, ๋ณต์กํ 3D ๋ชจ๋ธ์ ๊ฐํํฉ๋๋ค. ํ์ง๋ง ๊ฐ์ ๋๋ ์ฆ๊ฐ ์ธ๊ณ์์ ์ง์ ํ ์กด์ฌ๊ฐ๊ณผ ํ์ค๊ฐ์ ๋ง๋๋ ๊ฐ์ฅ ๊ฐ๋ ฅํ ๋๊ตฌ ์ค ํ๋๋ ์ข ์ข ๊ฐ๊ณผ๋ฉ๋๋ค. ๋ฐ๋ก ์ค๋์ค์ ๋๋ค. ๋จ์ํ ์ค๋์ค๊ฐ ์๋๋ผ, ์ฐ๋ฆฌ๊ฐ ์ ๋ง๋ก ๊ทธ๊ณณ์ ์๋ค๊ณ ๋๋ฅผ ์ค๋์ํค๋ ์์ ํ ๊ณต๊ฐํ๋ 3์ฐจ์ ์ฌ์ด๋์ ๋๋ค.
WebXR ๊ณต๊ฐ ์ํฅ์ ์ธ๊ณ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค. ์ด๊ฒ์ '์ผ์ชฝ ๊ท์์' ์๋ฆฌ๋ฅผ ๋ฃ๋ ๊ฒ๊ณผ, ๊ณต๊ฐ์ ํน์ ์ง์ โ๋น์ ์, ๋ฒฝ ๋ค, ๋๋ ๋จธ๋ฆฌ๋ฅผ ์ค์ณ ์ง๋๊ฐ๋โ์์ ์๋ฆฌ๋ฅผ ๋ฃ๋ ๊ฒ์ ์ฐจ์ด์ ๋๋ค. ์ด ๊ธฐ์ ์ ๋ค์ ๋จ๊ณ์ ๋ชฐ์ ๊ฐ์ ์ฌ๋ ์ด์ ์ด๋ฉฐ, ์๋์ ์ธ ๊ฒฝํ์ ์น ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ์ง์ ์ ๊ทผํ ์ ์๋ ๊น์ด ์๊ณ ์ํธ์์ฉ์ ์ธ ์ธ๊ณ๋ก ๋ณํ์ํต๋๋ค.
์ด ์ข ํฉ ๊ฐ์ด๋๋ ์ ์ธ๊ณ์ ๊ฐ๋ฐ์, ์ค๋์ค ์์ง๋์ด, ๊ธฐ์ ์ ํธ๊ฐ๋ฅผ ์ํด ์ค๊ณ๋์์ต๋๋ค. ์ฐ๋ฆฌ๋ WebXR์์ 3D ์ฌ์ด๋ ํฌ์ง์ ๋์ ํต์ฌ ๊ฐ๋ ๊ณผ ๊ณ์ฐ์ ๋ช ํํ๊ฒ ์ค๋ช ํ ๊ฒ์ ๋๋ค. ๊ธฐ๋ฐ์ด ๋๋ Web Audio API๋ฅผ ํ์ํ๊ณ , ์์น ์ง์ ์ ์ํ์ ๋ถ์ํ๋ฉฐ, ์์ ์ ํ๋ก์ ํธ์ ๊ณ ํ์ง ๊ณต๊ฐ ์ํฅ์ ํตํฉํ๋ ๋ฐ ๋์์ด ๋๋ ์ค์ฉ์ ์ธ ํต์ฐฐ๋ ฅ์ ์ ๊ณตํ ๊ฒ์ ๋๋ค. ์คํ ๋ ์ค๋ฅผ ๋์ด ํ์ค์ฒ๋ผ ๋ณด์ด๋ ๊ฒ๋ฟ๋ง ์๋๋ผ ํ์ค์ฒ๋ผ ๋ค๋ฆฌ๋ ์ธ๊ณ๋ฅผ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ธ ์ค๋น๋ฅผ ํ์ญ์์ค.
๊ณต๊ฐ ์ํฅ์ด WebXR์ ํ๋๋ฅผ ๋ฐ๊พธ๋ ์ด์
๊ธฐ์ ์ ์ธ ์ธ๋ถ ์ฌํญ์ผ๋ก ๋ค์ด๊ฐ๊ธฐ ์ ์, ๊ณต๊ฐ ์ํฅ์ด ์ XR ๊ฒฝํ์ ๊ทธํ ๋ก ๊ทผ๋ณธ์ ์ธ์ง ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ฐ๋ฆฌ์ ๋๋ ์ฃผ๋ณ ํ๊ฒฝ์ ์ดํดํ๊ธฐ ์ํด ์๋ฆฌ๋ฅผ ํด์ํ๋๋ก ํ๊ณ ๋ฌ์ต๋๋ค. ์ด ์์ด์ ์ธ ์์คํ ์ ์์ผ ๋ฐ์ ์๋ ๊ฒ๋ค์ ๋ํด์๋ ์ฐ๋ฆฌ ์ฃผ๋ณ์ ๋ํ ์ ๋ณด์ ๋์์๋ ํ๋ฆ์ ์ ๊ณตํฉ๋๋ค. ๊ฐ์ ํ๊ฒฝ์์ ์ด๋ฅผ ๋ณต์ ํจ์ผ๋ก์จ ์ฐ๋ฆฌ๋ ๋ ์ง๊ด์ ์ด๊ณ ๋ฏฟ์ ์ ์๋ ๊ฒฝํ์ ๋ง๋ญ๋๋ค.
์คํ ๋ ์ค๋ฅผ ๋์ด: ๋ชฐ์ ํ ์ฌ์ด๋์ค์ผ์ดํ๋ก์ ๋์ฝ
์์ญ ๋ ๋์ ๋์งํธ ์ค๋์ค๋ ์คํ ๋ ์ค ์ฌ์ด๋๊ฐ ์ง๋ฐฐํด ์์ต๋๋ค. ์คํ ๋ ์ค๋ ์ผ์ชฝ๊ณผ ์ค๋ฅธ์ชฝ์ ๊ฐ๊ฐ์ ๋ง๋๋ ๋ฐ ํจ๊ณผ์ ์ด์ง๋ง, ๊ทผ๋ณธ์ ์ผ๋ก๋ ๋ ๊ฐ์ ์คํผ์ปค๋ ํค๋ํฐ ์ฌ์ด์ ํผ์ณ์ง 2์ฐจ์์ ์ธ ์ฌ์ด๋ ํ๋ฉด์ ๋๋ค. ์ด๋ 3D ๊ณต๊ฐ์์ ์์์ ๋์ด, ๊น์ด ๋๋ ์ ํํ ์์น๋ฅผ ์ ํํ๊ฒ ํํํ ์ ์์ต๋๋ค.
๋ฐ๋ฉด์ ๊ณต๊ฐ ์ํฅ์ 3์ฐจ์ ํ๊ฒฝ์์ ์๋ฆฌ๊ฐ ์ด๋ป๊ฒ ํ๋ํ๋์ง์ ๋ํ ๊ณ์ฐ ๋ชจ๋ธ์ ๋๋ค. ์์์์ ๋์จ ์ํ๊ฐ ์ฒญ์ทจ์์ ๋จธ๋ฆฌ์ ๊ท์ ์ํธ ์์ฉํ์ฌ ๊ณ ๋ง์ ๋๋ฌํ๋ ๋ฐฉ์์ ์๋ฎฌ๋ ์ด์ ํฉ๋๋ค. ๊ทธ ๊ฒฐ๊ณผ ๋ชจ๋ ์ฌ์ด๋๊ฐ ๊ณต๊ฐ์์ ๋๋ ทํ ์์ ์ ๊ฐ์ง๋ฉฐ, ์ฌ์ฉ์๊ฐ ๋จธ๋ฆฌ์ ๋ชธ์ ์์ง์์ ๋ฐ๋ผ ์ฌ์ค์ ์ผ๋ก ์์ง์ด๊ณ ๋ณํํ๋ ์ฌ์ด๋์ค์ผ์ดํ๊ฐ ๋ง๋ค์ด์ง๋๋ค.
XR ์ ํ๋ฆฌ์ผ์ด์ ์์์ ์ฃผ์ ์ด์
์ ๊ตฌํ๋ ๊ณต๊ฐ ์ํฅ์ ์ํฅ์ ์ฌ๋ํ๋ฉฐ ๋ชจ๋ ์ ํ์ XR ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฑธ์ณ ํ์ฅ๋ฉ๋๋ค:
- ํฅ์๋ ํ์ค๊ฐ๊ณผ ์กด์ฌ๊ฐ: ๊ฐ์์ ์๊ฐ ๋น์ ์ ๋๋ญ๊ฐ์ง์์ ๋ ธ๋ํ๊ฑฐ๋, ํน์ ๋ณต๋ ์๋์์ ๋ฐ์๊ตญ ์๋ฆฌ๊ฐ ๋ค๊ฐ์ฌ ๋, ์ธ๊ณ๋ ๋ ๊ฒฌ๊ณ ํ๊ณ ์ค์ ์ฒ๋ผ ๋๊ปด์ง๋๋ค. ์๊ฐ์ ๋จ์์ ์ฒญ๊ฐ์ ๋จ์ ์ฌ์ด์ ์ด๋ฌํ ์ผ์น๋ '์กด์ฌ๊ฐ'โ๊ฐ์ ํ๊ฒฝ์ ์๋ค๋ ์ฌ๋ฆฌ์ ๊ฐ๊ฐโ์ ๋ง๋๋ ์ด์์ ๋๋ค.
- ์ฌ์ฉ์ ์๋ด ๋ฐ ์ธ์ ๊ฐ์ : ์ค๋์ค๋ ์ฌ์ฉ์์ ์ฃผ์๋ฅผ ๋๋ ๊ฐ๋ ฅํ๊ณ ๋น์นจ์ ์ ์ธ ๋ฐฉ๋ฒ์ด ๋ ์ ์์ต๋๋ค. ์ฃผ์ ๊ฐ์ฒด ๋ฐฉํฅ์์ ๋ค๋ ค์ค๋ ๋ฏธ๋ฌํ ์ฌ์ด๋ ํ๋ ๋ฒ์ฉ์ด๋ ํ์ดํ๋ณด๋ค ๋ ์์ฐ์ค๋ฝ๊ฒ ์ฌ์ฉ์์ ์์ ์ ์ ๋ํ ์ ์์ต๋๋ค. ๋ํ ์ํฉ ์ธ์์ ๋์ฌ ์ฌ์ฉ์๊ฐ ์ฆ๊ฐ์ ์ธ ์์ผ ๋ฐ์์ ์ผ์ด๋๋ ์ฌ๊ฑด์ ์ธ์งํ๊ฒ ํฉ๋๋ค.
- ๋ ํฐ ์ ๊ทผ์ฑ: ์๊ฐ ์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์์๊ฒ ๊ณต๊ฐ ์ํฅ์ ํ์ ์ ์ธ ๋๊ตฌ๊ฐ ๋ ์ ์์ต๋๋ค. ๊ฐ์ ๊ณต๊ฐ์ ๋ ์ด์์, ๊ฐ์ฒด์ ์์น, ๋ค๋ฅธ ์ฌ์ฉ์์ ์กด์ฌ์ ๋ํ ํ๋ถํ ์ ๋ณด ๊ณ์ธต์ ์ ๊ณตํ์ฌ ๋ณด๋ค ์์ ๊ฐ ์๋ ํ์๊ณผ ์ํธ ์์ฉ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
- ๋ ๊น์ ๊ฐ์ ์ ์ํฅ: ๊ฒ์, ํ๋ จ, ์คํ ๋ฆฌํ ๋ง์์ ์ฌ์ด๋ ๋์์ธ์ ๋ถ์๊ธฐ๋ฅผ ์ค์ ํ๋ ๋ฐ ์ค์ํฉ๋๋ค. ๋ฉ๋ฆฌ์ ๋ค๋ฆฌ๋ ๋ฉ์๋ฆฌ์น๋ ์๋ฆฌ๋ ๊ท๋ชจ๊ฐ๊ณผ ์ธ๋ก์์ ๋ง๋ค ์ ์๊ณ , ๊ฐ์์ค๋ฝ๊ณ ๊ฐ๊น์ด ์๋ฆฌ๋ ๋๋ผ์์ด๋ ์ํ์ ์ ๋ฐํ ์ ์์ต๋๋ค. ๊ณต๊ฐํ๋ ์ด ๊ฐ์ ์ ๋๊ตฌ ์์๋ฅผ ์์ฒญ๋๊ฒ ์ฆํญ์ํต๋๋ค.
ํต์ฌ ๊ตฌ์ฑ ์์: Web Audio API ์ดํดํ๊ธฐ
๋ธ๋ผ์ฐ์ ๋ด ๊ณต๊ฐ ์ํฅ์ ๋ง๋ฒ์ Web Audio API์ ์ํด ๊ฐ๋ฅํด์ก์ต๋๋ค. ์ด ๊ฐ๋ ฅํ ๊ณ ๊ธ ์๋ฐ์คํฌ๋ฆฝํธ API๋ ์ต์ ๋ธ๋ผ์ฐ์ ์ ์ง์ ๋ด์ฅ๋์ด ์์ผ๋ฉฐ ์ค๋์ค๋ฅผ ์ ์ดํ๊ณ ํฉ์ฑํ๊ธฐ ์ํ ํฌ๊ด์ ์ธ ์์คํ ์ ์ ๊ณตํฉ๋๋ค. ๋จ์ํ ์ฌ์ด๋ ํ์ผ์ ์ฌ์ํ๋ ๊ฒ ์ด์์ผ๋ก, ๋ณต์กํ ์ค๋์ค ์ฒ๋ฆฌ ๊ทธ๋ํ๋ฅผ ๋ง๋ค๊ธฐ ์ํ ๋ชจ๋์ ํ๋ ์์ํฌ์ ๋๋ค.
AudioContext: ๋น์ ์ ์ฌ์ด๋ ์ฐ์ฃผ
Web Audio API์ ๋ชจ๋ ๊ฒ์ AudioContext
์์์ ์ผ์ด๋ฉ๋๋ค. ์ด๊ฒ์ ์ ์ฒด ์ค๋์ค ์ฌ์ ์ํ ์ปจํ
์ด๋๋ ์์
๊ณต๊ฐ์ผ๋ก ์๊ฐํ ์ ์์ต๋๋ค. ์ค๋์ค ํ๋์จ์ด, ํ์ด๋ฐ, ๊ทธ๋ฆฌ๊ณ ๋ชจ๋ ์ฌ์ด๋ ๊ตฌ์ฑ ์์ ๊ฐ์ ์ฐ๊ฒฐ์ ๊ด๋ฆฌํฉ๋๋ค.
์ด๊ฒ์ ๋ง๋๋ ๊ฒ์ด ๋ชจ๋ Web Audio ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒซ ๋ฒ์งธ ๋จ๊ณ์ ๋๋ค:
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
์ค๋์ค ๋ ธ๋: ์ฌ์ด๋์ ๊ตฌ์ฑ ๋ธ๋ก
Web Audio API๋ ๋ผ์ฐํ ๊ฐ๋ ์ ๋ฐ๋ผ ์๋ํฉ๋๋ค. ๋ค์ํ ์ค๋์ค ๋ ธ๋๋ฅผ ์์ฑํ๊ณ ์ด๋ค์ ํจ๊ป ์ฐ๊ฒฐํ์ฌ ์ฒ๋ฆฌ ๊ทธ๋ํ๋ฅผ ํ์ฑํฉ๋๋ค. ์ฌ์ด๋๋ ์์ค ๋ ธ๋์์ ํ๋ฌ๋์ ํ๋ ์ด์์ ์ฒ๋ฆฌ ๋ ธ๋๋ฅผ ํต๊ณผํ ํ ์ต์ข ์ ์ผ๋ก ๋์ ๋ ธ๋(์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ์์ ์คํผ์ปค)์ ๋๋ฌํฉ๋๋ค.
- ์์ค ๋
ธ๋: ์ด ๋
ธ๋๋ค์ ์ฌ์ด๋๋ฅผ ์์ฑํฉ๋๋ค. ์ผ๋ฐ์ ์ธ ์๋ ๋ฉ๋ชจ๋ฆฌ ๋ด ์ค๋์ค ์์ฐ(๋์ฝ๋ฉ๋ MP3 ๋๋ WAV ํ์ผ ๋ฑ)์ ์ฌ์ํ๋
AudioBufferSourceNode
์ ๋๋ค. - ์ฒ๋ฆฌ ๋
ธ๋: ์ด ๋
ธ๋๋ค์ ์ฌ์ด๋๋ฅผ ์์ ํฉ๋๋ค.
GainNode
๋ ๋ณผ๋ฅจ์ ๋ณ๊ฒฝํ๊ณ ,BiquadFilterNode
๋ ์ดํ๋ผ์ด์ ์ญํ ์ ํ ์ ์์ผ๋ฉฐ, ์ฐ๋ฆฌ์ ๋ชฉ์ ์ ๊ฐ์ฅ ์ค์ํPannerNode
๋ ์ฌ์ด๋๋ฅผ 3D ๊ณต๊ฐ์ ๋ฐฐ์นํฉ๋๋ค. - ๋์ ๋
ธ๋: ์ด๊ฒ์
audioContext.destination
์ผ๋ก ํํ๋๋ ์ต์ข ์ถ๋ ฅ์ ๋๋ค. ๋ชจ๋ ํ์ฑ ์ค๋์ค ๊ทธ๋ํ๋ ์๋ฆฌ๊ฐ ๋ค๋ฆฌ๊ธฐ ์ํด ๊ฒฐ๊ตญ ์ด ๋ ธ๋์ ์ฐ๊ฒฐ๋์ด์ผ ํฉ๋๋ค.
PannerNode: ๊ณต๊ฐํ์ ์ฌ์ฅ
PannerNode
๋ Web Audio API์์ 3D ๊ณต๊ฐ ์ํฅ์ ํต์ฌ ๊ตฌ์ฑ ์์์
๋๋ค. ์์์ `PannerNode`๋ฅผ ํตํด ๋ผ์ฐํ
ํ๋ฉด, ์ฒญ์ทจ์๋ฅผ ๊ธฐ์ค์ผ๋ก 3D ๊ณต๊ฐ์์ ์ธ์๋๋ ์์น๋ฅผ ์ ์ดํ ์ ์๊ฒ ๋ฉ๋๋ค. ๋จ์ผ ์ฑ๋(๋ชจ๋
ธ) ์
๋ ฅ์ ๋ฐ์ ๊ณ์ฐ๋ ์์น์ ๋ฐ๋ผ ์ฒญ์ทจ์์ ๋ ๊ท๋ก ํด๋น ์ฌ์ด๋๊ฐ ์ด๋ป๊ฒ ๋ค๋ฆด์ง๋ฅผ ์๋ฎฌ๋ ์ด์
ํ๋ ์คํ
๋ ์ค ์ ํธ๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
PannerNode
๋ ์์น(positionX
, positionY
, positionZ
)์ ๋ฐฉํฅ(orientationX
, orientationY
, orientationZ
)์ ์ ์ดํ๋ ์์ฑ์ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์ด์ ๋ํด ์์ธํ ์ดํด๋ณผ ๊ฒ์
๋๋ค.
3D ์ฌ์ด๋์ ์ํ: ์์น์ ๋ฐฉํฅ ๊ณ์ฐํ๊ธฐ
๊ฐ์ ํ๊ฒฝ์ ์ฌ์ด๋๋ฅผ ์ ํํ๊ฒ ๋ฐฐ์นํ๋ ค๋ฉด ๊ณต์ ๋ ์ฐธ์กฐ ํ๋ ์์ด ํ์ํฉ๋๋ค. ์ฌ๊ธฐ์ ์ขํ๊ณ์ ์ฝ๊ฐ์ ๋ฒกํฐ ์ํ์ด ๋ฑ์ฅํฉ๋๋ค. ๋คํํ๋, ์ด ๊ฐ๋ ๋ค์ ๋งค์ฐ ์ง๊ด์ ์ด๋ฉฐ WebGL ๋ฐ THREE.js๋ Babylon.js์ ๊ฐ์ ์ธ๊ธฐ ์๋ ํ๋ ์์ํฌ์์ 3D ๊ทธ๋ํฝ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์๊ณผ ์๋ฒฝํ๊ฒ ์ผ์นํฉ๋๋ค.
์ขํ๊ณ ์ค์
WebXR๊ณผ Web Audio API๋ ์ค๋ฅธ์ ์ขํ๊ณ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ฌผ๋ฆฌ์ ๊ณต๊ฐ์ ์ค์์ ์ ์๋ค๊ณ ์์ํด ๋ณด์ธ์:
- X์ถ์ ์ํ์ผ๋ก ์ด์ด์ง๋๋ค (์ค๋ฅธ์ชฝ์ด ์์, ์ผ์ชฝ์ด ์์).
- Y์ถ์ ์์ง์ผ๋ก ์ด์ด์ง๋๋ค (์๊ฐ ์์, ์๋๊ฐ ์์).
- Z์ถ์ ๊น์ด๋ฅผ ๋ํ๋ ๋๋ค (๋ค๊ฐ ์์, ์์ด ์์).
์ด๊ฒ์ ์ค์ํ ๊ท์น์ ๋๋ค. ์ฒญ์ทจ์์ ๋ชจ๋ ์์์ ํฌํจํ์ฌ ์ฌ์ ๋ชจ๋ ๊ฐ์ฒด๋ ์ด ์์คํ ๋ด์์ (x, y, z) ์ขํ๋ก ์์น๊ฐ ์ ์๋ฉ๋๋ค.
์ฒญ์ทจ์: ๊ฐ์ ์ธ๊ณ ์ ๋น์ ์ ๊ท
Web Audio API๋ ์ฌ์ฉ์์ "๊ท"๊ฐ ์ด๋์ ์์นํ๊ณ ์ด๋ ๋ฐฉํฅ์ ํฅํ๊ณ ์๋์ง ์์์ผ ํฉ๋๋ค. ์ด๊ฒ์ AudioContext
์ ์๋ listener
๋ผ๋ ํน๋ณํ ๊ฐ์ฒด์ ์ํด ๊ด๋ฆฌ๋ฉ๋๋ค.
const listener = audioContext.listener;
listener
๋ 3D ๊ณต๊ฐ์์์ ์ํ๋ฅผ ์ ์ํ๋ ์ฌ๋ฌ ์์ฑ์ ๊ฐ์ง๋๋ค:
- ์์น:
listener.positionX
,listener.positionY
,listener.positionZ
. ์ด๋ค์ ์ฒญ์ทจ์์ ์์ชฝ ๊ท ์ฌ์ด์ ์ค์ฌ์ ์ (x, y, z) ์ขํ๋ฅผ ๋ํ๋ ๋๋ค. - ๋ฐฉํฅ: ์ฒญ์ทจ์๊ฐ ๋ฐ๋ผ๋ณด๋ ๋ฐฉํฅ์ "์์ชฝ" ๋ฒกํฐ์ "์์ชฝ" ๋ฒกํฐ ๋ ๊ฐ๋ก ์ ์๋ฉ๋๋ค. ์ด๋ค์
listener.forwardX/Y/Z
์listener.upX/Y/Z
์์ฑ์ผ๋ก ์ ์ด๋ฉ๋๋ค.
์ฌ์ฉ์๊ฐ ์์ Z์ถ์ ๋ฐ๋ผ ์ ๋ฉด์ ๋ฐ๋ผ๋ณด๋ ๊ฒฝ์ฐ, ๊ธฐ๋ณธ ๋ฐฉํฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์์ชฝ: (0, 0, -1)
- ์์ชฝ: (0, 1, 0)
์ค์ํ ์ ์, WebXR ์ธ์ ์์๋ ์ด ๊ฐ๋ค์ ์๋์ผ๋ก ์ค์ ํ์ง ์๋๋ค๋ ๊ฒ์ ๋๋ค. ๋ธ๋ผ์ฐ์ ๋ VR/AR ํค๋์ ์ ๋ฌผ๋ฆฌ์ ์ถ์ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋งค ํ๋ ์๋ง๋ค ์ฒญ์ทจ์์ ์์น์ ๋ฐฉํฅ์ ์๋์ผ๋ก ์ ๋ฐ์ดํธํฉ๋๋ค. ๋น์ ์ ์ญํ ์ ์์์ ๋ฐฐ์นํ๋ ๊ฒ์ ๋๋ค.
์์: PannerNode ์์น ์ง์
๊ณต๊ฐํํ๋ ค๋ ๊ฐ ์ฌ์ด๋๋ ์์ฒด PannerNode
๋ฅผ ํตํด ๋ผ์ฐํ
๋ฉ๋๋ค. ํจ๋์ ์์น๋ ์ฒญ์ทจ์์ ๋์ผํ ์๋ ์ขํ๊ณ์์ ์ค์ ๋ฉ๋๋ค.
const panner = audioContext.createPanner();
์ฌ์ด๋๋ฅผ ๋ฐฐ์นํ๋ ค๋ฉด ์์น ์์ฑ์ ๊ฐ์ ์ค์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์์ (0,0,0) ๋ฐ๋ก ์์ 5๋ฏธํฐ ๋จ์ด์ง ๊ณณ์ ์ฌ์ด๋๋ฅผ ๋ฐฐ์นํ๋ ค๋ฉด:
panner.positionX.value = 0;
panner.positionY.value = 0;
panner.positionZ.value = -5;
๊ทธ๋ฌ๋ฉด Web Audio API์ ๋ด๋ถ ์์ง์ด ํ์ํ ๊ณ์ฐ์ ์ํํฉ๋๋ค. ์ฒญ์ทจ์์ ์์น์์ ํจ๋์ ์์น๊น์ง์ ๋ฒกํฐ๋ฅผ ๊ฒฐ์ ํ๊ณ , ์ฒญ์ทจ์์ ๋ฐฉํฅ์ ๊ณ ๋ คํ์ฌ, ์ฌ์ด๋๊ฐ ํด๋น ์์น์์ ์ค๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ฒ ํ๋ ์ ์ ํ ์ค๋์ค ์ฒ๋ฆฌ(๋ณผ๋ฅจ, ์ง์ฐ, ํํฐ๋ง)๋ฅผ ๊ณ์ฐํฉ๋๋ค.
์ค์ฉ์ ์ธ ์์ : ๊ฐ์ฒด์ ์์น๋ฅผ PannerNode์ ์ฐ๊ฒฐํ๊ธฐ
๋์ ์ธ XR ์ฌ์์๋ ๊ฐ์ฒด(๋ฐ๋ผ์ ์์)๊ฐ ์์ง์
๋๋ค. ์ ํ๋ฆฌ์ผ์ด์
์ ๋ ๋ ๋ฃจํ(`requestAnimationFrame`์ ์ํด ํธ์ถ๋๋ ํจ์) ๋ด์์ ์ง์์ ์ผ๋ก PannerNode
์ ์์น๋ฅผ ์
๋ฐ์ดํธํด์ผ ํฉ๋๋ค.
THREE.js์ ๊ฐ์ 3D ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค๊ณ ์์ํด ๋ด ์๋ค. ์ฌ์ 3D ๊ฐ์ฒด๊ฐ ์๊ณ , ๊ทธ์ ๊ด๋ จ๋ ์ฌ์ด๋๊ฐ ๊ฐ์ฒด๋ฅผ ๋ฐ๋ผ๊ฐ๋๋ก ํ๊ณ ์ถ์ต๋๋ค.
// 'audioContext'์ 'panner'๊ฐ ์ด๋ฏธ ์์ฑ๋์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. // 'virtualObject'๊ฐ 3D ์ฌ์ ๊ฐ์ฒด(์: THREE.Mesh)๋ผ๊ณ ๊ฐ์ ํฉ๋๋ค. // ์ด ํจ์๋ ๋งค ํ๋ ์๋ง๋ค ํธ์ถ๋ฉ๋๋ค. function renderLoop() { // 1. ๊ฐ์ ๊ฐ์ฒด์ ์๋ ์ขํ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. // ๋๋ถ๋ถ์ 3D ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ด๋ฅผ ์ํ ๋ฉ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค. const objectWorldPosition = new THREE.Vector3(); virtualObject.getWorldPosition(objectWorldPosition); // 2. ์ ๋ฐํ ์ค์ผ์ค๋ง์ ์ํด AudioContext์์ ํ์ฌ ์๊ฐ์ ๊ฐ์ ธ์ต๋๋ค. const now = audioContext.currentTime; // 3. ํจ๋์ ์์น๋ฅผ ๊ฐ์ฒด์ ์์น์ ์ผ์นํ๋๋ก ์ ๋ฐ์ดํธํฉ๋๋ค. // ๋ถ๋๋ฌ์ด ์ ํ์ ์ํด setValueAtTime ์ฌ์ฉ์ด ๊ถ์ฅ๋ฉ๋๋ค. panner.positionX.setValueAtTime(objectWorldPosition.x, now); panner.positionY.setValueAtTime(objectWorldPosition.y, now); panner.positionZ.setValueAtTime(objectWorldPosition.z, now); // 4. ๋ค์ ํ๋ ์์ ์์ฒญํ์ฌ ๋ฃจํ๋ฅผ ๊ณ์ํฉ๋๋ค. requestAnimationFrame(renderLoop); }
๋งค ํ๋ ์๋ง๋ค ์ด ์์ ์ ์ํํจ์ผ๋ก์จ, ์ค๋์ค ์์ง์ ์ง์์ ์ผ๋ก ๊ณต๊ฐํ๋ฅผ ์ฌ๊ณ์ฐํ๊ณ , ์ฌ์ด๋๋ ์์ง์ด๋ ๊ฐ์ ๊ฐ์ฒด์ ์๋ฒฝํ๊ฒ ๊ณ ์ ๋ ๊ฒ์ฒ๋ผ ๋ณด์ผ ๊ฒ์ ๋๋ค.
์์น๋ฅผ ๋์ด: ๊ณ ๊ธ ๊ณต๊ฐํ ๊ธฐ์
๋จ์ํ ์ฒญ์ทจ์์ ์์์ ์์น๋ฅผ ์๋ ๊ฒ์ ์์์ ๋ถ๊ณผํฉ๋๋ค. ์ง์ ์ผ๋ก ์ค๋๋ ฅ ์๋ ์ค๋์ค๋ฅผ ๋ง๋ค๊ธฐ ์ํด Web Audio API๋ ์ฌ๋ฌ ๋ค๋ฅธ ์ค์ ์ธ๊ณ์ ์ํฅ ํ์์ ์๋ฎฌ๋ ์ด์ ํฉ๋๋ค.
๋จธ๋ฆฌ์ ๋ฌํจ์(HRTF): ํ์ค์ ์ธ 3D ์ค๋์ค์ ์ด์
๋น์ ์ ๋๋ ์๋ฆฌ๊ฐ ๋น์ ์ ์, ๋ค, ๋๋ ์์ ์๋์ง ์ด๋ป๊ฒ ์๊น์? ๊ทธ๊ฒ์ ์ํ๊ฐ ๋น์ ์ ๋จธ๋ฆฌ, ๋ชธํต, ๊ทธ๋ฆฌ๊ณ ์ธ์ด(๊ท๋ฐํด)์ ๋ฌผ๋ฆฌ์ ๋ชจ์์ ์ํด ๋ฏธ๋ฌํ๊ฒ ๋ณ๊ฒฝ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๋ฌํ ๋ณํ๋คโ์์ฃผ ์์ ์ง์ฐ, ๋ฐ์ฌ, ์ฃผํ์ ๊ฐ์ โ๋ ์๋ฆฌ๊ฐ ์ค๋ ๋ฐฉํฅ์ ๋ฐ๋ผ ๊ณ ์ ํฉ๋๋ค. ์ด ๋ณต์กํ ํํฐ๋ง์ ๋จธ๋ฆฌ์ ๋ฌํจ์(Head-Related Transfer Function, HRTF)๋ก ์๋ ค์ ธ ์์ต๋๋ค.
PannerNode
๋ ์ด ํจ๊ณผ๋ฅผ ์๋ฎฌ๋ ์ด์
ํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํ์ฑํํ๋ ค๋ฉด panningModel
์์ฑ์ `'HRTF'`๋ก ์ค์ ํด์ผ ํฉ๋๋ค. ์ด๊ฒ์ ํนํ ํค๋ํฐ์ ์ํ ๋ชฐ์
๊ฐ ์๊ณ ๊ณ ํ์ง์ ๊ณต๊ฐํ๋ฅผ ์ํ ํฉ๊ธ ํ์ค์
๋๋ค.
panner.panningModel = 'HRTF';
๋์์ธ `'equalpower'`๋ ์คํ ๋ ์ค ์คํผ์ปค์ ์ ํฉํ ๋ ๊ฐ๋จํ ์ข์ฐ ํจ๋์ ์ ๊ณตํ์ง๋ง HRTF์ ์์ง์ฑ๊ณผ ์ ํ๋ฐฉ ๊ตฌ๋ถ์ด ๋ถ์กฑํฉ๋๋ค. WebXR์ ๊ฒฝ์ฐ, ์์น ์ค๋์ค์ ๋ํด ๊ฑฐ์ ํญ์ HRTF๊ฐ ์ฌ๋ฐ๋ฅธ ์ ํ์ ๋๋ค.
๊ฑฐ๋ฆฌ ๊ฐ์ : ๊ฑฐ๋ฆฌ์ ๋ฐ๋ผ ์๋ฆฌ๊ฐ ์ฌ๋ผ์ง๋ ๋ฐฉ์
ํ์ค ์ธ๊ณ์์๋ ์๋ฆฌ๊ฐ ๋ฉ์ด์ง์๋ก ์กฐ์ฉํด์ง๋๋ค. PannerNode
๋ distanceModel
์์ฑ๊ณผ ์ฌ๋ฌ ๊ด๋ จ ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ์ฌ ์ด ๋์์ ๋ชจ๋ธ๋งํฉ๋๋ค.
distanceModel
: ๊ฑฐ๋ฆฌ์ ๋ฐ๋ผ ์ฌ์ด๋ ๋ณผ๋ฅจ์ ์ค์ด๋ ๋ฐ ์ฌ์ฉ๋๋ ์๊ณ ๋ฆฌ์ฆ์ ์ ์ํฉ๋๋ค. ๊ฐ์ฅ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์ ํํ ๋ชจ๋ธ์'inverse'
(์ญ์ ๊ณฑ ๋ฒ์น ๊ธฐ๋ฐ)์ด์ง๋ง, ๋ ์์ ์ ์ธ ์ ์ด๋ฅผ ์ํด'linear'
๋ฐ'exponential'
๋ชจ๋ธ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.refDistance
: ์ฌ์ด๋ ๋ณผ๋ฅจ์ด 100%๊ฐ ๋๋ ๊ธฐ์ค ๊ฑฐ๋ฆฌ(๋ฏธํฐ ๋จ์)๋ฅผ ์ค์ ํฉ๋๋ค. ์ด ๊ฑฐ๋ฆฌ ์ด์ ์๋ ๋ณผ๋ฅจ์ด ์ฆ๊ฐํ์ง ์์ต๋๋ค. ์ด ๊ฑฐ๋ฆฌ ์ดํ์๋ ์ ํํ ๋ชจ๋ธ์ ๋ฐ๋ผ ๊ฐ์ ํ๊ธฐ ์์ํฉ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ 1์ ๋๋ค.rolloffFactor
: ๋ณผ๋ฅจ์ด ์ผ๋ง๋ ๋นจ๋ฆฌ ๊ฐ์ํ๋์ง๋ฅผ ์ ์ดํฉ๋๋ค. ๊ฐ์ด ๋์์๋ก ์ฒญ์ทจ์๊ฐ ๋ฉ์ด์ง์ ๋ฐ๋ผ ์๋ฆฌ๊ฐ ๋ ๋นจ๋ฆฌ ์ฌ๋ผ์ง๋๋ค. ๊ธฐ๋ณธ๊ฐ์ 1์ ๋๋ค.maxDistance
: ์ด ๊ฑฐ๋ฆฌ๋ฅผ ๋์ด์๋ฉด ์ฌ์ด๋ ๋ณผ๋ฅจ์ด ๋ ์ด์ ๊ฐ์ ๋์ง ์๋ ๊ฑฐ๋ฆฌ์ ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ 10000์ ๋๋ค.
์ด๋ฌํ ๋งค๊ฐ๋ณ์๋ฅผ ์กฐ์ ํ์ฌ ๊ฑฐ๋ฆฌ์ ๋ฐ๋ฅธ ์ฌ์ด๋์ ๋์์ ์ ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ต๋๋ค. ๋ฉ๋ฆฌ ์๋ ์๋ ๋์ refDistance
์ ์๋งํ rolloffFactor
๋ฅผ ๊ฐ์ง ์ ์๊ณ , ์กฐ์ฉํ ์์ญ์์ ๊ฐ๊น์ด์์๋ง ๋ค๋ฆฌ๋๋ก ๋งค์ฐ ์งง์ refDistance
์ ๊ฐํ๋ฅธ rolloffFactor
๋ฅผ ๊ฐ์ง ์ ์์ต๋๋ค.
์ฌ์ด๋ ์ฝ: ๋ฐฉํฅ์ฑ ์ค๋์ค ์์ค
๋ชจ๋ ์๋ฆฌ๊ฐ ๋ชจ๋ ๋ฐฉํฅ์ผ๋ก ๋์ผํ๊ฒ ํผ์ ธ๋๊ฐ๋ ๊ฒ์ ์๋๋๋ค. ๋งํ๋ ์ฌ๋, ํ
๋ ๋น์ ๋๋ ํ์ฑ๊ธฐ๋ฅผ ์๊ฐํด ๋ณด์ธ์โ์๋ฆฌ๋ ์ ๋ฉด์์ ๊ฐ์ฅ ํฌ๊ณ ์ธก๋ฉด๊ณผ ํ๋ฉด์์๋ ๋ ์กฐ์ฉํฉ๋๋ค. PannerNode
๋ ์ฌ์ด๋ ์ฝ ๋ชจ๋ธ๋ก ์ด๋ฅผ ์๋ฎฌ๋ ์ด์
ํ ์ ์์ต๋๋ค.
์ด๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ orientationX/Y/Z
์์ฑ์ ์ฌ์ฉํ์ฌ ํจ๋์ ๋ฐฉํฅ์ ์ ์ํด์ผ ํฉ๋๋ค. ์ด๊ฒ์ ์ฌ์ด๋๊ฐ "ํฅํ๋" ๋ฐฉํฅ์ ๊ฐ๋ฆฌํค๋ ๋ฒกํฐ์
๋๋ค. ๊ทธ๋ฐ ๋ค์ ์ฝ์ ๋ชจ์์ ์ ์ํ ์ ์์ต๋๋ค:
coneInnerAngle
: ์์ค์์ ๋ป์ด ๋๊ฐ๋ ์๋ฟ์ ๊ฐ๋(0์์ 360๋). ์ด ์๋ฟ ๋ด๋ถ์์๋ ๋ณผ๋ฅจ์ด ์ต๋์ ๋๋ค(์ฝ ์ค์ ์ ์ํฅ์ ๋ฐ์ง ์์). ๊ธฐ๋ณธ๊ฐ์ 360(์ ๋ฐฉํฅ)์ ๋๋ค.coneOuterAngle
: ๋ ํฐ ๋ฐ๊นฅ์ชฝ ์๋ฟ์ ๊ฐ๋. ์์ชฝ๊ณผ ๋ฐ๊นฅ์ชฝ ์๋ฟ ์ฌ์ด์์ ๋ณผ๋ฅจ์ ์ ์ ์์ค์์coneOuterGain
์ผ๋ก ๋ถ๋๋ฝ๊ฒ ์ ํ๋ฉ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ 360์ ๋๋ค.coneOuterGain
: ์ฒญ์ทจ์๊ฐconeOuterAngle
์ธ๋ถ์ ์์ ๋ ์ฌ์ด๋์ ์ ์ฉ๋๋ ๋ณผ๋ฅจ ์น์์ ๋๋ค. ๊ฐ์ด 0์ด๋ฉด ์๋ฆฌ๊ฐ ๋์ง ์๊ณ , 0.5์ด๋ฉด ๋ณผ๋ฅจ์ด ์ ๋ฐ์ ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ 0์ ๋๋ค.
์ด๊ฒ์ ๋ฏฟ์ ์ ์์ ์ ๋๋ก ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ๊ฐ์ ํ ๋ ๋น์ ์ ์ฌ์ด๋๊ฐ ์คํผ์ปค์์ ์ฌ์ค์ ์ผ๋ก ๋์ค๊ฒ ํ๊ฑฐ๋, ์บ๋ฆญํฐ์ ๋ชฉ์๋ฆฌ๊ฐ ๊ทธ๋ค์ด ํฅํ๋ ๋ฐฉํฅ์ผ๋ก ํฌ์ฌ๋๊ฒ ํ์ฌ ์ฌ์ ๋ ๋ค๋ฅธ ๋์ ๋ฆฌ์ผ๋ฆฌ์ฆ์ ์ธต์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
WebXR๊ณผ ํตํฉ: ๋ชจ๋ ๊ฒ์ ํจ๊ป ์ฐ๊ฒฐํ๊ธฐ
์ด์ ์ฌ์ฉ์์ ๋จธ๋ฆฌ ์์ธ๋ฅผ ์ ๊ณตํ๋ WebXR Device API์ ๊ทธ ์ ๋ณด๊ฐ ํ์ํ Web Audio API์ ๋ฆฌ์ค๋ ์ฌ์ด์ ์ ๋ค์ ์ฐ๊ฒฐํด ๋ณด๊ฒ ์ต๋๋ค.
WebXR Device API์ ๋ ๋ ๋ฃจํ
WebXR ์ธ์ ์ ์์ํ๋ฉด ํน๋ณํ `requestAnimationFrame` ์ฝ๋ฐฑ์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์ด ํจ์๋ ํค๋์ ์ ๋์คํ๋ ์ด ์ฃผ์ฌ์จ๊ณผ ๋๊ธฐํ๋๋ฉฐ ๋งค ํ๋ ์๋ง๋ค `timestamp`์ `xrFrame` ๊ฐ์ฒด ๋ ๊ฐ์ ์ธ์๋ฅผ ๋ฐ์ต๋๋ค.
`xrFrame` ๊ฐ์ฒด๋ ์ฌ์ฉ์์ ์์น์ ๋ฐฉํฅ์ ๋ํ ์ง์ค์ ์์ฒ์ ๋๋ค. `xrFrame.getViewerPose(referenceSpace)`๋ฅผ ํธ์ถํ์ฌ `XRViewerPose` ๊ฐ์ฒด๋ฅผ ์ป์ ์ ์์ผ๋ฉฐ, ์ฌ๊ธฐ์๋ `AudioListener`๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ํ์ํ ์ ๋ณด๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
XR Pose์์ `AudioListener` ์ ๋ฐ์ดํธํ๊ธฐ
`XRViewerPose` ๊ฐ์ฒด์๋ `transform` ์์ฑ์ด ํฌํจ๋์ด ์์ผ๋ฉฐ, ์ด๋ `XRRigidTransform`์ ๋๋ค. ์ด ๋ณํ์ ๊ฐ์ ์ธ๊ณ์์ ์ฌ์ฉ์์ ๋จธ๋ฆฌ ์์น์ ๋ฐฉํฅ์ ๋ชจ๋ ๋ด๊ณ ์์ต๋๋ค. ๋งค ํ๋ ์๋ง๋ค ๋ฆฌ์ค๋๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
// ์ฐธ๊ณ : ์ด ์์ ๋ 'audioContext'์ 'referenceSpace'๊ฐ ์กด์ฌํ๋ค๊ณ ๊ฐ์ ํ๋ ๊ธฐ๋ณธ ์ค์ ์ ์ ์ ๋ก ํฉ๋๋ค. // ๋ช ํ์ฑ์ ์ํด ๋ฒกํฐ/์ฟผํฐ๋์ธ ์ํ์ THREE.js์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ข ์ข ์ฌ์ฉํฉ๋๋ค. // ์์ ์ํ์ผ๋ก ์ด ์์ ์ ์ํํ๋ฉด ์ฅํฉํด์ง ์ ์์ต๋๋ค. function onXRFrame(time, frame) { const session = frame.session; session.requestAnimationFrame(onXRFrame); const pose = frame.getViewerPose(referenceSpace); if (pose) { // ๋ทฐ์ด์ ํฌ์ฆ์์ ๋ณํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค. const transform = pose.transform; const position = transform.position; const orientation = transform.orientation; // ์ด๊ฒ์ ์ฟผํฐ๋์ธ์ ๋๋ค. const listener = audioContext.listener; const now = audioContext.currentTime; // 1. ๋ฆฌ์ค๋ ์์น ์ ๋ฐ์ดํธ // ์์น๋ DOMPointReadOnly (x, y, z ์์ฑ์ ๊ฐ์ง)๋ก ์ง์ ์ฌ์ฉํ ์ ์์ต๋๋ค. listener.positionX.setValueAtTime(position.x, now); listener.positionY.setValueAtTime(position.y, now); listener.positionZ.setValueAtTime(position.z, now); // 2. ๋ฆฌ์ค๋ ๋ฐฉํฅ ์ ๋ฐ์ดํธ // ๋ฐฉํฅ ์ฟผํฐ๋์ธ์์ 'forward'์ 'up' ๋ฒกํฐ๋ฅผ ํ์์์ผ์ผ ํฉ๋๋ค. // 3D ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฐ์ฅ ์ฌ์ด ๋ฐฉ๋ฒ์ ๋๋ค. // ์ ๋ฐฉ ๋ฒกํฐ(0, 0, -1)๋ฅผ ์์ฑํ๊ณ ํค๋์ ์ ๋ฐฉํฅ์ผ๋ก ํ์ ์ํต๋๋ค. const forwardVector = new THREE.Vector3(0, 0, -1); forwardVector.applyQuaternion(new THREE.Quaternion(orientation.x, orientation.y, orientation.z, orientation.w)); // ์ํฅ ๋ฒกํฐ(0, 1, 0)๋ฅผ ์์ฑํ๊ณ ๋์ผํ ๋ฐฉํฅ์ผ๋ก ํ์ ์ํต๋๋ค. const upVector = new THREE.Vector3(0, 1, 0); upVector.applyQuaternion(new THREE.Quaternion(orientation.x, orientation.y, orientation.z, orientation.w)); // ๋ฆฌ์ค๋์ ๋ฐฉํฅ ๋ฒกํฐ๋ฅผ ์ค์ ํฉ๋๋ค. listener.forwardX.setValueAtTime(forwardVector.x, now); listener.forwardY.setValueAtTime(forwardVector.y, now); listener.forwardZ.setValueAtTime(forwardVector.z, now); listener.upX.setValueAtTime(upVector.x, now); listener.upY.setValueAtTime(upVector.y, now); listener.upZ.setValueAtTime(upVector.z, now); } // ... ๋๋จธ์ง ๋ ๋๋ง ์ฝ๋ ... }
์ด ์ฝ๋ ๋ธ๋ก์ ์ฌ์ฉ์์ ์ค์ ๋จธ๋ฆฌ ์์ง์๊ณผ ๊ฐ์ ์ค๋์ค ์์ง ์ฌ์ด์ ํ์์ ์ธ ์ฐ๊ฒฐ ๊ณ ๋ฆฌ์ ๋๋ค. ์ด๊ฒ์ด ์คํ๋๋ฉด, ์ฌ์ฉ์๊ฐ ๋จธ๋ฆฌ๋ฅผ ๋๋ฆด ๋ ์ ์ฒด 3D ์ฌ์ด๋์ค์ผ์ดํ๋ ์ค์ ์ธ๊ณ์์์ฒ๋ผ ์์ ์ ์ด๊ณ ์ ํํ๊ฒ ์ ์ง๋ ๊ฒ์ ๋๋ค.
์ฑ๋ฅ ๊ณ ๋ ค ์ฌํญ ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก
ํ๋ถํ ๊ณต๊ฐ ์ํฅ ๊ฒฝํ์ ๊ตฌํํ๋ ค๋ฉด ์ํํ๊ณ ๊ณ ์ฑ๋ฅ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด์ฅํ๊ธฐ ์ํด ๋ฆฌ์์ค๋ฅผ ์ ์คํ๊ฒ ๊ด๋ฆฌํด์ผ ํฉ๋๋ค.
์ค๋์ค ์์ฐ ๊ด๋ฆฌ
์ค๋์ค๋ฅผ ๋ก๋ํ๊ณ ๋์ฝ๋ฉํ๋ ๊ฒ์ ๋ฆฌ์์ค ์ง์ฝ์ ์ผ ์ ์์ต๋๋ค. XR ๊ฒฝํ์ด ์์๋๊ธฐ ์ ์ ํญ์ ์ค๋์ค ์์ฐ์ ๋ฏธ๋ฆฌ ๋ก๋ํ๊ณ ๋์ฝ๋ฉํ์ญ์์ค. ๋ค์ด๋ก๋ ์๊ฐ๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ค์ด๋ ค๋ฉด ์์ถ๋์ง ์์ WAV ํ์ผ ๋์ Opus๋ AAC์ ๊ฐ์ ์ต์ ์์ถ ์ค๋์ค ํ์์ ์ฌ์ฉํ์ญ์์ค. `fetch` API์ `audioContext.decodeAudioData`๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒ์ด ์ด๋ฅผ ์ํ ํ์ค์ ์ธ ์ต์ ์ ๊ทผ ๋ฐฉ์์ ๋๋ค.
๊ณต๊ฐํ์ ๋น์ฉ
HRTF ๊ธฐ๋ฐ ๊ณต๊ฐํ๋ ๊ฐ๋ ฅํ์ง๋ง `PannerNode`์์ ๊ฐ์ฅ ๊ณ์ฐ ๋น์ฉ์ด ๋ง์ด ๋๋ ๋ถ๋ถ์ ๋๋ค. ์ฌ์ ๋ชจ๋ ๋จ์ผ ์ฌ์ด๋๋ฅผ ๊ณต๊ฐํํ ํ์๋ ์์ต๋๋ค. ์ค๋์ค ์ ๋ต์ ๊ฐ๋ฐํ์ญ์์ค:
- HRTF์ ํจ๊ป `PannerNode` ์ฌ์ฉ ๋์: ๊ฒ์ ํ๋ ์ด๋ ๋ชฐ์ ์ ์์น๊ฐ ์ค์ํ ํต์ฌ ์์(์: ์บ๋ฆญํฐ, ์ํธ์์ฉ ๊ฐ์ฒด, ์ค์ํ ์ฌ์ด๋ ํ).
- ๋จ์ ์คํ ๋ ์ค ๋๋ ๋ชจ๋ ธ ์ฌ์ฉ ๋์: ์ฌ์ฉ์ ์ธํฐํ์ด์ค ํผ๋๋ฐฑ, ๋ฐฐ๊ฒฝ ์์ ๋๋ ํน์ ๋ฐ์ ์ง์ ์ด ์๋ ์ฃผ๋ณ ์ฌ์ด๋ ๋ฒ ๋์ ๊ฐ์ ๋น-๋ค์ด์ ํฑ ์ฌ์ด๋. ์ด๋ค์ `PannerNode` ๋์ ๊ฐ๋จํ `GainNode`๋ฅผ ํตํด ์ฌ์ํ ์ ์์ต๋๋ค.
๋ ๋ ๋ฃจํ์์ ์ ๋ฐ์ดํธ ์ต์ ํ
์์น์ ๊ฐ์ ์ค๋์ค ๋งค๊ฐ๋ณ์์ `.value` ์์ฑ์ ์ง์ ์ค์ ํ๋ ๋์ ํญ์ `setValueAtTime()` ๋๋ ๋ค๋ฅธ ์์ฝ๋ ๋งค๊ฐ๋ณ์ ๋ณ๊ฒฝ(`linearRampToValueAtTime` ๋ฑ)์ ์ฌ์ฉํ์ญ์์ค. ์ง์ ์ค์ ํ๋ฉด ๋ค๋ฆด ์ ์๋ ํด๋ฆญ์ด๋ ํ์ด ๋ฐ์ํ ์ ์์ง๋ง, ์์ฝ๋ ๋ณ๊ฒฝ์ ๋ถ๋๋ฝ๊ณ ์ํ-์ ํํ ์ ํ์ ๋ณด์ฅํฉ๋๋ค.
๋งค์ฐ ๋ฉ๋ฆฌ ์๋ ์ฌ์ด๋์ ๊ฒฝ์ฐ ์์น ์ ๋ฐ์ดํธ๋ฅผ ์กฐ์ ํ๋ ๊ฒ์ ๊ณ ๋ คํ ์ ์์ต๋๋ค. 100๋ฏธํฐ ๋จ์ด์ง ์ฌ์ด๋๋ ์๋ง๋ ์ด๋น 90๋ฒ ์์น๋ฅผ ์ ๋ฐ์ดํธํ ํ์๊ฐ ์์ ๊ฒ์ ๋๋ค. 5๋ฒ์งธ ๋๋ 10๋ฒ์งธ ํ๋ ์๋ง๋ค ์ ๋ฐ์ดํธํ์ฌ ๋ฉ์ธ ์ค๋ ๋์์ ์๋์ CPU ์๊ฐ์ ์ ์ฝํ ์ ์์ต๋๋ค.
๊ฐ๋น์ง ์ปฌ๋ ์ ๋ฐ ๋ฆฌ์์ค ๊ด๋ฆฌ
`AudioContext`์ ๊ทธ ๋ ธ๋๋ค์ ์ฐ๊ฒฐ๋์ด ์คํ ์ค์ธ ํ ๋ธ๋ผ์ฐ์ ์ ์ํด ์๋์ผ๋ก ๊ฐ๋น์ง ์ปฌ๋ ์ ๋์ง ์์ต๋๋ค. ์ฌ์ด๋ ์ฌ์์ด ๋๋๊ฑฐ๋ ์ฌ์์ ๊ฐ์ฒด๊ฐ ์ ๊ฑฐ๋๋ฉด, ์์ค ๋ ธ๋๋ฅผ ๋ช ์์ ์ผ๋ก ์ค์ง(`source.stop()`)ํ๊ณ ์ฐ๊ฒฐ์ ๋์ด์ผ(`source.disconnect()`) ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ํ ์ ์๋ ๋ฆฌ์์ค๊ฐ ํ๋ณด๋์ด ์ฅ์๊ฐ ์คํ๋๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
WebXR ์ค๋์ค์ ๋ฏธ๋
ํ์ฌ์ Web Audio API๊ฐ ๊ฒฌ๊ณ ํ ๊ธฐ๋ฐ์ ์ ๊ณตํ์ง๋ง, ์ค์๊ฐ ์ค๋์ค์ ์ธ๊ณ๋ ๋์์์ด ๋ฐ์ ํ๊ณ ์์ต๋๋ค. ๋ฏธ๋๋ ํจ์ฌ ๋ ํฐ ํ์ค๊ฐ๊ณผ ๋ ์ฌ์ด ๊ตฌํ์ ์ฝ์ํฉ๋๋ค.
์ค์๊ฐ ํ๊ฒฝ ํจ๊ณผ: ์ํฅ๊ณผ ์ฐจํ
๋ค์ ๊ณผ์ ๋ ์ฌ์ด๋๊ฐ ํ๊ฒฝ๊ณผ ์ด๋ป๊ฒ ์ํธ ์์ฉํ๋์ง๋ฅผ ์๋ฎฌ๋ ์ด์ ํ๋ ๊ฒ์ ๋๋ค. ์ฌ๊ธฐ์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค:
- ์ํฅ(Reverberation): ๊ณต๊ฐ์์ ์๋ฆฌ์ ๋ฉ์๋ฆฌ์ ๋ฐ์ฌ๋ฅผ ์๋ฎฌ๋ ์ด์ ํฉ๋๋ค. ํฐ ๋์ฑ๋น์ ์๋ฆฌ๋ ์๊ณ ์นดํซ์ด ๊น๋ฆฐ ๋ฐฉ์ ์๋ฆฌ์ ๋ฌ๋ผ์ผ ํฉ๋๋ค. `ConvolverNode`๋ฅผ ์ฌ์ฉํ์ฌ ์ํ์ค ์๋ต์ผ๋ก ์ํฅ์ ์ ์ฉํ ์ ์์ง๋ง, ๋์ ์ธ ์ค์๊ฐ ํ๊ฒฝ ๋ชจ๋ธ๋ง์ ํ๋ฐํ ์ฐ๊ตฌ ๋ถ์ผ์ ๋๋ค.
- ์ฐจํ(Occlusion) ๋ฐ ์ฅ์ (Obstruction): ์๋ฆฌ๊ฐ ๊ณ ์ฒด ๋ฌผ์ฒด๋ฅผ ํต๊ณผํ ๋(์ฐจํ) ๋๋ ๊ทธ ์ฃผ์๋ฅผ ๋์๊ฐ ๋(์ฅ์ ) ์ด๋ป๊ฒ ์ฝํด์ง๋์ง๋ฅผ ์๋ฎฌ๋ ์ด์ ํฉ๋๋ค. ์ด๊ฒ์ ํ์ค ๊ธฐ๊ด๊ณผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ฑ์๋ค์ด ์น์์ ์ฑ๋ฅ์ ์ผ๋ก ํด๊ฒฐํ๊ธฐ ์ํด ๋ ธ๋ ฅํ๊ณ ์๋ ๋ณต์กํ ๊ณ์ฐ ๋ฌธ์ ์ ๋๋ค.
์ฑ์ฅํ๋ ์ํ๊ณ
์๋์ผ๋ก `PannerNode`๋ฅผ ๊ด๋ฆฌํ๊ณ ์์น๋ฅผ ์ ๋ฐ์ดํธํ๋ ๊ฒ์ ๋ณต์กํ ์ ์์ต๋๋ค. ๋คํํ๋ WebXR ๋๊ตฌ์ ์ํ๊ณ๊ฐ ์ฑ์ํ๊ณ ์์ต๋๋ค. THREE.js(`PositionalAudio` ํฌํผ ํฌํจ), Babylon.js์ ๊ฐ์ ์ฃผ์ 3D ํ๋ ์์ํฌ์ A-Frame๊ณผ ๊ฐ์ ์ ์ธ์ ํ๋ ์์ํฌ๋ ๊ธฐ๋ณธ Web Audio API์ ๋ฒกํฐ ์ํ์ ๋ง์ ๋ถ๋ถ์ ์ฒ๋ฆฌํ๋ ๋ ๋์ ์์ค์ ์ถ์ํ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ฌํ ๋๊ตฌ๋ฅผ ํ์ฉํ๋ฉด ๊ฐ๋ฐ ์๋๋ฅผ ํฌ๊ฒ ๋์ด๊ณ ์์ฉ๊ตฌ ์ฝ๋๋ฅผ ์ค์ผ ์ ์์ต๋๋ค.
๊ฒฐ๋ก : ์ฌ์ด๋๋ก ๋ฏฟ์ ์ ์๋ ์ธ๊ณ ๋ง๋ค๊ธฐ
๊ณต๊ฐ ์ํฅ์ WebXR์ ์ฌ์น์ค๋ฌ์ด ๊ธฐ๋ฅ์ด ์๋๋ผ, ๋ชฐ์ ์ ๊ทผ๋ณธ์ ์ธ ๊ธฐ๋ฅ์ ๋๋ค. Web Audio API์ ํ์ ์ดํดํ๊ณ ํ์ฉํจ์ผ๋ก์จ, ๋น์ ์ ์กฐ์ฉํ๊ณ ๋ฌด๋ฏธ๊ฑด์กฐํ 3D ์ฌ์ ์ ์ฌ์์ ์์ค์์ ์ฌ์ฉ์๋ฅผ ์ฌ๋ก์ก๊ณ ์ค๋ํ๋ ์ด์ ์จ ์ฌ๋ ์ธ๊ณ๋ก ๋ณํ์ํฌ ์ ์์ต๋๋ค.
์ฐ๋ฆฌ๋ 3D ์ฌ์ด๋์ ๊ธฐ๋ณธ ๊ฐ๋ ์์๋ถํฐ ์ด๋ฅผ ์คํํ๋ ๋ฐ ํ์ํ ํน์ ๊ณ์ฐ๊ณผ API ํธ์ถ์ ์ด๋ฅด๊ธฐ๊น์ง ์ฌ์ ์ ํจ๊ปํ์ต๋๋ค. `PannerNode`๊ฐ ์ด๋ป๊ฒ ์ฐ๋ฆฌ์ ๊ฐ์ ์์ ์ญํ ์ ํ๋์ง, `AudioListener`๊ฐ ์ฌ์ฉ์์ ๊ท๋ฅผ ์ด๋ป๊ฒ ๋ํํ๋์ง, ๊ทธ๋ฆฌ๊ณ WebXR Device API๊ฐ ์ด ๋์ ์ฐ๊ฒฐํ๋ ์ค์ํ ์ถ์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์ ๊ณตํ๋์ง๋ฅผ ๋ณด์์ต๋๋ค. ์ด๋ฌํ ๋๊ตฌ๋ฅผ ๋ง์คํฐํ๊ณ ์ฑ๋ฅ๊ณผ ๋์์ธ์ ๋ํ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ ์ฉํจ์ผ๋ก์จ, ๋น์ ์ ์ฐจ์ธ๋ ๋ชฐ์ ํ ์น ๊ฒฝํโ๋จ์ํ ๋ณด์ด๋ ๊ฒ์ด ์๋๋ผ ์ง์ ์ผ๋ก ๋ค๋ฆฌ๋ ๊ฒฝํโ์ ๊ตฌ์ถํ ์ค๋น๊ฐ ๋์์ต๋๋ค.