Model mesh shadow of Shader in Unity

Article directory

  • Preface
  • 1. Grid Shadow Principle
    • 1. In the world space, compress the character model into a patch on the Y axis, and modify the color to resemble the color of the shadow.
    • 2. Move the compressed patch to a suitable position and stagger the model and shadow patch.
    • 3. The shadows that are close to the feet have less offset, and the shadows that are far from the feet have more offsets.
  • 2. Advantages and Disadvantages of Grid Shadows
    • advantage:
    • shortcoming:
  • 3. Implementation of model mesh shadows
    • 1. In the first Pass of SubShader of LOD 400, we only retain the most basic model rendering effect
    • 2. Then continue to add a Pass to achieve model mesh shadowing

Foreword

The model mesh shadow of Shader in Unity is generally used to simulate the appearance of shadows in low configuration settings to save performance. (Suitable for games with relatively flat ground)

1. Grid shadow principle

1. In the world space, compress the character model into a patch on the Y axis, and change the color to resemble the color of the shadow

2. Move the compressed patch to the appropriate position and stagger the model and shadow patch

3. The shadows that are close to the feet have less offset, and the shadows that are far from the feet have more offsets

2. Advantages and Disadvantages of Grid Shadows

Advantages:

1. Simple to implement
2. The effect is real-time and clear

Disadvantages:

1. Two Passes are required for rendering
2. It is easy to wear when the ground is undulating.

3. Implementation of model mesh shadow

We continue to use the Shader from the previous article for testing

  • ShaderLOD of Shader in Unity

We implement grid shading in the Shader provided in the middle

Two Passes are required, one Pass implements basic rendering, and the other Pass implements model mesh shading.

1. In the first Pass of SubShader of LOD 400, we only retain the most basic model rendering effect

Pass
        {
            //Tags{"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragment

            //#pragma multi_compile_fwdbase
            //Eliminate useless variants
            //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON
            //Define yourself, the variant you need to use for shadows
            
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            
            sampler2D _MainTex;
            float _Clip;
            sampler2D _DissolveTex;
            //This four-dimensional vector, xyzw represents the xy of Tilling and Offset respectively. The naming method is to add _ST after the texture name.
            float4 _DissolveTex_ST;


            //Because when using the gradient texture, only the u coordinate of the gradient texture is used, so replace sampler2D with sampler
            sampler _RampTex;

            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
            };
            
            
            struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                
                float4 worldPos :TEXCOORD1;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                
                //In order to reduce the value passed in, we do not create new variables for storage, but change uv to a four-dimensional vector.
                //Use xy of o.uv to store the original character map
                //Use zw of o.uv to store the scaled and offset values of the noise map
                o.uv.xy = v.uv.xy;
                //o.uv.zw = v.uv * _DissolveTex_ST.xy + _DissolveTex_ST.zw;
                o.uv.zw = TRANSFORM_TEX(v.uv,_DissolveTex);

                TRANSFER_SHADOW(o)
                //Convert the vertices to world space
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv.xy);
                
                //Texture obtained externally needs to be sampled before use
                fixed4 dissolveTex = tex2D(_DissolveTex,i.uv.zw);
                
                //fragment selection
                clip(dissolveTex.r - _Clip);

                //Perform normalization
                fixed4 dissolveValue = saturate((dissolveTex.r - _Clip) / (_Clip + 0.1 - _Clip));

                fixed4 rampTex = tex1D(_RampTex,dissolveValue.r);

                //col + = rampTex;
                return col;
            }
            ENDCG
        }

2. Then continue to add a Pass to achieve model mesh shadow

  1. Because the things we need to modify are only related to vertices, so when the application passes in and the vertices are passed into the fragment, we only need to pass in the vertex information.

struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 pos : SV_POSITION;
};

  1. Compress the model into a surface (in order to prevent the angle of the surface from changing, it needs to be converted to world space and compressed)

  2. When compressing, just modify the y value in world space

  3. To convert vertex information from world space to clipping space, use UNITY_MATRIX_VP

