LoginSignup
8
10

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-11


3Dモデルにアタッチすることで「液体の様にモデルをくり抜くことが出来るシェーダ」を作成したのでコードを貼っておきます。
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空間に置いてみても良いかもしれません。

8
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
10