Unity’s Scriptable Render Pipeline represents a great advance on the way that unity deals with graphics, giving more power to the users to customize the pipeline the way they want. I have started to use the Universal Render Pipeline (URP) recently and, despite all its advantages over the built-in pipeline, it still suffers of lack of documentation. I mean, you can find information of every function available in the package documentation, but it’s still hard to find examples and translations from built-in to URP. Unity’s docs are (were?) good because when you look for something, you usually find the explanation of that and sometimes it’s followed by an example of how to use that.
I am pretty sure that Unity is working on improving the current situation of the docs (specially regarding packages), but while that’s not available I decided to write this article with a kind of translation from built-in to URP. I’m doing this not only to help others, but to help myself as well, so I can find all things in one place.
Before starting, here are some useful links to help you dive into URP stuff:
- URP package docs
- URP github
- Official examples
- Unlit template
- Boat attack demo project
- Phil Lira’s shader examples
- Outline post-effect using ScriptableRendererFeature
- Phil Lira’s twitter
Summary
Okay! So let’s go! All things here are mostly based on version 7.3 (version I’m using currently) sooo… you know… things might change. Some of the sections follow the built-in documentation order. The article is organized as follows:
- General Structure
- Shader Include Files
- Light Modes
- Variants
- Predefined Shader Preprocessor Macros
- Built-in Shader Helper Functions
- Built-in Shader Variables
- Random Stuff
- Post-processing/VFX
- Conclusion
General Structure ↑
First of all, add "RenderPipeline" = "UniversalPipeline"
to your tags. Next, all URP shaders are written using HLSL
embraced by HLSLPROGRAM/ENDHLSL/etc.
macros. To avoid headaches, use them as well.
Built-in | URP |
---|---|
CGPROGRAM HLSLPROGRAM | HLSLPROGRAM |
ENDCG ENDHLSL | ENDHLSL |
CGINCLUDE HLSLINCLUDE | HLSLINCLUDE |
Shader Include Files ↑
Content | Built-in | URP |
---|---|---|
Core | Unity.cginc | Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl |
Light | AutoLight.cginc | Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl |
Shadows | AutoLight.cginc | Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl |
Surface shaders | Lighting.cginc | None, but you can find a side project for this here |
Other useful includes:
- Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl
- Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl
- Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl
- Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl
- Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl
- Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl
- Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTextue.hlsl
Light Modes ↑
Built-in | URP |
---|---|
ForwardBase | UniversalForward |
ForwardAdd | Gone |
Deferred and related | UniversalGBuffer seems to have just been added to URP |
Vertex and related | Gone |
ShadowCaster | ShadowCaster |
MotionVectors | Not suppoted yet |
The other light modes supported are:
- DepthOnly
- Meta (for lightmap baking)
- Universal2D
Variants ↑
URP support some variants, so depending on the things you are using, you might need to add some #pragma multi_compile
for some of the following keywords:
_MAIN_LIGHT_SHADOWS
_MAIN_LIGHT_SHADOWS_CASCADE
_ADDITIONAL_LIGHTS_VERTEX
_ADDITIONAL_LIGHTS
_ADDITIONAL_LIGHT_SHADOWS
_SHADOWS_SOFT
_MIXED_LIGHTING_SUBTRACTIVE
Predefined Shader Preprocessor Macros ↑
Helpers ↑
Built-in | URP |
---|---|
UNITY_PROJ_COORD(a) | Gone. Do a.xy/a.w instead |
UNITY_INITIALIZE_OUTPUT(type, name) | ZERO_INITIALIZE(type, name) |
Shadow Mapping ↑
You must include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl”.
Built-in | URP |
---|---|
UNITY_DECLARE_SHADOWMAP(tex) | TEXTURE2D_SHADOW_PARAM(textureName, samplerName) |
UNITY_SAMPLE_SHADOW(tex, uv) | SAMPLE_TEXTURE2D_SHADOW(textureName, samplerName, coord3) |
UNITY_SAMPLE_SHADOW_PROJ(tex, uv) | SAMPLE_TEXTURE2D_SHADOW(textureName, samplerName, coord4.xyz/coord4.w) |
Texture/Sampler Declaration Macros ↑
Unity has a bunch of texture/sampler macros to improve cross compatibility between APIs, but people are not used to use them. Those still exist in URP, but now with different names and new additions. I will not put all of them here because it’s a lot, but you can check their definitions per platform in the API includes.
Built-in | URP |
---|---|
UNITY_DECLARE_TEX2D(name) | TEXTURE2D(textureName); SAMPLER(samplerName); |
UNITY_DECLARE_TEX2D_NOSAMPLER(name) | TEXTURE2D(textureName); |
UNITY_DECLARE_TEX2DARRAY(name) | TEXTURE2D_ARRAY(textureName); SAMPLER(samplerName); |
UNITY_SAMPLE_TEX2D(name, uv) | SAMPLE_TEXTURE2D(textureName, samplerName, coord2) |
UNITY_SAMPLE_TEX2D_SAMPLER(name, samplername, uv) | SAMPLE_TEXTURE2D(textureName, samplerName, coord2) |
UNITY_SAMPLE_TEX2DARRAY(name, uv) | SAMPLE_TEXTURE2D_ARRAY(textureName, samplerName, coord2, index) |
UNITY_SAMPLE_TEX2DARRAY_LOD(name, uv, lod) | SAMPLE_TEXTURE2D_ARRAY_LOD(textureName, samplerName, coord2, index, lod) |
Important to note that SCREENSPACE_TEXTURE
has become TEXTURE2D_X
. If you are working on some screen space effect for VR in Single Pass Instanced or Multi-view modes, you must declare the textures used with TEXTURE2D_X
. This macro will handle for you the correct texture (array or not) declaration. You also have to sample the textures using SAMPLE_TEXTURE2D_X
and use UnityStereoTransformScreenSpaceTex
for the uv.
Built-in Shader Helper Functions ↑
You can find them all in “Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl”.
Vertex Transformation Functions ↑
Built-in | URP |
---|---|
float4 UnityObjectToClipPos(float3 pos) | float4 TransformObjectToHClip(float3 positionOS) |
float3 UnityObjectToViewPos(float3 pos) | TransformWorldToView(TransformObjectToWorld(positionOS)) |
Generic Helper Functions ↑
Built-in | URP | |
---|---|---|
float3 WorldSpaceViewDir (float4 v) | float3 GetWorldSpaceViewDir(float3 positionWS) | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl” |
float3 ObjSpaceViewDir (float4 v) | Gone. Do TransformWorldToObject(GetCameraPositionWS()) - objectSpacePosition; | |
float2 ParallaxOffset (half h, half height, half3 viewDir) | Gone? Copy from UnityCG.cginc | |
fixed Luminance (fixed3 c) | real Luminance(real3 linearRgb) | Include “Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl” |
fixed3 DecodeLightmap (fixed4 color) | real3 DecodeLightmap(real4 encodedIlluminance, real4 decodeInstructions) | Include “Packages/com.unity.render-pipelines.core/ShaderLibrary/EntityLighting.hlsl”decodeInstructions is used as half4(LIGHTMAP_HDR_MULTIPLIER, LIGHTMAP_HDR_EXPONENT, 0.0h, 0.0h) by URP |
float4 EncodeFloatRGBA (float v) | Gone? Copy from UnityCG.cginc | |
float DecodeFloatRGBA (float4 enc) | Gone? Copy from UnityCG.cginc | |
float2 EncodeFloatRG (float v) | Gone? Copy from UnityCG.cginc | |
float DecodeFloatRG (float2 enc) | Gone? Copy from UnityCG.cginc | |
float2 EncodeViewNormalStereo (float3 n) | Gone? Copy from UnityCG.cginc | |
float3 DecodeViewNormalStereo (float4 enc4) | Gone? Copy from UnityCG.cginc |
Forward Rendering Helper Functions ↑
Built-in | URP | |
---|---|---|
float3 WorldSpaceLightDir (float4 v) | _MainLightPosition.xyz - TransformObjectToWorld(objectSpacePosition) | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl” |
float3 ObjSpaceLightDir (float4 v) | TransformWorldToObject(_MainLightPosition.xyz) - objectSpacePosition | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl” |
float3 Shade4PointLights (…) | Gone. You can try to use half3 VertexLighting(float3 positionWS, half3 normalWS) | For VertexLighting(...) include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl” |
Screen-space Helper Functions ↑
Built-in | URP | |
---|---|---|
float4 ComputeScreenPos (float4 clipPos) | float4 ComputeScreenPos(float4 positionCS) | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl” |
float4 ComputeGrabScreenPos (float4 clipPos) | Gone. |
Vertex-lit Helper Functions ↑
Built-in | URP | |
---|---|---|
float3 ShadeVertexLights (float4 vertex, float3 normal) | Gone. You can try to use UNITY_LIGHTMODEL_AMBIENT.xyz + VertexLighting(...) | For VertexLighting(...) include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl” |
A bunch of utilities can be found in “Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl”.
Built-in Shader Variables ↑
Most of the shader variables remains the same, except by lighting.
Lighting ↑
Built-in | URP | |
---|---|---|
_LightColor0 | _MainLightColor | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl” |
_WorldSpaceLightPos0 | _MainLightPosition | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl” |
_LightMatrix0 | Gone ? Cookies are not supported yet | |
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0 | In URP, additional lights are stored in an array/buffer (depending on platform). Retrieve light information using Light GetAdditionalLight(uint i, float3 positionWS) | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl” |
unity_4LightAtten0 | In URP, additional lights are stored in an array/buffer (depending on platform). Retrieve light information using Light GetAdditionalLight(uint i, float3 positionWS) | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl” |
unity_LightColor | In URP, additional lights are stored in an array/buffer (depending on platform). Retrieve light information using Light GetAdditionalLight(uint i, float3 positionWS) | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl” |
unity_WorldToShadow | float4x4 _MainLightWorldToShadow[MAX_SHADOW_CASCADES + 1] or _AdditionalLightsWorldToShadow[MAX_VISIBLE_LIGHTS] | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl” |
If you want to loop over all additional lights using GetAdditionalLight(...)
, you can query the additional lights count by using GetAdditionalLightsCount()
.
Random Stuff ↑
Shadows ↑
For more info about shadows, check “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl”.
Built-in | URP | |
---|---|---|
UNITY_SHADOW_COORDS(x) | Gone? DIY, e.g. float4 shadowCoord : TEXCOORD0; | |
TRANSFER_SHADOW(a) | a.shadowCoord = TransformWorldToShadowCoord(worldSpacePosition) | With cascades on, do this on fragment to avoid visual artifacts |
SHADOWS_SCREEN | Gone. Not supported. |
Fog ↑
For more info about fog, check “Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl”.
Built-in | URP |
---|---|
UNITY_FOG_COORDS(x) | Gone? DIY, e.g. float fogCoord : TEXCOORD0; |
UNITY_TRANSFER_FOG(o, outpos) | o.fogCoord = ComputeFogFactor(clipSpacePosition.z); |
UNITY_APPLY_FOG(coord, col) | color = MixFog(color, i.fogCoord); |
Depth ↑
To use the camera depth texture, include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl” and the _CameraDepthTexture
will be declared for you as well as helper the functions SampleSceneDepth(...)
and LoadSceneDepth(...)
.
Built-in | URP | |
---|---|---|
LinearEyeDepth(sceneZ) | LinearEyeDepth(sceneZ, _ZBufferParams) | Include “Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl” |
Linear01Depth(sceneZ) | Linear01Depth(sceneZ, _ZBufferParams) | Include “Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl” |
Etc. ↑
Built-in | URP | |
---|---|---|
ShadeSH9(normal) | SampleSH(normal) | Include “Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl” |
unity_ColorSpaceLuminance | Gone. Use Luminance() | Include “Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl” |
Post-processing/VFX ↑
URP does not support OnPreCull
, OnPreRender
, OnPostRender
and OnRenderImage
. It does support OnRenderObject
and OnWillRenderObject
, but you might find issues depending on what you want to do. So, If you used to use those when creating your visual effects, I recommend learning how to use the new approaches available. The RenderPipelineManager
provides the following injection points in the pipeline:
beginCameraRendering(ScriptableRenderContext context, Camera camera)
endCameraRendering(ScriptableRenderContext context, Camera camera)
beginFrameRendering(ScriptableRenderContext context,Camera[] cameras)
endFrameRendering(ScriptableRenderContext context,Camera[] cameras)
Example of usage:
void OnEnable()
{
RenderPipelineManager.beginCameraRendering += MyCameraRendering;
}
void OnDisable()
{
RenderPipelineManager.beginCameraRendering -= MyCameraRendering;
}
void MyCameraRendering(ScriptableRenderContext context, Camera camera)
{
...
if(camera == myEffectCamera)
{
...
}
...
}
Like I said, OnWillRenderObject
is supported, however if you need to perform a render call inside of it (e.g. water reflection/refraction), it will not work. As soon as you call Camera.Render()
, you will see the following message:
Recursive rendering is not supported in SRP (are you calling Camera.Render from within a render pipeline?)
In this case, replace the OnWillRenderObject
by begin/endCameraRendering
(like the example above) and call RenderSingleCamera()
from URP instead of Camera.Render()
. Changing the example above, you would have something like
void MyCameraRendering(ScriptableRenderContext context, Camera camera)
{
...
if(camera == myEffectCamera)
{
...
UniversalRenderPipeline.RenderSingleCamera(context, camera);
}
...
}
The other approach to work with Post-processing is to use a ScriptableRendererFeature
. This post has a great explanation of an outline effect using this feature. A ScriptableRendererFeature
allows you to inject ScriptableRenderPass(es)
at different stages of the pipeline, thus being a powerful tool for creating post-processing effects. The injection places are the following:
BeforeRendering
BeforeRenderingShadows
AfterRenderingShadows
BeforeRenderingPrepasses
AfterRenderingPrePasses
BeforeRenderingOpaques
AfterRenderingOpaques
BeforeRenderingSkybox
AfterRenderingSkybox
BeforeRenderingTransparents
AfterRenderingTransparents
BeforeRenderingPostProcessing
AfterRenderingPostProcessing
AfterRendering
This is a simple example of a ScriptableRendererFeature
performing a blit with a custom material:
public class CustomRenderPassFeature : ScriptableRendererFeature
{
class CustomRenderPass : ScriptableRenderPass
{
CustomRPSettings _CustomRPSettings;
RenderTargetHandle _TemporaryColorTexture;
private RenderTargetIdentifier _Source;
private RenderTargetHandle _Destination;
public CustomRenderPass(CustomRPSettings settings)
{
_CustomRPSettings = settings;
}
public void Setup(RenderTargetIdentifier source, RenderTargetHandle destination)
{
_Source = source;
_Destination = destination;
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
_TemporaryColorTexture.Init("_TemporaryColorTexture");
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get("My Pass");
if (_Destination == RenderTargetHandle.CameraTarget)
{
cmd.GetTemporaryRT(_TemporaryColorTexture.id, renderingData.cameraData.cameraTargetDescriptor, FilterMode.Point);
cmd.Blit(_Source, _TemporaryColorTexture.Identifier());
cmd.Blit(_TemporaryColorTexture.Identifier(), _Source, _CustomRPSettings.m_Material);
}
else
{
cmd.Blit(_Source, _Destination.Identifier(), _CustomRPSettings.m_Material, 0);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void FrameCleanup(CommandBuffer cmd)
{
if (_Destination == RenderTargetHandle.CameraTarget)
{
cmd.ReleaseTemporaryRT(_TemporaryColorTexture.id);
}
}
}
[System.Serializable]
public class CustomRPSettings
{
public Material m_Material;
}
public CustomRPSettings m_CustomRPSettings = new CustomRPSettings();
CustomRenderPass _ScriptablePass;
public override void Create()
{
_ScriptablePass = new CustomRenderPass(m_CustomRPSettings);
_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
_ScriptablePass.Setup(renderer.cameraColorTarget, RenderTargetHandle.CameraTarget);
renderer.EnqueuePass(_ScriptablePass);
}
}
You can create a ScriptableRendererFeature
by clicking on “Create > Rendering > Universal Render Pipeline > Renderer Feature”. The feature that you have created has to be added to your ForwardRenderer
. To do so, select the ForwardRenderer
, click on “Add Renderer Feature” and select the feature to be added. You can expose properties in the feature inspector, for example, if you add the example above, you will see a material slot available.
Conclusion ↑
That’s it for now. I will try to keep this updated (narrator: he won’t) according to the new things I’m learning or new features that I see added. If you have comments or suggestions, you can find me on twitter.