urp-pipeline
Universal Render Pipeline — URP asset configuration, renderer features, 2D renderer, lighting, shadows, post-processing volumes, SRP Batcher.
Files1
View on GitHubSKILL.md.claude/skills/urp-pipeline/SKILL.md
GitHubUniversal Render Pipeline (URP)
URP Pipeline Asset Configuration
The URP Pipeline Asset controls global rendering settings. Create via Assets > Create > Rendering > URP Asset (with Universal Renderer).
Key Pipeline Asset Settings
| Setting | Recommended | Notes |
|---|---|---|
| HDR | Enabled | Required for Bloom and color grading |
| Anti-Aliasing | MSAA 4x or FXAA | MSAA on mobile, FXAA on desktop |
| Shadow Resolution | 2048 (desktop) / 1024 (mobile) | Balance quality vs performance |
| Shadow Cascade Count | 4 (desktop) / 2 (mobile) | More cascades = better shadow distribution |
| Shadow Distance | 50-150 | Depends on game scale |
| SRP Batcher | Enabled | Major draw call optimization |
| Dynamic Batching | Disabled when SRP Batcher is on | They conflict; SRP Batcher is superior |
Configuring Pipeline Asset via Script
using UnityEngine;
using UnityEngine.Rendering.Universal;
public class URPQualityManager : MonoBehaviour
{
[SerializeField] private UniversalRenderPipelineAsset[] qualityLevels;
public void SetQualityLevel(int level)
{
if (level >= 0 && level < qualityLevels.Length)
{
QualitySettings.renderPipeline = qualityLevels[level];
}
}
public void AdjustShadowDistance(float distance)
{
var urpAsset = (UniversalRenderPipelineAsset)QualitySettings.renderPipeline;
urpAsset.shadowDistance = distance;
}
}
Renderer Features (Custom Render Passes)
Renderer Features let you inject custom rendering logic into URP's render pipeline.
Creating a Custom Renderer Feature
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class OutlineRendererFeature : ScriptableRendererFeature
{
[System.Serializable]
public class OutlineSettings
{
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
public Material outlineMaterial;
public LayerMask layerMask;
[Range(1, 4)] public int downSample = 1;
}
public OutlineSettings settings = new OutlineSettings();
private OutlineRenderPass _outlinePass;
public override void Create()
{
_outlinePass = new OutlineRenderPass(settings);
_outlinePass.renderPassEvent = settings.renderPassEvent;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.outlineMaterial == null) return;
renderer.EnqueuePass(_outlinePass);
}
}
Custom Render Pass
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class OutlineRenderPass : ScriptableRenderPass
{
private readonly OutlineRendererFeature.OutlineSettings _settings;
private RTHandle _tempTexture;
public OutlineRenderPass(OutlineRendererFeature.OutlineSettings settings)
{
_settings = settings;
profilingSampler = new ProfilingSampler("OutlinePass");
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
var desc = renderingData.cameraData.cameraTargetDescriptor;
desc.depthBufferBits = 0;
RenderingUtils.ReAllocateIfNeeded(ref _tempTexture, desc, name: "_TempOutline");
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, profilingSampler))
{
var source = renderingData.cameraData.renderer.cameraColorTargetHandle;
Blitter.BlitCameraTexture(cmd, source, _tempTexture, _settings.outlineMaterial, 0);
Blitter.BlitCameraTexture(cmd, _tempTexture, source);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
_tempTexture?.Release();
}
}
Forward vs Forward+ Renderer
- Forward: Traditional forward rendering. Good for mobile, limited additional lights per object.
- Forward+: Uses clustered lighting. Removes per-object light limit. Better for scenes with many lights. Requires Unity 2022.2+.
Set in the Universal Renderer Data asset under Rendering Path.
2D Renderer Setup
For 2D games, use the 2D Renderer:
- Create URP Asset with 2D Renderer (Assets > Create > Rendering > URP Asset with 2D Renderer)
- 2D Renderer supports: Light2D, ShadowCaster2D, Sprite-Lit-Default shader
- Use Light2D components: Global, Freeform, Sprite, Point, Spot
using UnityEngine;
using UnityEngine.Rendering.Universal;
public class DynamicLight2DController : MonoBehaviour
{
private Light2D _light2D;
private void Awake()
{
_light2D = GetComponent<Light2D>();
}
public void SetIntensity(float intensity)
{
_light2D.intensity = intensity;
}
public void FlickerLight(float minIntensity, float maxIntensity)
{
_light2D.intensity = Random.Range(minIntensity, maxIntensity);
}
}
URP Lighting Configuration
Main Light (Directional)
- Shadow type: Soft Shadows for quality, Hard for performance
- Shadow resolution: Set per-light or globally in Pipeline Asset
- Shadow bias: Normal Bias 0.4, Depth Bias 1.0 (starting values)
Additional Lights
- Per-object limit in Pipeline Asset (default 4 for Forward)
- Forward+ removes this limit via clustered lighting
- Shadow support for additional lights must be enabled in Pipeline Asset
Shadow Settings
Pipeline Asset:
Shadow Distance: 100
Cascade Count: 4
Cascade Ratios: 0.067, 0.2, 0.467 (default)
Depth Bias: 1
Normal Bias: 1
Soft Shadows: Enabled
Post-Processing (Volume Framework)
URP uses the Volume framework for post-processing. No separate post-processing package needed.
Setting Up Post-Processing
- Enable Post Processing on the Camera component
- Create a Global Volume (or local with collider)
- Add Volume overrides (Bloom, Color Grading, etc.)
Common Volume Profile Script
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class PostProcessController : MonoBehaviour
{
[SerializeField] private Volume globalVolume;
private Bloom _bloom;
private ColorAdjustments _colorAdjustments;
private Vignette _vignette;
private ChromaticAberration _chromaticAberration;
private void Awake()
{
var profile = globalVolume.profile;
profile.TryGet(out _bloom);
profile.TryGet(out _colorAdjustments);
profile.TryGet(out _vignette);
profile.TryGet(out _chromaticAberration);
}
public void SetDamageEffect(float intensity)
{
if (_vignette != null)
{
_vignette.intensity.Override(Mathf.Lerp(0.2f, 0.6f, intensity));
_vignette.color.Override(Color.Lerp(Color.black, Color.red, intensity));
}
if (_chromaticAberration != null)
{
_chromaticAberration.intensity.Override(intensity * 0.5f);
}
}
public void SetBloomIntensity(float intensity)
{
if (_bloom != null)
{
_bloom.intensity.Override(intensity);
}
}
public void SetExposure(float exposure)
{
if (_colorAdjustments != null)
{
_colorAdjustments.postExposure.Override(exposure);
}
}
}
Tonemapping Modes
- None: Raw HDR values clamped. Not recommended.
- Neutral: Minimal color shift. Good default.
- ACES: Film-like look, saturated. Industry standard.
SRP Batcher Compatibility
The SRP Batcher groups draw calls by shader variant. To make a shader compatible:
Shader Must Use CBUFFER
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
float _Smoothness;
float4 _BaseMap_ST;
CBUFFER_END
Rules for SRP Batcher compatibility:
- All material properties must be in the
UnityPerMaterialCBUFFER - Built-in engine properties must be in
UnityPerDrawCBUFFER - MaterialPropertyBlock breaks batching (avoid in URP)
Checking SRP Batcher Compatibility
Select any shader in the Inspector. The "SRP Batcher" field shows Compatible or Not Compatible with a reason.
URP Shader Includes
// Core URP includes for custom shaders
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
Camera Stacking
Camera stacking renders multiple cameras in sequence (e.g., world camera + UI camera + minimap).
using UnityEngine;
using UnityEngine.Rendering.Universal;
public class CameraStackManager : MonoBehaviour
{
[SerializeField] private Camera baseCamera;
[SerializeField] private Camera uiCamera;
[SerializeField] private Camera minimapCamera;
private void Awake()
{
var baseCameraData = baseCamera.GetUniversalAdditionalCameraData();
baseCameraData.renderType = CameraRenderType.Base;
var uiCameraData = uiCamera.GetUniversalAdditionalCameraData();
uiCameraData.renderType = CameraRenderType.Overlay;
var minimapData = minimapCamera.GetUniversalAdditionalCameraData();
minimapData.renderType = CameraRenderType.Overlay;
// Add overlay cameras to the stack
baseCameraData.cameraStack.Add(uiCamera);
baseCameraData.cameraStack.Add(minimapCamera);
}
}
Camera stacking rules:
- Only one Base camera renders first
- Overlay cameras render on top in stack order
- Each overlay camera adds a full render pass (expensive)
- Use sparingly; prefer single camera with layers when possible
Render Pass Events
When creating custom render passes, choose the appropriate injection point:
| Event | Use Case |
|---|---|
| BeforeRenderingShadows | Custom shadow passes |
| AfterRenderingShadows | Shadow post-processing |
| BeforeRenderingOpaques | Pre-opaque effects |
| AfterRenderingOpaques | Outlines, SSAO |
| BeforeRenderingTransparents | Effects behind transparents |
| AfterRenderingTransparents | Distortion effects |
| BeforeRenderingPostProcessing | Custom pre-post effects |
| AfterRenderingPostProcessing | Final overlays, UI effects |
| AfterRendering | Debug visualization |