บทนำ
บทความนี้จะอธิบายวิธีใช้ฟีเจอร์เสียงตามตำแหน่งใน 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 และการแพน 3 มิติ
มาดูกันว่าโค้ดเสียงของฉากด้านบนมีลักษณะเป็นอย่างไร นี่คือโค้ด 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 เท่านี้คุณก็มีเสียง 3 มิติที่ย้ายตำแหน่งได้แล้ว ตำแหน่งผู้ฟังบริบทเสียงอยู่ที่ (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 แล้ว เรามาดูอัตราความเร็วของพวกเขากัน การเปลี่ยนคุณสมบัติความเร็วของ Listener และ AudioPannerNode ทำให้คุณสามารถเพิ่มเอฟเฟ็กต์ดอปเพลอร์ให้กับเสียงได้ มีตัวอย่างเอฟเฟกต์ 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 มิติที่ส่งเสียง แล้วคูณกับ 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;
...
รวมกันทั้งหมด
เมื่อรวมทั้งหมดเข้าด้วยกัน ผู้ฟังบริบทเสียงจะติดตามตำแหน่ง การวางแนว และความเร็วของกล้อง และ AudioPannerNodes จะติดตามตำแหน่ง การวางแนว และความเร็วของแหล่งที่มาของเสียงที่เกี่ยวข้อง คุณต้องอัปเดตตำแหน่ง ความเร็ว และการวางแนวของ AudioPannerNodes และเครื่องฟังบริบทของเสียงในทุกเฟรม
ผลกระทบต่อสิ่งแวดล้อม
หลังจากตั้งค่าเสียงตามตำแหน่งแล้ว คุณสามารถตั้งค่าเอฟเฟกต์เสียงของสภาพแวดล้อมเพื่อเพิ่มความสมจริงให้กับฉาก 3 มิติ สมมติว่าฉากของคุณอยู่ในมหาวิหารขนาดใหญ่ ในการตั้งค่าเริ่มต้น เสียงในฉากจะฟังดูเหมือนคุณยืนอยู่กลางแจ้ง ความคลาดเคลื่อนระหว่างภาพและเสียงนี้จะทำให้ความสมจริงน้อยลงและทำให้ฉากของคุณน่าประทับใจน้อยลง
Web Audio API มี ConvolverNode ที่ช่วยให้คุณตั้งค่าเอฟเฟกต์เสียงแวดล้อมได้ เพิ่มลงในกราฟการประมวลผลสำหรับแหล่งที่มาของเสียง แล้วคุณจะปรับเสียงให้เข้ากับฉากได้ คุณดูตัวอย่างการตอบสนองต่อแรงกระตุ้นบนเว็บได้ ซึ่งนำไปใช้กับ ConvolverNodes ได้ หรือจะสร้างเองก็ได้ การดำเนินการนี้อาจยุ่งยากเล็กน้อยเนื่องจากคุณต้องบันทึกการตอบสนองต่อแรงกระตุ้นของสถานที่ที่ต้องการจำลอง แต่ฟีเจอร์นี้พร้อมใช้งานหากคุณต้องการ
การใช้ ConvolverNodes เพื่อทำเสียงสภาพแวดล้อมจะต้องเดินสายกราฟการประมวลผลเสียงใหม่ คุณต้องส่งเสียงผ่าน ConvolverNode แทนการส่งไปยังระดับเสียงหลักโดยตรง และเนื่องจากคุณอาจต้องควบคุมความแรงของเสียงสภาพแวดล้อม คุณจึงต้องกำหนดเส้นทางเสียงรอบ ConvolverNode ด้วย ในการควบคุมระดับเสียงของมิกซ์ ConvolverNode และเสียงธรรมดาจะต้องแนบ QuolverNode มาด้วย
กราฟการประมวลผลเสียงขั้นสุดท้ายที่ฉันใช้มีเสียงจากวัตถุที่ส่งผ่าน 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();
}
สรุป
ในบทความนี้ คุณได้เรียนรู้วิธีเพิ่มเสียงตามตำแหน่งไปยังฉาก 3 มิติโดยใช้ Web Audio API Web Audio API ให้คุณกำหนดตำแหน่ง การวางแนว และความเร็วของแหล่งที่มาของเสียงและผู้ฟังได้ คุณสามารถสร้างเสียงที่สวยงามสำหรับแอปพลิเคชัน 3 มิติได้ด้วยการตั้งค่าให้ติดตามวัตถุในฉาก 3 มิติของคุณ
หากต้องการให้ประสบการณ์เสียงน่าสนใจยิ่งขึ้น คุณสามารถใช้ ConvolverNode ใน Web Audio API เพื่อตั้งค่าเสียงทั่วไปของสภาพแวดล้อม คุณจำลองเอฟเฟกต์และสภาพแวดล้อมที่หลากหลายได้ตั้งแต่มหาวิหารไปจนถึงห้องปิดโดยใช้ Web Audio API
ข้อมูลอ้างอิง
- ข้อกำหนดของ Web Audio API
- การตอบสนองต่อแรงกระตุ้น
- Three.js
- ตัวอย่างเสียงตำแหน่ง 3 มิติสุดเจ๋ง
- ตัวอย่างเสียงบนเว็บมีตัวอย่างที่ดีมากมายเกี่ยวกับการใช้ฟีเจอร์ Web Audio API