渋谷ほととぎす通信

「Unityをわかりやすく」初心者のためのゲーム作りブログ

【Unityでポストエフェクト】モーションブラーを最初から作る方法

【Unityでポストエフェクト】モーションブラーを最初から作る方法

こんにちは、エンジニアのオオバです。

お悩みさん
お悩みさん
  • Unityでモーションブラーの作り方を知りたい
  • ポストエフェクトの作り方を知りたい
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    モーションブラーとは動いている対象がブレる表現です。動きのあるゲームを作るときに速さを表現するときに使います。

    本記事ではライブラリを使わずにモーションブラーを自分で実装してみたいと思います。正直PostProcessing Stack などを使うと「秒」でモーションブラーを実装できて楽です。しかし、自分自身で仕組みを理解することも大事。独自の表現を作り出すためには必要なのです。

    ぜひ、この記事を通してモーションブラーの作り方をマスターしてみてください。

    この記事は西川 善司さんの「ゲーム制作者になるための3Dグラフィックス技術」を参考にさせていただきました。ゲームに使われる3D技術の歴史から、各機能の実装方法について分かりやすく説明されています。限られたリソースでハードウェアのポテンシャルを工夫で乗り越える話がとても面白いです。興味ある方は読んでみてください。

    👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!

    モーションブラーを作る手順

    用意するものはレンダーテクスチャ3枚です。この3枚を1フレームずらしながら書き込み、シェーダーで合成するのです。
    レンダーテクスチャの合成はフラグメントシェーダーで実行します。

    シェーダーにレンダーテクスチャの変数を宣言

    3枚のレンダーテクスチャを受け取るために _Tex0_Tex2 を宣言します。

    sampler2D _Tex0;  
    sampler2D _Tex1;  
    sampler2D _Tex2;  
    

    この変数にC#側からレンダーテクスチャをセットします。

    C#側からレンダーテクスチャをシェーダーにセット

    画面サイズのレンダーテクスチャを3枚作り配列(m_rtList)に格納します。

    int w = Screen.width;  
    int h = Screen.height;  
    // 1フレームずつずらしたRenderTextureの配列  
    m_rtList = new[] {  
        new RenderTexture(w, h, 0, RenderTextureFormat.RGB565),  
        new RenderTexture(w, h, 0, RenderTextureFormat.RGB565),  
        new RenderTexture(w, h, 0, RenderTextureFormat.RGB565)  
    };  
    

    この3枚のレンダーテクスチャをシェーダーにセットします。

    for (int i = 0; i < m_rtList.Length; i++)  
    {
        // シェーダにレンダーテクスチャをセット  
        m_material.SetTexture("_Tex" + i, m_rtList[i]);  
    }
    

    フラグメントシェーダーでレンダーテクスチャの合成

    フラグメントシェーダーでレンダーテクスチャを合成します。3枚を加算合成するため、各レンダーテクスチャの色を「3」で割っているのです。

    fixed4 frag (v2f_img i) : SV_Target  
    {
        fixed4 col0 = tex2D(_Tex0, i.uv) / 3;  
        fixed4 col1 = tex2D(_Tex1, i.uv) / 3;  
        fixed4 col2 = tex2D(_Tex2, i.uv) / 3;  
        return col0 + col1 + col2;  
    }
    

    1フレームずらしながらレンダーテクスチャに書き込む

    3枚のレンダーテクスチャに1フレームずつずらした画面をキャプチャさせます。シェーダー内で3枚のレンダーテクスチャを合成して画面に出力するのです。

    void OnRenderImage(RenderTexture src, RenderTexture dst)  
    {
        var tmp = m_rtList[Time.frameCount % 3];  
        // 1フレームずらした画面をレンダーテクスチャにコピー  
        Graphics.Blit(src, tmp);  
        // シェーダに渡されたレンダーテクスチャを合成して出力  
        Graphics.Blit(src, dst, m_material);  
    }
    

    モーションブラー完成品はこちら

    モーションブラー適用前

    【Unityでポストエフェクト】モーションブラーを最初から作る方法_0

    モーションブラー適用後

    【Unityでポストエフェクト】モーションブラーを最初から作る方法_1

    動いているところだけブレる表現ができました。

    まとめ

    今回はUnityでモーションブラーを作る方法を紹介しました。モーションブラー自体いろんな作り方がありますが、最も作りがシンプルなものを採用しています。本来であれば 動いている部分の向きと強さ を取得してブレの強さを表現した方がよりリアルになります。この辺りはまた別の記事で解説しようと思います。

    今回紹介した方法もレンダーテクスチャを3枚使うため、メモリをモーションブラーだけにかなり割くことになります。ポストエフェクト全般に言えますが、実践での使用は負荷検証した上でご利用ください。

    今回の記事が難しかった方は、こちらの記事がおすすめです。画面を移動(スクロール)させるポストエフェクトです。

    👉 【Unity】画面がスクロールするポストエフェクトの作り方

    【Unityでポストエフェクト】モーションブラーを最初から作る方法_2

    この記事より基礎的なないようなので、理解しやすいかもしれません。あわせて読んでみてください。

    ソースコード全文

    最後に今回作成したソースコード全文を紹介します。C#ファイル1枚、シェーダーファイル1枚です。

    using UnityEngine;  
    
    public class MotionBlur : MonoBehaviour  
    {
        Material m_material;  
        RenderTexture[] m_rtList;  
    
        private void Awake()  
        {
            Application.targetFrameRate = 30;  
        }
    
        void OnRenderImage(RenderTexture src, RenderTexture dst)  
        {
            if (m_material == null)  
            {
                var shader = Shader.Find("Hidden/MotionBlur");  
                m_material = new Material(shader);  
                m_material.hideFlags = HideFlags.DontSave;  
    
                int w = src.width;  
                int h = src.height;  
                // 1フレームずつずらしたRenderTextureの配列  
                m_rtList = new[] {  
                    new RenderTexture(w, h, 0, RenderTextureFormat.RGB565),  
                    new RenderTexture(w, h, 0, RenderTextureFormat.RGB565),  
                    new RenderTexture(w, h, 0, RenderTextureFormat.RGB565)  
                };  
    
                for (int i = 0; i < m_rtList.Length; i++)  
                {
                    // シェーダにレンダーテクスチャを渡す  
                    m_material.SetTexture("_Tex" + i, m_rtList[i]);  
                }
            }
    
            // 1フレームずらしたフレームバッファをコピーする  
            var tmp = m_rtList[Time.frameCount % 3];  
            Graphics.Blit(src, tmp);  
            // シェーダに渡されたレンダーテクスチャを合成して出力  
            Graphics.Blit(src, dst, m_material);  
        }
    }
    
    Shader "Hidden/MotionBlur"  
    {
        SubShader  
        {
            Pass  
            {
                CGPROGRAM  
                #pragma vertex vert_img  
                #pragma fragment frag  
    
                #include "UnityCG.cginc"  
    
                sampler2D _Tex0;  
                sampler2D _Tex1;  
                sampler2D _Tex2;  
    
                fixed4 frag (v2f_img i) : SV_Target  
                {
                    fixed4 col0 = tex2D(_Tex0, i.uv) / 3;  
                    fixed4 col1 = tex2D(_Tex1, i.uv) / 3;  
                    fixed4 col2 = tex2D(_Tex2, i.uv) / 3;  
                    return col0 + col1 + col2;  
                }
                ENDCG  
            }
        }
    }
    

    記事の内容は以上です。この記事があなたのゲーム開発に少しでもお役に立てたら嬉しいです。

    「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
    ぜひチャンネル登録をお願いします!

    最後まで読んでいただきありがとうございました!
    すばらしいポストエフェクトライフをお過ごしください。

    オススメ記事
    検証環境
    • Unity2020.3.31f1
    • ビルトインパイプライン