book/20-urp-pass-tags-and-lightmode-contract.md

20. URP Pass Tags & LightMode Contract

Based on Unity 6.3 (6000.3) / URP 17.3.0, organizes when/why ShaderLab Pass's LightMode is consumed (=contract) in the URP pipeline.

20. URP Pass tags & LightMode contract (Unity 6.3 / URP 17.3.0)

This chapter fixes the Pass contract, which is the most common breaking point when creating “URP compatible shaders”.

It's quicker if you rephrase the question this way.

  • “If this LightMode Pass is not present, which step of the URP will fail (or fallback)?”
  • “What output (RenderTarget/Stencil/ColorMask) does this Pass promise?”
  • “Which project features (Deferred/TAA/XR/2D) consume this Pass?”

Principle

  • “Accurate list” is fixed (generated) based on local sources.
  • “When/Why is it consumed?” looks at URP Runtime (C#) and shader (Pass states) together.

20.0 Accurate Reference (Generated)

20.1 ShaderLab Pass selection rules (summary): URP selects a Pass by ShaderTagId list

URP의 “오브젝트 그리기 패스”는 내부적으로 ShaderTagId 리스트를 가지고 있고, 그 리스트에 맞는 ShaderLab Pass를 찾아 그립니다.

URP 17.3.0 example (Forward Opaques/Transparents default tag list):

  • <URP>/Runtime/Passes/DrawObjectsPass.cs:91
    • SRPDefaultUnlit, UniversalForward, UniversalForwardOnly

When you turn on the Deferred(=GBuffer) renderer, the “GBuffer + ForwardOnly” strategy is mixed.

  • <URP>/Runtime/UniversalRenderer.cs:388 Comment Summary
    • Deferred possible materials: UniversalForward + UniversalGBuffer recommended to be provided
    • Deferred not possible material (unlit/special): UniversalForwardOnly recommended
    • (Legacy) unnamed pass is considered SRPDefaultUnlit and can be processed as forward-only

In other words, LightMode is not just a tag, but a contract key that is 1:1 linked to a pipeline consumption rule.

20.1.1 Understanding pass selection priority like code (concept)

URP의 오브젝트 드로우 패스는 내부 ShaderTagId 목록을 순회하면서 “첫 매칭 pass”를 선택하는 방식으로 이해하면 디버깅이 빨라집니다.

C#
// 개념 의사코드(URP DrawObjectsPass 동작 모델)
ShaderTagId[] tags =
{
    new ShaderTagId("SRPDefaultUnlit"),
    new ShaderTagId("UniversalForward"),
    new ShaderTagId("UniversalForwardOnly")
};

foreach (var tag in tags)
{
    if (material.HasPassWithLightMode(tag))
    {
        Draw(tag);
        break; // 첫 매칭 pass 사용
    }
}

In practice, check the final matching result in Frame Debugger's Shader Pass as a standard.

20.1.2 LightMode is the basis for the contract rather than Name

ShaderLab's Pass Name is useful for debugging readability, but URP's pass selection contract basically operates based on Tags { "LightMode"="..." }.

That is:

  • If it is Name "ForwardLit" but without LightMode, URP may not be able to find it on the expected path.
  • Conversely, even if Name is different, if LightMode is correct, normal matching can be achieved at the consumption stage.

As a practical rule, it is safe to manage them separately as “Name is for people, LightMode is for pipeline contracts.”

20.2 URP 17.3.0 9 types of LightMode based on Lit.shader (“actual list”)

LightMode that Lit.shader actually includes (based on Generated):

  • UniversalForward
  • UniversalGBuffer
  • ShadowCaster
  • DepthOnly
  • DepthNormals
  • Meta
  • MotionVectors
  • XRMotionVectors
  • Universal2D

Check the exact include roots/number here:

20.2.1 Why is UniversalForwardOnly not in the list of 9 Lit types?

UniversalForwardOnly is a contract that prepares for “Materials that cannot use the Deferred path”.
Since the URP Lit default shader handles Deferred with the combination of UniversalForward + UniversalGBuffer, it is normal for UniversalForwardOnly to not be in the default Pass list.

That is:

  • Lit basic type: UniversalForward + UniversalGBuffer
  • Special/Unlit/Deferred incompatible shaders: use UniversalForwardOnly as a separate strategy

20.3 LightMode Contract Table (as of URP 17.3.0 Lit)

The table below is intended to show both “what steps consume this LightMode” and “what the shader should output” at once.

How to read a table

  • Consumption Stage: Representative pipeline stage at which the URP calls its Pass (may vary depending on mode/function)
  • Core output: RenderTarget/Stencil/ColorMask perspective contract (learning/debugging core)
  • include root: Core files that Lit.shader directly includes in the pass (as of URP 17.3.0) LightMode Consumption stage (representative) Key output (summary) include root (representative) Typical symptoms when falling out Required/Optional Guide
    UniversalForward Forward color (including Forward+/transparent) Color(SV_Target0) + (Option) Rendering Layers(SV_Target1) <URP>/Shaders/LitForwardPass.hlsl, <URP>/Shaders/LitInput.hlsl Object is abnormal in default color path (fallback/not printed) Essential for almost every project
    UniversalGBuffer Deferred(GBuffer) Creating GBuffer with MRT <URP>/Shaders/LitGBufferPass.hlsl, <URP>/Shaders/LitInput.hlsl Lighting/materials break or forward-only fallback increases in Deferred If deferred, it is essential
    ShadowCaster shadow map depth recording (+alpha clip) <URP>/Shaders/ShadowCasterPass.hlsl, <URP>/Shaders/LitInput.hlsl Certain material shadows disappear/cutout shadow errors A must if you use shadows
    DepthOnly DepthTexture/Depth prepass depth recording (color is usually masked) <URP>/Shaders/DepthOnlyPass.hlsl, <URP>/Shaders/LitInput.hlsl SceneDepth based effects are unstable/broken depth dependent path Important when using DepthTexture/priming
    DepthNormals DepthNormalsTexture depth+normal recording <URP>/Shaders/LitDepthNormalsPass.hlsl, <URP>/Shaders/LitInput.hlsl SSAO/Outline/SSR types are broken immediately SSAO/Outline/SSR is virtually required
    Meta Baking (Lightmap) meta(albedo/emission) <URP>/Shaders/LitMetaPass.hlsl, <URP>/Shaders/LitInput.hlsl Bake results are abnormal (dark or missing emissions) A must-have for baking
    MotionVectors motion vector (velocity) RG velocity output <URP>/ShaderLibrary/ObjectMotionVectors.hlsl, <URP>/Shaders/LitInput.hlsl TAA/Motion Blur/Reprojection Ghosting/Blurring Important if TAA/motion blur/reprojection
    XRMotionVectors XR motion vector RGBA velocity (+stencil contract available) <URP>/ShaderLibrary/ObjectMotionVectors.hlsl, <URP>/Shaders/LitInput.hlsl Reprojection/motion related artifacts only in XR Important if you need motion vectors in XR
    Universal2D 2D Renderer 2D contract color printing <URP>/Shaders/Utils/Universal2D.hlsl, <URP>/Shaders/LitInput.hlsl Material renders abnormally in 2D Renderer Required for 2D Renderer compatibility

20.3.1 Minimum pass set for each function (for quick decision-making)

Project conditions Minimum Recommended LightMode Set
Basic 3D (Forward) UniversalForward, ShadowCaster, DepthOnly, Meta
+ Screen-based normal effect (SSAO/Outline/SSR) Basic + DepthNormals
Use Deferred Basic + UniversalGBuffer
+ TAA/Motion Blur/Reprojection Corresponding set + MotionVectors
+ XR Motion Reprojection Corresponding set + XRMotionVectors
2D Renderer support Add Universal2D (or separate dedicated shader)

When starting quickly, it is safe to leave only the necessary passes based on samples/urp/URP_LitCompatibleTemplate.shader.

20.3.2 Pass State Contract Quick Reference

Even if the LightMode is correct, the result may be broken if the pass status is incorrect. At least check the conditions below.

LightMode Status Contract (Representative) Inspection points
DepthOnly ZWrite On, ColorMask 0 Is only depth recorded and color writing blocked?
ShadowCaster ZWrite On, appropriate ZTest Whether the alpha clip matches the cutoff in the cutout material
MotionVectors ColorMask RG velocity is recorded in RG channel
XRMotionVectors ColorMask RGBA + (per project) stencil Whether the XR reprojection path includes a stencil contract
UniversalForward Normal color output + when necessary SV_Target1 Maintain additional target output contracts when using Rendering Layers ## 20.4 MotionVectors contract (particularly frequently broken)

In URP 17.3.0, Motion Vectors also explicitly consumes the “MotionVectors” tag in C#.

  • <URP>/Runtime/Passes/MotionVectorRenderPass.cs:14
    • k_MotionVectorsLightModeTag = "MotionVectors"

Key points on the shader side:

  • Lit.shader includes ObjectMotionVectors.hlsl as #include_with_pragmas.
    • If the pragma is inside an include, it may be missed if you just use the plain #include.
  • MotionVectors is a step that outputs “velocity” rather than “color,” so it must be considered separately from color/alpha logic.

20.4.1 Minimal MotionVectors pass snippet

ShaderLab
Pass
{
    Name "MotionVectors"
    Tags { "LightMode" = "MotionVectors" }
    ColorMask RG

    HLSLPROGRAM
    #pragma shader_feature_local _ALPHATEST_ON
    #pragma multi_compile _ LOD_FADE_CROSSFADE
    #pragma shader_feature_local_vertex _ADD_PRECOMPUTED_VELOCITY

    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
    #include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ObjectMotionVectors.hlsl"
    ENDHLSL
}

If XR response is required, it is safe to separate the XRMotionVectors pass and match the stencil/define contract (sample: samples/urp/URP_LitCompatibleTemplate.shader).

20.4.2 Common misimplementation: Missing pragma by using only #include

Below is a representative example where contract stability changes even if the same file is included.

HLSL
// 위험: include 내부 pragma가 반영되지 않을 수 있음
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ObjectMotionVectors.hlsl"

// 권장: Lit.shader와 같은 방식
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ObjectMotionVectors.hlsl"

When an issue related to MotionVectors recurs, checking the include type first will help reduce the cause quickly.

20.5 DepthNormals contract (based on SSAO/Outline)

DepthNormals are consumed as a separate pass in URP.

  • <URP>/Runtime/Passes/DepthNormalOnlyPass.cs:19
    • Default tag list: DepthNormals, DepthNormalsOnly

Breaking points in practice:

  • If the normal space (WS/VS) and encoding method are different from the URP implementation, SSAO/Outline will break immediately.
  • If “my shader does not participate in DepthNormals”, the texture expected by the post effect will be an empty value.

20.5.1 Minimum DepthNormals pass snippet

ShaderLab
Pass
{
    Name "DepthNormals"
    Tags { "LightMode"="DepthNormals" }
    ZWrite On

    HLSLPROGRAM
    #pragma target 4.5
    #pragma vertex DepthNormalsVertex
    #pragma fragment DepthNormalsFragment
    #pragma multi_compile_instancing
    #pragma shader_feature_local_fragment _ALPHATEST_ON

    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitDepthNormalsPass.hlsl"
    ENDHLSL
}

When changing to a direct implementation, normal encoding/space conversion must be verified together with the consumer side (SSAO/Outline).

20.6 Deferred Contract: UniversalForward vs UniversalForwardOnly vs UniversalGBuffer

This is a quick way to check compatibility in projects with the Deferred renderer turned on.

  1. Check whether opaque draw goes into GBuffer in Frame Debugger.
  2. Check if the material provides UniversalGBuffer pass
  3. Check that materials that cannot be deferred are separated by UniversalForwardOnly

URP Source Comments (Key Summary, URP 17.3.0):

  • <URP>/Runtime/UniversalRenderer.cs:388
    • “Deferred-capable materials provide UniversalForward + UniversalGBuffer”
    • “Undeferred materials are provided with UniversalForwardOnly”

20.6.1 Minimum Pass Combination Snippet for Deferred

ShaderLab
// Deferred 친화형 머티리얼
Pass { Tags { "LightMode"="UniversalForward" } }   // 투명/특수 경로 포함
Pass { Tags { "LightMode"="UniversalGBuffer" } }   // Opaque deferred 경로
Pass { Tags { "LightMode"="ShadowCaster" } }
Pass { Tags { "LightMode"="DepthOnly" } }
Pass { Tags { "LightMode"="DepthNormals" } }       // 화면기반 효과가 있으면 권장

Deferred For incompatible materials (special unlit/custom), it is operationally safer to separate shaders using the UniversalForwardOnly strategy.

20.6.2 UniversalForwardOnly Minimum snippet (for special/non-deferred materials)

ShaderLab
Pass
{
    Name "ForwardOnly"
    Tags { "LightMode"="UniversalForwardOnly" }

    HLSLPROGRAM
    #pragma target 4.5
    #pragma vertex LitPassVertex
    #pragma fragment LitPassFragment
    #pragma multi_compile_instancing
    #pragma multi_compile_fog
    #pragma shader_feature_local_fragment _ALPHATEST_ON

    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl"
    ENDHLSL
}

To reliably separate the material in a forward-only manner even in the deferred renderer, it is safer to specify this pass as a “separation strategy.”

20.7 Debugging routine: “Is URP using my pass?”

A) Frame Debugger

  1. Select a draw call to see what the “Shader Pass” is (ForwardLit/GBuffer/DepthNormals/MotionVectors, etc.)
  2. Check whether it is being drawn in the expected LightMode

B) RenderGraph Viewer

  1. Check whether Depth/Normals/History resources are created and in which path they are used.
  2. Check the flow to see if MotionVectors depend on depth (reprojection).

C) Quickly narrow down the cause with Generated

D) Find missing contracts with source search (quick recipe)

POWERSHELL
rg -n 'LightMode"\\s*=\\s*"(UniversalGBuffer|DepthNormals|MotionVectors|XRMotionVectors)"' samples Assets
rg -n 'include_with_pragmas\\s+".*ObjectMotionVectors.hlsl"' samples Assets

You can quickly check whether the pass exists with the first command and whether the MotionVectors pragma is missing with the second command.

E) Symptom-based primary verification matrix

Symptoms What to See First Next check
Material broken only in Deferred UniversalGBuffer pass existence/matching UniversalForwardOnly Necessity of separation
SSAO/Outline defective DepthNormals pass matching Normal space/encoding matching
TAA/Motion Blur Ghosting MotionVectors pass matching include_with_pragmas Use or not
More than just reprojection in XR XRMotionVectors pass matching Whether stencil/define contract is included
Missing shadows/cutout errors ShadowCaster pass matching _ALPHATEST_ON + _Cutoff Match or not

20.8 Checklist (acceptance criteria)

  • If the project is Deferred, is UniversalGBuffer actually consumed?
  • Is MotionVectors pass actually consumed when TAA/motion blur/reprojection is involved?
  • If SSAO/Outline is present, is DepthNormals pass actually consumed?
  • In the Forward+ environment, do the _CLUSTER_LIGHT_LOOP variant and LIGHT_LOOP_BEGIN/END patterns exist?

20.8.1 Completion criteria (recommended)

If the four conditions below are satisfied, the “Pass contract” reinforcement can be considered completed.

  1. The LightMode expected from the Frame Debugger matches the actual drawcall.
  2. The DepthNormals/MotionVectors resource creation-consumption flow is confirmed in the RenderGraph Viewer.
  3. The same material operates without functional regression in the Deferred/Forward+/XR ON/OFF combination.
  4. Compared to samples/urp/URP_LitCompatibleTemplate.shader, the minimum set leaving only the necessary passes is documented.