v2f vert(appdata v)
{
v2f o;
//Compress the model vertices in world space to prevent the angle from changing after compression.
float4 worldPos = mul(unity_ObjectToWorld,v.vertex);
worldPos.y = 0.001;
//Convert to coordinates in clipping space. Use UNITY_MATRIX_VP to convert from world space to clipping space.
o.pos = mul(UNITY_MATRIX_VP,worldPos);
return o;
}

  1. Because the position of the character’s feet is not static, you need to define variables to receive the real-time position of the character’s feet.

_Shadow(“Shadow”,Float) = 0.001 Use this value to replace the 0.001 above

The effect after replacement (the position of shadow generation can be modified):
Please add image description

  1. Add and subtract values to the xz value in world space to achieve shadow offset

worldPos.xz + = float2(-1,2);

  1. After the offset, it was found that the offset value was not as close to the foot as the offset was, but as it was far away from the foot, the offset was large.

To achieve this effect, you only need to multiply the plus and minus offset values by the y value of the character with the feet as the origin of the coordinates.
However, the origin of our model is at the center of the model, so we need to obtain the y value from the feet as the origin of the coordinates
Just use the y value of the character minus the y value of the previous shadow.

worldPos.xz + = float2(-1,2) * (worldPosY – _Shadow);

Effect after implementation:

  1. After achieving the above effect, our shadow angle also needs to be modified, so change the previously defined shadow position variable to a four-dimensional vector. xz controls the shadow offset angle, y controls the shadow position, and z controls the shadow transparency.

(The reason for using four-dimensional vectors is to save performance. To make the shadow transparent, you need to modify the blending mode to see the effect)

In the property bar:

_Shadow(“Offset(XZ) Height(Y) Alpha(W)”,Vector) = (-1,0.001,1,0)

In SubShader:

Blend SrcAlpha OneMinusSrcAlpha

In vertex shader and fragment shader:

v2f vert(appdata v)
            {
                v2f o;
                //After compressing the model vertices in world space, prevent the angle from changing after compression.
                float4 worldPos = mul(unity_ObjectToWorld,v.vertex);

                float worldPosY = worldPos.y;
                    
                worldPos.y = _Shadow.y;
                worldPos.xz + = _Shadow.xz * (worldPosY - _Shadow.y);
                //Convert to coordinates in clipping space. Use UNITY_MATRIX_VP to convert from world space to clipping space.
                o.pos = mul(UNITY_MATRIX_VP,worldPos);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = 0;

                col.a = _Shadow.w;
                
                return col;
                
            }

Effect after modification:
Please add image description
However, we will find that after making the shadow transparent, the compressed model will have the effect of the other side of the model. This situation is exactly the same as the previous template test, which can be solved by using the template test.

//Use template testing to prevent the transparent model from seeing the subsequent effects (after comparison, directly replace those that are not equal)
Stencil
{
//This value is currently taken casually
Ref 100
//If it is not equal to the above value
Comp NotEqual
//replacement
Pass Replace
}

Final code:

