소개
이 도움말에서는 Web Audio API의 위치 오디오 기능을 사용하여 WebGL 장면에 3D 사운드를 추가하는 방법을 설명합니다. 오디오의 생생한 느낌을 살리기 위해 Web Audio API로 가능한 환경적 효과도 소개해 드리겠습니다. Web Audio API에 대해 자세히 알아보려면 보리스 스무스의 Web Audio API 시작하기 도움말을 참고하세요.
위치 오디오를 실행하려면 Web Audio API에서 AudioPannerNode를 사용합니다. AudioPannerNode는 소리의 위치, 방향, 속도를 정의합니다. 또한 Web Audio API 오디오 컨텍스트에는 리스너의 위치, 방향, 속도를 정의할 수 있는 리스너 속성이 있습니다. 이 두 가지를 사용하면 도플러 효과와 3D 패닝으로 방향성 사운드를 만들 수 있습니다.
위 장면의 오디오 코드가 어떻게 표시되는지 살펴보겠습니다. 매우 기본적인 Audio API 코드입니다. 여러 Audio API 노드를 만들고 서로 연결합니다. 오디오 노드는 개별 사운드, 볼륨 컨트롤러, 이펙트 노드, 분석기 등에 해당합니다. 이 그래프를 빌드한 후에는 오디오 컨텍스트 대상에 연결하여 소리를 들을 수 있도록 해야 합니다.
// Detect if the audio context is supported.
window.AudioContext = (
window.AudioContext ||
window.webkitAudioContext ||
null
);
if (!AudioContext) {
throw new Error("AudioContext not supported!");
}
// Create a new audio context.
var ctx = new AudioContext();
// Create a AudioGainNode to control the main volume.
var mainVolume = ctx.createGain();
// Connect the main volume node to the context destination.
mainVolume.connect(ctx.destination);
// Create an object with a sound source and a volume control.
var sound = {};
sound.source = ctx.createBufferSource();
sound.volume = ctx.createGain();
// Connect the sound source to the volume control.
sound.source.connect(sound.volume);
// Hook up the sound volume control to the main volume.
sound.volume.connect(mainVolume);
// Make the sound source loop.
sound.source.loop = true;
// Load a sound file using an ArrayBuffer XMLHttpRequest.
var request = new XMLHttpRequest();
request.open("GET", soundFileName, true);
request.responseType = "arraybuffer";
request.onload = function(e) {
// Create a buffer from the response ArrayBuffer.
ctx.decodeAudioData(this.response, function onSuccess(buffer) {
sound.buffer = buffer;
// Make the sound source use the buffer and start playing it.
sound.source.buffer = sound.buffer;
sound.source.start(ctx.currentTime);
}, function onFailure() {
alert("Decoding the audio buffer failed");
});
};
request.send();
위치
위치 오디오는 오디오 소스의 위치와 청취자의 위치를 사용하여 스피커에 사운드를 믹스하는 방법을 결정합니다. 청취자의 왼쪽에 있는 오디오 소스는 왼쪽 스피커에서 더 크게 들리고 오른쪽에 있는 오디오 소스는 오른쪽 스피커에서 더 크게 들립니다.
시작하려면 오디오 소스를 만들고 AudioPannerNode에 연결합니다. 그런 다음 AudioPannerNode의 위치를 설정합니다. 이제 움직일 수 있는 3D 사운드가 생성되었습니다. 오디오 컨텍스트 리스너 위치는 기본적으로 (0,0,0)이므로 이 방식으로 사용할 경우 AudioPannerNode 위치는 카메라 위치를 기준으로 합니다. 카메라를 움직일 때마다 AudioPannerNode 위치를 업데이트해야 합니다. AudioPannerNode 위치를 월드에 상대적으로 설정하려면 오디오 컨텍스트 리스너 위치를 카메라 위치로 변경해야 합니다.
위치 추적을 설정하려면 AudioPannerNode를 만들고 기본 볼륨에 연결해야 합니다.
...
sound.panner = ctx.createPanner();
// Instead of hooking up the volume to the main volume, hook it up to the panner.
sound.volume.connect(sound.panner);
// And hook up the panner to the main volume.
sound.panner.connect(mainVolume);
...
모든 프레임에서 AudioPannerNodes의 위치를 업데이트합니다. 아래 예에서는 Three.js를 사용합니다.
...
// In the frame handler function, get the object's position.
object.position.set(newX, newY, newZ);
object.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);
// And copy the position over to the sound of the object.
sound.panner.setPosition(p.x, p.y, p.z);
...
리스너 위치를 추적하려면 오디오 컨텍스트의 리스너 위치를 카메라 위치와 일치하도록 설정합니다.
...
// Get the camera position.
camera.position.set(newX, newY, newZ);
camera.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(camera.matrixWorld);
// And copy the position over to the listener.
ctx.listener.setPosition(p.x, p.y, p.z);
...
속도
이제 리스너와 AudioPannerNode의 위치를 알았으므로 속도에 주목해 보겠습니다. 리스너 및 AudioPannerNode의 속도 속성을 변경하여 사운드에 도플러 효과를 추가할 수 있습니다. Web Audio API 예시 페이지에 멋진 도플러 효과 예시가 있습니다.
리스너와 AudioPannerNode의 속도를 얻는 가장 쉬운 방법은 프레임별 위치를 추적하는 것입니다. 리스너의 속도는 카메라의 현재 위치에서 이전 프레임의 카메라 위치를 뺀 값입니다. 마찬가지로 AudioPannerNode의 속도는 현재 위치에서 이전 위치를 뺀 값입니다.
객체의 이전 위치를 가져와 현재 위치에서 이를 빼고 결과를 마지막 프레임 이후 경과된 시간으로 나누어 속도를 추적할 수 있습니다. Three.js에서 이를 실행하는 방법은 다음과 같습니다.
...
var dt = secondsSinceLastFrame;
var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);
var px = p.x, py = p.y, pz = p.z;
object.position.set(newX, newY, newZ);
object.updateMatrixWorld();
var q = new THREE.Vector3();
q.setFromMatrixPosition(object.matrixWorld);
var dx = q.x-px, dy = q.y-py, dz = q.z-pz;
sound.panner.setPosition(q.x, q.y, q.z);
sound.panner.setVelocity(dx/dt, dy/dt, dz/dt);
...
방향
방향은 음원의 방향과 청취자가 향하는 방향입니다. 방향을 사용하면 지향성 사운드 소스를 시뮬레이션할 수 있습니다. 예를 들어 방향성 스피커를 생각해 보세요. 스피커 앞에 서면 스피커 뒤에 서 있을 때보다 소리가 커집니다. 더 중요한 것은 리스너의 어느 쪽에서 소리가 들리는지 확인하려면 리스너 방향이 필요하다는 점입니다. 돌아갔을 때 왼쪽에서 나오는 소리가 오른쪽으로 전환되어야 합니다.
AudioPannerNode의 방향 벡터를 가져오려면 소리를 방출하는 3D 객체의 모델 매트릭스 중 회전 부분을 가져와 vec3(0,0,1)에 vec3(0,0,1)을 곱하여 어느 지점이 가리키는지 확인해야 합니다. 컨텍스트 리스너 방향의 경우 카메라의 방향 벡터를 가져와야 합니다. 리스너 방향에도 up 벡터가 필요합니다. 리스너의 머리 롤 각도를 알아야 하기 때문입니다. 리스너 방향을 계산하려면 카메라 뷰 매트릭스의 회전 부분을 가져와 방향의 경우 vec3(0, 0,1)을,위 벡터의 경우 vec3(0,-1,0)을 곱합니다.
방향이 사운드에 영향을 미치도록 하려면 사운드의 원뿔도 정의해야 합니다. 음향 뿔은 내부 각도, 외부 각도, 외부 이득을 사용합니다. 소리는 내부 각도 내에서는 정상 볼륨으로 재생되고 외부 각도에 가까워질수록 게인이 외부 게인으로 점진적으로 변경됩니다. 외부 각도를 벗어나면 소리가 외부 게인으로 재생됩니다.
Three.js에서 방향을 추적하는 것은 벡터 수학과 4x4 월드 행렬의 변환 부분을 0으로 설정하는 작업이 포함되므로 약간 더 까다롭습니다. 하지만 코드가 많지는 않습니다.
...
var vec = new THREE.Vector3(0,0,1);
var m = object.matrixWorld;
// Save the translation column and zero it.
var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;
// Multiply the 0,0,1 vector by the world matrix and normalize the result.
vec.applyProjection(m);
vec.normalize();
sound.panner.setOrientation(vec.x, vec.y, vec.z);
// Restore the translation column.
m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...
카메라 방향 추적에도 up 벡터가 필요하므로 up 벡터와 변환 행렬을 곱해야 합니다.
...
// The camera's world matrix is named "matrix".
var m = camera.matrix;
var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;
// Multiply the orientation vector by the world matrix of the camera.
var vec = new THREE.Vector3(0,0,1);
vec.applyProjection(m);
vec.normalize();
// Multiply the up vector by the world matrix.
var up = new THREE.Vector3(0,-1,0);
up.applyProjection(m);
up.normalize();
// Set the orientation and the up-vector for the listener.
ctx.listener.setOrientation(vec.x, vec.y, vec.z, up.x, up.y, up.z);
m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...
사운드의 사운드 콘을 설정하려면 패너 노드의 적절한 속성을 설정합니다. 원뿔 각도는 도 단위로 표시되며 0~360도 범위입니다.
...
sound.panner.coneInnerAngle = innerAngleInDegrees;
sound.panner.coneOuterAngle = outerAngleInDegrees;
sound.panner.coneOuterGain = outerGainFactor;
...
모두 합쳐
모두 종합하면 오디오 컨텍스트 리스너는 카메라의 위치, 방향, 속도를 따르고 AudioPannerNodes는 각 오디오 소스의 위치, 방향, 속도를 따릅니다. 모든 프레임에서 AudioPannerNodes 및 오디오 컨텍스트 리스너의 위치, 속도, 방향을 업데이트해야 합니다.
환경적 영향
위치 지정 오디오를 설정한 후 오디오에 환경 효과를 설정하여 3D 장면의 몰입도를 높일 수 있습니다. 장면이 대성당 내부에서 촬영되고 있다고 가정해 보겠습니다. 기본 설정에서는 장면의 소리가 마치 야외에 있는 것처럼 들립니다. 시각적 요소와 오디오 간의 이러한 불일치는 몰입감을 떨어뜨리고 장면의 인상을 흐리게 만듭니다.
Web Audio API에는 소리의 환경 효과를 설정할 수 있는 ConvolverNode가 있습니다. 오디오 소스의 처리 그래프에 추가하면 사운드를 설정에 맞게 조정할 수 있습니다. 웹에서 ConvolverNodes와 함께 사용할 수 있는 임펄스 응답 샘플을 찾을 수 있으며 직접 만들 수도 있습니다. 시뮬레이션하려는 장소의 임펄스 응답을 기록해야 하므로 약간 번거로울 수 있지만 필요한 경우 이 기능을 사용할 수 있습니다.
ConvolverNodes를 사용하여 환경 오디오를 실행하려면 오디오 처리 그래프를 다시 배선해야 합니다. 사운드를 기본 볼륨에 직접 전달하는 대신 ConvolverNode를 통해 라우팅해야 합니다. 또한 환경 효과의 강도를 제어할 수 있으므로 ConvolverNode를 중심으로 오디오를 라우팅해야 합니다. 믹스 볼륨을 제어하려면 ConvolverNode와 일반 오디오에 GainNode가 연결되어 있어야 합니다.
사용 중인 최종 오디오 처리 그래프에는 패스 스루 믹서로 사용되는 GainNode를 통과하는 객체의 오디오가 있습니다. 믹서에서 오디오를 ConvolverNode 및 다른 GainNode에 전달합니다. 이 노드는 일반 오디오의 볼륨을 제어하는 데 사용됩니다. ConvolverNode는 자체 GainNode에 연결되어 컨볼루션된 오디오 볼륨을 제어합니다. GainNode의 출력은 기본 볼륨 컨트롤러에 연결됩니다.
...
var ctx = new webkitAudioContext();
var mainVolume = ctx.createGain();
// Create a convolver to apply environmental effects to the audio.
var convolver = ctx.createConvolver();
// Create a mixer that receives sound from the panners.
var mixer = ctx.createGain();
sounds.forEach(function(sound){
sound.panner.connect(mixer);
});
// Create volume controllers for the plain audio and the convolver.
var plainGain = ctx.createGain();
var convolverGain = ctx.createGain();
// Send audio from the mixer to plainGain and the convolver node.
mixer.connect(plainGain);
mixer.connect(convolver);
// Hook up the convolver to its volume control.
convolver.connect(convolverGain);
// Send audio from the volume controls to the main volume control.
plainGain.connect(mainVolume);
convolverGain.connect(mainVolume);
// Finally, connect the main volume to the audio context's destination.
volume.connect(ctx.destination);
...
ConvolverNode가 작동하도록 하려면 임펄스 응답 샘플을 버퍼에 로드하고 ConvolverNode가 이를 사용하도록 해야 합니다. 샘플은 일반 사운드 샘플과 동일한 방식으로 로드됩니다. 다음은 이 작업을 실행하는 한 가지 방법의 예입니다.
...
loadBuffer(ctx, "impulseResponseExample.wav", function(buffer){
convolver.buffer = buffer;
convolverGain.gain.value = 0.7;
plainGain.gain.value = 0.3;
})
...
function loadBuffer(ctx, filename, callback) {
var request = new XMLHttpRequest();
request.open("GET", soundFileName, true);
request.responseType = "arraybuffer";
request.onload = function() {
// Create a buffer and keep the channels unchanged.
ctx.decodeAudioData(request.response, callback, function() {
alert("Decoding the audio buffer failed");
});
};
request.send();
}
요약
이 도움말에서는 Web Audio API를 사용하여 3D 장면에 위치 오디오를 추가하는 방법을 알아봤습니다. Web Audio API를 사용하면 오디오 소스와 리스너의 위치, 방향, 속도를 설정할 수 있습니다. 3D 장면의 물체를 추적하도록 설정하면 3D 애플리케이션에 풍부한 사운드스케이프를 만들 수 있습니다.
오디오 환경을 더욱 매력적으로 만들려면 Web Audio API의 ConvolverNode를 사용하여 환경의 일반적인 사운드를 설정할 수 있습니다. 성당에서부터 폐쇄된 방에 이르기까지 Web Audio API를 사용하여 다양한 효과와 환경을 시뮬레이션할 수 있습니다.
참조
- Web Audio API 사양
- 임펄스 응답
- Three.js
- 멋진 3D 위치 오디오 예시
- Web Audio 예에는 Web Audio API 기능을 사용하는 여러 가지 좋은 예가 있습니다.