web-dev-qa-db-ja.com

Shadertoyのオーディオシェーダーはどのように機能しますか?

まず、この質問を投稿するのに適切なコミュニティが本当に見つからなかったので、これを選びました。人気のあるwebGLベースのシェーダーツールのオーディオシェーダーがどのように機能するのか疑問に思っていました。「通常の」GLSLシェーダーについては明らかに聞いたことがありますが、手続き型オーディオを生成するためのシェーダーについて最初に聞いたとき、私は驚きました。手がかりはありますか?

22
tbvanderwoude

これらは基本的に、timeを指定すると、オーディオシングル(左チャネルと右チャネル)に対して2つの値を返す関数です。値は-1から1になります。

このシェーダーに貼り付けると、多分あなたはそれを手に入れるでしょう

vec2 mainSound( float time )
{
    return vec2( sin(time * 1000.0), sin(time * 1000.0) );
}

ここで音を出す同様のスタイル のよりライブな例を見ることができます。

あなたはそれをこのように想像することができます

function generateAudioSignal(time) {
   return Math.sin(time * 4000); // generate a 4khz sign wave.
}

var audioData = new Float32Array(44100 * 4); // 4 seconds of audio at 44.1khz
for (var sample = 0; sample < audioData.length; ++sample) {
  var time = sample / 44100;
  audioData[sample] = generateAudioSignal(time);
}

次に、audioDataをWeb AudioAPIに渡します

ステレオの場合は

function generateStereoAudioSignal(time) {
   return [Math.sin(time * 4000), Math.sin(time * 4000)]; // generate a 4khz stereo sign wave.
}

var audioData = new Float32Array(44100 * 4 * 2); // 4 seconds of stereo audio at 44.1khz
for (var sample = 0; sample < audioData.length; sample += 2) {
  var time = sample / 44100 / 2;
  var stereoData = generateAudioSignal(time);
  audioData[sample + 0] = stereoData[0];
  audioData[sample + 1] = stereoData[1];
}

それらがWebGLに含まれる正当な理由は実際にはありません(それらが含まれていると仮定します)。 WebGLでは、これらを使用して、フレームバッファーにアタッチされたテクスチャにデータを生成します。次に、生成されたデータをgl.readPixelsを使用してGPUからメインメモリにコピーして戻し、Web Audio APIに渡す必要があります。これは低速であり、少なくともWebGLでは、処理をブロックする方法がありません。非同期でデータをWebGLに読み戻します。その上、WebGLでfloatデータを簡単に読み戻すことはできません。もちろん、shadertoyが実際にWebGLを使用している場合は、オーディオシェーダーを書き直して、データを8ビットRGBAテクスチャにエンコードし、JavaScriptでfloatに戻すことができます。このためにWebGLを使用しないさらに多くの理由。 WebGLを使用する主な理由は、WebGLを対称にするだけです。同じ言語を使用するすべてのシェーダー

上にリンクされているバイトビートの例は、JavaScriptで完全に実行されています。デフォルトはbytebeatで、関数が返すと予想される値は0〜255 unsigned intですが、の設定があります。 )floatbeatこの場合、shadertoyのシェーダーと同様に、-1から1までの値が必要です。


更新

そこで、Shadertoyをチェックしました。これは、WebGLシェーダーを使用しており、値を8ビットテクスチャにエンコードしています。

これが実際のシェーダーです(私は クロームシェーダーエディター を使用してシェーダーを簡単に確認しました)。

precision highp float;

uniform float     iChannelTime[4];
uniform float     iBlockOffset; 
uniform vec4      iDate;
uniform float     iSampleRate;
uniform vec3      iChannelResolution[4];
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;

vec2 mainSound( float time )
{
    return vec2( sin(time * 1000.0), sin(time * 1000.0) );
}

void main() {
   // compute time `t` based on the pixel we're about to write
   // the 512.0 means the texture is 512 pixels across so it's
   // using a 2 dimensional texture, 512 samples per row
   float t = iBlockOffset + ((gl_FragCoord.x-0.5) + (gl_FragCoord.y-0.5)*512.0)/iSampleRate;

   // Get the 2 values for left and right channels
   vec2 y = mainSound( t );

   // convert them from -1 to 1 to 0 to 65536
   vec2 v  = floor((0.5+0.5*y)*65536.0);

   // separate them into low and high bytes
   vec2 vl = mod(v,256.0)/255.0;
   vec2 vh = floor(v/256.0)/255.0;

   // write them out where 
   // RED   = channel 0 low byte
   // GREEN = channel 0 high byte
   // BLUE  = channel 1 low byte
   // ALPHA = channel 2 high byte
   gl_FragColor = vec4(vl.x,vh.x,vl.y,vh.y);
}

これは、この特定のケースでWebGLを使用する利点の1つは、オーディオシェーダーにフラグメントシェーダーと同じ入力をすべて取得できることです(フラグメントシェーダーであるため)。つまり、たとえば、オーディオシェーダーは最大4つのテクスチャを参照できます。

JavaScriptでは、gl.readPixelsを使用してテクスチャを読み取り、サンプルを次のようなフロートに変換し直します。

   var pixels = new Uint8Array(width * height * 4);
   gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
   for (var sample = 0; sample < numSamples; ++sample) {
     var offset = sample * 4;  // RGBA
     audioData[sample * 2    ] = backToFloat(pixels[offset + 0], pixels[offset + 1]);
     audioData[sample * 2 + 1] = backToFloat(pixels[offset + 2], pixels[offset + 3]);
   }

   float backToFloat(low, high) {
     // convert back to 0 to 65536
     var value = low + high * 256;

     // convert from 0 to 65536 to -1 to 1
     return value / 32768 - 1;
   } 

また、上で言ったが、シェーダーが常にオーディオシェーダーを呼び出していると思ったのは良い考えではなかったので、ブロック処理に関して私が提起した問題は本当だろうが、...どうやらシェーダーは事前に生成されただけだNが明らかに60秒である再生を押したときに、シェーダーを使用したN秒のオーディオ。したがって、ブロッキングはありませんが、サウンドは60秒しか持続しません。

22
gman