Rasagar/Library/PackageCache/com.unity.render-pipelines.high-definition/Documentation~/Custom-Pass-Scripting.md
2024-08-26 23:07:20 +03:00

15 KiB
Raw Blame History

Create a Custom Pass in a C# script

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.

When you create your own C# Custom Pass using the instructions in The Custom Pass C# Template, it automatically appears in the list of available Custom Passes in the Custom Pass Volume component.

The Custom Pass C# template

To create a new Custom pass, go to Assets > Create > Rendering > HDRP C# Custom Pass. This creates a new script that contains the Custom Pass C# template:

class #SCRIPTNAME# : CustomPass
{
    protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd) {}

    protected override void Execute(CustomPassContext ctx) {}

    protected override void Cleanup() {}
}

The C# Custom Pass template includes the following entry points to code your custom pass:

Entry Point Description
Setup Use this to allocate all the resources you need to render your pass, such as render textures, materials, and compute buffers.
Execute Use this to describe what HDRP renders during the Custom Pass.
Cleanup Use this to clear the resources you allocated in the Setup method .Make sure to include every allocated resource to avoid memory leaks.

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.

Create a full-screen Custom Pass in C#

This section demonstrates how to create a full-screen Custom Pass that applies an outline effect to an object in your scene.

A mesh in a scene rendered using this outline effect

This effect uses a transparent full screen pass with a blend mode that replaces the pixels around the GameObject you assign the script to.

This shader code performs the following steps:

  1. Renders the objects in the outline layer to a buffer called outlineBuffer.
  2. Samples the color in outlineBuffer. If the color is below the threshold, it means that the pixel might be in an outline.
  3. Searches neighboring pixels to check if this is the case.
  4. If Unity finds a pixel above the threshold, it applies the outline effect.

Create a CustomPass script

This section provides an example of a Custom Pass C# script that applies an outline effect to every GameObject in a Layer.

The following pass renders all the GameObjects in the Outline Layer into a custom buffer named outlineBuffer in the code. It performs an edge detection algorithm over the screen that checks for all the GameObjects rendered in the outlineBuffer. This edge detection algorithm creates an outline around objects rendered in the outlineBuffer. CoreUtils.DrawFullScreen applies the full screen effect.

This example only supports a single outline color for all the objects in a Layer.

To use this example Custom Pass script:

  1. Create a new C# script (menu: Assets > Create > C# Script).
  2. Name your script. In this example, the new script is called “Outline”.
  3. Enter the following code:
using UnityEngine;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;

class Outline : CustomPass
{
    public LayerMask    outlineLayer = 0;
    [ColorUsage(false, true)]
    public Color        outlineColor = Color.black;
    public float        threshold = 1;

    // To make sure the shader ends up in the build, we keep a reference to it
    [SerializeField, HideInInspector]
    Shader                  outlineShader;

    Material                fullscreenOutline;
    RTHandle                outlineBuffer;

    protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
    {
        outlineShader = Shader.Find("Hidden/Outline");
        fullscreenOutline = CoreUtils.CreateEngineMaterial(outlineShader);

        // Define the outline buffer
        outlineBuffer = RTHandles.Alloc(
            Vector2.one, TextureXR.slices, dimension: TextureXR.dimension,
            colorFormat: GraphicsFormat.B10G11R11_UFloatPack32,
// We don't need alpha for this effect
            useDynamicScale: true, name: "Outline Buffer"
        );
    }

    protected override void Execute(CustomPassContext ctx)
    {
        // Render meshes we want to apply the outline effect to in the outline buffer
        CoreUtils.SetRenderTarget(ctx.cmd, outlineBuffer, ClearFlag.Color);
        CustomPassUtils.DrawRenderers(ctx, outlineLayer);

        // Set up outline effect properties
        ctx.propertyBlock.SetColor("_OutlineColor", outlineColor);
        ctx.propertyBlock.SetTexture("_OutlineBuffer", outlineBuffer);
        ctx.propertyBlock.SetFloat("_Threshold", threshold);

        // Render the outline buffer fullscreen
        CoreUtils.SetRenderTarget(ctx.cmd, ctx.cameraColorBuffer, ClearFlag.None);
        CoreUtils.DrawFullScreen(ctx.cmd, fullscreenOutline, ctx.propertyBlock, shaderPassId: 0);
    }

    protected override void Cleanup()
    {
        CoreUtils.Destroy(fullscreenOutline);
        outlineBuffer.Release();
    }
}

Script a Custom Pass shader

To create a new shader:

  1. Create a new Unity shader using Assets > Create > Shader > HDRP Custom FullScreen Pass
  2. Name the new shader source file “Outline”
  3. Enter the following code:
Shader "Hidden/Outline"
{
    HLSLINCLUDE

    #pragma vertex Vert

    #pragma target 4.5
    #pragma only_renderers d3d11 playstation xboxone vulkan metal switch

    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassCommon.hlsl"

    TEXTURE2D_X(_OutlineBuffer);
    float4 _OutlineColor;
    float _Threshold;

    #define v2 1.41421
    #define c45 0.707107
    #define c225 0.9238795
    #define s225 0.3826834

    #define MAXSAMPLES 8
    // Neighbour pixel positions
    static float2 samplingPositions[MAXSAMPLES] =
    {
        float2( 1,  1),
        float2( 0,  1),
        float2(-1,  1),
        float2(-1,  0),
        float2(-1, -1),
        float2( 0, -1),
        float2( 1, -1),
        float2( 1, 0),
    };

    float4 FullScreenPass(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);

        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
        float4 color = float4(0.0, 0.0, 0.0, 0.0);
        float luminanceThreshold = max(0.000001, _Threshold * 0.01);

        // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
        if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
            color = float4(CustomPassSampleCameraColor(posInput.positionNDC.xy, 0), 1);

        // When sampling RTHandle texture, always use _RTHandleScale.xy to scale your UVs first.
        float2 scaling = _RTHandleScale.xy;
        if (_CustomPassInjectionPoint == CUSTOMPASSINJECTIONPOINT_AFTER_POST_PROCESS)
            scaling *= rcp(_DynamicResolutionFullscreenScale.xy);

        // When sampling RTHandle texture, always use _RTHandleScale.xy to scale your UVs first.
        float2 uv = posInput.positionNDC.xy * scaling;
        float4 outline = SAMPLE_TEXTURE2D_X_LOD(_OutlineBuffer, s_linear_clamp_sampler, uv, 0);
        outline.a = 0;

      // If this sample is below the threshold
        if (Luminance(outline.rgb) < luminanceThreshold)
        {
            // Search neighbors
            for (int i = 0; i < MAXSAMPLES; i++)
            {
                float2 uvN = uv + _ScreenSize.zw * scaling * samplingPositions[i];
                float4 neighbour = SAMPLE_TEXTURE2D_X_LOD(_OutlineBuffer, s_linear_clamp_sampler, uvN, 0);

                if (Luminance(neighbour) > luminanceThreshold)
                {
                    outline.rgb = _OutlineColor.rgb;
                    outline.a = 1;
                    break;
                }
            }
        }

        return outline;
    }

    ENDHLSL

    SubShader
    {
        Pass
        {
            Name "Custom Pass 0"

            ZWrite Off
            ZTest Always
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off

            HLSLPROGRAM
                #pragma fragment FullScreenPass
            ENDHLSL
        }
    }
    Fallback Off
}

Use a C# Custom Pass Shader effect

To enable a full-screen effect that you have created in a shader, assign it to the FullScreen Material property of a FullScreeenCustomPass component.

To enable a Draw renderers Custom Pass that you have created in a shader, assign it to the Material property of a DrawRenderersCustomPass component.

You can also make your Custom Pass effect visible automatically in script. To do this, assign a Material to a shader within a Custom Pass script. The example script provided in Creating a Custom Pass script does this in the following lines:

Reference the shader in a serialized field:

