|
| 1 | +# Scripting your own Custom Pass in C# |
| 2 | + |
| 3 | +You can extend the CustomPass class in the Custom Pass API to create complex effects, such as a Custom Pass that has more than one buffer or uses [Compute Shaders](https://docs.unity3d.com/Manual/class-ComputeShader.html). |
| 4 | + |
| 5 | +When you create your own C# Custom Pass using the instructions in [The Custom Pass C# Template](#Custom-Pass-C#-template), it automatically appears in the list of available Custom Passes in the Custom Pass Volume component. |
| 6 | + |
| 7 | +<a name="Custom-Pass-C#-template"></a> |
| 8 | + |
| 9 | +## **The Custom Pass C# template** |
| 10 | + |
| 11 | +To create a new Custom pass, go to **Assets > Create > Rendering > C# Custom Pass**. This creates a new script that contains the Custom Pass C# template: |
| 12 | + |
| 13 | +```C# |
| 14 | +class #SCRIPTNAME# : CustomPass |
| 15 | +{ |
| 16 | + protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd) {} |
| 17 | + |
| 18 | + protected override void Execute(CustomPassContext ctx) {} |
| 19 | + |
| 20 | + protected override void Cleanup() {} |
| 21 | +} |
| 22 | +``` |
| 23 | + |
| 24 | +The C# Custom Pass template includes the following entry points to code your custom pass: |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +| **Entry Point** | **Description** | |
| 29 | +| --------------- | ------------------------------------------------------------ | |
| 30 | +| `Setup` | Use this to allocate all the resources you need to render your pass, such as render textures, materials, and compute buffers. | |
| 31 | +| `Execute` | Use this to describe what HDRP renders during the Custom Pass. | |
| 32 | +| `Cleanup` | Use this to clear the resources you allocated in the Setup method .Make sure to include every allocated resource to avoid memory leaks. | |
| 33 | + |
| 34 | +The `Setup` and `Execute` methods give you access to a `ScriptableRenderContext` and a `CommandBuffer`. For information on using `CommandBuffers` with a `ScriptableRenderContext`, see [Scheduling and executing commands in the Scriptable Render Pipeline](https://docs.unity3d.com/Manual/srp-using-scriptable-render-context.html). |
| 35 | + |
| 36 | +## **Creating a full-screen Custom Pass in C#** |
| 37 | + |
| 38 | +The following code demonstrates how to create a full-screen Custom Pass that applies an outline effect to an object in your scene. |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +This effect uses a transparent full screen pass with a blend mode that replaces the pixels around the GameObject you assign the script to. |
| 43 | + |
| 44 | +This shader code performs the following steps: |
| 45 | + |
| 46 | +1. Renders the objects in the outline layer to a buffer called `outlineBuffer`. |
| 47 | +2. Samples the color in `outlineBuffer`. If the color is below the threshold, then it means that the pixel might be in an outline. |
| 48 | +3. Searches neighboring pixels to check if this is the case. |
| 49 | +4. If Unity finds a pixel above the threshold, it applies the outline effect. |
| 50 | + |
| 51 | +### Creating a CustomPass script |
| 52 | + |
| 53 | +To create a CustomPass script: |
| 54 | + |
| 55 | +1. Create a new C# script using **Assets > Create > C# Script**. |
| 56 | +2. Name your script. In this example, the new script is called “Outline” |
| 57 | +3. Enter the following code: |
| 58 | + |
| 59 | +```C# |
| 60 | +using UnityEngine; |
| 61 | +using UnityEngine.Rendering.HighDefinition; |
| 62 | +using UnityEngine.Rendering; |
| 63 | +using UnityEngine.Experimental.Rendering; |
| 64 | + |
| 65 | +class Outline : CustomPass |
| 66 | +{ |
| 67 | + public LayerMask outlineLayer = 0; |
| 68 | + [ColorUsage(false, true)] |
| 69 | + public Color outlineColor = Color.black; |
| 70 | + public float threshold = 1; |
| 71 | + |
| 72 | + // To make sure the shader ends up in the build, we keep a reference to it |
| 73 | + [SerializeField, HideInInspector] |
| 74 | + Shader outlineShader; |
| 75 | + |
| 76 | + Material fullscreenOutline; |
| 77 | + RTHandle outlineBuffer; |
| 78 | + |
| 79 | + protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd) |
| 80 | + { |
| 81 | + outlineShader = Shader.Find("Hidden/Outline"); |
| 82 | + fullscreenOutline = CoreUtils.CreateEngineMaterial(outlineShader); |
| 83 | + |
| 84 | + // Define the outline buffer |
| 85 | + outlineBuffer = RTHandles.Alloc( |
| 86 | + Vector2.one, TextureXR.slices, dimension: TextureXR.dimension, |
| 87 | + colorFormat: GraphicsFormat.B10G11R11_UFloatPack32, |
| 88 | +// We don't need alpha for this effect |
| 89 | + useDynamicScale: true, name: "Outline Buffer" |
| 90 | + ); |
| 91 | + } |
| 92 | + |
| 93 | + protected override void Execute(CustomPassContext ctx) |
| 94 | + { |
| 95 | + // Render meshes we want to apply the outline effect to in the outline buffer |
| 96 | + CoreUtils.SetRenderTarget(ctx.cmd, outlineBuffer, ClearFlag.Color); |
| 97 | + CustomPassUtils.DrawRenderers(ctx, outlineLayer); |
| 98 | + |
| 99 | + // Set up outline effect properties |
| 100 | + ctx.propertyBlock.SetColor("_OutlineColor", outlineColor); |
| 101 | + ctx.propertyBlock.SetTexture("_OutlineBuffer", outlineBuffer); |
| 102 | + ctx.propertyBlock.SetFloat("_Threshold", threshold); |
| 103 | + |
| 104 | + // Render the outline buffer fullscreen |
| 105 | + CoreUtils.SetRenderTarget(ctx.cmd, ctx.cameraColorBuffer, ClearFlag.None); |
| 106 | + CoreUtils.DrawFullScreen(ctx.cmd, fullscreenOutline, ctx.propertyBlock, shaderPassId: 0); |
| 107 | + } |
| 108 | + |
| 109 | + protected override void Cleanup() |
| 110 | + { |
| 111 | + CoreUtils.Destroy(fullscreenOutline); |
| 112 | + outlineBuffer.Release(); |
| 113 | + } |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +### Creating a Unity shader |
| 118 | + |
| 119 | +To create a new shader: |
| 120 | + |
| 121 | +1. Create a new Unity shader using **Assets> Create> Shader** |
| 122 | +2. Name the new shader source file “Outline” |
| 123 | +3. Enter the following code: |
| 124 | + |
| 125 | +```C# |
| 126 | +Shader "Hidden/Outline" |
| 127 | +{ |
| 128 | + HLSLINCLUDE |
| 129 | + |
| 130 | + #pragma vertex Vert |
| 131 | + |
| 132 | + #pragma target 4.5 |
| 133 | + #pragma only_renderers d3d11 playstation xboxone vulkan metal switch |
| 134 | + |
| 135 | + #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassCommon.hlsl" |
| 136 | + |
| 137 | + TEXTURE2D_X(_OutlineBuffer); |
| 138 | + float4 _OutlineColor; |
| 139 | + float _Threshold; |
| 140 | + |
| 141 | + #define v2 1.41421 |
| 142 | + #define c45 0.707107 |
| 143 | + #define c225 0.9238795 |
| 144 | + #define s225 0.3826834 |
| 145 | + |
| 146 | + #define MAXSAMPLES 8 |
| 147 | + // Neighbour pixel positions |
| 148 | + static float2 samplingPositions[MAXSAMPLES] = |
| 149 | + { |
| 150 | + float2( 1, 1), |
| 151 | + float2( 0, 1), |
| 152 | + float2(-1, 1), |
| 153 | + float2(-1, 0), |
| 154 | + float2(-1, -1), |
| 155 | + float2( 0, -1), |
| 156 | + float2( 1, -1), |
| 157 | + float2( 1, 0), |
| 158 | + }; |
| 159 | + |
| 160 | + float4 FullScreenPass(Varyings varyings) : SV_Target |
| 161 | + { |
| 162 | + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings); |
| 163 | + |
| 164 | + float depth = LoadCameraDepth(varyings.positionCS.xy); |
| 165 | + PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V); |
| 166 | + float4 color = float4(0.0, 0.0, 0.0, 0.0); |
| 167 | + float luminanceThreshold = max(0.000001, _Threshold * 0.01); |
| 168 | + |
| 169 | + // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point |
| 170 | + if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING) |
| 171 | + color = float4(CustomPassSampleCameraColor(posInput.positionNDC.xy, 0), 1); |
| 172 | + |
| 173 | + // When sampling RTHandle texture, always use _RTHandleScale.xy to scale your UVs first. |
| 174 | + float2 uv = posInput.positionNDC.xy * _RTHandleScale.xy; |
| 175 | + float4 outline = SAMPLE_TEXTURE2D_X_LOD(_OutlineBuffer, s_linear_clamp_sampler, uv, 0); |
| 176 | + outline.a = 0; |
| 177 | + |
| 178 | + // If this sample is below the threshold |
| 179 | + if (Luminance(outline.rgb) < luminanceThreshold) |
| 180 | + { |
| 181 | + // Search neighbors |
| 182 | + for (int i = 0; i < MAXSAMPLES; i++) |
| 183 | + { |
| 184 | + float2 uvN = uv + _ScreenSize.zw * _RTHandleScale.xy * samplingPositions[i]; |
| 185 | + float4 neighbour = SAMPLE_TEXTURE2D_X_LOD(_OutlineBuffer, s_linear_clamp_sampler, uvN, 0); |
| 186 | + |
| 187 | + if (Luminance(neighbour) > luminanceThreshold) |
| 188 | + { |
| 189 | + outline.rgb = _OutlineColor.rgb; |
| 190 | + outline.a = 1; |
| 191 | + break; |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + return outline; |
| 197 | + } |
| 198 | + |
| 199 | + ENDHLSL |
| 200 | + |
| 201 | + SubShader |
| 202 | + { |
| 203 | + Pass |
| 204 | + { |
| 205 | + Name "Custom Pass 0" |
| 206 | + |
| 207 | + ZWrite Off |
| 208 | + ZTest Always |
| 209 | + Blend SrcAlpha OneMinusSrcAlpha |
| 210 | + Cull Off |
| 211 | + |
| 212 | + HLSLPROGRAM |
| 213 | + #pragma fragment FullScreenPass |
| 214 | + ENDHLSL |
| 215 | + } |
| 216 | + } |
| 217 | + Fallback Off |
| 218 | +} |
| 219 | +``` |
| 220 | + |
| 221 | +### Using a C# Custom Pass effect |
| 222 | + |
| 223 | +To enable an effect you created in a shader, assign it to the **FullScreen Material** property of a [Full-screeen Custom Pass](Custom-Pass-Creating.md#Full-Screen-Custom-Pass) component. |
| 224 | + |
| 225 | +## Controlling a Custom Pass Volume component using code |
| 226 | + |
| 227 | +You can retrieve the `CustomPassVolume` in a script using [GetComponent](https://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html) and access most of the things available from the UI like `isGlobal`, `fadeRadius` and `injectionPoint`. |
| 228 | + |
| 229 | +You can also dynamically change the list of Custom Passes executed by modifying the `customPasses` list. |
| 230 | + |
| 231 | +### Scripting the Custom Pass Volume component properties |
| 232 | + |
| 233 | +To customize the properties of a Custom Pass in the Inspector window, you can use a similar pattern to the [CustomPropertyDrawer](https://docs.unity3d.com/ScriptReference/CustomPropertyDrawer.html) MonoBehaviour Editor, but with different attributes. |
| 234 | + |
| 235 | +The following example is a part of the full-screen Custom Pass drawer: |
| 236 | + |
| 237 | +```C# |
| 238 | +[CustomPassDrawerAttribute(typeof(FullScreenCustomPass))] |
| 239 | +public class FullScreenCustomPassDrawer : CustomPassDrawer |
| 240 | +{ |
| 241 | + protected override void Initialize(SerializedProperty customPass) |
| 242 | + { |
| 243 | + // Initialize the local SerializedProperty you will use in your pass. |
| 244 | + } |
| 245 | + |
| 246 | + protected override void DoPassGUI(SerializedProperty customPass, Rect rect) |
| 247 | + { |
| 248 | + // Draw your custom GUI using `EditorGUI` calls. Note that the Layout methods don't work here |
| 249 | + } |
| 250 | + |
| 251 | + protected override float GetPassHeight(SerializedProperty customPass) |
| 252 | + { |
| 253 | + // Return the vertical height in pixels that you used in the DoPassGUI method above. |
| 254 | + // Can be dynamic. |
| 255 | + return (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * X; |
| 256 | + } |
| 257 | +} |
| 258 | +``` |
| 259 | + |
| 260 | +When you create a Custom Pass drawer, Unity provides a default list of Custom Pass properties. Unity still does this when `DoPassGUI` is empty. These properties are the same properties that Unity provides in the [draw renderers CustomPass Volume](Custom-Pass-Creating.md#Draw-Renderers-Custom-Pass) component by default. |
| 261 | + |
| 262 | +If you don't need all of these settings, you can override the `commonPassUIFlags` property to remove some of them. The following example only keeps the name and the target buffer enum: |
| 263 | + |
| 264 | +```c# |
| 265 | +protected override PassUIFlag commonPassUIFlags => PassUIFlag.Name | PassUIFlag.TargetColorBuffer; |
| 266 | +``` |
0 commit comments