# **URP Shaders and Shader Graph in Unity 6 (Shader Graph, Hand-Written HLSL, Render Graph Renderer Features)** **Unity Version: 6000.5 and above, Universal Render Pipeline (URP 17.x+).** In Unity 6, URP's renderer runs on the **Render Graph** system. Old-style `ScriptableRenderPass.Execute()` and the `cameraColorTargetHandle` pattern are legacy (Compatibility Mode, deprecated and being removed). Everything below uses `RecordRenderGraph`. ## Part 0 – URP vs HDRP: Which One? | Choose **URP** when | Choose **HDRP** when | |---|---| | Any platform: mobile, Switch, XR/Quest, WebGPU, consoles, PC | High-end PC / current consoles only | | You want stylised or performant visuals | You want physically-based, photoreal lighting (physical light units, volumetrics, path tracing) | | Small to mid team, fast iteration | Teams with lighting/tech-art expertise | | You need custom render passes that are easy to write | You need Custom Passes, ray tracing, advanced post | Unity has stated URP is the long-term focus for the unified renderer direction, so for most new projects in Unity 6 **URP is the default choice**. Switching later is painful (materials, shaders, lighting all differ), so decide early. --- ## Part 1 – Project Setup 1. Unity Hub > New Project > **Universal 3D** template (6000.5+). 2. Confirm the pipeline asset: *Edit > Project Settings > Graphics > Default Render Pipeline* = `PC_RPAsset` (or Mobile). Each Quality level can override it. 3. Find the **Renderer** asset referenced by that pipeline asset (`PC_Renderer`). This is where Renderer Features are added in Part 5. 4. Make sure *Project Settings > Graphics > Render Graph > Compatibility Mode* is **off** (it is off by default in new Unity 6 projects). --- ## Part 2 – Shader Graph: Dissolve Effect **Goal:** an object that dissolves with a glowing edge, driven by a `Dissolve` float (0 = solid, 1 = gone). ### Steps 1. *Project window > Create > Shader Graph > URP > Lit Shader Graph*. Name it `SG_Dissolve`. Double-click to open. 2. In the **Graph Inspector > Graph Settings**: Surface Type **Opaque**, enable **Alpha Clipping**. (Alpha clip on opaque is cheaper than transparency and still writes depth/shadows correctly.) 3. **Blackboard** (+ button) – add properties: - `BaseMap` (Texture2D), `BaseColor` (Color, white) - `Dissolve` (Float, Slider 0–1, default 0) - `NoiseScale` (Float, default 20) - `EdgeWidth` (Float, default 0.05) - `EdgeColor` (Color, **HDR** mode, e.g. orange intensity 4) 4. Build the nodes: - `Sample Texture 2D` (BaseMap) × `BaseColor` → **Base Color**. - `Simple Noise` (UV default, Scale = `NoiseScale`) → call this **N**. - **Alpha:** connect **N** to the master stack **Alpha**. - **Alpha Clip Threshold:** connect `Dissolve` directly. Pixels where N < Dissolve are clipped. - **Edge glow:** `Add` (Dissolve + EdgeWidth) → `Step` (Edge = that sum, In = N) gives 1 where noise is *above* the band. `One Minus` that result → band mask (1 inside the thin edge band just above the clip line). Multiply band mask × `EdgeColor` → **Emission**. 5. **Save Asset** (top left). Right-click the graph > Create > Material. Assign to a mesh. 6. Animate `Dissolve` in the material Inspector (or from script below). **Why it works:** Alpha clipping discards fragments where `Alpha < Threshold`. Since Alpha = noise and Threshold = Dissolve, raising Dissolve eats away the low-noise regions first. The edge band is simply "noise between Dissolve and Dissolve + EdgeWidth". Driving it from code (use property IDs, and `MaterialPropertyBlock`-free instancing via the renderer's material instance is fine for one-off objects): ```csharp using UnityEngine; public class DissolveDriver : MonoBehaviour { [SerializeField] float duration = 2f; static readonly int DissolveId = Shader.PropertyToID("_Dissolve"); Material mat; float t; void Awake() => mat = GetComponent<Renderer>().material; // instance per object void Update() { t += Time.deltaTime / duration; mat.SetFloat(DissolveId, Mathf.PingPong(t, 1f)); } } ``` > **Reference name:** select `Dissolve` in the Blackboard and check the **Reference** field in the Graph Inspector. By default it is `_Dissolve`, which is what `Shader.PropertyToID` must match. > **SRP Batcher:** Shader Graph properties declared in the Blackboard live in the `UnityPerMaterial` CBUFFER automatically, so the graph stays SRP Batcher compatible. Avoid per-object `MaterialPropertyBlock` with SRP Batcher materials; use material instances or GPU Resident Drawer–friendly approaches. --- ## Part 3 – Shader Graph: Rim / Fresnel Glow 1. Create > Shader Graph > URP > **Lit Shader Graph**, name `SG_Rim`. 2. Blackboard: `BaseColor` (Color), `RimColor` (Color, HDR), `RimPower` (Float, default 3), `RimStrength` (Float, default 1). 3. Nodes: - `Fresnel Effect` node (Normal = world normal, View Dir = world view direction — both defaults; Power = `RimPower`). - Multiply Fresnel × `RimColor` × `RimStrength` → **Emission**. - `BaseColor` → **Base Color**. 4. Save, create material, try RimPower 1.5 (soft) vs 6 (thin outline). **Explanation:** Fresnel = `pow(1 - saturate(dot(N, V)), Power)`. Surfaces facing you give ~0; grazing angles give ~1. That's the hand-written formula we'll reuse in Part 4. **Variation – hologram:** set Surface Type to **Transparent**, Blend Additive, feed Fresnel into Alpha, and add scrolling lines: `Position (World).y` × 40 + `Time` → `Fraction` → `Step(0.5)` × Fresnel. --- ## Part 4 – Hand-Written HLSL Shaders for URP URP shaders must use **HLSLPROGRAM** (not CGPROGRAM), include URP's `Core.hlsl`, and put material properties in `CBUFFER_START(UnityPerMaterial)` for SRP Batcher compatibility. ### 4a. Unlit (textured + tint + fresnel) Create *Assets/Shaders/URPUnlitRim.shader*: ```hlsl Shader "Tutorial/URP Unlit Rim" { Properties { [MainTexture] _BaseMap ("Base Map", 2D) = "white" {} [MainColor] _BaseColor ("Base Color", Color) = (1,1,1,1) [HDR] _RimColor ("Rim Color", Color) = (0,1,1,1) _RimPower ("Rim Power", Range(0.5, 8)) = 3 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } Pass { Name "Unlit" Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #pragma multi_compile_fog #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); // Everything from Properties (except textures) goes here => SRP Batcher compatible. CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half4 _RimColor; half _RimPower; CBUFFER_END struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 positionWS : TEXCOORD2; half fogFactor : TEXCOORD3; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; Varyings vert (Attributes IN) { Varyings OUT; UNITY_SETUP_INSTANCE_ID(IN); UNITY_TRANSFER_INSTANCE_ID(IN, OUT); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); VertexPositionInputs pos = GetVertexPositionInputs(IN.positionOS.xyz); VertexNormalInputs nrm = GetVertexNormalInputs(IN.normalOS); OUT.positionHCS = pos.positionCS; OUT.positionWS = pos.positionWS; OUT.normalWS = nrm.normalWS; OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); OUT.fogFactor = ComputeFogFactor(pos.positionCS.z); return OUT; } half4 frag (Varyings IN) : SV_Target { UNITY_SETUP_INSTANCE_ID(IN); half4 col = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor; float3 N = normalize(IN.normalWS); float3 V = GetWorldSpaceNormalizeViewDir(IN.positionWS); half rim = pow(1.0 - saturate(dot(N, V)), _RimPower); col.rgb += _RimColor.rgb * rim; col.rgb = MixFog(col.rgb, IN.fogFactor); return col; } ENDHLSL } } } ``` **Key points:** - `"RenderPipeline"="UniversalPipeline"` makes URP pick this SubShader. - `LightMode = UniversalForward` is the main forward pass. (In Forward+ / Deferred URP still renders unlit through the forward path.) - `GetVertexPositionInputs` / `GetVertexNormalInputs` are URP helpers that compute world/view/clip positions in one go. - Check SRP Batcher compatibility: select the shader asset, the Inspector shows **SRP Batcher: compatible**. ### 4b. Simple Lit (Main + Additional lights + Shadows, Forward+ aware) Create *Assets/Shaders/URPSimpleLit.shader*. This hand-rolls a Lambert + Blinn-Phong model using URP's light API. It includes a ShadowCaster and DepthOnly pass so the object casts shadows and works with depth prepass/SSAO. ```hlsl Shader "Tutorial/URP Simple Lit" { Properties { [MainTexture] _BaseMap ("Base Map", 2D) = "white" {} [MainColor] _BaseColor ("Base Color", Color) = (1,1,1,1) _Smoothness ("Smoothness (spec power)", Range(1, 256)) = 32 _SpecColor ("Specular Color", Color) = (0.3,0.3,0.3,1) } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half _Smoothness; half4 _SpecColor; CBUFFER_END ENDHLSL Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma vertex vert #pragma fragment frag // URP lighting keywords (main light shadows, additional lights, Forward+ clustering, soft shadows) #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH #pragma multi_compile _ _CLUSTER_LIGHT_LOOP #pragma multi_compile_fog #pragma multi_compile_instancing #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; float3 positionWS : TEXCOORD2; half fogFactor : TEXCOORD3; UNITY_VERTEX_INPUT_INSTANCE_ID }; Varyings vert (Attributes IN) { Varyings OUT = (Varyings)0; UNITY_SETUP_INSTANCE_ID(IN); UNITY_TRANSFER_INSTANCE_ID(IN, OUT); VertexPositionInputs p = GetVertexPositionInputs(IN.positionOS.xyz); OUT.positionCS = p.positionCS; OUT.positionWS = p.positionWS; OUT.normalWS = TransformObjectToWorldNormal(IN.normalOS); OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); OUT.fogFactor = ComputeFogFactor(p.positionCS.z); return OUT; } half3 Shade(Light light, float3 N, float3 V, half3 albedo) { half atten = light.distanceAttenuation * light.shadowAttenuation; half NdotL = saturate(dot(N, light.direction)); half3 H = normalize(light.direction + V); half spec = pow(saturate(dot(N, H)), _Smoothness) * NdotL; return (albedo * NdotL + _SpecColor.rgb * spec) * light.color * atten; } half4 frag (Varyings IN) : SV_Target { UNITY_SETUP_INSTANCE_ID(IN); half3 albedo = (SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor).rgb; float3 N = normalize(IN.normalWS); float3 V = GetWorldSpaceNormalizeViewDir(IN.positionWS); // Main light with shadows float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS); Light mainLight = GetMainLight(shadowCoord); half3 color = Shade(mainLight, N, V, albedo); // Ambient from spherical harmonics (light probes / skybox) color += albedo * SampleSH(N); // Additional lights. LIGHT_LOOP_BEGIN handles both classic and Forward+ clustered loops. // Forward+ needs InputData for the cluster lookup. InputData inputData = (InputData)0; inputData.positionWS = IN.positionWS; inputData.normalWS = N; inputData.viewDirectionWS = V; inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(IN.positionCS); // Forward+/Deferred+: non-main DIRECTIONAL lights are not in the clustered loop. #if USE_CLUSTER_LIGHT_LOOP UNITY_LOOP for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++) { Light dl = GetAdditionalLight(lightIndex, IN.positionWS, half4(1,1,1,1)); color += Shade(dl, N, V, albedo); } #endif uint lightCount = GetAdditionalLightsCount(); LIGHT_LOOP_BEGIN(lightCount) Light l = GetAdditionalLight(lightIndex, IN.positionWS, half4(1,1,1,1)); color += Shade(l, N, V, albedo); LIGHT_LOOP_END color = MixFog(color, IN.fogFactor); return half4(color, 1); } ENDHLSL } // Reuse URP's built-in passes so we cast shadows and write depth. UsePass "Universal Render Pipeline/Lit/ShadowCaster" UsePass "Universal Render Pipeline/Lit/DepthOnly" UsePass "Universal Render Pipeline/Lit/DepthNormals" } } ``` **Explanation:** - `Lighting.hlsl` provides `Light`, `GetMainLight`, `GetAdditionalLight`, `GetAdditionalLightsCount` and the `LIGHT_LOOP_BEGIN/END` macros that work in both Forward and Forward+ (the macro reads `inputData` for the cluster lookup, so the variable must be named `inputData`). - The Forward+ cluster keyword was renamed in recent URP versions; Unity 6.1+ uses `_CLUSTER_LIGHT_LOOP` (it covers Forward+ and Deferred+); `_FORWARD_PLUS` from 6000.0 is deprecated, so don't use it in 6000.5. In the clustered path, extra directional lights are looped separately (the `URP_FP_DIRECTIONAL_LIGHTS_COUNT` loop above), exactly as in Unity's *Render additional lights in a shader* manual page. - `UsePass` borrows the Lit shader's ShadowCaster/DepthOnly/DepthNormals passes. Those read `_BaseMap`/`_BaseColor` from **their own** CBUFFER layout, which differs from ours, so this breaks SRP Batcher compatibility. For production, copy URP's `ShadowCasterPass.hlsl` pattern into your own pass instead. For learning, `UsePass` keeps the file short. - If you just want "PBR with a twist", it's usually better to write a Shader Graph, or to call URP's `UniversalFragmentPBR(inputData, surfaceData)` from your own HLSL, rather than reimplementing BRDFs. --- ## Part 5 – A Fullscreen Effect with a ScriptableRendererFeature + Render Graph **Goal:** a tinted, vignette + grayscale post effect applied after post-processing, written the Unity 6 way. There are two routes: 1. **No code:** Add the built-in **Full Screen Pass Renderer Feature** to your Renderer asset and assign a material made from a **Fullscreen Shader Graph** (*Create > Shader Graph > URP > Fullscreen Shader Graph*, use the `URP Sample Buffer` node set to *BlitSource*). Great for most effects. 2. **Code:** a custom `ScriptableRendererFeature` that records a Render Graph pass. That's what we build here, since it teaches the Render Graph model. ### 5a. The fullscreen shader *Assets/Shaders/FullscreenTint.shader*: ```hlsl Shader "Tutorial/Fullscreen Tint Vignette" { Properties { _Tint ("Tint", Color) = (1, 0.9, 0.8, 1) _Grayscale ("Grayscale", Range(0,1)) = 0.5 _Vignette ("Vignette Strength", Range(0,2)) = 0.8 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } ZWrite Off ZTest Always Cull Off Blend Off Pass { Name "TintVignette" HLSLPROGRAM #pragma vertex Vert // Provided by Blit.hlsl: fullscreen triangle, sets texcoord #pragma fragment Frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // Declares _BlitTexture, _BlitScaleBias, Attributes/Varyings, and Vert() #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" half4 _Tint; half _Grayscale; half _Vignette; half4 Frag (Varyings input) : SV_Target { UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); float2 uv = input.texcoord; half4 col = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv); half lum = dot(col.rgb, half3(0.2126, 0.7152, 0.0722)); col.rgb = lerp(col.rgb, lum.xxx, _Grayscale) * _Tint.rgb; float2 d = uv - 0.5; half vig = saturate(1.0 - dot(d, d) * _Vignette * 2.0); col.rgb *= vig; return col; } ENDHLSL } } } ``` Create a material from it: `M_FullscreenTint`. ### 5b. The Renderer Feature (Render Graph) *Assets/Scripts/Rendering/TintVignetteFeature.cs*: ```csharp using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.RenderGraphModule.Util; using UnityEngine.Rendering.Universal; public class TintVignetteFeature : ScriptableRendererFeature { [System.Serializable] public class Settings { public Material material; public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingPostProcessing; [Range(0, 1)] public float grayscale = 0.5f; } public Settings settings = new(); TintVignettePass pass; public override void Create() { pass = new TintVignettePass { renderPassEvent = settings.passEvent }; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (settings.material == null) return; // Skip preview / reflection cameras. var camType = renderingData.cameraData.cameraType; if (camType == CameraType.Preview || camType == CameraType.Reflection) return; pass.Setup(settings); renderer.EnqueuePass(pass); } class TintVignettePass : ScriptableRenderPass { static readonly int GrayscaleId = Shader.PropertyToID("_Grayscale"); Settings s; public TintVignettePass() { // We read the camera color and write a new texture, so we need an // intermediate texture rather than rendering straight to the backbuffer. requiresIntermediateTexture = true; } public void Setup(Settings settings) => s = settings; public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { UniversalResourceData resourceData = frameData.Get<UniversalResourceData>(); // Can't sample the backbuffer as a texture. if (resourceData.isActiveTargetBackBuffer) { Debug.LogWarning("TintVignette skipped: camera is rendering to the backbuffer."); return; } s.material.SetFloat(GrayscaleId, s.grayscale); TextureHandle source = resourceData.activeColorTexture; // Describe a destination with the same size/format as the camera color. TextureDesc desc = renderGraph.GetTextureDesc(source); desc.name = "_TintVignetteColor"; desc.clearBuffer = false; TextureHandle destination = renderGraph.CreateTexture(desc); // AddBlitPass records a raster pass that draws a fullscreen triangle with our // material, binding 'source' as _BlitTexture. var blit = new RenderGraphUtils.BlitMaterialParameters(source, destination, s.material, 0); renderGraph.AddBlitPass(blit, passName: "Tint Vignette"); // Swap: later passes (and the final blit to screen) now read our result. // No copy-back needed. resourceData.cameraColor = destination; } } } ``` **Explanation – the Render Graph model:** - You don't execute commands directly. `RecordRenderGraph` *declares* passes and the resources they read/write. Render Graph then culls unused passes, allocates/aliases textures from a pool, and merges passes (native render passes on tile GPUs). - `ContextContainer frameData` holds frame data objects: `UniversalResourceData` (camera color, depth, GBuffer, motion vectors handles), `UniversalCameraData`, `UniversalLightData`, etc. - `TextureHandle`s are only valid inside the current graph recording. Never cache them across frames; cache `RTHandle`s and import them with `renderGraph.ImportTexture` if you need persistent textures (e.g., history buffers). - Setting `resourceData.cameraColor = destination` is the idiomatic "ping-pong" in URP 17: avoids a second copy. ### 5c. Writing the pass manually (when AddBlitPass isn't enough) If you need multiple draws, custom MRTs, or extra inputs (e.g. the depth texture), build a raster pass yourself. Replace the `AddBlitPass` call with: ```csharp class PassData { public TextureHandle source; public Material material; } // ...inside RecordRenderGraph, after creating 'destination': using (var builder = renderGraph.AddRasterRenderPass<PassData>("Tint Vignette (manual)", out var passData)) { passData.source = source; passData.material = s.material; builder.UseTexture(source, AccessFlags.Read); // declare read builder.SetRenderAttachment(destination, 0, AccessFlags.Write); // declare color target 0 builder.SetRenderFunc((PassData data, RasterGraphContext ctx) => { // Binds data.source as _BlitTexture and draws a fullscreen triangle. Blitter.BlitTexture(ctx.cmd, data.source, new Vector4(1, 1, 0, 0), data.material, 0); }); } resourceData.cameraColor = destination; ``` Rules: the render func must only use what's in `PassData` (it may run later, and lambdas must not capture outside state); every texture you touch must be declared with `UseTexture` / `SetRenderAttachment`, or Render Graph will error or cull the pass. ### 5d. Hook it up 1. Select `PC_Renderer` (the Universal Renderer Data asset). 2. **Add Renderer Feature > Tint Vignette Feature**. Assign `M_FullscreenTint`. 3. Enter Play mode or look at the Game view: grayscale tint + vignette. 4. Open **Window > Analysis > Render Graph Viewer** to see your "Tint Vignette" pass, its reads/writes, and whether it merged with neighbours. This is the #1 debugging tool for Render Graph work; the **Frame Debugger** is still useful to see actual draw calls. ### 5e. Making it Volume-driven (optional) To let artists blend the effect per area, create a `VolumeComponent`: ```csharp using UnityEngine.Rendering; [System.Serializable, VolumeComponentMenu("Tutorial/Tint Vignette")] public class TintVignetteVolume : VolumeComponent { public ClampedFloatParameter grayscale = new(0f, 0f, 1f); public bool IsActive() => grayscale.value > 0f; } ``` Then in `RecordRenderGraph` read it: ```csharp var vol = VolumeManager.instance.stack.GetComponent<TintVignetteVolume>(); if (vol == null || !vol.IsActive()) return; s.material.SetFloat(GrayscaleId, vol.grayscale.value); ``` Add a Global Volume, Add Override > Tutorial > Tint Vignette. --- ## Troubleshooting | Symptom | Fix | |---|---| | Pink material | Shader missing `"RenderPipeline"="UniversalPipeline"` or compile error (check shader Inspector). | | "SRP Batcher: not compatible" | A property is outside `UnityPerMaterial` CBUFFER, or passes have different CBUFFER layouts. | | Feature does nothing | Material not assigned, effect running before post-process overwrote it (check `renderPassEvent`), or camera writes to backbuffer – keep `requiresIntermediateTexture = true`. | | Errors about `Execute` / `OnCameraSetup` obsolete | Old non-Render-Graph code. Port to `RecordRenderGraph`. | | Additional lights missing in Forward+ | Missing `_CLUSTER_LIGHT_LOOP` keyword or `inputData` not set before `LIGHT_LOOP_BEGIN`. | ## Further Reading (Unity 6 docs) - URP manual: *Render graph system in URP* and *Write a render pass using the render graph system*. - URP manual: *Writing custom shaders* (URP unlit basic shader examples). - Shader Graph manual: *Fullscreen Master Stack*, *Fresnel Effect node*, *Alpha Clipping*. - Package samples: *Package Manager > Universal RP > Samples > URP RenderGraph Samples* – official working examples of every pattern above. ## See also - [[Lighting and Baking]] — GPU lightmapper, lightmaps, APV / Light Probes, reflection probes, Quest lighting budgets