위치 오디오 및 WebGL 혼합

Ilmari Heikkinen

소개

이 도움말에서는 Web Audio API의 위치 오디오 기능을 사용하여 WebGL 장면에 3D 사운드를 추가하는 방법을 설명합니다. 더욱 실감 나는 오디오를 만들기 위해 Web Audio API로 가능한 환경 효과도 소개해 드리겠습니다. 웹 오디오 API에 대한 자세한 소개는 보리스 스머스의 웹 오디오 API 시작하기 자료를 참조하세요.

위치 기반 오디오를 수행하려면 웹 오디오 API에서 AudioPannerNode를 사용합니다. AudioPannerNode는 사운드의 위치, 방향, 속도를 정의합니다. 또한 Web Audio API 오디오 컨텍스트에는 리스너의 위치, 방향, 속도를 정의할 수 있는 리스너 속성이 있습니다. 이 두 가지를 사용하면 도플러 효과와 3D 패닝으로 방향성 사운드를 만들 수 있습니다.

위 장면에서 오디오 코드가 어떻게 표시되는지 살펴보겠습니다. 이는 매우 기본적인 오디오 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);
...

모든 프레임에서 AudioPannerNode의 위치를 업데이트합니다. 아래 예에서는 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)을 곱하고 위쪽 벡터에 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;
...

카메라 방향 추적에도 업 벡터가 필요하므로 업 벡터와 변환 행렬을 곱해야 합니다.

...
// 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;
...

모두 함께

종합해보면, 오디오 컨텍스트 리스너는 카메라의 위치, 방향, 속도를 따르고 AudioPannerNode는 각 오디오 소스의 위치, 방향 및 속도를 따릅니다. 모든 프레임에서 AudioPannerNode 및 오디오 컨텍스트 리스너의 위치, 속도, 방향을 업데이트해야 합니다.

환경적 영향

위치 지정 오디오를 설정한 후에는 오디오에 환경 효과를 설정하여 3D 장면의 몰입도를 높일 수 있습니다. 대형 성당 내부를 배경으로 한다면 기본 설정에서는 실외에 서 있는 것 같은 소리가 납니다. 이러한 영상과 오디오의 차이는 몰입도를 떨어뜨리고 장면의 인상을 떨어뜨립니다.

Web Audio API에는 사운드의 환경 효과를 설정할 수 있는 ConvolverNode가 있습니다. 오디오 소스의 처리 그래프에 추가하면 설정에 맞춰 사운드를 만들 수 있습니다. ConvolverNode와 함께 사용할 수 있는 임펄스 응답 샘플을 웹에서 찾아 직접 만들 수도 있습니다. 시뮬레이션하려는 장소의 임펄스 응답을 기록해야 하므로 약간 번거로운 경험이 될 수 있지만 필요한 경우 기능이 있습니다.

ConvolverNode를 사용하여 주변 오디오를 실행하려면 오디오 처리 그래프를 다시 연결해야 합니다. 사운드를 기본 볼륨에 직접 전달하는 대신 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를 사용하여 성당부터 밀폐된 공간까지 다양한 효과와 환경을 시뮬레이션할 수 있습니다.

참조