【Unity】【シェーダ】Custom Render Textureを使ってみる

Unity2017から追加されたCustom Render Textureを簡単に使ってみました。

Custom Render Texture?

Custom Render Textureとは、機能が拡張されたRender Textureで、以下のような特徴があります。

  • 描画に使うマテリアルをインスペクタから設定できる
  • 簡単で自由度の高い更新処理ができる
  • 部分的な更新処理ができる
  • 前回更新時のテクスチャを取得できる

うーん・・実際使ってみるのが早そうです。

作成する

Custom Render TextureはProjectビューで右クリック > Create > Custom Render Textureで作れます。

f:id:halya_11:20180611223406p:plain

真っ黒なのが出来上がればOK。

更新する

まずこのテクスチャを更新するシェーダを書いてみます。

Shader "CRTUpdate"
{
    SubShader
    {
        Cull Off ZWrite Off ZTest Always
        Pass
        {
            // インスペクタに表示したときにわかりやすいように名前を付けておく
            Name "Update"

            CGPROGRAM
            
            // UnityCustomRenderTexture.cgincをインクルードする
           #include "UnityCustomRenderTexture.cginc"

            // 頂点シェーダは決まったものを使う
           #pragma vertex CustomRenderTextureVertexShader
           #pragma fragment frag

            // v2f構造体は決まったものを使う
            half4 frag(v2f_customrendertexture i) : SV_Target
            {
                return half4(_SinTime.z * 0.5 + 0.5, _CosTime.w * 0.5 + 0.5, _SinTime.w * 0.5 + 0.5, 1);
            }

            ENDCG
        }
    }
}

Custom Render Texture用のシェーダは書き方に決まりがあるため、コメントに説明を書いています。
このシェーダを割り当てたマテリアルをCustom Render Textureのマテリアルとしてアサインし、パス名にUPDATEを指定します。

f:id:halya_11:20180611233947p:plain:w400

Update ModeをRealtimeにして再生すると1frameごとにテクスチャが先ほどのシェーダによって更新されていくのが確認できます。

f:id:halya_11:20180611234550g:plain

また、Update Modeの下のPeriodフィールドに秒数を入れると更新間隔を設定できます(0を入れると毎フレーム更新)。

更新をスクリプトで制御する

Update ModeをOn Demandにしたうえで、下記のようなスクリプトをかけばスクリプトで更新タイミングを制御することもできます。

using UnityEngine;

public class CustomRenderTextureSample : MonoBehaviour
{
    [SerializeField] CustomRenderTexture _texture;

    void Update()
    {
        // 1フレームに5回更新
        _texture.Update(5);
    }
}

一部分だけ更新する

次にUpdateZoneをこんな感じの設定にすると・・

f:id:halya_11:20180612000841p:plain

テクスチャの一部分だけを更新することができます。

f:id:halya_11:20180612001045g:plain

前フレームの結果を使う

シェーダ内で前フレームのテクスチャを取るためにはまずDouble Burfferedにチェックを入れます。

f:id:halya_11:20180612001627p:plain

そして更新用シェーダでこのように記述します。

Shader "CRTUpdate"
{
    SubShader
    {
        Cull Off ZWrite Off ZTest Always
        Pass
        {
            Name "Update"

            CGPROGRAM
            
           #include "UnityCustomRenderTexture.cginc"

           #pragma vertex CustomRenderTextureVertexShader
           #pragma fragment frag

            half4 frag(v2f_customrendertexture i) : SV_Target
            {
                float tw = 1 / _CustomRenderTextureWidth;

                // UVはこのように取得する
                float2 uv = i.globalTexcoord;
                // _SelfTexture2Dで前フレームの結果を取得する
                return tex2D(_SelfTexture2D, uv + half2(tw, 0) * 10);
            }

            ENDCG
        }
    }
}

前フレームの情報を使うということは最初のフレームのために初期化が必要になります。

Initialization ModeをOn Loadに設定することで初期化時の設定ができます。
今回はTextureに適当なテクスチャをアサインしてください。

f:id:halya_11:20180612002104p:plain

Wrap ModeをRepeatにして再生すると、

f:id:halya_11:20180612002310g:plain

前フレームの結果を使って少しずつずらすことができました。

ただしDoubleBufferedは今のところまだパフォーマンスがよくないようです(マニュアル参照)。

初期化用シェーダを書く

初期化には独自のマテリアルを使うこともできます。
この場合のシェーダも更新時と同じようにCustom Render Textureならではの書き方があるので注意してください。

Shader "CRTInitialization"
{
    SubShader
    {
        Cull Off ZWrite Off ZTest Always
        Pass
        {
            Name "Initialize"
            CGPROGRAM

            // UnityCustomRenderTexture.cgincをインクルードする
           #include "UnityCustomRenderTexture.cginc"

            // 頂点シェーダは決まったものを使う
           #pragma vertex InitCustomRenderTextureVertexShader
           #pragma fragment frag

            // v2f構造体は決まったものを使う
            half4 frag(v2f_init_customrendertexture i) : SV_Target
            {
                return 1.0 - step(0.1, distance(0.5, i.texcoord));
            }
            ENDCG
        }
    }
}

注意点はコメントの通りです。
処理はただ白い丸を書いているだけです。

再生すると次のような結果が得られます。

f:id:halya_11:20180612003448g:plain

参考

Unity - Scripting API: CustomRenderTexture

Unity - Manual: Custom Render Textures