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.

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 (ide) based on local sources.
  • “When/Why is it consumed?” looks at URP Runtime (C#) and shader (Pass states) together.

20.0 Accurate Reference (IDE)

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”를 선택하는 방식으로 이해하면 디버깅이 빨라집니다.

// 개념 의사코드(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 IDE):

  • 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 conditionsMinimum Recommended LightMode Set
Basic 3D (Forward)UniversalForward, ShadowCaster, DepthOnly, Meta
+ Screen-based normal effect (SSAO/Outline/SSR)Basic + DepthNormals
Use DeferredBasic + UniversalGBuffer
+ TAA/Motion Blur/ReprojectionCorresponding set + MotionVectors
+ XR Motion ReprojectionCorresponding set + XRMotionVectors
2D Renderer supportAdd 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.

LightModeStatus Contract (Representative)Inspection points
DepthOnlyZWrite On, ColorMask 0Is only depth recorded and color writing blocked?
ShadowCasterZWrite On, appropriate ZTestWhether the alpha clip matches the cutoff in the cutout material
MotionVectorsColorMask RGvelocity is recorded in RG channel
XRMotionVectorsColorMask RGBA + (per project) stencilWhether the XR reprojection path includes a stencil contract
UniversalForwardNormal color output + when necessary SV_Target1Maintain additional target output contracts when using Rendering Layers

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

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.

// 위험: 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

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

// 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)

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 IDE

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

Terminal window
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

SymptomsWhat to See FirstNext check
Material broken only in DeferredUniversalGBuffer pass existence/matchingUniversalForwardOnly Necessity of separation
SSAO/Outline defectiveDepthNormals pass matchingNormal space/encoding matching
TAA/Motion Blur GhostingMotionVectors pass matchinginclude_with_pragmas Use or not
More than just reprojection in XRXRMotionVectors pass matchingWhether stencil/define contract is included
Missing shadows/cutout errorsShadowCaster 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?

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.