rn.log

備忘録など

UnityのShaderGraphで音を作る

はじめに

ShaderGraphを利用して、サウンド合成をやってみました。

以下の動画は、ShaderGraphを使って実際に音を作っているところをキャプチャしたものです。

www.youtube.com

www.youtube.com

サンプル

github.com

サウンド合成までの流れ

f:id:r-ngtm:20190110220303p:plain

まずはテクスチャを用意し、これをShaderGraphで加工します。
そして、加工したテクスチャをOnAudioFIlterReadメソッド経由でオーディオデータに書き込み、音としてスピーカーから再生します。

OnAudioFilterReadについて

Unityのサウンド再生では、ゲームシーン内のAudioSourceが再生した音をAudioListenerが拾いスピーカーへ流します。
f:id:r-ngtm:20190111002131p:plain

OnAudioFilterReadメソッドを利用することで、AudioSourceからAudioListenerへのデータの流れに独自の処理を挟み、音を加工することができます。
例えば、自分でディレイエフェクトを実装して音に反響を付けたり、ローパスフィルターを実装して音の高周波成分を削ったり・・・

Unity公式リファレンス

Unity公式リファレンスにも解説やサンプルが載っています。
docs.unity3d.com

OnAudioFilterReadの利用例 : ディレイエフェクト

ディレイエフェクトの実装例が載っている記事
qiita.com

実装

STEP1. ソースコード

実装自体は以下のソースコードで完結します。

using UnityEngine;
using Random = System.Random;

[RequireComponent(typeof(AudioSource))]
public class SoundShader : MonoBehaviour
{
    const int Width = 128; // texture width
    const int Height = 64; // texture height

    [SerializeField, Range(0f, 1f)] private float soundVolume = 0.05f;
    [SerializeField] private Material soundMaterial; // Material for audio synthesis
    RenderTexture soundRT; // シェーダーで加工した結果を保持するためのRenderTexture
    Texture2D soundTexture; 
    float[] textureBuffer; // オーディオに渡すためのデータ
    int bufferReadPos = 0; // データの読み取り位置

    void Start()
    {
        soundRT = new RenderTexture(Width, Height, 0);
        soundRT.Create();
        textureBuffer = new float[soundRT.width * soundRT.height];
        soundTexture = new Texture2D(soundRT.width, soundRT.height);
    }

    void Update()
    {
        UpdateBuffer();
    }
    
    private void UpdateBuffer()
    {
        // シェーダーでテクスチャを加工し、結果をsoundRTに保存
        Graphics.Blit(soundRT, soundRT, soundMaterial, 0);

        // RenderTextureはそのままではピクセルにアクセスできないのでTexture2Dに変換
        RenderTexture.active = soundRT;
        soundTexture.ReadPixels(new Rect(0, 0, Width, Height), 0, 0); // RenderTexture -> Texture2D

        // Texture2D -> float[]
        // Texture2D.GetPixel()をOnAudioFilterRead()の中で使うと怒られるので注意
        int dst = 0;
        for (int y = 0; y < soundRT.height; y++)
        {
            for (int x = 0; x < soundRT.width; x++)
            {
                // とりあえず、テクスチャのrチャンネルをオーディオに渡す
                textureBuffer[dst++] = soundTexture.GetPixel(x, y).r * 2f - 1f; // [0:1] -> [-1:1]
            }
        }
    }

    void OnDestroy()
    {
        soundRT.Release();
        DestroyImmediate(soundTexture);
    }
    
    void OnAudioFilterRead(float[] data, int channels)
    {
        int dst = 0;
        while (dst < data.Length)
        {
            float value = textureBuffer[bufferReadPos] * soundVolume;
            for (int i = 0; i < channels; i++)
            {
                data[dst + i] = value; // write
            }

            dst += channels;
            bufferReadPos ++;
            if (bufferReadPos == textureBuffer.Length)
            {
                bufferReadPos = 0;
            }
        }
    }
}


STEP2. シェーダーグラフを作る

今回は以下のようなノイズを出力するシェーダーグラフを作ってみます。
f:id:r-ngtm:20190111002723p:plain

STEP3. マテリアル作成

シェーダーグラフを右クリックしてマテリアル作成します f:id:r-ngtm:20190111002952p:plain

STEP4. マテリアル登録

適当なオブジェクトにSTEP1.のコンポーネント(SoundShader)をアタッチし、STEP3のマテリアルを登録します
f:id:r-ngtm:20190111003218p:plain

STEP5 ゲーム再生(完成)

ゲームを再生すると、ノイズのような音が再生されます。

Ryoji.Ikeda 風のノイズを作って遊んでみた

ノイズを作って遊んでみました。
www.youtube.com

ノード全体

f:id:r-ngtm:20190111004840p:plain