When developing 3D action games or open-world titles in Unity, the LOD Group (Level of Detail) component is essential for rendering performance optimization. By reducing the polygon count of distant meshes, you can save significant rendering overhead. However, if the transition between LOD levels is instantaneous, it causes a noticeable visual pop (pop-in artifacts).

To mask this transition, developers often enable the "Animate Cross-fading" Fade Mode in the LOD Group. This enables meshes to smoothly dissolve (cross-fade) into one another. However, if you are using custom materials created with Shader Graph, you will frequently find that they pop instantly instead of cross-fading, or their shadows completely disappear and flicker during the transition.

In this article, we'll explain the root causes of LOD cross-fading failures for custom Shader Graph materials in URP, and walk you through the step-by-step procedures to implement proper cross-fade dithering and shadow transitions.

1. Root Cause ①: Unprocessed Global Keyword "LOD_FADE_CROSSFADE"

When "Animate Cross-fading" is enabled on a LOD Group, the Unity engine renders both meshes (e.g., LOD 0 and LOD 1) simultaneously during the transition, sending a blend ratio (0 to 1) to the shaders.

This blending is controlled by the global shader keyword "LOD_FADE_CROSSFADE". Standard Lit and Unlit shaders support this keyword out of the box, automatically applying a dither pattern to perform the fade-out and fade-in blending.

However, new custom shaders created with Shader Graph do not contain the processing logic for this keyword by default. Because the shader ignores the transition ratio sent by the engine, the meshes pop instantly instead of fading.

2. Root Cause ②: Disabled Alpha Clipping

To maintain high performance, LOD cross-fading is designed to run within the opaque rendering queue. Using alpha blending (transparent queue) for cross-fading would cause sorting issues and a massive performance spike due to overdraw (multiple overlapping layers of semi-transparent pixels).

Therefore, Unity uses dithering (also known as screen-door transparency) to simulate semi-transparency. This grid-like pixel culling requires the shader's "Alpha Clipping" (clip function) to be enabled.

If Alpha Clipping is disabled (turned off) in the Shader Graph Settings, the dither pattern cannot cull the pixels, and the object remains fully opaque until it pops out of existence.

3. Root Cause ③: Shadows Lacking Cross-fade in Shadow Caster Pass

Even if the main object fades smoothly using dithering, if the shadow (rendered during the Shadow Caster pass) does not support dithering, the shadow will vanish instantly at the start of the transition.

This causes a sudden flicker on the ground, ruinous to the visual polish of the game. Shadows must share the same alpha-mask clipping calculations and evaluate the cross-fade ratio inside the Shadow Caster pass.

4. Step-by-Step Guide: Implementing LOD Cross-fade in Shader Graph

Follow these steps to configure your Shader Graph for LOD cross-fading in URP.

Important Pre-requisite: Keep the material's Surface Type set to "Opaque." There is no need to change it to "Transparent."
  1. Open your target Shader Graph file.
  2. Go to the Graph Settings panel (gear icon on the top right).
  3. Ensure "Surface Type" is set to Opaque, and check the Alpha Clipping box.
  4. Add a LOD Cross Fade node to the graph. This node automatically retrieves the current transition ratio (0 to 1) from the LOD Group.
  5. Add a Dither node to the graph.
  6. Connect the output of the LOD Cross Fade node to the In (or Screen Position / alpha input) of the Dither node.
    *The Dither node uses screen space coordinates to generate a pixelated mesh pattern.
  7. Connect the output of the Dither node to the Master Stack's Alpha Clip Threshold input.
  8. If your material has an existing alpha mask (e.g., from an Albedo texture), multiply it with the Dither output using a Multiply node before connecting it to Alpha Clip Threshold.

Your custom material will now use dither clipping to cross-fade between LOD levels smoothly.

Enabling Cross-fade for the Shadow Caster Pass

To fade the shadow, verify your Master Stack configuration. By connecting the dither math to the Alpha Clip Threshold, URP automatically propagates this culling logic to the Shadow Caster pass. Ensure that Cast Shadows is enabled in your Material Inspector and verify in the Scene view that the shadow dithers during the transition.

5. LOD Cross-fade Implementation Methods

Method Settings Pros & Cons
1. Dither Node Clipping (Recommended) Combine LOD Cross Fade and Dither nodes, routing them to the Alpha Clip Threshold. Pros: No sorting issues, extremely lightweight. Perfect for mobile and VR.
Cons: A pixelated grid pattern is visible under close examination.
2. Custom HLSL Alpha Blending Change Surface Type to Transparent and write custom HLSL to perform alpha blending. Pros: Smooth blending without screen-door noise.
Cons: Transparent queue causes rendering sorting bugs (depth sorting failure) and high overdraw. Not recommended for production.

6. Monitoring LOD State via C# Script

Here is a helper script to debug LOD level transitions in real-time, printing LOD changes to the console during gameplay.

using UnityEngine;

[RequireComponent(typeof(LODGroup))]
public class LODTransitionDebugger : MonoBehaviour
{
    private LODGroup lodGroup;
    private int lastLODIndex = -1;

    void Start()
    {
        lodGroup = GetComponent<LODGroup>();
    }

    void Update()
    {
        if (lodGroup == null) return;

        // Determine current LOD index by checking renderer visibility
        int currentLODIndex = GetVisibleLODIndex();

        if (currentLODIndex != lastLODIndex)
        {
            Debug.Log($"[LODDebugger] LOD changed for {gameObject.name}: LOD{lastLODIndex} -> LOD{currentLODIndex}");
            lastLODIndex = currentLODIndex;
        }
    }

    private int GetVisibleLODIndex()
    {
        LOD[] lods = lodGroup.GetLODs();
        for (int i = 0; i < lods.Length; i++)
        {
            foreach (Renderer renderer in lods[i].renderers)
            {
                if (renderer != null && renderer.isVisible)
                {
                    return i;
                }
            }
        }
        return -1;
    }
}

Attach this script to any GameObject with a LOD Group component. When the camera moves, it prints the active LOD index to the Console, allowing you to verify that the transitions line up with your dither configurations.