混合位置音频和 WebGL

发布于 2022-01-04 12:58:29 字数 10721 浏览 1327 评论 0

在本文中,我将讨论如何使用 的位置音频功能 Web Audio API 中 将 3D 声音添加到您的 WebGL 场景中。 为了使音频更可信,我还将向您介绍 Web Audio API 可能产生的环境效果。 要更全面地介绍 Web Audio API,请查看 Web Audio API 入门 Boris Smus 撰写的 文章。

要进行定位音频,您可以使用 Web Audio API 中的 AudioPannerNode。 AudioPannerNode 定义了声音的位置、方向和速度。 此外,Web Audio API 音频上下文有一个 listener 属性,可让您定义侦听器的位置、方向和速度。 通过这两件事,您可以创建具有多普勒效应和 3D 平移的定向声音。

这是一个带有平面音频的 3D 场景。 不是很令人兴奋。 只是一个声音播放,没有任何效果。 使用 WASD 或箭头键移动并用鼠标拖动以查看四周。

让我们看看上面场景的音频代码是什么样的。 这是非常基本的音频 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);
...

Velocity

Now that we have the positions of the listener and the AudioPannerNode, let’s turn our attention to their velocities. By changing the velocity properties of the listener and the AudioPannerNode, you can add a doppler effect to the sound. There are some nice doppler effect examples on the Web Audio API examples page.

The easiest way to get the velocities for the listener and the AudioPannerNode is to keep track of their per-frame positions. The velocity of the listener is the camera’s current position minus the camera’s position in the previous frame. Similarly, the velocity of the AudioPannerNode is its current position minus its previous position.

Tracking the velocity can be done by getting the object's previous position, subtracting it from the current position and dividing the result by the time elapsed since last frame. Here's how to do it in 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 中跟踪方向有点棘手,因为它涉及一些向量数学并将 4×4 世界矩阵的平移部分归零。 尽管如此,代码行数并不多。

...
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 遵循各自音频源的位置、方向和速度。 您需要在每一帧上更新 AudioPannerNodes 和音频上下文侦听器的位置、速度和方向。

这是一个演示,其中包含上述所有位置音频功能的协同工作:

环境影响

设置好位置音频后,您可以为音频设置环境效果,以增强 3D 场景的沉浸感。 假设您的场景设置在一座大型大教堂内。 在默认设置下,场景中的声音听起来就像您站在户外一样。 视觉和音频之间的这种差异破坏了沉浸感,使您的场景不那么令人印象深刻。

Web Audio API 有一个 ConvolverNode,可让您设置声音的环境效果。 将其添加到音频源的处理图表中,您将能够使声音适合​​设置。 您可以在网络上找到可以与 ConvolverNodes 一起使用的脉冲响应样本,也可以制作自己的。 由于您需要记录要模拟的地方的脉冲响应,因此可能会稍微麻烦一些,但如果您需要,该功能就在那里。

在下面的演示中,您可以在不同的音景之间切换。

使用 ConvolverNodes 做环境音频需要重新连接音频处理图。 不是将声音直接传递到主音量,您需要通过 ConvolverNode 将其路由。 由于您可能想要控制环境效果的强度,您还需要围绕 ConvolverNode 路由音频。 为了控制混合音量,ConvolverNode 和普通音频需要附加 GainNodes。

我使用的最终音频处理图包含来自对象的音频,这些音频通过用作直通混音器的 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 模拟各种效果和环境。

参考

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

夜无邪

暂无简介

0 文章
0 评论
19448 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文