Hey Guys,
I’ve been having some trouble with custom passes with the new HDRP and was hoping I could get some help making sense of it. I’m trying to do a fullscreen shader pass in an attempt to create a pixel perfect selection feature. I’ve managed to get the feature working but the rendertexture that is being created by the buffer, that the shader is being applied to, doesn’t seem to have per pixel depth sorting, which messes with the selection. I’m not sure if the issue is related to the shader, where the shader pass itself doesn’t include depth, or if its the buffer not including depth when created.
Below is the code used for the custom pass as well as the code for the highlight shader, much of this code is just modified versions of the HDRP custom pass examples. I’ve also attached screenshots of the rendertextures being created by the buffer to show the depth issue. As you can see, when zoomed out the objects render incorrectly and when zoomed in they seem to render correctly.
Apologises, the code is pretty messy as I’ve been constantly fiddling with it.
Custom Pass Code:
class HighlightPass : CustomPass {
[SerializeField] private LayerMask interactionLayers;
[SerializeField] private Material replacementMat;
[ColorUsage(false, true)]
public Color outlineColor = Color.black;
public float thickness = 4;
Material fullscreenOutline;
MaterialPropertyBlock outlineProperties;
ShaderTagId[] shaderTags;
RTHandle buffer;
[SerializeField] private RenderTexture renderTexture;
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
{
fullscreenOutline = CoreUtils.CreateEngineMaterial(Shader.Find("FullScreen/Highlight"));
outlineProperties = new MaterialPropertyBlock();
//
// List all the materials that will be replaced in the frame
shaderTags = new ShaderTagId[3]
{
new ShaderTagId("Forward"),
new ShaderTagId("ForwardOnly"),
new ShaderTagId("SRPDefaultUnlit")
};
buffer = RTHandles.Alloc(
Vector2.one, TextureXR.slices, dimension: TextureXR.dimension, enableRandomWrite: true,
colorFormat: GraphicsFormat.R8G8B8A8_SRGB,
useDynamicScale: true, name: "Buffer"
);
renderTexture = buffer.rt;
}
void DrawOutlineMeshes(ScriptableRenderContext renderContext, CommandBuffer cmd, HDCamera hdCamera, CullingResults cullingResult, RenderTargetIdentifier buffer)
{
var result = new RendererListDesc(shaderTags, cullingResult, hdCamera.camera)
{
// We need the lighting render configuration to support rendering lit objects
rendererConfiguration = PerObjectData.LightProbe | PerObjectData.LightProbeProxyVolume | PerObjectData.Lightmaps,
renderQueueRange = RenderQueueRange.all,
sortingCriteria = SortingCriteria.BackToFront,
excludeObjectMotionVectors = false,
// overrideMaterial = replacementMat,
//overrideMaterialPassIndex = 3,
layerMask = interactionLayers
};
CoreUtils.SetRenderTarget(cmd, buffer, ClearFlag.Color);
HDUtils.DrawRendererList(renderContext, cmd, RendererList.Create(result));
}
protected override void Execute(ScriptableRenderContext renderContext, CommandBuffer cmd, HDCamera camera, CullingResults cullingResult)
{
DrawOutlineMeshes(renderContext, cmd, camera, cullingResult, buffer);
SetCameraRenderTarget(cmd);
// if(InputManager.Instance == null) {
// return;
// }
//Color selectionColour = InputManager.Instance.SelectionColor;
//outlineProperties.SetVector("_MousePosition", Input.mousePosition);
//outlineProperties.SetColor("_Identifier", selectionColour);
//outlineProperties.SetColor("_OutlineColor", outlineColor);
// outlineProperties.SetFloat("_Thickness", thickness);
outlineProperties.SetTexture("_Buffer", buffer);
CoreUtils.DrawFullScreen(cmd, fullscreenOutline, outlineProperties, shaderPassId: 0);
}
Texture2D ToTexture2D(RenderTexture rTex) {
Texture2D tex = new Texture2D(rTex.width, rTex.height);
RenderTexture.active = rTex;
tex.ReadPixels(new Rect(0, 0, rTex.width, rTex.height), 0, 0);
tex.Apply();
return tex;
}
protected override void Cleanup()
{
CoreUtils.Destroy(fullscreenOutline);
buffer.Release();
} // Cleanup code
}
Highlight Shader Code:
Shader "FullScreen/Highlight"
{
HLSLINCLUDE
#pragma vertex Vert
#pragma target 4.5
#pragma only_renderers d3d11 ps4 xboxone vulkan metal switch
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassCommon.hlsl"
TEXTURE2D_X(_Buffer);
float2 _MousePosition;
//float3 _Identifier;
float4 _OutlineColor;
int _Thickness;
#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),
};
bool CompareFloat1(float1 a, float1 b) {
float1 MIN_NORMAL = 1.17549435E-38;
float1 epsilon = 0.9999999999999999999999999999999999999999999999999999999999999999999999999999999;
float1 absA = abs(a);
float1 absB = abs(b);
float1 diff = abs(a - b);
if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || absA + absB < MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * MIN_NORMAL);
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}
bool CompareFloat3(float3 a, float3 b) {
if (CompareFloat1(a.r, b.r)) {
if(CompareFloat1(a.g, b.g)){
if(CompareFloat1(a.b, b.b)){
return true;
}
}
}
return false;
}
// The PositionInputs struct allow you to retrieve a lot of useful information for your fullScreenShader:
// 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]
// };
// To sample custom buffers, you have access to these functions:
// But be careful, on most platforms you can't sample to the bound color buffer. It means that you
// can't use the SampleCustomColor when the pass color buffer is set to custom (and same for camera the buffer).
// float4 SampleCustomColor(float2 uv);
// float4 LoadCustomColor(uint2 pixelCoords);
// float LoadCustomDepth(uint2 pixelCoords);
// float SampleCustomDepth(float2 uv);
// There are also a lot of utility function you can use inside Common.hlsl and Color.hlsl,
// you can check them out in the source code of the core SRP package.
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);
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 we're not at the before rendering injection point
if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
color = float4(CustomPassSampleCameraColor(posInput.positionNDC.xy, 0), 1);
// Add your custom pass code here
float2 uv = posInput.positionNDC.xy * _RTHandleScale.xy;
//float4 buffer = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_linear_clamp_sampler, uv, 0);
PositionInputs mouseInput = GetPositionInput(_MousePosition.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
float4 _Identifier = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_linear_clamp_sampler, mouseInput.positionNDC.xy, 0);
float4 buffer = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_linear_clamp_sampler, posInput.positionNDC.xy, 0);
if(_Identifier.r==0 && _Identifier.g==0 && _Identifier.b==0) {
return buffer.a = 0;
return buffer;
}
//buffer.rgb = test.rgb;
buffer.a = 0;
if(buffer.r == _Identifier.r && buffer.g == _Identifier.g && buffer.b == _Identifier.b){
for(int x=0; x<_Thickness+1; x++) {
float3 o = float3(_ScreenSize.zw, 0);
for (int i = 0; i < MAXSAMPLES; i++)
{
float2 uvN = posInput.positionNDC.xy + _ScreenSize.zw * samplingPositions[i] * x;
float4 neighbour = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_linear_clamp_sampler, uvN, 0);
half3 delta = abs(neighbour.rgb - _Identifier.rgb);
if (length(delta) > 0.01)
{
buffer.rgb = _OutlineColor.rgb;
buffer.a = 1;
break;
}
}
}
}
// return float4(0,0,0,0);
// //buffer.a = 1;
// float3 delta = abs(buffer.rgb - _Identifier.rgb);
// if(length(delta) < 0.01568628) {
// for(int x=0; x<_Thickness+1; x++) {
// float3 o = float3(_ScreenSize.zw, 0);
// for (int i = 0; i < MAXSAMPLES; i++)
// {
// float2 uvN = uv + _ScreenSize.zw * samplingPositions[i] * x;
// float4 neighbour = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_linear_clamp_sampler, uvN, 0);
// delta = abs(neighbour.rgb - _Identifier.rgb);
// if (length(delta) > 0.01568628)
// {
// buffer.rgb = _OutlineColor.rgb;
// buffer.a = 1;
// break;
// }
// }
// }
// }
// if(buffer.r == _Identifier.r && buffer.g == _Identifier.g && buffer.b == _Identifier.b){
// buffer.rgb = float4(0,0,1,1);
// buffer.a = 1;
// } else {
// buffer.a = 0;
// }
// if (Luminance(buffer.rgb) < 0.01)
// {
// buffer.a = 1;
// }
return buffer;
}
ENDHLSL
SubShader
{
Pass
{
Name "Custom Pass 0"
ZWrite On
ZTest Always
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
HLSLPROGRAM
#pragma fragment FullScreenPass
ENDHLSL
}
}
Fallback Off
}
Screen Shots:
When zoomed out renders with depth issue: