【Unity】サーフェスシェーダーで Grab Pass を使うと出るエラーを解消する

:::note info
Unity 2022.3.22f1 における内容です。
:::

# 概要

サーフェスシェーダーで Grab Pass を使うときに、Unity のマクロ `UNITY_DECLARE_SCREENSPACE_TEXTURE` を使うとエラーが出ます。マクロを使わず普通に `sampler2D` で宣言すれば解決するのですが、気持ち悪いので原因と対策を考えます。

# 問題
サーフェスシェーダーでももちろん Grab Pass は使えます。例えばこんな感じです。

```hlsl:SurfaceGrab.shader
Shader "Test/SurfaceGrab"
{
    Properties
    {
        _Shift ("Shift", Range(-1, 1)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue" = "Overlay" }

        GrabPass { "_MyGrabTex" }

        CGPROGRAM
        #pragma surface surf Standard vertex:vert
        #pragma multi_compile_instancing
        
        #include "UnityCG.cginc"
        // #include "OverrideUnity.cginc"  // 後で使います

        struct Input
        {
            float4 grabPos;
            UNITY_VERTEX_OUTPUT_STEREO
        };

        float _Shift;

        UNITY_DECLARE_SCREENSPACE_TEXTURE( _MyGrabTex );

        void vert ( inout appdata_full v, out Input o )
        {
            UNITY_SETUP_INSTANCE_ID(v);
            UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

            // スクリーン座標の計算
            o.grabPos = ComputeGrabScreenPos(UnityObjectToClipPos(v.vertex));
        }
        
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            float2 grabPos = IN.grabPos.xy / IN.grabPos.w;
            grabPos.x += _Shift;
            float4 grabTex = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MyGrabTex, grabPos);
            
            o.Albedo = grabTex.rgb;
        }

        ENDCG
    }
}
```

ただ、これをコンパイルするとこんなエラーが出ます。

> Shader error in 'Test/SurfaceGrab': Unexpected token ';'. Expected one of: typedef const void inline uniform nointerpolation extern shared static volatile row_major column_major struct or a user-defined type at line 28

曰く、`UNITY_DECLARE_SCREENSPACE_TEXTURE( _MyGrabTex )` の後ろに `;` がついてるのはおかしい、とのことなので、`;` を削除してもう一度コンパイルしてみます。

すると、今度はこんなエラーが出ます。

> Shader error in 'Test/SurfaceGrab': syntax error: unexpected token 'void' at line 30 (on d3d11)

曰く、`UNITY_DECLARE_SCREENSPACE_TEXTURE( _MyGrabTex )` の後ろに `;` がないのはおかしい、とのこと。

うーん、理不尽。

# 原因

どうやら、シェーダーのバリアントによってエラーの内容が変わっているようなので、`UNITY_DECLARE_SCREENSPACE_TEXTURE` がどういうマクロなのかを調べてみます。
`UNITY_DECLARE_SCREENSPACE_TEXTURE` は `HLSLSupport.cginc` で定義されています。

```hlsl:HLSLSupport.cginc

...

#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)

    #undef UNITY_DECLARE_DEPTH_TEXTURE_MS
    #define UNITY_DECLARE_DEPTH_TEXTURE_MS(tex)  UNITY_DECLARE_TEX2DARRAY_MS (tex)

    #undef UNITY_DECLARE_DEPTH_TEXTURE
    #define UNITY_DECLARE_DEPTH_TEXTURE(tex) UNITY_DECLARE_TEX2DARRAY (tex)

    #undef SAMPLE_DEPTH_TEXTURE
    #define SAMPLE_DEPTH_TEXTURE(sampler, uv) UNITY_SAMPLE_TEX2DARRAY(sampler, float3((uv).x, (uv).y, (float)unity_StereoEyeIndex)).r

    #undef SAMPLE_DEPTH_TEXTURE_PROJ
    #define SAMPLE_DEPTH_TEXTURE_PROJ(sampler, uv) UNITY_SAMPLE_TEX2DARRAY(sampler, float3((uv).x/(uv).w, (uv).y/(uv).w, (float)unity_StereoEyeIndex)).r

    #undef SAMPLE_DEPTH_TEXTURE_LOD
    #define SAMPLE_DEPTH_TEXTURE_LOD(sampler, uv) UNITY_SAMPLE_TEX2DARRAY_LOD(sampler, float3((uv).xy, (float)unity_StereoEyeIndex), (uv).w).r

    #undef SAMPLE_RAW_DEPTH_TEXTURE
    #define SAMPLE_RAW_DEPTH_TEXTURE(tex, uv) UNITY_SAMPLE_TEX2DARRAY(tex, float3((uv).xy, (float)unity_StereoEyeIndex))

    #undef SAMPLE_RAW_DEPTH_TEXTURE_PROJ
    #define SAMPLE_RAW_DEPTH_TEXTURE_PROJ(sampler, uv) UNITY_SAMPLE_TEX2DARRAY(sampler, float3((uv).x/(uv).w, (uv).y/(uv).w, (float)unity_StereoEyeIndex))

    #undef SAMPLE_RAW_DEPTH_TEXTURE_LOD
    #define SAMPLE_RAW_DEPTH_TEXTURE_LOD(sampler, uv) UNITY_SAMPLE_TEX2DARRAY_LOD(sampler, float3((uv).xy, (float)unity_StereoEyeIndex), (uv).w)

    #define UNITY_DECLARE_SCREENSPACE_SHADOWMAP UNITY_DECLARE_TEX2DARRAY
    #define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) UNITY_SAMPLE_TEX2DARRAY( tex, float3((uv).x/(uv).w, (uv).y/(uv).w, (float)unity_StereoEyeIndex) ).r

    #define UNITY_DECLARE_SCREENSPACE_TEXTURE UNITY_DECLARE_TEX2DARRAY
    #define UNITY_SAMPLE_SCREENSPACE_TEXTURE(tex, uv) UNITY_SAMPLE_TEX2DARRAY(tex, float3((uv).xy, (float)unity_StereoEyeIndex))
#else
    #define UNITY_DECLARE_DEPTH_TEXTURE_MS(tex)  Texture2DMS<float> tex;
    #define UNITY_DECLARE_DEPTH_TEXTURE(tex) sampler2D_float tex
    #define UNITY_DECLARE_SCREENSPACE_SHADOWMAP(tex) sampler2D tex
    #define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) tex2Dproj( tex, UNITY_PROJ_COORD(uv) ).r
    #define UNITY_DECLARE_SCREENSPACE_TEXTURE(tex) sampler2D_float tex;
    #define UNITY_SAMPLE_SCREENSPACE_TEXTURE(tex, uv) tex2D(tex, uv)
#endif

...

```

`#else` の後を見ると、`UNITY_DECLARE_SCREENSPACE_TEXTURE` の定義の末尾に `;` がついています。（`UNITY_DECLARE_DEPTH_TEXTURE_MS` にもついていますね...）
`#if` 節のほうは `;` がついていないので、この差が理不尽エラーの原因と考えられます。

# 対策

`HLSLSupport.cginc` を改造するのは面倒なので、`UNITY_DECLARE_SCREENSPACE_TEXTURE` マクロだけオーバーライドすることにします。
こんな感じのファイルを作ります。（ついでに `UNITY_DECLARE_DEPTH_TEXTURE_MS` のほうも変更することにします。）

```hlsl:OverrideUnity.cginc
#ifndef _OVERRIDE_UNITY_INCLUDED_
#define _OVERRIDE_UNITY_INCLUDED_

#if (!(defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)))
    #undef UNITY_DECLARE_DEPTH_TEXTURE_MS
    #define UNITY_DECLARE_DEPTH_TEXTURE_MS(tex)  Texture2DMS<float> tex
    #undef UNITY_DECLARE_SCREENSPACE_TEXTURE
    #define UNITY_DECLARE_SCREENSPACE_TEXTURE(tex) sampler2D_float tex
#endif

#endif  // _OVERRIDE_UNITY_INCLUDED_

```

これを、冒頭の `SurfaceGrab.shader` の `#include "UnityCG.cginc"` の直後にインクルードすれば、マクロが書き換えられてコンパイルが通るようになります。

# 雑記

これは... バグなんでしょうか？あからさまなので意図的なような気がしますが、コンパイルが通らないなら変えるしかないですよね...