Shader shadow reception in Unity

Article directory

  • Preface
  • 1. Steps for Shadow Acceptance
    • 1. Add UNITY_SHADOW_COORDS (idx) to v2f. Unity will automatically declare a float4 variable called _ShadowCoord, which is used as the sampling coordinates of the shadow.
    • 2. Add TRANSFER_SHADOW(o) in the vertex shader, which is used to transform the _ShadowCoord texture sampling coordinates defined above into the corresponding screen space texture coordinates for sampling shadow textures.
    • 3. Add UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos) in the fragment shader, where atten stores the sampled shadow.
    • 4. Add the required macros
  • 2. Supplement
    • 1. In the scene, when the camera is rotated or the camera is zoomed in, the shadow will disappear from time to time. This is because we have not turned on the lighting mode correctly.
    • 2. The shadows in the scene are pure black, so black that the original color cannot be seen.
    • 3. To correctly accept shadows, it is best to turn on ShadowCaster in shadow casting.

Foreword

Reception of Shader shadows in Unity (based on the previous article)

  • Shader shadow casting in Unity

1. Steps for shadow acceptance

This is the effect before shadow reception is written (although shadow reception is checked, there is no shadow effect)

sample shadow
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.
2. Add TRANSFER_SHADOW(o) in the vertex shader, which is used to transform the _ShadowCoord texture sampling coordinates defined above into the corresponding screen space texture coordinates for sampling shadow textures.
3. Add UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos) in the fragment shader, where atten stores the sampled shadow.

Because it accepts shadows, the effect is written in the Pass that implements the effect

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.

AutoLighting.cginc needs to be introduced here

#include “AutoLight.cginc”

Looking at the source code, we can see why it is a float4 variable called _ShadowCoord

UNITY_SHADOW_COORDS(1)

2. Add TRANSFER_SHADOW(o) in the vertex shader to transform the _ShadowCoord texture sampling coordinates defined above into the corresponding screen space texture coordinates for sampling shadow textures.

TRANSFER_SHADOW(o)

3. Add UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos) in the fragment shader, where atten stores the sampled shadow.

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)

Before using it, you need to get i.worldPos

  • First, in v2f, define a float4 to store worldPos

float4 worldPos :TEXCOORD2;

  • Then, in the vertex shader, convert the vertex information into world space

o.worldPos = mul(unity_ObjectToWorld,v.vertex);

  • Finally, use UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos) in the fragment shader

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)

Now let’s output and see the effect (you will find that the effect is wrong, only white)

return atten;


At this time, let’s compare the difference between the built-in Shader and the Shader we wrote.
The built-in Shader has more SHADOWS_SCREEN macros than our Shader.


4. Add the required macros

  • Method 1, use a more comprehensive macro to define (a waste of performance)

#pragma multi_compile_fwdbase novertexlight nodynlightmap nodirlightmap
Defined in the Pass of LightMode = ForwardBase, this Pass only holds one parallel light (pixel-by-pixel) and other per-vertex lights and SH. The function of this instruction is to generate various built-in macros that Unity needs in ForwardBase at once. .
DIRECTIONAL DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SCREEN SHADOWS_SHADOWMASK VERTEXLIGHT_ON

  1. DIRECTIONAL: The effect under the main parallel light is turned on, and the macro must be turned on under the forwardBase
  2. DIRLIGHTMAP_COMBINED: DirecitonalMode in the baking interface is set to Directional
  3. DYNAMICLIGHTMAP_ON :RealtimeGI is on
  4. LIGHTMAP_ON: turned on when the object is marked as LightMap Static and the scene is baked
  5. LIGHTMAP_SHADOW_MIXING: Enabled when the light is set to Mixed and the lighting baking mode is set to Subtractive or shadowMask. It is invalid in Baked Indirect.
  6. LIGHTPROBE_SH: Turn on the light probe. Dynamic objects will be affected by LightProbe. Static objects are not related to this.
  7. SHADOWS_SCREEN: When the hardware supports screen shadows, turn it on when processing shadows at the same time.
  8. SHADOWS_SHADOWMASK: Enabled when the lighting is set to Mixed and the lighting baking mode is set to shadowMask
  9. VERTEXLIGHT_ON: Whether to be illuminated on a per-vertex basis

After using it, we will find that the little fox can accept shadows
Please add image description
Then, we multiply the atten value with the sampled texture value and output it.
Please add image description
We will find that the shadow is darker, which can be adjusted in the light or modified later using GI.

Although, we have achieved the shadow effect
However, using this comprehensive macro is particularly performance-intensive


As you can see, there are an astonishing 86 variants of this Shader, but they are not used. This is extremely wasteful, so we need to eliminate useless variants

#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

After elimination, only 5 variants remain.

  • Method 2: Define the required variants yourself

#pragma multi_compile DIRECTIONAL SHADOWS_SCREEN


You can see that our number of variants is still 5, and the effect is the same

Please add image description

Test code:

Shader "MyShader/P1_7_3"
{
    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" {}
        
    }
    SubShader
    {
        Tags{"Queue" = "Geometry"}
        Blend Off
        Cull Back
        /*ZWrite [_ZWrite]
        
        ZTest [_ZTest]*/
        
        Offset -1,-1
        
        UsePass "MyShader/P1_6_4/XRay"
        
        Pass
        {
            
            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
        }
    }
}

2. Supplement

1. In the scene, when the camera is rotated or the camera is zoomed in, the shadow will disappear from time to time. This is because we have not turned on the lighting mode correctly

Tags{“LightMode”=“ForwardBase”}

2. The shadows in the scene are pure black, so dark that the original color cannot be seen

Please add image description
Can be adjusted at the light or modified later using GI

3. To receive shadows correctly, it is best to turn on ShadowCaster in shadow casting

Tags{“LightMode” = “ShadowCaster”}

,