【Unity】Replaced Shaderを使ってレンダリング時にシェーダを差し替える

UnityでReplaced Shaderを使ってレンダリング時にシェーダを差し替える方法です。

Replacement Shader?

UnityのCameraにはReplaced Shaderという機能があります。
これを使うと、下記二つのことが実現可能になります。

  1. レンダリングに使う全てのシェーダをまるごと差し替える
  2. レンダリングに使う全てのシェーダをTagの情報に基づいて差し替える

この二つが一つの機能としてまとめられているのでややこしくなっています。
この記事ではそれぞれを分けて説明します。

1.レンダリングに使う全てのシェーダをまるごと差し替える

Replaced Shaderの一つ目の機能として、レンダリングに使う全てのシェーダをまるごと差し替えるというものがあります。
この効果を検証するため、まずシーン上に適当なオブジェクトを生成します。
シェーダはStandardシェーダをアサインしておきます。

f:id:halya_11:20190704141358p:plain

次に適当なシェーダを作成します。
今回は赤色を返すだけのシェーダにしました。

Shader "Example"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            float4 vert(float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }

            fixed4 frag() : SV_Target
            {
                return fixed4(1, 0, 0, 1);
            }

            ENDCG
        }
    }
}

そしてこのシェーダをCamera.SetReplacementShader()に渡します。
このとき第二引数をnullに指定することで、レンダリング対象のオブジェクトが持つシェーダをすべて置換します。

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class Example : MonoBehaviour
{
    private Camera _camera;

    private void OnEnable()
    {
        _camera = GetComponent<Camera>();
        // 第二引数をnullにするとシェーダが丸ごとこれに置換される
        _camera?.SetReplacementShader(Shader.Find("Example"), null);
    }

    private void OnDisable()
    {
        _camera?.ResetReplacementShader();
    }
}

このスクリプトをカメラにアタッチして再生します。

f:id:halya_11:20190704141450p:plain

シェーダが置換されてオブジェクトが赤くなることが確認できました。

2.レンダリングに使う全てのシェーダをTagの情報に基づいて差し替える

次にレンダリングに使う全てのシェーダをTagの情報に基づいて差し替える方法を説明します。
まず、前節のシェーダを少し変えてこんな感じにします。

Shader "Example"
{
    SubShader
    {
        // RenderTypeをOpaqueに設定しておく
        Tags{ "RenderType" = "Opaque" }

        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            float4 vert(float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }

            fixed4 frag() : SV_Target
            {
                return fixed4(1, 0, 0, 1);
            }

            ENDCG
        }
    }
}

Tagsに"RenderType" = "Opaque"を定義しました。
次にスクリプトの方も少し変えます。

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class Example : MonoBehaviour
{
    private Camera _camera;

    private void OnEnable()
    {
        _camera = GetComponent<Camera>();
        // 第二引数に文字列を指定するとこのタグが一致するものパスだけを差し替える
        _camera?.SetReplacementShader(Shader.Find("RenderType"), "RenderType");
    }

    private void OnDisable()
    {
        _camera?.ResetReplacementShader();
    }
}

Camera.SetReplacementShaderの第二引数にRenderTypeを指定しました。
これをカメラにアタッチして再生すると、以下の処理が行われます。

  1. レンダリングするオブジェクトのシェーダを見て、RenderTypeというタグが定義されてるSubShaderがあるか確認
  2. 該当するSubShaderがあったら、RenderTypeの値を見る("RenderType" = "Opaque"と定義されている場合のOpaqueのこと)
  3. 該当するSubShaderがなかったら、そのオブジェクトはレンダリングしない
  4. 今度はそのRenderTypeと値が一致するSubShaderが、置換対象のシェーダにあるか見に行く
  5. 一致するものがあったら該当するSubShaderを使ってレンダリングを行う
  6. 一致するものがなかったら、そのオブジェクトはレンダリングしない

今回の場合、Standardシェーダには"RenderType" = "Opaque"が定義されているので置換ができるはずです。
再生結果は以下の通りとなります。

f:id:halya_11:20190704142958p:plain

SubShaderが置き換えられていることが確認できました。
次にオブジェクトにアサインされているStandardシェーダのRendering ModeをTransparentにします。

f:id:halya_11:20190704143127p:plain

これを再生すると・・

f:id:halya_11:20190704143202p:plain

Standardシェーダでは半透明描画用のパスに"RenderType" = "Transparent"が定義されています。
今回は置換用のシェーダに"RenderType" = "Transparent"が定義されていないため、オブジェクトがレンダリング対象から外れました。

ちなみにこの性質を利用して置換用を以下のように定義すれば、「不透明のオブジェクトは赤、半透明のオブジェクトは緑」にすることができます。

Shader "Example"
{
    SubShader
    {
        // RenderTypeをOpaqueに設定しておく
        Tags{ "RenderType" = "Opaque" }

        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            float4 vert(float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }

            fixed4 frag() : SV_Target
            {
                return fixed4(1, 0, 0, 1);
            }

            ENDCG
        }
    }

    SubShader
    {
        // RenderTypeをTransparentに設定しておく
        Tags{ "RenderType" = "Transparent" }

        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            float4 vert(float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }

            fixed4 frag() : SV_Target
            {
                return fixed4(0, 1, 0, 1);
            }

            ENDCG
        }
    }
}

レンダリングまでやりたければCamera.RenderWithShader

ちなみに上記のCamera.SetReplacementShader()はReplaced Shaderの設定を行うだけで
実際のレンダリングタイミングはカメラに任せることになりますが、
もしレンダリングまで行いたければCamera.RenderWithShader()を使います。

_camera?.RenderWithShader(Shader.Find("Example"), "RenderType");

参考

docs.unity3d.com