Konumsal ses ile WebGL'yi karıştırma

Ilmari Heikkinen

Giriş

Bu makalede, WebGL sahnelerinize 3D ses eklemek için Web Audio API'deki konumsal ses özelliğinin nasıl kullanılacağından bahsedeceğim. Sesin daha inandırıcı olmasını sağlamak için Web Audio API ile kullanılabilen çevresel efektleri de tanıtacağım. Web Audio API'ye daha ayrıntılı bir giriş için Boris Smus'un Web Audio API'yi kullanmaya başlama makalesine göz atın.

Konumsal ses için Web Audio API'deki AudioPannerNode'u kullanırsınız. AudioPannerNode, bir sesin konumunu, yönünü ve hızını tanımlar. Ayrıca Web Audio API ses bağlamında, dinleyicinin konumunu, yönünü ve hızını tanımlamanıza olanak tanıyan bir dinleyici özelliği bulunur. Bu iki özellik sayesinde Doppler efektleri ve 3D kaydırma ile yönlü sesler oluşturabilirsiniz.

Yukarıdaki sahnenin ses kodunun nasıl göründüğüne bakalım. Bu, çok temel bir Audio API kodudur. Bir dizi Audio API düğümü oluşturup bunları birbirine bağlarsınız. Ses düğümleri tek sesler, ses seviyesi kontrolörleri, efekt düğümleri ve analizörler gibi öğelerdir. Bu grafiği oluşturduktan sonra sesli hale getirmek için ses bağlamı hedefine bağlamanız gerekir.

// 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();

Konum

Konumsal ses, sesin hoparlörlere nasıl karıştırılacağını belirlemek için ses kaynaklarınızın ve dinleyicinin konumunu kullanır. Dinleyicinin sol tarafındaki bir ses kaynağı, sol hoparlörden daha yüksek sesle çalar. Sağ taraf için de durum tam tersidir.

Başlamak için bir ses kaynağı oluşturun ve bunu bir AudioPannerNode'a ekleyin. Ardından AudioPannerNode öğesinin konumunu ayarlayın. Artık hareketli bir 3D sese sahipsiniz. Ses bağlamı dinleyicisi konumu varsayılan olarak (0,0,0) değerindedir. Bu nedenle, bu şekilde kullanıldığında AudioPannerNode konumu kamera konumuna göredir. Kamerayı her hareket ettirdiğinizde AudioPannerNode konumunu güncellemeniz gerekir. AudioPannerNode konumunu dünyaya göre yapmak için ses bağlamı dinleyicisinin konumunu kamera konumunuza değiştirmeniz gerekir.

Konum izlemeyi ayarlamak için bir AudioPannerNode oluşturmamız ve ana ses düzeyine bağlamamız gerekir.

...
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);
...

Her karede AudioPannerNodes'ın konumlarını güncelleyin. Aşağıdaki örneklerde Three.js'yi kullanacağım.

...
// 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);
...

Dinleyicinin konumunu izlemek için ses bağlamının dinleyici konumunu kamera konumuyla eşleşecek şekilde ayarlayın.

...
// 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);
...

Hız

Dinleyicinin ve AudioPannerNode öğesinin konumlarını öğrendiğimize göre şimdi hızlarına bakalım. Dinleyicinin ve AudioPannerNode'un hız özelliklerini değiştirerek sese Doppler etkisi ekleyebilirsiniz. Web Audio API örnekler sayfasında Doppler etkisiyle ilgili bazı güzel örnekler vardır.

Dinleyicinin ve AudioPannerNode'un hızlarını almanın en kolay yolu, kare başına konumlarını takip etmektir. Dinleyicinin hızı, kameranın mevcut konumu ile önceki karedeki kamera konumu arasındaki farktır. Benzer şekilde, AudioPannerNode'un hızı, mevcut konumu ile önceki konumu arasındaki farktır.

Hız, nesnenin önceki konumunu elde edip mevcut konumdan çıkararak ve sonucu son kareden bu yana geçen süreye bölerek izlenebilir. Bunu Three.js'de nasıl yapacağınız aşağıda açıklanmıştır:

...
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);
...

Yön

Yön, ses kaynağının gösterdiği yön ve dinleyicinin baktığı yöndür. Yön özelliğini kullanarak yönlü ses kaynaklarını simüle edebilirsiniz. Örneğin, yönlü bir hoparlör düşünün. Hoparlörün önünde durursanız ses, hoparlörün arkasında durursanız olduğundan daha yüksek olur. Daha da önemlisi, seslerin dinleyicinin hangi tarafından geldiğini belirlemek için dinleyicinin yönüne ihtiyacınız vardır. Döndüğünüzde solunuzdan gelen sesin sağa geçmesi gerekir.

AudioPannerNode için yön vektörünü almak üzere, ses yayan 3D nesnenin model matrisinin dönme bölümünü almanız ve nereye işaret ettiğini görmek için bir vec3(0,0,1) ile çarpmanız gerekir. Bağlam dinleyicisi yönü için kameranın yön vektörünü almanız gerekir. Dinleyicinin kafasının yuvarlanma açısını bilmesi gerektiğinden, dinleyicinin oryantasyonu için de bir yukarı vektörü gerekir. Dinleyicinin yönünü hesaplamak için kameranın görüntü matrisinin dönme bölümünü alın ve yön için bir vec3(0,0,1) ve yukarı vektörü için bir vec3(0,-1,0) ile çarpın.

