混合位置音訊與 WebGL

Ilmari Heikkinen

簡介

本文將說明如何使用 Web Audio API 中的定位音訊功能,在 WebGL 場景中加入 3D 音效。為了讓音訊更逼真,我也會介紹 Web Audio API 可使用的環境效果。如要進一步瞭解 Web Audio API,請參閱 Boris Smus 撰寫的「Getting started with 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) 和向上向量的 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;
...

如要設定音效的聲音圓錐,請設定 Panner 節點的適當屬性。圓錐角度以度為單位,範圍為 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 模擬各種效果和環境,例如大教堂和密閉房間。

參考資料