การรวมเสียงตามตำแหน่งและ WebGL

เกริ่นนำ

ในบทความนี้ ผมจะพูดถึงวิธีใช้ฟีเจอร์เสียงตามตำแหน่งใน Web Audio API เพื่อเพิ่มเสียง 3 มิติในฉาก WebGL เพื่อให้เสียงมีความน่าเชื่อถือยิ่งขึ้น ฉันจะแนะนำให้คุณรู้จักกับผลกระทบด้านสิ่งแวดล้อมที่เป็นไปได้ด้วย Web Audio API หากต้องการดูข้อมูลเบื้องต้นที่ละเอียดยิ่งขึ้นเกี่ยวกับ Web Audio API โปรดดูบทความการเริ่มต้นใช้งาน Web Audio API โดย Boris Smus

ในการใช้เสียงตามตำแหน่ง ให้ใช้ AudioPannerNode ใน Web Audio API AudioPannerNode จะกำหนดตำแหน่ง การวางแนว และความเร็วของเสียง นอกจากนี้ บริบทเสียงของ Web Audio API ยังมีแอตทริบิวต์ Listener ที่ให้คุณกำหนดตำแหน่ง การวางแนว และความเร็วของ Listener ได้อีกด้วย ด้วย 2 อย่างนี้ คุณจะสามารถสร้างเสียงที่มีทิศทางได้ด้วยเอฟเฟกต์ Doppler และการแพนแบบ 3D

ดูว่าโค้ดเสียงสำหรับฉากด้านบนเป็นอย่างไร นี่คือโค้ด 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 เมื่อมีเสียง 3 มิติแบบเคลื่อนย้ายได้ ตำแหน่ง Listener บริบทเสียงจะอยู่ที่ (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 จะช่วยให้คุณเพิ่มเอฟเฟกต์ Doppler ให้กับเสียงได้ มีตัวอย่างเอฟเฟกต์ Doppler สวยๆ ในหน้าตัวอย่างของ 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 คุณต้องนำส่วนการหมุนของเมทริกซ์โมเดลของวัตถุ 3 มิติที่ปล่อยเสียงของวัตถุ 3 มิติมาคูณกับ vec3(0,0,1) เพื่อดูว่ามันชี้ตรงไหน คุณต้องมีเวกเตอร์การวางแนวของกล้องสำหรับการวางแนว Listener บริบท การวางแนวผู้ฟังก็ต้องมีเวกเตอร์ตั้งขึ้นด้วย เนื่องจากต้องทราบมุมม้วนศีรษะของผู้ฟัง ในการคำนวณการวางแนวของ Listener ให้หาส่วนการหมุนของเมทริกซ์มุมมองของกล้อง แล้วคูณ 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;
...

รวมทั้งหมดไว้ด้วยกัน

เมื่อนำมารวมกันแล้ว Listener บริบทเสียงจะเป็นไปตามตำแหน่ง การวางแนว และอัตราความเร็วของกล้อง และ AudioPannerNodes จะยึดตามตำแหน่ง การวางแนว และความเร็วของแหล่งที่มาของเสียงที่เกี่ยวข้อง คุณต้องอัปเดตตำแหน่ง อัตราความเร็ว และการวางแนวของ AudioPannerNodes และเครื่องมือฟังบริบทเสียงในทุกเฟรม

ผลกระทบต่อสิ่งแวดล้อม

หลังจากตั้งค่าเสียงตามตำแหน่งแล้ว คุณสามารถตั้งค่าเอฟเฟกต์สภาพแวดล้อมสำหรับเสียงเพื่อเพิ่มความสมจริงของฉาก 3 มิติ สมมติว่าฉากของคุณอยู่ภายในอาสนวิหารขนาดใหญ่ ในการตั้งค่าเริ่มต้น เสียงในฉากจะฟังเหมือนคุณยืนอยู่กลางแจ้ง ความแตกต่างนี้ระหว่างภาพกับเสียงพักสมจริง และทำให้ฉากของคุณน่าประทับใจน้อยลง

Web Audio API มี ConvolverNode ที่ช่วยให้คุณตั้งค่าเอฟเฟ็กต์ด้านสิ่งแวดล้อมสำหรับเสียง เพิ่มลงในกราฟการประมวลผลสำหรับแหล่งที่มาของเสียง แล้วคุณจะปรับแต่งเสียงให้เหมาะกับการตั้งค่าได้ คุณสามารถค้นหาตัวอย่างการตอบสนองแบบ ImpulverNodes บนเว็บซึ่งใช้งานกับ ConvolverNodes ได้ และยังสามารถสร้างตัวอย่างของคุณเองได้อีกด้วย อาจจะมีประสบการณ์ที่ยุ่งยากเล็กน้อยเนื่องจากคุณจำเป็นต้องบันทึกการตอบสนองที่โลดแล่นของสถานที่ที่คุณต้องการจำลอง แต่ความสามารถก็ยังได้หากคุณต้องการ

การใช้ ConvolverNodes เพื่อสร้างเสียงสภาพแวดล้อมจำเป็นต้องต่อสายไฟของกราฟการประมวลผลเสียงใหม่ คุณจะต้องกำหนดเส้นทางเสียงผ่าน ConvolverNode แทนการส่งเสียงไปยังระดับเสียงหลักโดยตรง และเนื่องจากคุณอาจต้องการควบคุมความแรงของผลกระทบจากสภาพแวดล้อม คุณก็ต้องกำหนดเส้นทางเสียงรอบๆ ConvolverNode ด้วย หากต้องการควบคุมระดับเสียงการมิกซ์ ConvolverNode และเสียงธรรมดา จะต้องมี GETNodes ติดมาด้วย

กราฟการประมวลผลเสียงสุดท้ายที่ฉันใช้มีเสียงจากออบเจ็กต์ที่ไหลผ่าน GETNode ซึ่งใช้เป็นตัวมิกซ์สัญญาณแบบส่งผ่าน จากมิกเซอร์ ผมจะส่งเสียงไปยัง ConvolverNode และ GETNode อีกตัวหนึ่งซึ่งใช้ควบคุมระดับเสียงของเสียงธรรมดา ConvolverNode เชื่อมโยงกับ GETNode ของตนเองเพื่อควบคุมระดับเสียงที่สอดคล้องกัน เอาต์พุตของ GETNodes จะเชื่อมต่อกับตัวควบคุมระดับเสียงหลัก

...
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 ใช้งานได้ คุณต้องโหลดตัวอย่างการตอบสนองของ Impulse ลงในบัฟเฟอร์และทำให้ 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();
}

สรุป

บทความนี้จะอธิบายวิธีเพิ่มเสียงตามตำแหน่งลงในฉาก 3 มิติโดยใช้ Web Audio API Web Audio API ช่วยให้คุณสามารถกำหนดตำแหน่ง การวางแนว และความเร็วของแหล่งที่มาของเสียงและผู้ฟัง ด้วยการตั้งค่าสิ่งเหล่านั้นให้ติดตามวัตถุในฉาก 3 มิติ คุณจะสามารถสร้างสภาพแวดล้อมที่สมบูรณ์ให้กับแอปพลิเคชัน 3 มิติได้

เพื่อทำให้ประสบการณ์ด้านเสียงน่าสนใจยิ่งขึ้น คุณสามารถใช้ ConvolverNode ใน Web Audio API เพื่อตั้งค่าเสียงทั่วไปของสภาพแวดล้อมได้ คุณจำลองเอฟเฟกต์และสภาพแวดล้อมที่หลากหลายได้โดยใช้ Web Audio API ไม่ว่าจะเป็นอาสนวิหารไปจนถึงห้องปิด

รายการอ้างอิง