【Unity】【URP】Cutoffシェーダのアウトラインと選択領域をSceneビューで正常に表示する

UnityのUniversal Render Pipeline(URP)でCutoffシェーダのアウトラインと選択領域をSceneビューで正常に表示する方法についてまとめました。

Unity2020.3.15f2
Universal RP 10.5.0

やりたいこと

今、テクスチャの色に応じてピクセルクリッピングするCutoffシェーダを考えます。
シェーダがシーンビューにちゃんと対応していない状態でこのオブジェクトを選択すると以下のような表示になります。

f:id:halya_11:20210902133731p:plain
シーンビュー未対応

クリッピングしている領域までアウトラインが引かれ、またその領域をクリックすると選択できてしまいます。
シーンビューにちゃんと対応すると、アウトラインやクリック領域が以下のように正常になります。

f:id:halya_11:20210902133921p:plain
シーンビュー対応済み

本記事ではこの対応の仕方についてまとめます。
レンダリングパイプラインはUniversal Render Pipelineを想定します。

シーンビュー未対応シェーダを書く

まずシーンビューに対応していない状態のシェーダを書きます。

Shader "ScenePathTest"
{
    Properties
    {
        [MainTexture] _BaseMap("Base Map", 2D) = "white" {}
        [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "IgnoreProjector" = "True"
        }
        
        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

        struct Attributes
        {
            float4 positionOS : POSITION;
            float2 uv : TEXCOORD0;
        };

        struct Varyings
        {
            float4 positionHCS : SV_POSITION;
            float2 uv : TEXCOORD0;
        };

        sampler2D _BaseMap;
        
        CBUFFER_START(UnityPerMaterial)
        float4 _BaseMap_ST;
        half4 _BaseColor;
        CBUFFER_END

        Varyings vert(Attributes IN)
        {
            Varyings OUT;
            OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
            OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
            return OUT;
        }
        
        ENDHLSL

        Pass
        {
            HLSLPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            half4 frag(Varyings IN) : SV_Target
            {
                const float r = (tex2D(_BaseMap, IN.uv).r * _BaseColor).x;
                clip(r - 0.5);
                return 1;
            }
            
            ENDHLSL
        }
    }
}

R値が0.5より小さい部分をクリッピングするだけのシンプルなシェーダです。
フラグメントシェーダだけHLSLINCLUDEに入れていないのは次節の伏線です。

シーンビューに対応する

さてそれでは前節のシェーダをシーンビューに対応します。
シーンビューに対応するには、LightModeが異なるパスを二つ追加します。
アウトラインを決めるためのSceneSelectionPassと、選択領域を決めるためのPickingパスです。

Shader "ScenePathTest"
{
    Properties
    {
        [MainTexture] _BaseMap("Base Map", 2D) = "white" {}
        [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "IgnoreProjector" = "True"
        }
        
        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

        struct Attributes
        {
            float4 positionOS : POSITION;
            float2 uv : TEXCOORD0;
        };

        struct Varyings
        {
            float4 positionHCS : SV_POSITION;
            float2 uv : TEXCOORD0;
        };

        sampler2D _BaseMap;
        
        CBUFFER_START(UnityPerMaterial)
        float4 _BaseMap_ST;
        half4 _BaseColor;
        CBUFFER_END

        Varyings vert(Attributes IN)
        {
            Varyings OUT;
            OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
            OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
            return OUT;
        }
        
        ENDHLSL

        Pass
        {
            HLSLPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            half4 frag(Varyings IN) : SV_Target
            {
                const float r = (tex2D(_BaseMap, IN.uv).r * _BaseColor).x;
                clip(r - 0.5);
                return 1;
            }
            
            ENDHLSL
        }
        
        // シーンビューにおけるアウトライン領域を決めるためのパス
        Pass
        {
            Tags { "LightMode" = "SceneSelectionPass" }

            BlendOp Add
            Blend One Zero
            ZWrite On
            Cull Off

            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            
            float _ObjectId;
            float _PassValue;
            
            half4 frag(Varyings IN) : SV_Target
            {
                const float r = (tex2D(_BaseMap, IN.uv).r * _BaseColor).x;
                // アウトライン対象じゃない部分はクリップ
                clip(r - 0.5);
                // R値に_ObjectIdを、G値に_PassValueを入れて返す
                return float4(_ObjectId, _PassValue, 1, 1);
            }
            
            ENDHLSL
        }
        
        // シーンビューにおける選択可能領域を決めるためのパス
        Pass
        {
            Tags{ "LightMode" = "Picking" }

            BlendOp Add
            Blend One Zero
            ZWrite On
            Cull Off

            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            float4 _SelectionID;
            
            half4 frag(Varyings IN) : SV_Target
            {
                const float r = (tex2D(_BaseMap, IN.uv).r * _BaseColor).x;
                // 選択領域対象じゃない部分はクリップ
                clip(r - 0.5);
                // _SelectionIDを返す
                return _SelectionID;
            }
            
            ENDHLSL
        }
    }
}

説明が必要な部分はコメントに記載しました。

結果

このシェーダをアサインしたオブジェクトをシーンビューに表示した結果は以下の通りです。

f:id:halya_11:20210902135021g:plain
シーンビュー対応

アウトラインと選択領域が正常に処理できていることを確認できました。