【Unity】シェーダーを利用して音声波形を描く
今、広い範囲の音声波形を高速にリアルタイム描画する問題に取り組んでいます。
要件として描画対象の範囲をグリグリ変更できる必要があって、これまでLineRendererやGLによる描画を試みましたがどれも欲しいパフォーマンスに届きませんでした。
そこで、波形情報をテクスチャに埋め込んでシェーダーで描画する方法を試してみました。
AudioClipから波形情報を取得する
var audioSource = GetComponent<AudioSource>(); var samples = new float[audioSource.clip.samples * audioSource.clip.channels]; audioSource.clip.GetData(samples, 0);
で取得できます。
docs.unity3d.com
波形情報をテクスチャに埋め込む
描画領域の横幅 x 1サイズのテクスチャを生成して、色情報に波形データを埋め込んでいきます。
手抜き実装なので r 成分しか使ってませんが、rgba 全ての成分を使えばより小さいテクスチャサイズで実現できます。
また、1サンプルを1ピクセルに対応させるとテクスチャサイズが膨大になりすぎるので適当に間引きします。
public class WaveformRenderer : MonoBehaviour { [SerializeField] AudioSource audioSource; [SerializeField] RawImage image; [SerializeField] int imageWidth; Texture2D texture; float[] samples = new float[500000]; void Start() { texture = new Texture2D(imageWidth, 1); texture.SetPixels(Enumerable.Range(0, imageWidth).Select(_ => Color.clear).ToArray()); texture.Apply(); image.texture = texture; } void Update() { audioSource.clip.GetData(samples, audioSource.timeSamples); int textureX = 0; int skipSamples = 200; float maxSample = 0; for (int i = 0, l = samples.Length; i < l && textureX < imageWidth; i++) { maxSample = Mathf.Max(maxSample, samples[i]); if (i % skipSamples == 0) { texture.SetPixel(textureX, 0, new Color(maxSample, 0, 0)); maxSample = 0; textureX++; } } texture.Apply(); } }
説明に余分なコードは省いています。実際のソースコードはここにあります。
NoteEditor/WaveformRenderer.cs at 21a1878556f1b492a7a9a150339512a6ba5330ca · setchi/NoteEditor · GitHub
このようなテクスチャが生成されます。
(実際は縦1pxです。見やすく縦方向に伸ばしています)
シェーダーで波形を描画する
フルソースはここにあります。
NoteEditor/Waveform.shader at a56ec2d55987a8f22c392bfe1739af87a9e97bc9 · setchi/NoteEditor · GitHub
重要なのは下記の部分です。
テクスチャの r 成分からボリュームを取り出して、自身のV座標がボリュームの範囲内なら緑、そうでなければ黒を出力しています。
fixed4 frag(v2f v) : SV_Target{ float volume = tex2D(_MainTex, v.uv.x).r * 0.5; float uvY = v.uv.y - 0.5; return lerp( fixed4(0, 0, 0, 1), fixed4(0, 1, 0, 1), -volume < uvY && uvY < volume ); }
最終的にこのように出力されます。