Yönlendirmenin seslerinizi etkilemesi için sesin konisini de tanımlamanız gerekir. Ses konisi iç açı, dış açı ve dış kazanç alır. Ses, iç açının içinde normal ses seviyesinde çalar ve dış açıya yaklaştığınızda kazancı kademeli olarak dış kazanca değiştirir. Dış açının dışında ses, dış kazançta oynatılır.

Üç boyutlu grafik kütüphanesi Three.js'de yönün izlenmesi, bazı vektör matematikleri ve 4x4 dünya matrislerinin çeviri bölümünün sıfırlanması içerdiğinden biraz daha karmaşıktır. Yine de çok fazla kod satırı yok.

...
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;
...

Kamera yönü izleme için yukarı vektörü de kullanmanız gerekir. Bu nedenle, yukarı vektörü dönüşüm matrisiyle çarpmanız gerekir.

...
// 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;
...

Sesinizin ses konisini ayarlamak için paner düğümünün uygun özelliklerini ayarlarsınız. Koni açıları derece cinsindendir ve 0 ile 360 arasındadır.

...
sound.panner.coneInnerAngle = innerAngleInDegrees;
sound.panner.coneOuterAngle = outerAngleInDegrees;
sound.panner.coneOuterGain = outerGainFactor;
...

Tümünü bir araya getirme

Tüm bunları bir araya getirdiğimizde, ses bağlamı dinleyicisi kameranın konumunu, yönünü ve hızını, AudioPannerNodes ise ilgili ses kaynaklarının konumlarını, yönlerini ve hızlarını izler. Her karede AudioPannerNodes ve ses bağlamı dinleyicisinin konumlarını, hızlarını ve yönlerini güncellemeniz gerekir.

Çevresel etkiler

Konumsal sesi ayarladıktan sonra 3D sahnenizin etkileyiciliğini artırmak için sesinize çevresel efektler ekleyebilirsiniz. Sahneniz büyük bir katedralin içinde geçiyor olsun. Varsayılan ayarlarda, sahnenizdeki sesler dışarıda olduğunuz gibi gelir. Görseller ile ses arasındaki bu tutarsızlık, izleyicinin içeriğe olan ilgisini azaltır ve sahnenizi daha az etkileyici hale getirir.

Web Audio API'de, bir sesin çevresel efektini ayarlamanıza olanak tanıyan bir ConvolverNode bulunur. Bu sesi ses kaynağının işleme grafiğine eklediğinizde sesi ayarlara uygun hale getirebilirsiniz. Web'de, ConvolverNodes ile kullanabileceğiniz dürtü yanıtı örnekleri bulabilir ve kendi örneklerinizi de oluşturabilirsiniz. Simüle etmek istediğiniz yerin dürtü yanıtını kaydetmeniz gerektiğinden bu işlem biraz zahmetli olabilir ancak ihtiyacınız olduğunda bu özelliği kullanabilirsiniz.

Ortam sesi oluşturmak için ConvolverNodes kullanmak, ses işleme grafiğinin yeniden bağlanmasını gerektirir. Sesi doğrudan ana ses seviyesine iletmek yerine ConvolverNode üzerinden yönlendirmeniz gerekir. Ayrıca, çevresel efektin gücünü kontrol etmek isteyebileceğiniz için sesi ConvolverNode'un etrafından yönlendirmeniz de gerekir. Karışım ses seviyelerini kontrol etmek için ConvolverNode ve düz sesin GainNode'lara sahip olması gerekir.

Kullandığım nihai ses işleme grafiğinde, geçiş karıştırıcı olarak kullanılan bir GainNode üzerinden geçen nesnelerin sesi bulunur. Mikserden sesi ConvolverNode'a ve düz sesin sesini kontrol etmek için kullanılan başka bir GainNode'a iletiyorum. ConvolverNode, convolve edilmiş ses hacmini kontrol etmek için kendi GainNode'una bağlanır. GainNode'ların çıkışları ana ses kontrol cihazına bağlanır.

...
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'un çalışmasını sağlamak için bir darbe yanıtı örneğini arabelleğe yüklemeniz ve ConvolverNode'un bunu kullanmasını sağlamanız gerekir. Örnek, normal ses örnekleriyle aynı şekilde yüklenir. Bunu yapmanın bir yolu aşağıda verilmiştir:

...
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();
}

Özet

Bu makalede, Web Audio API'yi kullanarak 3D sahnelerinize nasıl konumsal ses ekleyeceğinizi öğrendiniz. Web Audio API, ses kaynaklarının ve dinleyicinin konumunu, yönünü ve hızını ayarlamanızı sağlar. Bu sesleri 3D sahnenizdeki nesneleri izlemeye ayarlayarak 3D uygulamalarınız için zengin bir ses ortamı oluşturabilirsiniz.

Ses deneyimini daha da ilgi çekici hale getirmek için Web Audio API'deki ConvolverNode'u kullanarak ortamın genel sesini ayarlayabilirsiniz. Web Audio API'yi kullanarak katedrallerden kapalı odalara kadar çeşitli efektleri ve ortamları simüle edebilirsiniz.

Referanslar