3Dオブジェクトを液体のようにクリップするシェーダ

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">よっしゃ、ピクセル単位での対応も出来た！<br>これで回転もいけるね！ <a href="https://t.co/IF587SmLsq">pic.twitter.com/IF587SmLsq</a></p>&mdash; MIYAKE (@ScreenPocket) <a href="https://twitter.com/ScreenPocket/status/1359872362871623680?ref_src=twsrc%5Etfw">February 11, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
3Dモデルにアタッチすることで「液体の様にモデルをくり抜くことが出来るシェーダ」を作成したのでコードを貼っておきます。

```glsl:LiquidPixel.shader
Shader "ScreenPocket/3d/LiquidPixel"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        [Header(Shape)]
        _HeightMax ("Height Max", Float) = 1.0
        _HeightMin ("Height Min", Float) = 0.0
        _TopColor ("Top Color", Color) = (0.5,0.75,1,1)
        _BottomColor ("Bottom Color", Color) = (0,0.25,1,1)
        [Header(Wave)]
        _WaveSpeed ("Wave Speed", Float) = 1.0
        _WavePower ("Wave Power", Float) = 0.1
        _WaveLength ("Wave Length", Float) = 1.0
        [Header(Rim)]
        _RimColor ("Rim Light Color", Color) = (1,1,1,1)
        _RimPower("Rim Light Power", Float) = 3
        [Header(Surface)]
        _SurfaceColor ("Surface Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque"}
        LOD 100
        
        //先に前面を描く事でZテストで描画が少し軽くなる
        Pass
        {          
            ZWrite On
            ZTest LEqual
            Blend Off
            Cull Back
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float3 viewDir : TEXCOORD2;
                float4 worldPos : TEXCOORD3;
                float3 normal : NORMAL;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            half4 _TopColor;
            half4 _BottomColor;
            half4 _RimColor;
            float _RimPower;
            half4 _SurfaceColor;
            float _HeightMax;
            float _HeightMin;
            float _WaveSpeed;
            float _WavePower;
            float _WaveLength;

            v2f vert (appdata v)
            {
                v2f o;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex );
                o.vertex = mul(UNITY_MATRIX_VP,o.worldPos);

                o.normal = float4(UnityObjectToWorldNormal(v.normal),0);
                
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                //リムライト用
                o.viewDir = normalize(_WorldSpaceCameraPos - o.worldPos.xyz);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                float height = _HeightMax + sin( (i.worldPos.x + i.worldPos.z) * _WaveLength + _Time.w * _WaveSpeed) * _WavePower;
                //くり抜く
                clip(height - i.worldPos.y);

                half4 col = tex2D(_MainTex, i.uv);

                //グラデーションで色付け
                float rate = saturate((i.worldPos.y - _HeightMin) / (_HeightMax - _HeightMin));
                col.rgb *= lerp( _BottomColor.rgb, _TopColor.rgb, rate);

                //リムライト
                float rim = saturate(1 - dot(i.normal, i.viewDir));
                col.rgb += saturate(pow(rim, _RimPower) * _RimColor);
                
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
        
        //背面を描く。単色で塗って切断面かのように見せかける
        Pass
        {          
            ZWrite On
            ZTest LEqual
            Blend Off
            Cull Front
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                UNITY_FOG_COORDS(0)
                float4 worldPos : TEXCOORD3;
                float4 vertex : SV_POSITION;
            };

            half4 _SurfaceColor;
            float _HeightMax;
            float _HeightMin;
            float _WaveSpeed;
            float _WavePower;
            float _WaveLength;

            v2f vert (appdata v)
            {
                v2f o;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex );
                o.vertex = mul(UNITY_MATRIX_VP,o.worldPos);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                float height = _HeightMax + sin( (i.worldPos.x + i.worldPos.z) * _WaveLength + _Time.w * _WaveSpeed) * _WavePower;
                //くり抜く
                clip(height - i.worldPos.y);

                half4 col = _SurfaceColor;
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}
```

ポイントは

* 2パス描画して前面、背面を描画している
* _HeightMaxより上を基準に波で高さをずらしつつクリップ。_HeightMaxから_HeightMinにかけてTopColorとBottomColorでグラデーション色付けをしている
* 質感表現の為にリムライトも考慮。単色で良いなら削っても良い
* 切断面は面を貼っているわけではなく、背面を単色で塗りつぶして切断面に見せかけている
* _HeightMax,_HeightMinはどちらもWorld座標Y。モデルを動かす際は座標値とパラメータを同期させること。複数を同時に扱う場合はMaterialPropertyBlockを駆使しましょう。

でしょうか。
切断面については実際に面を貼っていないので、
深度バッファを扱う際に、「切断面の奥行きが背面の深度になってしまう」ので、そこだけ扱いを注意する必要があります。

--------

HPやMPなどのゲージ表現で使えそうですね…！
3DだけでなくUI空間に置いてみても良いかもしれません。