Skip to content

Instantly share code, notes, and snippets.

@ScottJDaley
Last active May 24, 2024 01:49
Show Gist options
  • Save ScottJDaley/6cddf0c8995ed61cac7088e22c983de1 to your computer and use it in GitHub Desktop.
Save ScottJDaley/6cddf0c8995ed61cac7088e22c983de1 to your computer and use it in GitHub Desktop.
Wide Outlines Renderer Feature for URP and ECS/DOTS/Hybrid Renderer

Wide Outlines Renderer Feature for URP and ECS/DOTS/Hybrid Renderer

outline_shader

Usage

  1. Create a new Unity game object layer for the objects you wish to outline.
  2. In your URP asset, turn on MSAA or disable the Depth Texture.
  3. In the ForwardRendererData asset of your URP asset, add the renderer feature named "Outline Feature"
  4. Set the Layer Mask to the layer created in step 1.
  5. Add the objects you wish to outline to the outline layer (this can be done at runtime in ECS by setting the layer of the RenderMesh).
  6. Adjust the color and width of the outlines as desired in the renderer feature settings.

Notes

When I first added support for the Hyrbid Renderer, render layers were not working correctly. This required the use of game object layers to filter out the outlined objects. This might be fixed now, but I have not tested it yet.

Credits

  • The technique for these wide outlines comes from Ben Golus which is described and implemented in this article.
  • Alexander Ameye created the renderer feature and shader modifications to make this work in URP, original shared here.
  • Scott Daley modified the shader to get it working with Unity ECS and the Hybrid Renderer.

Compatibility

Test with:

  • Unity 2020.3.30f1
  • URP 10.8.1
  • Entities 0.50.0-preview.24
  • Hybrid Renderer 0.50.0-preview.24
// Original shader by @bgolus, modified slightly by @alexanderameye for URP, modified slightly more
// by @gravitonpunch for ECS/DOTS/HybridRenderer.
// https://twitter.com/bgolus
// https://medium.com/@bgolus/the-quest-for-very-wide-outlines-ba82ed442cd9
// https://alexanderameye.github.io/
// https://twitter.com/alexanderameye/status/1332286868222775298
Shader "Hidden/Outline"
{
Properties
{
[HideInInspector] _MainTex ("Texture", 2D) = "white" {}
_OutlineColor("Color", Color) = (1, 1, 1, 1)
_OutlineWidth ("Width", Range (0, 20)) = 5
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
Cull Off ZWrite Off ZTest Always
HLSLINCLUDE
#define SNORM16_MAX_FLOAT_MINUS_EPSILON ((float)(32768-2) / (float)(32768-1))
#define FLOOD_ENCODE_OFFSET float2(1.0, SNORM16_MAX_FLOAT_MINUS_EPSILON)
#define FLOOD_ENCODE_SCALE float2(2.0, 1.0 + SNORM16_MAX_FLOAT_MINUS_EPSILON)
#define FLOOD_NULL_POS -1.0
#define FLOOD_NULL_POS_FLOAT2 float2(FLOOD_NULL_POS, FLOOD_NULL_POS)
ENDHLSL
Pass // 0
{
Name "STENCIL MASK"
Stencil {
Ref 1
ReadMask 1
WriteMask 1
Comp Always
Pass Replace
}
ColorMask 0
Blend Zero One
HLSLPROGRAM
#pragma multi_compile_instancing
#pragma multi_compile _ DOTS_INSTANCING_ON
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#pragma target 4.5
struct appdata
{
float4 positionOS : POSITION;
#if UNITY_ANY_INSTANCING_ENABLED
uint instanceID : INSTANCEID_SEMANTIC;
#endif
};
float4 vert (appdata i) : SV_POSITION
{
UNITY_SETUP_INSTANCE_ID(i);
return TransformObjectToHClip(i.positionOS.xyz);
}
void frag () {}
ENDHLSL
}
Pass // 1
{
Name "BUFFERFILL"
HLSLPROGRAM
#pragma multi_compile_instancing
#pragma multi_compile _ DOTS_INSTANCING_ON
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#pragma target 4.5
struct appdata
{
float4 positionOS : POSITION;
#if UNITY_ANY_INSTANCING_ENABLED
uint instanceID : INSTANCEID_SEMANTIC;
#endif
};
float4 vert (appdata i) : SV_POSITION
{
UNITY_SETUP_INSTANCE_ID(i);
float4 pos = TransformObjectToHClip(i.positionOS.xyz);
// flip the rendering "upside down" in non OpenGL to make things easier later
// you'll notice none of the later passes need to pass UVs
#ifdef UNITY_UV_STARTS_AT_TOP
// pos.y = -pos.y;
#endif
return pos;
}
half frag () : SV_TARGET
{
return 1.0;
}
ENDHLSL
}
Pass // 2
{
Name "JUMPFLOODINIT"
HLSLPROGRAM
#pragma multi_compile_instancing
#pragma multi_compile _ DOTS_INSTANCING_ON
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#pragma target 4.5
struct appdata
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
#if UNITY_ANY_INSTANCING_ENABLED
uint instanceID : INSTANCEID_SEMANTIC;
#endif
};
struct v2f
{
float4 positionCS : SV_POSITION;
#if UNITY_ANY_INSTANCING_ENABLED
uint instanceID : CUSTOM_INSTANCE_ID;
#endif
};
Texture2D _MainTex;
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_TexelSize;
half4 _OutlineColor;
float _OutlineWidth;
CBUFFER_END
v2f vert (appdata i)
{
UNITY_SETUP_INSTANCE_ID(i);
v2f o;
o.positionCS = TransformObjectToHClip(i.positionOS.xyz);
return o;
}
float2 frag (v2f i) : SV_TARGET
{
// integer pixel position
int2 uvInt = i.positionCS.xy;
// sample silhouette texture for sobel
half3x3 values;
UNITY_UNROLL
for(int u=0; u<3; u++)
{
UNITY_UNROLL
for(int v=0; v<3; v++)
{
uint2 sampleUV = clamp(uvInt + int2(u-1, v-1), int2(0,0), (int2)_MainTex_TexelSize.zw - 1);
values[u][v] = _MainTex.Load(int3(sampleUV, 0)).r;
}
}
// calculate output position for this pixel
float2 outPos = i.positionCS.xy * abs(_MainTex_TexelSize.xy) * FLOOD_ENCODE_SCALE - FLOOD_ENCODE_OFFSET;
// interior, return position
if (values._m11 > 0.99)
return outPos;
// exterior, return no position
if (values._m11 < 0.01)
return FLOOD_NULL_POS_FLOAT2;
// sobel to estimate edge direction
float2 dir = -float2(
values[0][0] + values[0][1] * 2.0 + values[0][2] - values[2][0] - values[2][1] * 2.0 - values[2][2],
values[0][0] + values[1][0] * 2.0 + values[2][0] - values[0][2] - values[1][2] * 2.0 - values[2][2]
);
// if dir length is small, this is either a sub pixel dot or line
// no way to estimate sub pixel edge, so output position
if (abs(dir.x) <= 0.005 && abs(dir.y) <= 0.005)
return outPos;
// normalize direction
dir = normalize(dir);
// sub pixel offset
float2 offset = dir * (1.0 - values._m11);
// output encoded offset position
return (i.positionCS.xy + offset) * abs(_MainTex_TexelSize.xy) * FLOOD_ENCODE_SCALE - FLOOD_ENCODE_OFFSET;
}
ENDHLSL
}
Pass // 3
{
Name "JUMPFLOOD_SINGLEAXIS"
HLSLPROGRAM
#pragma multi_compile_instancing
#pragma multi_compile _ DOTS_INSTANCING_ON
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#pragma target 4.5
struct appdata
{
float4 positionOS : POSITION;
#if UNITY_ANY_INSTANCING_ENABLED
uint instanceID : INSTANCEID_SEMANTIC;
#endif
};
struct v2f
{
float4 positionCS : SV_POSITION;
#if UNITY_ANY_INSTANCING_ENABLED
uint instanceID : CUSTOM_INSTANCE_ID;
#endif
};
Texture2D _MainTex;
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_TexelSize;
half4 _OutlineColor;
float _OutlineWidth;
CBUFFER_END
int2 _AxisWidth;
v2f vert (appdata i)
{
UNITY_SETUP_INSTANCE_ID(i);
v2f o;
o.positionCS = TransformObjectToHClip(i.positionOS.xyz);
return o;
}
half2 frag (v2f i) : SV_TARGET {
// integer pixel position
int2 uvInt = int2(i.positionCS.xy);
// initialize best distance at infinity
float bestDist = 100000000;
float2 bestCoord;
// jump samples
// only one loop
UNITY_UNROLL
for(int u=-1; u<=1; u++)
{
// calculate offset sample position
int2 offsetUV = uvInt + _AxisWidth * u;
// .Load() acts funny when sampling outside of bounds, so don't
offsetUV = clamp(offsetUV, int2(0,0), (int2)_MainTex_TexelSize.zw - 1);
// decode position from buffer
float2 offsetPos = (_MainTex.Load(int3(offsetUV, 0)).rg + FLOOD_ENCODE_OFFSET) * _MainTex_TexelSize.zw / FLOOD_ENCODE_SCALE;
// the offset from current position
float2 disp = i.positionCS.xy - offsetPos;
// square distance
float dist = dot(disp, disp);
// if offset position isn't a null position or is closer than the best
// set as the new best and store the position
if (offsetPos.x != -1.0 && dist < bestDist)
{
bestDist = dist;
bestCoord = offsetPos;
}
}
// if not valid best distance output null position, otherwise output encoded position
return isinf(bestDist) ? FLOOD_NULL_POS_FLOAT2 : bestCoord * _MainTex_TexelSize.xy * FLOOD_ENCODE_SCALE - FLOOD_ENCODE_OFFSET;
}
ENDHLSL
}
Pass // 4
{
Name "OUTLINE"
Stencil {
Ref 1
ReadMask 1
WriteMask 1
Comp NotEqual
Pass Zero
Fail Zero
}
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma multi_compile_instancing
#pragma multi_compile _ DOTS_INSTANCING_ON
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#pragma target 4.5
struct appdata
{
float4 positionOS : POSITION;
#if UNITY_ANY_INSTANCING_ENABLED
uint instanceID : INSTANCEID_SEMANTIC;
#endif
};
struct v2f
{
float4 positionCS : SV_POSITION;
#if UNITY_ANY_INSTANCING_ENABLED
uint instanceID : CUSTOM_INSTANCE_ID;
#endif
};
Texture2D _MainTex;
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_TexelSize;
half4 _OutlineColor;
float _OutlineWidth;
CBUFFER_END
v2f vert (appdata i)
{
UNITY_SETUP_INSTANCE_ID(i);
v2f o;
o.positionCS = TransformObjectToHClip(i.positionOS.xyz);
return o;
}
half4 frag (v2f i) : SV_Target {
// integer pixel position
int2 uvInt = int2(i.positionCS.xy);
// load encoded position
float2 encodedPos = _MainTex.Load(int3(uvInt, 0)).rg;
// early out if null position
if (encodedPos.y == -1)
return half4(0,0,0,0);
// decode closest position
float2 nearestPos = (encodedPos + FLOOD_ENCODE_OFFSET) * abs(_ScreenParams.xy) / FLOOD_ENCODE_SCALE;
// current pixel position
float2 currentPos = i.positionCS.xy;
// distance in pixels to closest position
half dist = length(nearestPos - currentPos);
// calculate outline
// + 1.0 is because encoded nearest position is half a pixel inset
// not + 0.5 because we want the anti-aliased edge to be aligned between pixels
// distance is already in pixels, so this is already perfectly anti-aliased!
half outline = saturate(_OutlineWidth - dist + 1.0);
// apply outline to alpha
half4 col = _OutlineColor;
col.a *= outline;
// profit!
return col;
}
ENDHLSL
}
}
}
// Modified version of outline renderer feature by Alexander Ameye.
// https://alexanderameye.github.io/
// https://twitter.com/alexanderameye/status/1332286868222775298
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class OutlineFeature : ScriptableRendererFeature
{
public Settings OutlineSettings;
private OutlinePass _outlinePass;
private StencilPass _stencilPass;
[Serializable]
public class Settings
{
[Header("Visual")]
[ColorUsage(true, true)]
public Color Color = new Color(0.2f, 0.4f, 1, 1f);
[Range(0.0f, 20.0f)]
public float Width = 5f;
[Header("Rendering")]
public LayerMask LayerMask = 0;
// TODO: Try this again when render layers are working with hybrid renderer.
// [Range(0, 32)]
// public int RenderLayer = 1;
public RenderPassEvent RenderPassEvent = RenderPassEvent.AfterRenderingTransparents;
public SortingCriteria SortingCriteria = SortingCriteria.CommonOpaque;
}
public override void Create()
{
if (OutlineSettings == null)
{
return;
}
_stencilPass = new StencilPass(OutlineSettings);
_outlinePass = new OutlinePass(OutlineSettings);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (OutlineSettings == null)
{
return;
}
renderer.EnqueuePass(_stencilPass);
renderer.EnqueuePass(_outlinePass);
}
}
// Modified version of outline render pass by Alexander Ameye.
// https://alexanderameye.github.io/
// https://twitter.com/alexanderameye/status/1332286868222775298
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class OutlinePass : ScriptableRenderPass
{
private const string ProfilerTag = "Outline Pass";
private const string ShaderName = "Hidden/Outline";
private static readonly ShaderTagId _srpDefaultUnlit = new ShaderTagId("SRPDefaultUnlit");
private static readonly ShaderTagId _universalForward = new ShaderTagId("UniversalForward");
private static readonly ShaderTagId _lightweightForward = new ShaderTagId("LightweightForward");
private static readonly List<ShaderTagId> _shaderTags = new List<ShaderTagId>
{
_srpDefaultUnlit, _universalForward, _lightweightForward,
};
private static readonly int _silhouetteBufferID = Shader.PropertyToID("_SilhouetteBuffer");
private static readonly int _nearestPointID = Shader.PropertyToID("_NearestPoint");
private static readonly int _nearestPointPingPongID = Shader.PropertyToID("_NearestPointPingPong");
private static readonly int _axisWidthID = Shader.PropertyToID("_AxisWidth");
private static readonly int _outlineColorID = Shader.PropertyToID("_OutlineColor");
private static readonly int _outlineWidthID = Shader.PropertyToID("_OutlineWidth");
private readonly Material _bufferFillMaterial;
private readonly Material _outlineMaterial;
private readonly OutlineFeature.Settings _settings;
private RenderTargetIdentifier _cameraColor;
private FilteringSettings _filteringSettings;
public OutlinePass(OutlineFeature.Settings settings)
{
profilingSampler = new ProfilingSampler(ProfilerTag);
_settings = settings;
renderPassEvent = settings.RenderPassEvent;
// TODO: Try this again when render layers are working with hybrid renderer.
// uint renderingLayerMask = 1u << settings.RenderLayer - 1;
// _filteringSettings = new FilteringSettings(RenderQueueRange.all, settings.LayerMask.value, renderingLayerMask);
_filteringSettings = new FilteringSettings(RenderQueueRange.all, settings.LayerMask.value);
if (!_outlineMaterial)
{
_outlineMaterial = CoreUtils.CreateEngineMaterial(ShaderName);
}
_outlineMaterial.SetColor(_outlineColorID, settings.Color);
_outlineMaterial.SetFloat(_outlineWidthID, Mathf.Max(1f, settings.Width));
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
descriptor.graphicsFormat = GraphicsFormat.R8_UNorm;
descriptor.msaaSamples = 1;
descriptor.depthBufferBits = 0;
descriptor.sRGB = false;
descriptor.useMipMap = false;
descriptor.autoGenerateMips = false;
cmd.GetTemporaryRT(_silhouetteBufferID, descriptor, FilterMode.Point);
descriptor.graphicsFormat = GraphicsFormat.R16G16_SNorm;
cmd.GetTemporaryRT(_nearestPointID, descriptor, FilterMode.Point);
cmd.GetTemporaryRT(_nearestPointPingPongID, descriptor, FilterMode.Point);
ConfigureTarget(_silhouetteBufferID);
ConfigureClear(ClearFlag.Color, Color.clear);
_cameraColor = renderingData.cameraData.renderer.cameraColorTarget;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
DrawingSettings drawingSettings = CreateDrawingSettings(
_shaderTags,
ref renderingData,
_settings.SortingCriteria
);
drawingSettings.overrideMaterial = _outlineMaterial;
drawingSettings.overrideMaterialPassIndex = 1;
int numMips = Mathf.CeilToInt(Mathf.Log(_settings.Width + 1.0f, 2f));
int jfaIterations = numMips - 1;
// TODO: Switch to this once mismatched markers bug is fixed.
// CommandBuffer cmd = CommandBufferPool.Get(ProfilerTag);
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, profilingSampler))
{
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings);
Blit(cmd, _silhouetteBufferID, _nearestPointID, _outlineMaterial, 2);
for (int i = jfaIterations; i >= 0; i--)
{
float stepWidth = Mathf.Pow(2, i) + 0.5f;
cmd.SetGlobalVector(_axisWidthID, new Vector2(stepWidth, 0f));
Blit(cmd, _nearestPointID, _nearestPointPingPongID, _outlineMaterial, 3);
cmd.SetGlobalVector(_axisWidthID, new Vector2(0f, stepWidth));
Blit(cmd, _nearestPointPingPongID, _nearestPointID, _outlineMaterial, 3);
}
cmd.Blit(_nearestPointID, _cameraColor, _outlineMaterial, 4);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
if (cmd == null)
{
throw new ArgumentNullException("cmd");
}
cmd.ReleaseTemporaryRT(_silhouetteBufferID);
cmd.ReleaseTemporaryRT(_nearestPointID);
cmd.ReleaseTemporaryRT(_nearestPointPingPongID);
}
}
// Modified version of stencil render pass by Alexander Ameye.
// https://alexanderameye.github.io/
// https://twitter.com/alexanderameye/status/1332286868222775298
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class StencilPass : ScriptableRenderPass
{
private const string ProfilerTag = "Stencil Pass";
private const string ShaderName = "Hidden/Outline";
private static readonly ShaderTagId _srpDefaultUnlit = new ShaderTagId("SRPDefaultUnlit");
private static readonly ShaderTagId _universalForward = new ShaderTagId("UniversalForward");
private static readonly ShaderTagId _lightweightForward = new ShaderTagId("LightweightForward");
private static readonly List<ShaderTagId> _shaderTags = new List<ShaderTagId>
{
_srpDefaultUnlit, _universalForward, _lightweightForward,
};
private readonly OutlineFeature.Settings _settings;
private readonly Material _stencilMaterial;
private FilteringSettings _filteringSettings;
public StencilPass(OutlineFeature.Settings settings)
{
profilingSampler = new ProfilingSampler(ProfilerTag);
_settings = settings;
renderPassEvent = settings.RenderPassEvent;
// TODO: Try this again when render layers are working with hybrid renderer.
// uint renderingLayerMask = 1u << settings.RenderLayer - 1;
// _filteringSettings = new FilteringSettings(RenderQueueRange.all, settings.LayerMask.value, renderingLayerMask);
_filteringSettings = new FilteringSettings(RenderQueueRange.all, settings.LayerMask.value);
if (!_stencilMaterial)
{
_stencilMaterial = CoreUtils.CreateEngineMaterial(ShaderName);
}
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
DrawingSettings drawingSettings = CreateDrawingSettings(
_shaderTags,
ref renderingData,
_settings.SortingCriteria
);
drawingSettings.overrideMaterial = _stencilMaterial;
drawingSettings.overrideMaterialPassIndex = 0;
// TODO: Switch to this once mismatched markers bug is fixed.
// CommandBuffer cmd = CommandBufferPool.Get(ProfilerTag);
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, profilingSampler))
{
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
@ArieLeo
Copy link

ArieLeo commented Jun 8, 2023

@keitzer I think because it's still using the old render feature API, not the new RTHandle API.

@keitzer
Copy link

keitzer commented Jun 14, 2023

@ArieLeo
image

possibly -- but even with the deprecated warnings i was able to figure out a workaround / possible solution to this

i have a Renderer Feature for each Player that would have their own highlight -- followed by a Renderer Feature which renders the same object as Opaque.

It even works with the object being obstructed which is nice:
image

@keitzer
Copy link

keitzer commented Jun 14, 2023

Basically using this as a targeting system to show which "enemy" or object you're aiming at (or in the case of multiple players aiming at the same target -- very similar to what Minecraft Dungeons does)

@keitzer
Copy link

keitzer commented Jun 14, 2023

image

There's still the issue of overlapping draw calls on these objects which actually removes the "outline" since all outlines happen behind these objects -- otherwise i think it looks great and is easy to change the Layer of the objects and the highlight simply applies with basically no change in Framerate

@lolisbest
Copy link

유니티 URP 2021.3.19f1 에서
외곽선이 오브젝트를 뒤덮는 현상을 해결했습니다.

저의 경우, Renderer Asset에서 Depth Priming Mode를 Disabled로 변경하니 올바르게 동작했습니다.

image

@lanmac03
Copy link

lanmac03 commented Nov 6, 2023

unfortunately, doesn't work. Is @keitzer's solution the recommended one? I get the same results, silhouette won't render below the model. I think I'll try and write an updated one when I have more time for a deep dive.

@lolisbest
Copy link

unfortunately, doesn't work. Is @keitzer's solution the recommended one? I get the same results, silhouette won't render below the model. I think I'll try and write an updated one when I have more time for a deep dive.

Render Type이 Overlay인 카메라를 추가한 이후로, 저도 다시 동일한 문제가 발생했습니다.

@keitzer
Copy link

keitzer commented Nov 6, 2023

Yeah if there's a better way that might increase FPS i'm all for it

But as of right now i got pretty high FPS on a macbook air using this method so i'm okay with it for now.

@babon
Copy link

babon commented Mar 8, 2024

Okay so I think i figured it out, for anyone following in my footsteps of trying to make a Renderer Feature read stencil and it bugging out:
So, sometimes, renderingData.cameraData.renderer.cameraColorTarget has both the depth (depthStencilFormat = D32_SFloat_S8_UInt) and color (graphicsFormat = B10G11R11_UFloatPack32) in one. Presumably cameraDepthTarget is unused.

But then sometimes URP goes and splits them into two textures, cameraColorTarget has color but (depthStencilFormat = None), and renderingData.cameraData.renderer.cameraDepthTarget has (depthStencilFormat = D32_SFloat_S8_UInt)
Screenshot 2024-03-08 at 04 01 55
example of this split mode being active

And here's the kicker: When this split is active (and god knows what can activate it, look into urp source code), calling CommandBuffer.SetRenderTarget(destination) / ConfigureTarget() / Blit() with the overload of only one RenderTargetIdentifier will leave the depth texture unbound, as you're binding only the color.

So solution: Store renderingData.cameraData.renderer.cameraDepthTarget into variable, then every time you call SetRenderTarget() and such call the overload with depth: cmd.SetRenderTarget(destination, rememberedDepthStencilVar);

@keitzer
Copy link

keitzer commented Mar 8, 2024

Interesting find @babon -- do you have an example of the modified shader code from OP that can make this work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment