When developing 3D games using Unity's Universal Render Pipeline (URP), you might set materials for objects like glass, water, sheer character costumes, or fading visual effects to "Transparent". However, you often run into two major issues: "The object's shadow no longer casts onto the floor or other objects," or "The transparent object remains completely unlit and glows brightly even when placed inside dark shadows cast by buildings or characters."

These rendering bugs are caused by differences in how the Built-in Render Pipeline and URP handle transparent render queues and depth buffers (Depth Write). In this article, we'll explain the physical and mathematical reasons why transparent objects fail to cast or receive shadows in URP, and provide step-by-step instructions on how to solve these problems using Shader Graph settings.

1. Root Cause ①: Why Transparent Objects "Do Not Cast Shadows"

The primary reason transparent objects do not cast shadows by default is that URP disables the "Shadow Caster" pass for transparent materials in their default configurations.

When Unity generates shadows, it renders all shadow-casting objects from the light's perspective to create a distance map called a "shadow map" (depth texture). The shader code that handles this pass is called the "Shadow Caster" pass.

However, transparent objects are meant to let light pass through. If a glass cup cast a fully opaque shadow, it would look like a solid black rock, which appears highly unnatural. Therefore, to save rendering performance, most standard transparent shaders in URP (like default Transparent Lit or Unlit) skip the Shadow Caster pass entirely or omit it from the compilation. This is why shadows disappear the moment you switch a material to Transparent.

2. Root Cause ②: Why Transparent Objects "Do Not Receive Shadows"

Conversely, when a transparent object is placed inside another object's shadow (e.g., in the shadow of a building), the reason it remains fully lit and glows is due to the render queue ordering and Depth Write settings in forward rendering.

In standard game engines, rendering occurs in this order:

  1. Opaque Objects: Rendered first from front to back, writing to the Depth Buffer. Shadow map comparisons are made here, and shadows are applied.
  2. Shadow Map Execution: Shadows for opaque objects are finalized.
  3. Transparent Objects: Rendered last from back to front, layering over the opaque objects.

During this process, most transparent objects have Depth Write (ZWrite) turned off (otherwise, transparent objects behind them would be culled and not rendered). Without depth information, the graphics pipeline cannot compare the transparent pixel's depth with the shadow map. Consequently, the transparent object is excluded from shadow calculations, receiving 100% lighting and appearing artificially bright in shadowed areas.

3. Step-by-Step Guide: How to "Cast Shadows" in Shader Graph

To configure a transparent object to cast shadows in URP, follow these steps to adjust your Shader Graph settings.

Important Key Point: You must configure the material parameters under the "Graph Settings" panel inside Shader Graph.
  1. Open your target Shader Graph file.
  2. Select the Graph Settings tab (gear icon on the top right).
  3. Set the "Surface Type" to Transparent.
  4. Enable the Cast Shadows checkbox below it (this adds the Shadow Caster pass to the compiled shader).
  5. If you want the shadow's density to match the texture's transparency, enable Alpha Clipping:
    • Check Alpha Clipping in Graph Settings.
    • Create an AlphaThreshold (or _Cutoff) property in the Blackboard and connect it to the Master Stack's Alpha Clip Threshold input.
    • Connect your texture's alpha channel to the Master Stack's Alpha input.

This ensures only pixels with alpha values above the threshold will write to the shadow map, allowing clean cutout shadows (ideal for transparent fences, foliage, or grid meshes).

Using Dithering for "Semi-Transparent Shadows"

If you need soft, semi-transparent shadows for glass or fabric instead of hard cutouts, you can use a Dither node. Connect the output of the Dither node to the Master Stack's Alpha Clip Threshold input. This generates a stippled pixel pattern, simulating a semi-transparent shadow without incurring the overhead of a dedicated transparent shadow pipeline.

4. Step-by-Step Guide: How to "Receive Shadows" in Shader Graph

To let transparent objects receive shadows, you need to combine the material's depth write settings with proper light sampling inside the shader.

Approach Method Settings and Setup Use Case and Warnings
A. Standard Receive Settings in Lit Shader Enable "Receive Shadows" in Graph Settings, and force Depth Write to Force Enabled in the material inspector. The simplest solution. However, it can cause rendering sorting artifacts (sorting issues) for overlapping transparent objects.
B. Shadow Sampling via Custom Function (Unlit) Use a Custom Function node in Shader Graph to retrieve the main light's shadow attenuation (MainLightRealtimeShadow) via HLSL code, and multiply it with the final color (see code below). Best for UI elements, transparent VFX, or fast shaders that do not require full lighting calculations but still need to react to shadows.

Implementing Shadow Reception via Custom HLSL (For Unlit Master Stack)

Since the Unlit Master Stack does not perform default lighting calculations, you must explicitly sample the shadow map using a custom function. Below is the HLSL code to use inside a Custom Function node in Shader Graph:

// HLSL Code for Custom Function Node
// Inputs: WorldPos (Vector3)
// Outputs: ShadowAtten (Vector1)

void GetMainLightShadow_float(float3 WorldPos, out float ShadowAtten)
{
    ShadowAtten = 1.0;
    #if defined(UNIVERSAL_LIGHTING_INCLUDED)
        // Convert world position to shadow map coordinates
        float4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
        
        // Sample main light real-time shadow attenuation
        #if VERSION_GREATER_EQUAL(10, 0)
            ShadowAtten = MainLightRealtimeShadow(shadowCoord);
        #else
            ShadowAtten = GetMainLight(shadowCoord).shadowAttenuation;
        #endif
    #endif
}

In Shader Graph, connect the World space output of a Position node to the input of this Custom Function, and multiply the output ShadowAtten (0 for fully shadowed, 1 for fully lit) with your Albedo or Base Color. This forces the Unlit transparent material to darken appropriately inside shadows.

5. Preventing Rendering Sorting Artifacts with "Depth Priming"

When you enable Depth Write (ZWrite) on a transparent material to receive shadows, you might notice a new rendering bug: other transparent objects or particles behind it are cut out and disappear, appearing as if the back of the object is hollowed out.

This is because writing depth coordinates for transparent pixels causes the GPU to assume those pixels are opaque, culling any geometry behind them (Z-Culling). To resolve this sorting bug while maintaining shadow reception, consider these project-wide adjustments:

  1. Adjust Depth Priming Mode:
    Select your Universal Render Pipeline Asset and under the "Rendering" section, set Depth Priming Mode to Auto or Forced. This executes a depth pre-pass for the scene camera, improving depth calculations.
  2. Create a Transparent Depth Prepass via Render Features:
    Add a custom Scriptable Renderer Feature to inject a depth prepass specifically for transparent geometry right before the transparent rendering queue starts. This establishes correct depth coordinates without forcing individual materials to write to the depth buffer, preventing sorting artifacts while maintaining shadow reception.