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