Create a Custom Pass GameObject
To create a Custom Pass, add a Custom Pass Volume component to your scene using GameObject > Volume > Custom Pass. This creates a GameObject called Custom Pass which includes the Custom Pass Volume component. You can use this component to create and configure Custom Passes.
Full-screen Custom Pass
A full-screen Custom Pass applies an effect to the whole screen. To achieve this, it renders a full-screen quad using a material that is configured for use with a full-screen Custom Pass, and executes a fragment shader for every pixel on the screen.
To create a full-screen Custom Pass, click the Add (+) button at the bottom right of the Custom Pass Volume component and select FullScreeenCustomPass.
For information about the Full-screen Custom Pass properties, refer to Custom pass reference.
Create a material to use with a full-screen Custom Pass
To use a full-screen Custom Pass, you need to create a material for your Custom Pass to use. You can do this in one of the following ways:
- Create a Material from a full-screen shader graph.
- Create a Material from a full-screen Custom Pass shader.
Create a Material from a full-screen Shader graph
HDRP includes the Fullscreen Master stack to create a full-screen shader graph that you can use with a custom pass.
To use a Fullscreen shader graph in a custom pass:
- Create a Fullscreen shader graph (Menu: Assets > Create > Shader Graph > HDRP > Fullscreen Shader Graph) or use one of the Fullscreen Samples.
- Create a Material from the Fullscreen Shader Graph. In the Project window:
- Right-click the Fullscreen Shader graph.
- Select Create > Material.
- Create a Fullscreen Custom Pass and open it in the inspector.
- In the Project window, select the arrow next to the full-screen shader graph.
- Drag the FullScreen material to the Fullscreen Material field.
Important: If you use a C# custom pass script that uses the Blit()
function, you need to take extra steps to make the full screen shader work. For more information, see Make a Fullscreen material Blit compatible.
Create a Material from a full-screen Custom Pass shader
HDRP includes a compatible shader for full-screen custom passes. To create and apply a full-screen shader:
- Create a full-screen Custom Pass shader. To do this, go to Assets > Create > Shader > HDRP Custom FullScreen Pass.
- Go to the Hierarchy and right click on the Custom FullScreen Pass shader, then select Create > Material. Unity creates a new material with the prefix “FullScreen_”.
- In the Custom Pass component, select Add (+) to create a Custom Pass and select FullScreenCustomPass.
- To assign this material to your FullScreenCustomPass, open the material picker using the icon at the right of the FullScreen Material box and select your “FullScreen_...” material.
- If you have multiple shader passes, select the Pass Name you would like this pass to use.
Modify a full-screen Custom Pass shader in code
When Unity creates a material for use with a full-screen Custom Pass, it also creates default template code for a Unity shader.
To edit the Unity shader:
- Select the material in the Assets window.
- Navigate to the Inspector.
- Select Open.
This Unity shader source file contains the FullScreenPass
method where you can add custom shader code for your full-screen Custom Pass:
float4 FullScreenPass(Varyings varyings) : SV_Target
{
float depth = LoadCameraDepth(varyings.positionCS.xy);
PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
float4 color = float4(0.0, 0.0, 0.0, 0.0);
// Load the camera color buffer at the mip 0 if not currently in the BeforeRendering injection point
if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
color = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);
// Add your custom pass code here
// Fade value allows you to increase the strength of the effect while the camera gets closer to the custom pass volume
float f = 1 - abs(_FadeValue * 2 - 1);
return float4(color.rgb + f, color.a);
}
In your custom code, you can use the following inputs:
depth
, which is the depth of the fragment, as a float2.viewDirection
which is the view direction, as a float3.posInput
which is a struct that contains the world space position, as a float3.color
which is the RGBA color value, as a float4.
The FullScreenPass
method also uses the _FadeValue
variable, which allows you to increase the strength of an effect as the Camera gets closer to the Custom Pass Volume. This variable only works on local Custom Pass Volumes that have a Fade Radius value above 0.
Write to and read from a custom color buffer
To create a full-screen material for a custom pass that uses the Camera color buffer, you need to read from and write to a custom color buffer. This is useful to create effects like blur, color correction or chromatic aberration.
However, you can’t read and write to the same render target in a custom pass. This means you need to create two full-screen custom passes:
- A custom pass that writes the Camera’s color buffer into a temporary (custom) color buffer.
- A custom pass that reads from the temporary color buffer.
Note: If a custom pass adjusts the color of each pixel and not their position, then you can read and write to the color buffer in a single pass. To do this, write a compute shader and dispatch it in a custom pass C# file. This method uses fewer resources on the GPU, but it is more complex.
Write to a custom color buffer
To write to the custom color buffer in a full-screen custom pass:
- Create a full-screen Custom Pass.
- Assign a Full Screen shader to the Fullscreen Material field. For example a shader graph that uses HD Scene Color node to write to the Base Color.
- Enable Fetch Color Buffer.
- Set the Target Color Buffer to Custom.
Read from the custom color buffer
To create and assign a full-screen shader graph that reads from the custom color buffer:
- Create a new full-screen shader graph.
- Press the Spacebar to open the Create Node window
- In the Create Node window, search for the Custom Color Buffer node
- Double-click the Custom Color Buffer node to create it in your shader graph.
- Connect the Output port of the Custom Color Buffer to the Base Color block in the Fragment context.
- In the Custom Pass Volume component that samples from the color buffer, select Add (+).
- Select Full Screen Custom Pass.
- In the FullScreen Material property, select the material picker (circle).
- Set the Target Color Buffer to Camera.
- Select the material that reads from the color buffer.
This copies the contents of the custom color buffer to the Camera color buffer.
For more information on how to modify a full-screen Shader Graph, see the full-screen master stack.
Draw renderers Custom Pass
Use a draw renderers Custom Pass to apply an effect to GameObjects that are in the Camera’s view.
You can also use this Custom Pass to apply an effect to objects outside of the camera’s view using the options in the Culling Mask drop-down property in the HD Camera component.
To create a draw renderers Custom Pass, click the Add (+) button in the Custom Pass Volume component and select DrawRenderersCustomPass.
For information about the Draw renderers Custom Pass properties, refer to Custom pass reference.
ForwardOnly support
Unity uses the Pass Name to select which pass of the shader it renders on an HDRP material. To render the object color, select Forward or ForwardOnly. Use the DepthForwardOnly Pass Name if you only want to render the depth of the object.
If you see the following warning when you create a new draw renderers CustomPass, this might be due to your HDRP Asset settings:
To fix this, navigate to your HDRP Asset in your Assets folder (if you are using the HDRP template, this is in Assets > Settings) and change the Lit Shader Mode to Both. For more information, see Changing the depth of a renderer in your scene.
Material and injection point compatibility
Injection Points determine when Unity executes a Custom Pass Volume in the render loop.
However, HDRP does not support all materials in every injection point in a draw renderers Custom Pass. The following table lists the materials that each injection point supports for a draw renderers Custom Pass:
Injection Point | Material Type(s) supported |
---|---|
Before Rendering | Unlit forward but without writing to the Camera color |
After Opaque Depth And Normal | Unlit |
Before PreRefraction | Unlit and Lit, ForwardOnly |
Before Transparent | Unlit and Lit, ForwardOnly |
Before Post Process | Unlit and Lit, ForwardOnly |
After Post Process | Unlit and Lit, ForwardOnly |
Note: You can only render Materials that use refraction at the Before Transparent, Before Post Process, and After Post Process injection points because this is when the refraction color pyramid is available.
When Unity renders a material that isn't supported by the current injection point, it results in an undefined behavior. For example, rendering GameObjects with lit shaders in the After Opaque Depth And Normal injection point produces unexpected results.
Note: HDRP doesn't support decals on GameObjects rendered in the DrawRenderers pass.
Note: When rendering a Terrain with the DrawRenderers custom pass, only the override mode "Shader" is supported. Also, the replacement shader must be compatible with terrain rendering (like the HDRP/TerrainLit shader).
Object ID Custom Pass
The Object ID Custom Pass draws GameObjects in the scene with a unique color controlled by the Object ID
. It uses a built-in Material to draw the Object ID colors.
For information about the Draw renderers Custom Pass properties, refer to Custom pass reference.
Generate Object IDs for a custom pass
HDRP generates Object IDs before executing an Object ID custom pass.
If you use a script to generate new GameObjects procedurally, call AssignObjectIDs
to generate new Object IDs.
The following script example shows the default AssignObjectIDs
implementation that assigns Object ID colors incrementally:
public virtual void AssignObjectIDs()
{
var rendererList = Resources.FindObjectsOfTypeAll(typeof(Renderer));
int index = 0;
foreach (Renderer renderer in rendererList)
{
MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock();
float hue = (float)index / rendererList.Length;
propertyBlock.SetColor("ObjectColor", Color.HSVToRGB(hue, 0.7f, 1.0f));
renderer.SetPropertyBlock(propertyBlock);
index++;
}
}
Generate a Custom Object ID
To customize the way Unity generates a Object ID, override the AssignObjectIDs
method. The following script example uses this method to assign Object ID colors randomly:
class RandomObjectIDs : ObjectIDCustomPass
{
public override void AssignObjectIDs()
{
var rendererList = Resources.FindObjectsOfTypeAll(typeof(Renderer));
System.Random rand = new System.Random();
int index = 0;
foreach (Renderer renderer in rendererList)
{
MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock();
float hue = (float)rand.NextDouble();
propertyBlock.SetColor("ObjectColor", Color.HSVToRGB(hue, 0.7f, 1.0f));
renderer.SetPropertyBlock(propertyBlock);
index++;
}
}
}
Custom Renderers Pass shader
You can create advanced Custom Pass effects using a Custom Renderers Pass shader. To create this shader, navigate to Assets > Create > Shader > HDRP > Custom Renderers Pass. This creates an unlit HDRP shader named New Renderers CustomPass. This Unity shader has one ShaderLab pass.
Use a Custom Renderers Pass shader to create advanced effects
HDRP includes a specific shader structure to store the data that the render pipeline uses to render materials in your scene. The Fragment shader code section describes these structures.
Fragment shader code
To write fragment shader code in the Custom Renderers Pass shader, use the GetSurfaceAndBuiltinData
method.
This method accepts the following inputs:
- A
FragInputs
structure that contains all the geometry inputs passed into the fragment shader. For information about the properties in this structure, see FragInput. - A
float3
that contains the current pixel's view direction. - A
PositionInputs
structure that contains position-based utility properties you might require in your shader. For information about the properties in this structure, see PositionInputs.
The GetSurfaceAndBuiltinData
method does not use return
to output results. Instead, it has two parameters that use the out modifier. The reason for this is to support different outputs depending on the shader's surface type. The outputs are:
- A
SurfaceData
structure that contains the final color. For information about the properties in this structure, see SurfaceData. - A
BuiltinData
structure that contains additional information that HDRP can use to calculate particular effects. For information about the properties in this structure, see BuiltinData.
The following example demonstrates how to use the GetSurfaceAndBuiltinData
method to write fragment shader code. This example samples the color of the _ColorMap
texture, uses the alpha channel to perform an alpha test, then outputs the texture color for the surface:
// Put the code to render the GameObjects in your Custom Pass in this method
void GetSurfaceAndBuiltinData(FragInputs fragInputs, float3 viewDirection, inout PositionInputs posInput, out SurfaceData surfaceData, out BuiltinData builtinData)
{
float2 colorMapUv = TRANSFORM_TEX(fragInputs.texCoord0.xy, _ColorMap);
float4 result = SAMPLE_TEXTURE2D(_ColorMap, s_trilinear_clamp_sampler, colorMapUv) * _Color;
float opacity = result.a;
float3 color = result.rgb;
#ifdef _ALPHATEST_ON
DoAlphaTest(opacity, _AlphaCutoff);
#endif
// Write back the data to the output structures
ZERO_INITIALIZE(BuiltinData, builtinData); // No call to InitBuiltinData as we don't have any lighting
builtinData.opacity = opacity;
builtinData.emissiveColor = float3(0, 0, 0);
surfaceData.color = color;
}
FragInputs
FragInputs
is a structure that stores all the geometry inputs passed to the fragment stage:
struct FragInputs
{
float4 positionSS; // In case depth offset is use, positionRWS.w is equal to depth offset
float3 positionRWS; // Relative camera space position
float4 texCoord0; // UV0
float4 texCoord1; // UV1
float4 texCoord2; // UV2
float4 texCoord3; // UV3
float4 color; // vertex color
float3x3 tangentToWorld;
bool isFrontFace;
};
PositionInputs
PositionInputs
provides another set of properties you may need to use in your shaders. You can think of it as a utility structure to access position-related properties. The depth comes from the current camera depth buffer and tileCoord
is not available (it’s only used in compute shaders):
struct PositionInputs
{
float3 positionWS; // World space position (could be camera-relative)
float2 positionNDC; // Normalized screen coordinates within the viewport : [0, 1) (with the half-pixel offset)
uint2 positionSS; // Screen space pixel coordinates : [0, NumPixels)
uint2 tileCoord; // Screen tile coordinates : [0, NumTiles)
float deviceDepth; // Depth from the depth buffer : [0, 1] (typically reversed)
float linearDepth; // View space Z coordinate : [Near, Far]
};
SurfaceData
The SurfaceData
structure allows you to set the color of an object’s surface. It has a different representation for each material type. For an unlit shader, it only includes a single field that represents the color of the unlit object:
struct SurfaceData
{
float3 color;
};
BuiltinData
The BuiltinData
structure contains additional information you can pass on to HDRP:
struct BuiltinData
{
real opacity; // Translucency of your object
real alphaClipTreshold; // The threshold for alpha clipping
real3 bakeDiffuseLighting;
real3 backBakeDiffuseLighting;
real shadowMask0;
real shadowMask1;
real shadowMask2;
real shadowMask3;
real3 emissiveColor; // Emissive color
real2 motionVector; // Not yet supported
real2 distortion; // Distortion vector
real distortionBlur; // Distortion blur level [0, 1]
uint renderingLayers;
float depthOffset; // Depth offset
real4 vtPackedFeedback;
};
Vertex shader code
To write vertex shader code in the Custom Renderers Pass shader, use the ApplyMeshModification method . By default, the shader comments this method out, so to use it, remove the comment slashes (//). You can use this method to add vertex deformation or vertex animation to your Unity shader.
For its input, this method takes:
- An AttributesMesh structure that contains the current vertex and its properties. For information about the properties in this structure, and which defines they map to, see AttributesMesh.
- A float3 that contains the current
timeParameters
. In this float, the x value refers to time in seconds, the y value is sin(x) and the z value is cos(x).
For its output, this method returns an AttributesMesh
that represents the modified vertex. The usual workflow is to modify the input AttributesMesh
and return that.
Note that transformations in this method are in object space. If you want to apply transformations in world space:
- Convert the object space data to world space. Use the
TransformObjectToWorld
method to convert position data andTransformObjectToWorldDir
to convert normal data. - Apply the transformations.
- Convert the world space data back to object space. Use the
TransformWorldToObject
method to convert position data andTransformWorldToObjectDir
to convert normal data.
For an example on how to use this method to write vertex shader code, see the following code sample. This example modifies the vertex data to slightly enlarge the mesh and avoid z-fighting:
#define HAVE_MESH_MODIFICATION
AttributesMesh ApplyMeshModification(AttributesMesh input, float3 timeParameters)
{
input.positionOS += input.normalOS * 0.0001; // Enlarges the mesh slightly to avoid z-fighting.
return input;
}
AttributesMesh
The AttributesMesh
structure has the following definitions:
struct AttributesMesh
{
float3 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT; // Store sign in w
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float2 uv2 : TEXCOORD2;
float2 uv3 : TEXCOORD3;
float4 color : COLOR;
};
Sample UVs in the fragment shader
Unity uses the following defines to send data to the vertex and fragment shaders:
ATTRIBUTES_NEED_TEXCOORD0
VARYINGS_NEED_TEXCOORD0
You can use the ATTRIBUTES_NEED
and VARYINGS_NEED
systems to determine what data Unity sends to the vertex and fragment shader. ATTRIBUTES_NEED
controls the vertex data and VARYINGS_NEED
controls the fragment data. You can control this data using the following list of defines:
#define ATTRIBUTES_NEED_NORMAL
#define ATTRIBUTES_NEED_TANGENT
#define ATTRIBUTES_NEED_TEXCOORD0
#define ATTRIBUTES_NEED_TEXCOORD1
#define ATTRIBUTES_NEED_TEXCOORD2
#define ATTRIBUTES_NEED_TEXCOORD3
#define ATTRIBUTES_NEED_COLOR
#define VARYINGS_NEED_POSITION_WS
#define VARYINGS_NEED_TANGENT_TO_WORLD
#define VARYINGS_NEED_TEXCOORD0
#define VARYINGS_NEED_TEXCOORD1
#define VARYINGS_NEED_TEXCOORD2
#define VARYINGS_NEED_TEXCOORD3
#define VARYINGS_NEED_COLOR
#define VARYINGS_NEED_CULLFACE
You can access UV 0 and normals by default using ATTRIBUTES_NEED_TEXCOORD0
and ATTRIBUTES_NEED_NORMAL
.
Change the depth of a renderer in your scene
You can override the depth state of any renderer component in your Custom Pass. This replaces the depth states of the GameObjects rendered in your scene with the depth render states assigned in the Custom Pass Volume component. You might want to use this to make some GameObjects in your scene invisible in a Custom Pass.
You can also use this method to render GameObjects in a Custom Pass that are not in the Camera culling mask. Unity renders opaque GameObjects that are not in the Camera Culling Mask in the Equal depth test. You can only change the depth state of a renderer if the GameObject is in the depth buffer. To include these opaque GameObjects in the depth buffer, go to the Custom Pass Volume component and set the Depth Test property to Less Equal.
Unity renders all objects in a Custom Pass using the Forward rendering path**.** This can cause issues if your scene is set to render using Deferred Only. In the HDRP Asset, change the Lit Shader Mode to Both to avoid issues when you build your project.
Use the Custom Buffer
You can use custom buffers to store the result of your passes, and execute that later in the rendering process, or between two Custom Passes. You can sample these custom buffers in any kind of Custom Pass shader using the following functions:
SampleCustomColor(float2 uv)
SampleCustomDepth(float2 uv)
You can also load custom buffers using the following functions:
LoadCustomColor(uint2 pixelCoords)
LoadCustomDepth(uint2 pixelCoords)
Note that depending on the injection point used for the Fullscreen custom pass, sampling the custom buffer can result in incorrect scaling. Thus it's recommended to use these functions instead:
CustomPassSampleCustomColor(float2 uv)
CustomPassLoadCustomColor(uint2 pixelCoords)
HDRP sets the custom pass target buffers to the Camera buffers by default. However, you can select a custom buffer in the UI of the Custom Pass. To do this, go to your Custom Pass component and change the Target Color Buffer or Target Depth Buffer properties.
To change the buffer format of the Custom Pass component in your HDRP asset, go to Rendering > Custom Pass > Custom Buffer Format and select one of the following formats from the drop down menu:
Format | Bits Per Pixel | Description |
---|---|---|
R8G8B8A8 | 32 | This format is the most resource efficient, but it might cause banding issues. HDRP uses this format by default. |
Signed R8G8B8A8 | 32 | This format is similar to R8G8B8A8 but you can store unsigned data. |
R11G11B10 | 32 | This format has a higher precision than R8G8B8A8 but does not support alpha channels. |
R16G16B16A16 | 64 | This format has the highest precision but uses twice as much memory as R8G8B8A8 and R11G11B10. |
You can sample the custom color and depth buffers in Shader Graph using the following nodes:
Disable custom passes
You can disable Custom Passes in one of the following ways:
In Frame Settings: Go to Edit > Project Settings > Graphics > HDRP Global Settings. You can then disable custom passes in one of the following places:
-
- To disable custom passes in the Camera, go to Frame Settings > Rendering section and disable the Custom Pass property. This disables custom passes in the Camera, but not for reflection probes.
- To disable custom passes for planar reflection probes and reflection probes that are not baked, go to Frame Settings > Realtime Reflection and disable the Custom Pass property.
- To disable custom passes for baked reflection probes, go to Frame Settings > Baked and disable the Custom Pass property.
- HDRP Asset: Open the HDRP Asset, navigate to Rendering and disable the Custom Pass property. This automatically disables the custom buffer allocation.