Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Help with Custom Render Feature

Discussion in 'Universal Render Pipeline' started by rainwizerd, Aug 25, 2023.

  1. rainwizerd

    rainwizerd

    Joined:
    Feb 19, 2020
    Posts:
    7
    I'm attempting to implement a similar outline post process effect as the game Rollerdrome.

    upload_2023-8-24_19-55-50.jpeg

    The developers have an awesome breakdown of this effect on Unity's Youtube channel, but I'm struggling with rendering the outline texture and outline mask texture to channels in a custom buffer. Right now I have a shader that outputs the vertex color, shadows, and a random color for the object but I'm struggling with packing the other two channels with the necessary texture information and I could use some help with how to do this.

    upload_2023-8-24_19-58-35.png
    Vertex color and shadow information packed into an RTHandle and used to set a global texture (so I can access it with a custom fullscreen post process and perform a sobel operation on it)

    My first instinct was to set the texture per renderer on the override material step of my custom renderer feature, but this doesn't seem like a good idea or possible. Any tips on how I can do this? I'm relatively new to URP and graphics programming so I would appreciate any advice!
     
  2. rainwizerd

    rainwizerd

    Joined:
    Feb 19, 2020
    Posts:
    7
    I should probably send the code, so here it is

    Render feature
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.Rendering;
    4. using UnityEngine.Rendering.Universal;
    5.  
    6. public class CustomRendererFeature : ScriptableRendererFeature
    7. {
    8.     public class CustomRenderPass : ScriptableRenderPass
    9.     {
    10.  
    11.         private Settings settings;
    12.         private FilteringSettings filteringSettings;
    13.         private ProfilingSampler _profilingSampler;
    14.         private List<ShaderTagId> shaderTagsList = new List<ShaderTagId>();
    15.         private RTHandle rtCustomColor, rtTempColor;
    16.  
    17.         public CustomRenderPass(Settings settings, string name)
    18.         {
    19.             this.settings = settings;
    20.             filteringSettings = new FilteringSettings(RenderQueueRange.opaque, settings.layerMask);
    21.  
    22.             // Use default tags
    23.             shaderTagsList.Add(new ShaderTagId("SRPDefaultUnlit"));
    24.             shaderTagsList.Add(new ShaderTagId("UniversalForward"));
    25.             shaderTagsList.Add(new ShaderTagId("UniversalForwardOnly"));
    26.  
    27.             _profilingSampler = new ProfilingSampler(name);
    28.         }
    29.  
    30.         public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    31.         {
    32.             var colorDesc = renderingData.cameraData.cameraTargetDescriptor;
    33.             colorDesc.depthBufferBits = 0;
    34.             colorDesc.colorFormat = RenderTextureFormat.ARGB32;
    35.  
    36.             // Set up temporary color buffer (for blit)
    37.             RenderingUtils.ReAllocateIfNeeded(ref rtTempColor, colorDesc, name: "_TemporaryColorTexture");
    38.  
    39.             // Set up custom color target buffer (to render objects into)
    40.             if (settings.colorTargetDestinationID != "")
    41.             {
    42.                 RenderingUtils.ReAllocateIfNeeded(ref rtCustomColor, colorDesc, name: settings.colorTargetDestinationID);
    43.             }
    44.             else
    45.             {
    46.                 // colorDestinationID is blank, use camera target instead
    47.                 rtCustomColor = renderingData.cameraData.renderer.cameraColorTargetHandle;
    48.             }
    49.  
    50.             // Using camera's depth target (that way we can ZTest with scene objects still)
    51.             RTHandle rtCameraDepth = renderingData.cameraData.renderer.cameraDepthTargetHandle;
    52.  
    53.             ConfigureTarget(rtCustomColor, rtCameraDepth);
    54.             ConfigureClear(ClearFlag.Color, new Color(0, 0, 0, 0));
    55.         }
    56.  
    57.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    58.         {
    59.             CommandBuffer cmd = CommandBufferPool.Get();
    60.             // Set up profiling scope for Profiler & Frame Debugger
    61.             using (new ProfilingScope(cmd, _profilingSampler))
    62.             {
    63.                 // Command buffer shouldn't contain anything, but apparently need to
    64.                 // execute so DrawRenderers call is put under profiling scope title correctly
    65.                 context.ExecuteCommandBuffer(cmd);
    66.                 cmd.Clear();
    67.  
    68.                 // Draw Renderers to Render Target (set up in OnCameraSetup)
    69.                 SortingCriteria sortingCriteria = renderingData.cameraData.defaultOpaqueSortFlags;
    70.                 DrawingSettings drawingSettings = CreateDrawingSettings(shaderTagsList, ref renderingData, sortingCriteria);
    71.                 if (settings.overrideMaterial != null)
    72.                 {
    73.                     drawingSettings.overrideMaterialPassIndex = settings.overrideMaterialPass;
    74.                     drawingSettings.overrideMaterial = settings.overrideMaterial;
    75.                 }
    76.                 context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
    77.  
    78.                 // Pass our custom target to shaders as a Global Texture reference
    79.                 // In a Shader Graph, you'd obtain this as a Texture2D property with "Exposed" unticked
    80.                 if (settings.colorTargetDestinationID != "")
    81.                     cmd.SetGlobalTexture(settings.colorTargetDestinationID, rtCustomColor);
    82.  
    83.                 // Apply material (e.g. Fullscreen Graph) to camera
    84.                 if (settings.blitMaterial != null)
    85.                 {
    86.                     RTHandle camTarget = renderingData.cameraData.renderer.cameraColorTargetHandle;
    87.                     if (camTarget != null && rtTempColor != null)
    88.                     {
    89.                         Blitter.BlitCameraTexture(cmd, camTarget, rtTempColor, settings.blitMaterial, 0);
    90.                         Blitter.BlitCameraTexture(cmd, rtTempColor, camTarget);
    91.                     }
    92.                 }
    93.             }
    94.             // Execute Command Buffer one last time and release it
    95.             // (otherwise we get weird recursive list in Frame Debugger)
    96.             context.ExecuteCommandBuffer(cmd);
    97.             cmd.Clear();
    98.             CommandBufferPool.Release(cmd);
    99.         }
    100.  
    101.         public override void OnCameraCleanup(CommandBuffer cmd) { }
    102.  
    103.         // Cleanup Called by feature below
    104.         public void Dispose()
    105.         {
    106.             if (settings.colorTargetDestinationID != "")
    107.                 rtCustomColor?.Release();
    108.             rtTempColor?.Release();
    109.         }
    110.     }
    111.  
    112.     // Exposed Settings
    113.  
    114.     [System.Serializable]
    115.     public class Settings
    116.     {
    117.         public bool showInSceneView = true;
    118.         public RenderPassEvent _event = RenderPassEvent.AfterRenderingOpaques;
    119.  
    120.         [Header("Draw Renderers Settings")]
    121.         public LayerMask layerMask = 1;
    122.         public Material overrideMaterial;
    123.         public int overrideMaterialPass;
    124.         public string colorTargetDestinationID = "";
    125.  
    126.         [Header("Blit Settings")]
    127.         public Material blitMaterial;
    128.     }
    129.  
    130.     public Settings settings = new Settings();
    131.  
    132.     // Feature Methods
    133.  
    134.     private CustomRenderPass m_ScriptablePass;
    135.  
    136.     public override void Create()
    137.     {
    138.         m_ScriptablePass = new CustomRenderPass(settings, name);
    139.         m_ScriptablePass.renderPassEvent = settings._event;
    140.     }
    141.  
    142.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    143.     {
    144.         CameraType cameraType = renderingData.cameraData.cameraType;
    145.         if (cameraType == CameraType.Preview) return; // Ignore feature for editor/inspector previews & asset thumbnails
    146.         if (!settings.showInSceneView && cameraType == CameraType.SceneView) return;
    147.         renderer.EnqueuePass(m_ScriptablePass);
    148.     }
    149.  
    150.     protected override void Dispose(bool disposing)
    151.     {
    152.         m_ScriptablePass.Dispose();
    153.     }
    154. }
    155.  

    HLSL (for override material, this is where I need a texture per object)
    Code (CSharp):
    1. Shader "SpacerGames/OutlineBufferTest"
    2. {
    3.     Properties
    4.     {
    5.     }
    6.     SubShader
    7.     {
    8.         Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalRenderPipeline"}
    9.         Cull Back
    10.  
    11.         Pass
    12.         {
    13.             Name "ForwardLit"
    14.             Tags { "LightMode" = "UniversalForward" }
    15.  
    16.             HLSLPROGRAM
    17.             #pragma vertex vert
    18.             #pragma fragment frag
    19.  
    20.             #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
    21.             #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
    22.             #pragma multi_compile _ _SHADOWS_SOFT
    23.  
    24.             #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    25.             #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    26.  
    27.             struct Attributes
    28.             {
    29.                 float4 vertex   : POSITION;
    30.                 float3 normal   : NORMAL;
    31.                 float4 color    : COLOR0;
    32.             };
    33.  
    34.             struct Varyings
    35.             {
    36.                 float4 posCS        : SV_POSITION;
    37.                 float4 vertColor    : COLOR0;
    38.                 float3 posWS        : TEXCOORD0;
    39.                 float3 normalWS     : TEXCOORD1;
    40.             };
    41.  
    42.             Varyings vert(Attributes IN)
    43.             {
    44.                 Varyings OUT = (Varyings)0;
    45.                 VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.vertex.xyz);
    46.                 OUT.posCS = vertexInput.positionCS;
    47.                 OUT.posWS = vertexInput.positionWS;
    48.  
    49.                 OUT.vertColor = IN.color;
    50.  
    51.                 VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normal);
    52.                 OUT.normalWS = normalInput.normalWS;
    53.  
    54.                 return OUT;
    55.             }
    56.  
    57.             float4 frag (Varyings IN) : SV_Target
    58.             {
    59.                 float4 shadowCoord = TransformWorldToShadowCoord(IN.posWS);
    60.                 float shadowMap = MainLightRealtimeShadow(shadowCoord);
    61.  
    62.                 float NdotL = saturate(dot(_MainLightPosition.xyz, IN.normalWS));
    63.    
    64.                 float combinedShadow = min(NdotL, shadowMap);
    65.                 float shadowValue = saturate(step(0.1, combinedShadow) + 0.2);
    66.  
    67.                 // Pseudo-random value between 0 and 1
    68.                 float randomValue = frac(sin(dot(unity_ObjectToWorld._m03_m13_m23 , float2(12.9898, 78.233))) * 43758.5453);
    69.  
    70.                 float4 output;
    71.                 output.r = (IN.vertColor.r + IN.vertColor.g + IN.vertColor.b) / 3.0 * shadowValue;
    72.                 output.g = 1.0;
    73.                 output.b = 1.0;
    74.                 output.a = randomValue;
    75.  
    76.                 return output;
    77.             }
    78.             ENDHLSL
    79.         }
    80.         pass
    81.         {
    82.             Name "ShadowCaster"
    83.             Tags{"LightMode" = "ShadowCaster"}
    84.  
    85.             ZWrite On
    86.             ZTest LEqual
    87.             ColorMask 0
    88.  
    89.             HLSLPROGRAM
    90.  
    91.             #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    92.             #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    93.  
    94.             #pragma vertex vert
    95.             #pragma fragment frag
    96.  
    97.             struct Attributes
    98.             {
    99.                 float4 vertex   : POSITION;
    100.                 float3 normal   : NORMAL;
    101.             };
    102.  
    103.             struct Varyings
    104.             {
    105.                 float4 posCS        : SV_POSITION;
    106.             };
    107.  
    108.             float3 _LightDirection;
    109.  
    110.             Varyings vert(Attributes IN)
    111.             {
    112.                     Varyings OUT = (Varyings)0;
    113.                     VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.vertex.xyz);
    114.                     float3 posWS = vertexInput.positionWS;
    115.  
    116.                     VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normal);
    117.                     float3 normalWS = normalInput.normalWS;
    118.  
    119.                     // Shadow biased ClipSpace position
    120.                     float4 positionCS = TransformWorldToHClip(ApplyShadowBias(posWS, normalWS, _LightDirection));
    121.  
    122.                     #if UNITY_REVERSED_Z
    123.                         positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
    124.                     #else
    125.                         positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
    126.                     #endif
    127.  
    128.                     OUT.posCS = positionCS;
    129.  
    130.                     return OUT;
    131.             }
    132.  
    133.             float4 frag (Varyings IN) : SV_Target
    134.             {
    135.                 return 0;
    136.             }
    137.             ENDHLSL
    138.         }
    139.     }
    140. }
     
  3. rainwizerd

    rainwizerd

    Joined:
    Feb 19, 2020
    Posts:
    7
    I have figured out how to achieve this and will post a full breakdown this weekend for feedback and reference :)
     
  4. Dravenceed

    Dravenceed

    Joined:
    Aug 28, 2023
    Posts:
    1
    Interested by your write-up!