HDRP SHADOW_ATTENUATION always 1 in Shader

Hi there. I am using Unity 2019.4 LTS.

I want to receive shadows.

What I tried:

I got shadows working by adding FallBack "HDRP/Lit" but I cannot receive shadows.

shadow attenuation is always 1 in my fragment shader.

SHADOW_ATTENUATION(i)

As you can see, world space coordinates seem to work using:

col.r = i.worldPosThing.x / 10;
col.g = i.worldPosThing.y / 10;
col.b = i.worldPosThing.z / 10;


But this is always white (and LIGHT_ATTENUATION is black…)

col.r = SHADOW_ATTENUATION(i);
col.g = SHADOW_ATTENUATION(i);
col.b = SHADOW_ATTENUATION(i);

Question: Can anybody aid me on how to receive shadows in hdrp?

Because LIGHT_ATTENUATION is a legacy pipeline macros. There isnt a macros like that in HDRP. For forward you’re supposed to use LightLoop function (com.unity.render-pipelines.high-definition\Runtime\Lighting\LightLoop\LightLoop.hlsl), which calculates all the lighting/shadowing.

Also you shouldnt use forward for drawing opaque things anyway, and instead use deferred (“LightMode” = “GBuffer”). With deferred you do not need to worry about lighting at all, since its gonna be lit as a part of regular deferred lighting path, you just need to supply the surface data (albedo, normals, smoothness, specular, etc.)

@customphase thanks replying. I now used this:

Tags { "LIGHTMODE" = "GBuffer" "QUEUE" = "Geometry+0" "RenderType" = "HDLitShader" "RenderPipeline" = "HDRenderPipeline" }

But my meshes are a transparent now, also when I get too close or too far away, they disappear and it looks like the buffer isn’t clearing correctly…

I simplified my shader to this, just to replicate the issue:

Shader "Custom/Grass" {
    Properties{
        _TintColor1("Tint Color 1", Color) = (1, 1, 1, 1)
        _TintColor2("Tint Color 2", Color) = (1, 1, 1, 1)
        _ColorHeight("Color Height", Range(0, 1)) = .3
    }

    SubShader{
       
        Pass {
            Tags { "LIGHTMODE" = "GBuffer" "QUEUE" = "Geometry+0" "RenderType" = "HDLitShader" "RenderPipeline" = "HDRenderPipeline" }
            //ZWrite On
            //Cull Back
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            #pragma multi_compile_instancing

            #include "UnityCG.cginc"

            struct Input {
                float4 vertex: POSITION;
                float localPos : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f {
                float4 vertex: SV_POSITION;
                float localPos : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            float4 _TintColor1;
            float4 _TintColor2;
            float _ColorHeight;

            UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(fixed4, _CollisionBending)
            UNITY_INSTANCING_BUFFER_END(Props)

            v2f vert(Input input) {
                v2f o;
                UNITY_INITIALIZE_OUTPUT(v2f, o);
                UNITY_SETUP_INSTANCE_ID(input);

                o.localPos = input.vertex.y;
                o.vertex = UnityObjectToClipPos(input.vertex);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed4 col = lerp(_TintColor1, _TintColor2, dot(_ColorHeight, i.localPos.x));
                col.a = 1;
                return col;
            }
            ENDCG
        }
    }
        FallBack "HDRP/Lit"
}

You also need a DepthOnly pass and a ShadowCaster pass.

Also im pretty sure that tags are case-sensitive, and it should be LightMode, not LIGHTMODE, and Queue, not QUEUE.

Also Queue and RenderType tags are subshader tags, not pass tags.

@customphase Thanks, I will try to implement the additional passes.
I usually use the Tags case-sensitive, but I copied this from Unity. I created a Graph, clicked “compile and show code”. It’s over 1 million lines, so I’m glad you help me with this. But to get the syntax for some things it’s helpful to look into the generated code.

@customphase I had no luck implementing the DepthOnly pass, it still looks the same :confused: No errors showing, it’s not pink. I’m a bit lost without any working reference…

Can you take a look?

Shader "Custom/Grass" {
    Properties{
        _TintColor1("Tint Color 1", Color) = (1, 1, 1, 1)
        _TintColor2("Tint Color 2", Color) = (1, 1, 1, 1)
        _ColorHeight("Color Height", Range(0, 1)) = .3
    }

    SubShader{
       
        Pass {
            Tags { "LightMode" = "GBuffer" "Queue" = "Geometry+0" "RenderType" = "HDLitShader" "RenderPipeline" = "HDRenderPipeline" }
            //ZWrite On
            //Cull Back
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            #pragma multi_compile_instancing

            #include "UnityCG.cginc"

            struct Input {
                float4 vertex: POSITION;
                float localPos : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f {
                float4 vertex: SV_POSITION;
                float localPos : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            float4 _TintColor1;
            float4 _TintColor2;
            float _ColorHeight;

            UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(fixed4, _CollisionBending)
            UNITY_INSTANCING_BUFFER_END(Props)

            v2f vert(Input input) {
                v2f o;
                UNITY_INITIALIZE_OUTPUT(v2f, o);
                UNITY_SETUP_INSTANCE_ID(input);

                o.localPos = input.vertex.y;
                o.vertex = UnityObjectToClipPos(input.vertex);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed4 col = lerp(_TintColor1, _TintColor2, dot(_ColorHeight, i.localPos.x));
                col.a = 1;
                return col;
            }
            ENDCG
        }
        Pass {
            Name "DepthOnly"
            Tags { "LightMode" = "DepthOnly" "Queue" = "Geometry+0" "RenderType" = "HDLitShader" "RenderPipeline" = "HDRenderPipeline" }
            ColorMask 0
            ZWrite On

            HLSLPROGRAM

            #pragma target 3.5

            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            #pragma vertex DepthOnlyPassVertex
            #pragma fragment DepthOnlyPassFragment

            struct VertexInput {
                float4 pos : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct VertexOutput {
                //float4 clipPos : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            VertexOutput DepthOnlyPassVertex(VertexInput input) {
                VertexOutput output;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));
                //output.clipPos = mul(unity_MatrixVP, worldPos);
                return output;
            }

            float4 DepthOnlyPassFragment(VertexOutput input) : SV_TARGET{
                //UNITY_SETUP_INSTANCE_ID(input);
                return 0;
            }

            ENDHLSL
        }
    }
    FallBack "HDRP/Lit"
}

Why did you comment out clip position output in the depth only pass? Clip position is absolutely required in ANY pass output, its what defines where the pixels would be drawn. The depthOnly pass should basically be a copy of a gBuffer pass, just with return 0; in the fragment program.

Hey @customphase - I got a bit of progress.

I added the “Stencil { … }” part. I got the mesh visible and also shadows (yay!) but the effect of shine-through is still there (the buffers not clearing correctly)

Shader "Custom/Grass" {
    Properties{
        _TintColor1("Tint Color 1", Color) = (1, 1, 1, 1)
        _TintColor2("Tint Color 2", Color) = (1, 1, 1, 1)
        _ColorHeight("Color Height", Range(0, 1)) = .3

        _StencilRef("Stencil Ref Value", Range(0, 100)) = 10
        _StencilWriteMask("Stencil WriteMask Value", Range(0, 100)) = 14
        _StencilRefDepth("Stencil Ref Value Depth", Range(0, 100)) = 8
        _StencilWriteMaskDepth("Stencil WriteMask Value Depth", Range(0, 100)) = 14
    }

    SubShader{
       
        Pass {
            Name "GBuffer"
            Tags { "LightMode" = "GBuffer" }
            ZWrite On
            Stencil {
                Ref [_StencilRef]
                WriteMask [_StencilWriteMask]
                Comp Always
                Pass Replace
                // Fail Keep
                // ZFail Keep
            }

            HLSLPROGRAM

            #pragma target 3.5

            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            #pragma vertex DepthOnlyPassVertex
            #pragma fragment DepthOnlyPassFragment

            struct VertexInput {
                float4 pos : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct VertexOutput {
                float4 clipPos : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            VertexOutput DepthOnlyPassVertex(VertexInput input) {
                VertexOutput output;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));
                output.clipPos = mul(unity_MatrixVP, worldPos);
                return output;
            }

            float4 DepthOnlyPassFragment(VertexOutput input) : SV_TARGET{
                UNITY_SETUP_INSTANCE_ID(input);
                return float4(.4,1,.4,1);
            }

            ENDHLSL
        }
        Pass {
            Name "DepthOnly"
            Tags { "LightMode" = "DepthOnly" "Queue" = "Geometry+0" "RenderType" = "Opaque" "RenderPipeline" = "HDRenderPipeline" }
            ColorMask 0
            ZWrite On
            Stencil {
                Ref[_StencilRefDepth]
                WriteMask[_StencilWriteMaskDepth]
                Comp Always
                Pass Replace
                // Fail Keep
                // ZFail Keep
            }



            HLSLPROGRAM

            #pragma target 3.5

            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            #pragma vertex DepthOnlyPassVertex
            #pragma fragment DepthOnlyPassFragment

            struct VertexInput {
                float4 pos : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct VertexOutput {
                float4 clipPos : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            VertexOutput DepthOnlyPassVertex(VertexInput input) {
                VertexOutput output;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));
                output.clipPos = mul(unity_MatrixVP, worldPos);
                return output;
            }

            float4 DepthOnlyPassFragment(VertexOutput input) : SV_TARGET{
                UNITY_SETUP_INSTANCE_ID(input);
                return 0;
            }

            ENDHLSL
        }
    }
    FallBack "HDRP/Lit"
}

@KYL3R You didnt fill out the GBuffer correctly. GBuffer consists of multiple render targets, you should check out the fragment program from com.unity.render-pipelines.high-definition\Runtime\RenderPipeline\ShaderPass\ShaderPassGBuffer.hlsl and see how they handle GBuffer there, how to correctly write and encode data into it.

@customphase Thanks, I will read into it :slight_smile:

I looked into the FrameDebugger and saw: Diffuse looks good, but my shader didn’t write anything into the specular buffer. I only found this out, because you told me about the Render Targets, so I understood what these are for:

So I changed my fragment shader to return a struct instead of a float4.

struct FragmentOutput {
    float4 gBuffer0 : SV_Target0;
    float4 gBuffer1 : SV_Target1;
    float4 gBuffer2 : SV_Target2;
    float4 gBuffer3 : SV_Target3;
};

Got this bit from here:

I’m not completely there, but the “buffer shining through mesh”-artifacts (as seen in the video I posted) are gone :slight_smile:

So thanks for the hints @customphase :slight_smile:

If anyone is interested:

Shader "Custom/Grass" {
    Properties{
        _TintColor1("Tint Color 1", Color) = (1, 1, 1)
        _TintColor2("Tint Color 2", Color) = (1, 1, 1)
        _ColorHeight("Color Height", Range(0, 1)) = .3

        _StencilRef("Stencil Ref Value", Range(0, 100)) = 10
        _StencilWriteMask("Stencil WriteMask Value", Range(0, 100)) = 14
        _StencilRefDepth("Stencil Ref Value Depth", Range(0, 100)) = 8
        _StencilWriteMaskDepth("Stencil WriteMask Value Depth", Range(0, 100)) = 14


        _SpecularRGB("Specular Color", Color) = (1, 1, 1)
        _RoughnessValue("Roughness Value", Range(0,1)) = 0.5
    }

    SubShader{
     
        Pass {
            Name "GBuffer"
            Tags { "LightMode" = "GBuffer" }
            ZWrite Off
            Stencil {
                Ref [_StencilRef]
                WriteMask [_StencilWriteMask]
                Comp Always
                Pass Replace
            }

            HLSLPROGRAM

            #pragma target 3.5

            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            #pragma vertex DepthOnlyPassVertex
            #pragma fragment DepthOnlyPassFragment

            struct VertexInput {
                float4 pos : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct VertexOutput {
                float4 clipPos : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            float4 _TintColor1;
            float4 _TintColor2;
            float _ColorHeight;

            float3 _SpecularRGB;
            float _RoughnessValue;

            VertexOutput DepthOnlyPassVertex(VertexInput input) {
                VertexOutput output;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));
                output.clipPos = mul(unity_MatrixVP, worldPos);
                return output;
            }

            struct FragmentOutput {
                float4 gBuffer0 : SV_Target0;
                float4 gBuffer1 : SV_Target1;
                float4 gBuffer2 : SV_Target2;
                float4 gBuffer3 : SV_Target3;
            };
         

            FragmentOutput DepthOnlyPassFragment(VertexOutput input) : SV_TARGET{
                UNITY_SETUP_INSTANCE_ID(input);
                FragmentOutput output;
                float4 albedo = lerp(_TintColor1, _TintColor2, _ColorHeight);
                output.gBuffer0.rgb = albedo; // Diffuse (RGB), unused (A)
                float3 specular_roughness = float4(_SpecularRGB.r, _SpecularRGB.g, _SpecularRGB.b, _RoughnessValue);
                output.gBuffer1.rgb = specular_roughness; // specular (RGB) Roughness (A)
                output.gBuffer2.rgb = float4(0, 0, 0, 0); // world space normal?
                output.gBuffer3.rgb = float4(0, 0, 0, 0); // emission + lighting + lightmaps + reflection probes buffer...

                return output;
            }

            ENDHLSL
        }
        Pass {
            Name "DepthOnly"
            Tags { "LightMode" = "DepthOnly" "Queue" = "Geometry+0" "RenderType" = "Opaque" "RenderPipeline" = "HDRenderPipeline" }
            ColorMask 0
            ZWrite On
            Stencil {
                Ref[_StencilRefDepth]
                WriteMask[_StencilWriteMaskDepth]
                Comp Always
                Pass Replace
            }

            HLSLPROGRAM

            #pragma target 3.5

            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            #pragma vertex DepthOnlyPassVertex
            #pragma fragment DepthOnlyPassFragment

            struct VertexInput {
                float4 pos : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct VertexOutput {
                float4 clipPos : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            VertexOutput DepthOnlyPassVertex(VertexInput input) {
                VertexOutput output;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));
                output.clipPos = mul(unity_MatrixVP, worldPos);
                return output;
            }

            float4 DepthOnlyPassFragment(VertexOutput input) : SV_TARGET{
                UNITY_SETUP_INSTANCE_ID(input);
                return 0;
            }

            ENDHLSL
        }
    }
    FallBack "HDRP/Lit"
}

@KYL3R That tutorial is for the regular default pipeline. HDRP uses different GBuffer data layout:

//FeatureName   Standard
//GBuffer0      baseColor.r,    baseColor.g,    baseColor.b,    specularOcclusion
//GBuffer1      normal.xy (1212),   perceptualRoughness
//GBuffer2      f0.r,   f0.g,   f0.b,   featureID(3) / coatMask(5)
//GBuffer3      bakedDiffuseLighting.rgb

This is from com.unity.render-pipelines.high-definition\Runtime\Material\Lit\Lit.hlsl.

And btw, they pack normals and featureID/coatMask in a special way, again, you need to look into the source code of the HDRP shaders, youre not gonna go far without it.

@customphase Thanks again. I only wish there was a proper HDRP Documentation, explaining such things. But I guess reading the source code has to work for now.

hmm. Including “common.hlsl” or “NormalBuffer.hlsl” give me some new problems like “xy is already defined” or “real3 not defined” which means I need more includes, leading to more problems…

  • In the End I copied “EncodeIntoNormalBuffer” and the required functions: BitFieldInsert, CopySign, PackNormalOctQuadEncode and PackFloat2To888.

Now it works, the normals look similar to the rest of the scene (even though they look a bit strange because of the packing)

And the grass now looks more “round” because of the lights working properly.

But it’s still black when the directional light is blocked by a cube. So… how come other objects are brighter than black in the shadows? Is this still related to my shader?
Do you have another hint, @customphase ? Thanks in advance.

btw. a point light seems to work as well. Imgur: The magic of the Internet

edit: Nevermind, apparently gBuffer2 (xyz) is shadow-color. I changed it from 0,0,0 to a value and now I have shadows working.

1 Like