//Grid shadow principle
Shader "MyShader/P1_7_6"
{
    Properties
    {
        [Enum(Off,0,On,1)]_ZWrite("ZWrite",int) = 0
        [Enum(UnityEngine.Rendering.CompareFunction)]_ZTest("ZTest",int) = 0
        //Use this tag to expose external attributes and have titles.
        [Header(Base)]
        [NoScaleOffset]_MainTex ("Texture", 2D) = "white" {}
        _Clip("Clip",Range(0,1)) = 0
        //Use this tag to add a gap between two rows of exposed attributes
        [Space(10)]
        [Header(Dissolve)]
        _DissolveTex("DissolveTex",2D) = "black"{}

        [NoScaleOffset]_RampTex("RampTex(RGB)",2D) = "black" {}
        
        [Header(Shadow)]
        _Shadow("Offset(XZ) Height(Y) Alpha(W)",Vector) = (-1,0.001,1,0)
        
    }
    SubShader
    {
        Tags{"Queue" = "Geometry"}
        LOD 600
        
        Blend Off
        Cull Back
        /*ZWrite [_ZWrite]
        
        ZTest [_ZTest]*/
        
        Offset -1,-1
        
        UsePass "MyShader/P1_6_4/XRay"
        
        Pass
        {
            //Tags{"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragment

            //#pragma multi_compile_fwdbase
            //Eliminate useless variants
            //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON
            //Define yourself, the variant you need to use for shadows
            #pragma multi_compile DIRECTIONAL SHADOWS_SCREEN
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            
            sampler2D _MainTex;
            float _Clip;
            sampler2D _DissolveTex;
            //This four-dimensional vector, xyzw represents the xy of Tilling and Offset respectively. The naming method is to add _ST after the texture name.
            float4 _DissolveTex_ST;


            //Because when using the gradient texture, only the u coordinate of the gradient texture is used, so replace sampler2D with sampler
            sampler _RampTex;

            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
            };
            
            //1. Add UNITY_SHADOW_COORDS(idx) in v2f, unity will automatically declare a float4 variable called _ShadowCoord, which is used as the sampling coordinates of the shadow.
            struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                UNITY_SHADOW_COORDS(1)
                float4 worldPos :TEXCOORD2;
            };
            //2. Add TRANSFER_SHADOW(o) in the vertex shader, which is used to transform the _ShadowCoord texture sampling coordinates defined above to the corresponding screen space texture coordinates for sampling shadow textures.
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                
                //In order to reduce the value passed in, we do not create new variables for storage, but change uv to a four-dimensional vector.
                //Use xy of o.uv to store the original character map
                //Use zw of o.uv to store the scaled and offset values of the noise map
                o.uv.xy = v.uv.xy;
                //o.uv.zw = v.uv * _DissolveTex_ST.xy + _DissolveTex_ST.zw;
                o.uv.zw = TRANSFORM_TEX(v.uv,_DissolveTex);

                TRANSFER_SHADOW(o)
                //Convert the vertices to world space
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                
                return o;
            }
            //3. Add UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos) in the fragment shader, where atten stores the sampled shadow.
            fixed4 frag (v2f i) : SV_Target
            {

                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
                
                fixed4 col = tex2D(_MainTex, i.uv.xy);

                //Multiply the shadow and texture
                col *= atten;
                
                //Texture obtained externally needs to be sampled before use
                fixed4 dissolveTex = tex2D(_DissolveTex,i.uv.zw);
                
                //fragment selection
                clip(dissolveTex.r - _Clip);

                //Perform normalization
                fixed4 dissolveValue = saturate((dissolveTex.r - _Clip) / (_Clip + 0.1 - _Clip));

                fixed4 rampTex = tex1D(_RampTex,dissolveValue.r);

                //col + = rampTex;
                return col;
            }
            ENDCG
        }
        //Shadow casting
        Pass
        {
            //1. Set "LightMode" = "ShadowCaster"
            Tags{"LightMode" = "ShadowCaster"}
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment fragment
            //Need to add a Unity variant
            #pragma multi_compile_shadowcaster

            
            #include "UnityCG.cginc"

            //Declare variables used for ablation
            float _Clip;
            sampler2D _DissolveTex;
            float4 _DissolveTex_ST;
            
            //2. Declare float4 vertex:POSITION; and half3 normal:NORMAL; in appdata. This is the semantics required to generate shadows.
            //Note: In the appdata part, we almost don't need to modify the name and corresponding type.
            //Because many methods encapsulated in Unity use these standard names.
            struct appdata
            {
                float4 vertex:POSITION;
                half3 normal:NORMAL;
                float4 uv:TEXCOORD;
            };
            //3. Add V2F_SHADOW_CASTER in v2f; used to declare the data that needs to be transferred to the fragment.
            struct v2f
            {
                float4 uv : TEXCOORD;
                V2F_SHADOW_CASTER;
            };
            //4. Add TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) in the vertex shader, mainly to calculate the offset of the shadow to solve the incorrect Shadow Acne and Peter Panning phenomena.
            v2f vert(appdata v)
            {
                v2f o;
                o.uv.zw = TRANSFORM_TEX(v.uv,_DissolveTex);
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
                return o;
            }
            //5. Add SHADOW_CASTER_FRAGMENT(i) in the fragment shader
            
            fixed4 frag(v2f i) : SV_Target
            {
                //Texture obtained externally needs to be sampled before use
                fixed4 dissolveTex = tex2D(_DissolveTex,i.uv.zw);
                
                //fragment selection
                clip(dissolveTex.r - _Clip);
                
                SHADOW_CASTER_FRAGMENT(i);
            }
            ENDCG
        }
    }
    SubShader
    {
        Blend SrcAlpha OneMinusSrcAlpha
        LOD 400
        //Use template testing to prevent the transparent model from seeing the subsequent effects (after comparison, directly replace those that are not equal)
        Stencil
        {
            //This value is currently taken casually
            Ref 100
            //If it is not equal to the above value
            Comp NotEqual
            //replacement
            Pass Replace
        }
        
        Pass
        {
            //Tags{"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragment

            //#pragma multi_compile_fwdbase
            //Eliminate useless variants
            //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON
            //Define yourself, the variant you need to use for shadows
            
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            
            sampler2D _MainTex;
            float _Clip;
            sampler2D _DissolveTex;
            //This four-dimensional vector, xyzw represents the xy of Tilling and Offset respectively. The naming method is to add _ST after the texture name.
            float4 _DissolveTex_ST;


            //Because when using the gradient texture, only the u coordinate of the gradient texture is used, so replace sampler2D with sampler
            sampler _RampTex;

            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
            };
            
            
            struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                
                float4 worldPos :TEXCOORD1;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                
                //In order to reduce the value passed in, we do not create new variables for storage, but change uv to a four-dimensional vector.
                //Use xy of o.uv to store the original character map
                //Use zw of o.uv to store the scaled and offset values of the noise map
                o.uv.xy = v.uv.xy;
                //o.uv.zw = v.uv * _DissolveTex_ST.xy + _DissolveTex_ST.zw;
                o.uv.zw = TRANSFORM_TEX(v.uv,_DissolveTex);

                TRANSFER_SHADOW(o)
                //Convert the vertices to world space
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv.xy);
                
                //Texture obtained externally needs to be sampled before use
                fixed4 dissolveTex = tex2D(_DissolveTex,i.uv.zw);
                
                //fragment selection
                clip(dissolveTex.r - _Clip);

                //Perform normalization
                fixed4 dissolveValue = saturate((dissolveTex.r - _Clip) / (_Clip + 0.1 - _Clip));

                fixed4 rampTex = tex1D(_RampTex,dissolveValue.r);

                //col + = rampTex;
                return col;
            }
            ENDCG
        }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragment
            #include "UnityCG.cginc"
            
            float4 _Shadow;
            
            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            v2f vert(appdata v)
            {
                v2f o;
                //After compressing the model vertices in world space, prevent the angle from changing after compression.
                float4 worldPos = mul(unity_ObjectToWorld,v.vertex);

                float worldPosY = worldPos.y;
                    
                worldPos.y = _Shadow.y;
                worldPos.xz + = _Shadow.xz * (worldPosY - _Shadow.y);
                //Convert to coordinates in clipping space. Use UNITY_MATRIX_VP to convert from world space to clipping space.
                o.pos = mul(UNITY_MATRIX_VP,worldPos);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = 0;

                col.a = _Shadow.w;
                
                return col;
                
            }
            
            ENDCG
            
        }
    }
    // Fallback "Legacy Shaders/VertexLit"
}

final effect:
Please add image description