// To make sure the shader ends up in the build, we keep a reference to it
    [SerializeField, HideInInspector]
    Shader                  outlineShader;

Reference the shader when you create a material:

fullscreenOutline = CoreUtils.CreateEngineMaterial(outlineShader);

Control a Custom Pass Volume component using code

You can use GetComponent to retrieve the CustomPassVolume in a script and access most of the properties available in the UI (for example,isGlobal, fadeRadius and injectionPoint).

You can also dynamically change the list of Custom Passes that Unity executes. To do this, modify the customPasses list.

The following example copies the current camera color buffer to a render texture asset:

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
// Custom pass to copy the current camera color buffer to a render texture asset
public class CopyPass : CustomPass
{
    public RenderTexture colorBuffer;
    protected override void Execute(CustomPassContext ctx)
    {
        if (colorBuffer != null)
        {
            var scale = RTHandles.rtHandleProperties.rtHandleScale;
            ctx.cmd.Blit(ctx.cameraColorBuffer, colorBuffer, new Vector2(scale.x, scale.y), Vector2.zero, 0, 0);
            // Notify Unity that the color buffer content changed.
            colorBuffer.IncrementUpdateCount();
        }
    }
}
public class ExtractColorBuffer : MonoBehaviour
{
    public RenderTexture            colorBuffer;
    public CustomPassInjectionPoint injectionPoint;
    CustomPassVolume    volume;
    CopyPass            copyPass;
    void Start()
    {
        volume = gameObject.AddComponent<CustomPassVolume>();
        // Make the volume invisible in the inspector
        volume.hideFlags = HideFlags.HideInInspector | HideFlags.DontSave;
        // Add the copy pass to the hidden custom pass volume
        copyPass = volume.AddPassOfType(typeof(CopyPass)) as CopyPass;
    }
    void Update()
    {
        // Sync properties in case they change in the editor
        copyPass.colorBuffer = colorBuffer;
        volume.injectionPoint = injectionPoint;
    }
}

Create a custom editor for a C# custom pass

To write a custom editor you can use a similar pattern to the CustomPropertyDrawer MonoBehaviour Editor, but with different attributes.

The following script creates a custom editor that you can use to customize the properties of a Custom Pass in the Inspector window:

public class OutlineDrawer : CustomPassDrawer
{
   // This property field allow you to control which common UI property to show.
   // For the outline we only need the name and target color buffer.
   protected override PassUIFlag commonPassUIFlags => PassUIFlag.TargetColorBuffer | PassUIFlag.Name;
   SerializedProperty outlineLayer, outlineColor, threshold;
   static float lineAndSpaceHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
   protected override void Initialize(SerializedProperty customPass)
   {
       // Initialize the local SerializedProperty you will use in your pass.
       outlineLayer = customPass.FindPropertyRelative(nameof(Outline.outlineLayer));
       outlineColor = customPass.FindPropertyRelative(nameof(Outline.outlineColor));
       threshold = customPass.FindPropertyRelative(nameof(Outline.threshold));
   }
   protected override void DoPassGUI(SerializedProperty customPass, Rect rect)
   {
       // Draw your custom GUI using `EditorGUI` calls. Note that the Layout methods don't work here
       Rect propertyRect = rect;
       propertyRect.height = EditorGUIUtility.singleLineHeight;
       EditorGUI.PropertyField(propertyRect, outlineLayer);
       propertyRect.y += lineAndSpaceHeight;
       EditorGUI.PropertyField(propertyRect, outlineColor);
       propertyRect.y += lineAndSpaceHeight;
       EditorGUI.PropertyField(propertyRect, threshold);
   }
   protected override float GetPassHeight(SerializedProperty customPass)
   {
       // Return the vertical height in pixels that you used in the DoPassGUI method above.
       // Can be dynamic.
       return (lineAndSpaceHeight) * 3;
   }
}

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 component by default.

If you don't need all 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:

protected override PassUIFlag commonPassUIFlags => PassUIFlag.Name | PassUIFlag.TargetColorBuffer;