混合位置音频和 WebGL

Ilmari Heikkinen

简介

在本文中,我将介绍如何使用 Web Audio API 中的位置音频功能,将 3D 声音添加到 WebGL 场景中。为了让音频更加可信,我还将向您介绍 Web Audio API 可能造成的环境影响。如需更全面地了解 Web Audio API,请参阅 Boris Smus 撰写的 Web Audio API 使用入门一文。

如需设置位置音频,请使用 Web Audio API 中的 AudioPannerNode。AudioPannerNode 定义了声音的位置、方向和速度。此外,Web Audio API 音频上下文还有监听器属性,可让您定义监听器的位置、方向和速度。利用这两项功能,您可以使用多普勒效果和 3D 平移创建定向声音。

我们来看一下上述场景的音频代码是什么样的。这是非常基本的 Audio API 代码。您可以创建多个音频 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),以查看其指向何处。对于上下文监听器方向,您需要获取相机的方向矢量。监听器方向也需要一个向上矢量,因为它需要知道监听器头部的滚转角。如需计算监听器方向,请获取相机视图矩阵的旋转部分,并乘以 vec3(0,0,1) 表示方向,乘以 vec3(0,-1,0) 表示向上矢量。

为了让屏幕方向对声音产生影响,您还需要定义声音的锥体。声锥分为内角、外角和外增益。声音在内角内以正常音量播放,并随着您接近外角而逐渐增益更改为外增益。若向外角度,则声音以外增益播放。

在 Three.js 中跟踪方向稍微有些复杂,因为这涉及一些向量数学和将 4x4 世界矩阵的转换部分清零。尽管如此,仍然没有太多代码行。

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

所有产品

综上所述,音频上下文监听器会跟随摄像头的位置、方向和速度,而 AudioPannerNodes 会跟随其各自音频源的位置、方向和速度。您需要在每一帧上更新 AudioPannerNodes 和音频上下文监听器的位置、速度和方向。

环境影响

设置好位置音频后,您可以为音频设置环境效果,以增强 3D 场景的沉浸感。假设您的场景位于一座大型大教堂内。在默认设置下,场景中的声音会像站在户外时一样。视听效果的这种差异会破坏沉浸感,让您的场景更加不那么惊艳。

Web Audio API 包含一个 ConvolverNode,用于设置声音的环境效果。将它添加到音频来源的处理图中,您便能调整声音。您可以在 Web 上找到可与 ConvolverNodes 搭配使用的脉冲响应示例,也可以创建自己的脉冲响应示例。这可能会有点麻烦的体验,因为您需要记录希望模拟的地点的脉冲响应,但此功能是您需要的。

使用 ConvolverNodes 处理环境音频时,需要重新布线音频处理图。您需要通过 ConvolverNode 路由声音,而不是直接将声音传递到主音量。由于您可能希望控制环境效应的强度,因此还需要围绕 ConvolverNode 传送音频。要控制混音音量,需要将 GainNodes 连接到 ConvolverNode 和普通音频中。

我要使用的最终音频处理图包含来自对象的音频,该对象通过用作直通混音器的 GainNode。我从混音器将音频传递到 ConvolverNode 和另一个 GainNode,用于控制普通音频的音量。ConvolverNode 挂接到自己的 GainNode 上,以控制卷积音频的音量。GainNodes 的输出连到主音量控制器。

...
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 模拟各种效果和环境。

参考编号