Shadow acne issues when using shadow atten/shadow mask for toon shader

Hi all,

I’ve been trying to create a toon shader for our game recently in URP 2022.1.15f1, however, I’ve been running into issues with shadow acne when it comes to the shadow attenuation as well as sampling the shadow mask.

I’ve recreated a basic setup for a toon shader that still has the problem. The trouble is, when I multiply the results by the shadow & distance attenuation there is really bad shadow acne occuring, however, when I don’t do this the shader no longer receives shadows.


Shadow acne when multiplied with shadow/distance attentuation

If I plug the shadow attenuation into the base colour you can see what is getting remapped to the texture gradient.
Shadow attentuation plugged directly into base colour

When I multiply by the sampled shadow mask instead of the shadow/distance attenuation I still get similar results.
Graph using shadow mask instead of attenuation

While it looks like this fixes it this actually doesn’t as this only works when the ramp is split down the middle at a value of 0.5.


If I change it to a smooth gradient for example you can see that the issue is still there, though it is improved slightly. I use a different ramp for different objects if I use a smooth gradient to as seem below to clearly show the issue or a gradient texture with multiple shading bandings it’s very clear the issue. I can adjust it slightly using the URP asset shadow settings as well as the depth/normal bias settings but this doesn’t fix the issue.
Shadow issues when using smooth gradient

This also occurs on additional light shadows as well.
Shadow acne from additional lights

This is the stripped down code used in the graph example above.
HLSL lighting code

#ifndef CUSTOM_LIGHTING_DEMO_INCLUDED
#define CUSTOM_LIGHTING_DEMO_INCLUDED
#ifdef UNIVERSAL_LIGHTING_INCLUDED
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#endif
void MainLight_float (float3 WorldPos, out float3 Direction, out float3 Color, out float DistanceAtten, out float ShadowAtten){
    #ifdef SHADERGRAPH_PREVIEW
        Direction = float3(0.5, 0.5, 0);
        Color = 1;
        DistanceAtten = 1;
        ShadowAtten = 1;
    #else
        #if SHADOWS_SCREEN
            half4 clipPos = TransformWorldToHClip(WorldPos);
            half4 shadowCoord = ComputeScreenPos(clipPos);
        #else
            half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
        #endif
        Light mainLight = GetMainLight(shadowCoord);
        Direction = mainLight.direction;
        Color = mainLight.color;
        DistanceAtten = mainLight.distanceAttenuation;
        //hadowAtten = mainLight.shadowAttenuation;
 
    #if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)
    ShadowAtten = 1.0f;
    #endif
 
    #if SHADOWS_SCREEN
    ShadowAtten = SampleScreenSpaceShadowmap(shadowCoord);
    #else
    ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
    float shadowStrength = GetMainLightShadowStrength();
    ShadowAtten = SampleShadowmap(shadowCoord, TEXTURE2D_ARGS(_MainLightShadowmapTexture,
    sampler_MainLightShadowmapTexture),
    shadowSamplingData, shadowStrength, false);
    #endif
    #endif
 
}
void Shadowmask_half (float2 lightmapUV, out half4 Shadowmask){
    #ifdef SHADERGRAPH_PREVIEW
        Shadowmask = half4(1,1,1,1);
    #else
        OUTPUT_LIGHTMAP_UV(lightmapUV, unity_LightmapST, lightmapUV);
        Shadowmask = SAMPLE_SHADOWMASK(lightmapUV);
    #endif
}
#ifndef SHADERGRAPH_PREVIEW
    #include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/ShaderPass.hlsl"
    #if (SHADERPASS != SHADERPASS_FORWARD)
        #undef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
    #endif
#endif
void MainLightShadows_float (float3 WorldPos, half4 Shadowmask, out float ShadowAtten){
    #ifdef SHADERGRAPH_PREVIEW
        ShadowAtten = 1;
    #else
        float4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
        ShadowAtten = MainLightShadow(shadowCoord, WorldPos, Shadowmask, _MainLightOcclusionProbes);
    #endif
}
#endif

Any help is greatly appreciated! I’m sure I must be missing something but I’ve search everywhere online and can’t find a solution anywhere.

2 Likes

Did you find a solution? I have the same problem in 2021.3.10f

I’ve noticed issues similar to that if I try to get the termination between light and shadow to go too far beyond the natural termination you would get from N dot L

bump


I’m getting same results as op:

Unfortunately, I’ve still not found a solution so would still love to hear if anyone does make any progress on this issue!

Maybe this helps? It also has toon shader graph

Ah, I was using that as a reference but that still has/doesn’t fix the issue

I think it’s because you have receive shadows on but are also moving the terminator of your toon shadow back behind the apex of the curve too much