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'sı hakkında daha kapsamlı bir tanıtım için Boris Smus'un Getting started with Web Audio API (Web Audio API'sını 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 özelliği kullanarak doppler efektleri ve 3D kaydırma ile yönlü sesler oluşturabilirsiniz.

Yukarıdaki sahne için ses kodunun nasıl göründüğüne bakalım. Bu, son derece 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ı dinleyici konumu varsayılan olarak (0,0,0) konumundadır. Dolayısıyla bu şekilde kullanıldığında, AudioPannerNode konumu kamera konumuna göre belirlenir. Kamerayı her hareket ettirdiğinizde AudioPannerNode konumunu güncellemeniz gerekir. AudioPannerNode konumunu dünyaya göre yapmak için ses bağlamı dinleyici konumunu kameranızın konumuna göre 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 AudioPannerNode'ların konumlarını güncelleyin. Aşağıdaki örneklerde Three.js 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);
...

Dinleyici 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ına sahip olduğumuza göre, 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 konumunun farkıdır. Benzer şekilde, AudioPannerNode'un hızı, mevcut konumunun önceki konumdan çıkarılmasıdı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 işaret ettiğ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 duruyorsanız ses, hoparlörün arkasında durduğunuz duruma göre daha yüksek olur. Daha da önemlisi, sesin dinleyicinin hangi tarafından geldiğini belirlemek için dinleyicinin yönünü öğrenmeniz gerekir. Döndüğünüzde solunuzdan gelen sesin sağa geçmesi gerekir.

AudioPannerNode'un yön vektörünü elde etmek için, ses yayan 3D nesnenin model matrisinin dönüş kısmını alıp sonucu nereye işaret ettiğini görmek üzere 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, dinleyici yönünün de yukarı vektörü olması 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.

Three.js'de oryantasyonu izlemek, bazı vektör matematikleri ve 4x4 dünya matrislerinin çeviri bölümünün sıfırlanması gibi işlemleri içerdiğinden biraz daha zordur. 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;
...

Hepsi bir arada

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 ayaktaymışsınız gibi gelir. Görseller ile ses arasındaki bu tutarsızlık, izleyicinin içine girmesini engeller 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 veya kendi örneklerinizi 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. Mix ses düzeylerini kontrol etmek için ConvolverNode ve düz sese GainNode değerlerinin eklenmesi gerekir.

Kullandığım son ses işleme grafiği, geçiş mikser olarak kullanılan bir GainNode işleminden geçen nesnelerin sesine sahip. Mikserden sesi ConvolverNode'a ve düz sesin sesini kontrol etmek için kullanılan başka bir GainNode'a aktarıyorum. ConvolverNode, konvolüsyon ses düzeyini kontrol etmek için kendi GainNode sistemine bağlıdır. GainNodes'un çıkışları, ana ses seviyesi denetleyicisine bağlıdı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. Örneği yükleme işlemi, normal ses örnekleriyle aynı şekilde yapılır. Aşağıda, bunu yapmanın bir yolu örneği 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. Bunları 3D sahnenizdeki nesneleri izleyecek şekilde 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