Pixel world position recreation from depth buffer in URP

Hello! I have been trying to implement a shader effect for quite some time now, but I am currently stuck and need your help.

I have been following this tutorial which demonstrates the effect I’m going for

The tutorial is however for the built in render pipeline, and I’m trying to convert it for usage with the URP.

For easy reference, here is the unmodified shader ( https://github.com/Broxxar/NoMansScanner/blob/master/Assets/Scanner Effect/ScannerEffect.shader )

Shader "Hidden/ScannerEffect"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _DetailTex("Texture", 2D) = "white" {}
        _ScanDistance("Scan Distance", float) = 0
        _ScanWidth("Scan Width", float) = 10
        _LeadSharp("Leading Edge Sharpness", float) = 10
        _LeadColor("Leading Edge Color", Color) = (1, 1, 1, 0)
        _MidColor("Mid Color", Color) = (1, 1, 1, 0)
        _TrailColor("Trail Color", Color) = (1, 1, 1, 0)
        _HBarColor("Horizontal Bar Color", Color) = (0.5, 0.5, 0.5, 0)
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
          
            #include "UnityCG.cginc"

            struct VertIn
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 ray : TEXCOORD1;
            };

            struct VertOut
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float2 uv_depth : TEXCOORD1;
                float4 interpolatedRay : TEXCOORD2;
            };

            float4 _MainTex_TexelSize;
            float4 _CameraWS;

            VertOut vert(VertIn v)
            {
                VertOut o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv.xy;
                o.uv_depth = v.uv.xy;

                #if UNITY_UV_STARTS_AT_TOP
                if (_MainTex_TexelSize.y < 0)
                    o.uv.y = 1 - o.uv.y;
                #endif              

                o.interpolatedRay = v.ray;

                return o;
            }

            sampler2D _MainTex;
            sampler2D _DetailTex;
            sampler2D_float _CameraDepthTexture;
            float4 _WorldSpaceScannerPos;
            float _ScanDistance;
            float _ScanWidth;
            float _LeadSharp;
            float4 _LeadColor;
            float4 _MidColor;
            float4 _TrailColor;
            float4 _HBarColor;

            float4 horizBars(float2 p)
            {
                return 1 - saturate(round(abs(frac(p.y * 100) * 2)));
            }

            float4 horizTex(float2 p)
            {
                return tex2D(_DetailTex, float2(p.x * 30, p.y * 40));
            }

            half4 frag (VertOut i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv);

                float rawDepth = DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv_depth));
                float linearDepth = Linear01Depth(rawDepth);
                float4 wsDir = linearDepth * i.interpolatedRay;
                float3 wsPos = _WorldSpaceCameraPos + wsDir;
                half4 scannerCol = half4(0, 0, 0, 0);

                float dist = distance(wsPos, _WorldSpaceScannerPos);

                if (dist < _ScanDistance && dist > _ScanDistance - _ScanWidth && linearDepth < 1)
                {
                    float diff = 1 - (_ScanDistance - dist) / (_ScanWidth);
                    half4 edge = lerp(_MidColor, _LeadColor, pow(diff, _LeadSharp));
                    scannerCol = lerp(_TrailColor, edge, diff) + horizBars(i.uv) * _HBarColor;
                    scannerCol *= diff;
                }

                return col + scannerCol;
            }
            ENDCG
        }
    }
}

And here is the logic script for giving the shader the essential data using built in renderer. (https://github.com/Broxxar/NoMansScanner/blob/master/Assets/Scanner Effect/ScannerEffectDemo.cs)

[ImageEffectOpaque]
    void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        EffectMaterial.SetVector("_WorldSpaceScannerPos",
                               ScannerOrigin.position);
        EffectMaterial.SetFloat("_ScanDistance", ScanDistance);
        RaycastCornerBlit(src, dst, EffectMaterial);
    }

    void RaycastCornerBlit(RenderTexture source,
                            RenderTexture dest,
                            Material mat)
    {
        // Compute Frustum Corners
        float camFar = _camera.farClipPlane;
        float camFov = _camera.fieldOfView;
        float camAspect = _camera.aspect;

        float fovWHalf = camFov * 0.5f;

        Vector3 toRight = _camera.transform.right
                               * Mathf.Tan(fovWHalf
                               * Mathf.Deg2Rad)
                               * camAspect;
        Vector3 toTop = _camera.transform.up
                               * Mathf.Tan(fovWHalf
                               * Mathf.Deg2Rad);

        Vector3 topLeft = (_camera.transform.forward - toRight + toTop);
        float camScale = topLeft.magnitude * camFar;

        topLeft.Normalize();
        topLeft *= camScale;

        Vector3 topRight = (_camera.transform.forward + toRight + toTop);
        topRight.Normalize();
        topRight *= camScale;

        Vector3 bottomRight = (_camera.transform.forward + toRight - toTop);
        bottomRight.Normalize();
        bottomRight *= camScale;

        Vector3 bottomLeft = (_camera.transform.forward - toRight - toTop);
        bottomLeft.Normalize();
        bottomLeft *= camScale;

        // Custom Blit, encoding Frustum Corners as
        // additional Texture Coordinates
        RenderTexture.active = dest;

        mat.SetTexture("_MainTex", source);

        GL.PushMatrix();
        GL.LoadOrtho();

        mat.SetPass(0);

        GL.Begin(GL.QUADS);

        GL.MultiTexCoord2(0, 0.0f, 0.0f);
        GL.MultiTexCoord(1, bottomLeft);
        GL.Vertex3(0.0f, 0.0f, 0.0f);

        GL.MultiTexCoord2(0, 1.0f, 0.0f);
        GL.MultiTexCoord(1, bottomRight);
        GL.Vertex3(1.0f, 0.0f, 0.0f);

        GL.MultiTexCoord2(0, 1.0f, 1.0f);
        GL.MultiTexCoord(1, topRight);
        GL.Vertex3(1.0f, 1.0f, 0.0f);

        GL.MultiTexCoord2(0, 0.0f, 1.0f);
        GL.MultiTexCoord(1, topLeft);
        GL.Vertex3(0.0f, 1.0f, 0.0f);

        GL.End();
        GL.PopMatrix();
    }

The first issue I encountered was that there are no “OnRenderImage” callback in the URP so I tried to implement a custom [ScriptableRendererFeature] and [ScriptableRenderPass]

I have “Depth Texture” and “Opaque Texture” enabled in the “UniversalRenderPipelineAsset” options.

This is my current implementation in the Execute function of the [ScriptableRenderPass]

public override void Execute(ScriptableRenderContext context,
                                        ref RenderingData renderingData)
            {
            CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);

            Camera _camera = renderingData.cameraData.camera;
            cmd.SetGlobalVector("_WorldSpaceScannerPos",
                                _camera.transform.position);
          
            // Compute Frustum Corners
            float camFar = _camera.farClipPlane;
            float camFov = _camera.fieldOfView;
            float camAspect = _camera.aspect;
            float fovWHalf = camFov * 0.5f;

            Vector3 toRight = _camera.transform.right
                            * Mathf.Tan(fovWHalf
                            * Mathf.Deg2Rad)
                            * camAspect;
            Vector3 toTop = _camera.transform.up
                            * Mathf.Tan(fovWHalf
                            * Mathf.Deg2Rad);
            Vector3 topLeft = (_camera.transform.forward - toRight + toTop);
            float camScale = topLeft.magnitude * camFar;
            topLeft.Normalize();
            topLeft *= camScale;

            Vector3 topRight = (_camera.transform.forward + toRight + toTop);
            topRight.Normalize();
            topRight *= camScale;

            Vector3 bottomRight = (_camera.transform.forward + toRight - toTop);
            bottomRight.Normalize();
            bottomRight *= camScale;

            Vector3 bottomLeft = (_camera.transform.forward - toRight - toTop);
            bottomLeft.Normalize();
            bottomLeft *= camScale;

            RenderTextureDescriptor opaqueDesc =
                    renderingData.cameraData.cameraTargetDescriptor;

            opaqueDesc.depthBufferBits = 16;

            cmd.GetTemporaryRT(m_TemporaryColorTexture.id,
                                opaqueDesc,
                                filterMode);
            Blit(cmd, source, m_TemporaryColorTexture.id,
                                blitMaterial,
                                blitShaderPassIndex);
          
            // Custom Blit, encoding Frustum Corners
            // as additional Texture Coordinates
            cmd.SetRenderTarget(m_TemporaryColorTexture.id);
            cmd.SetGlobalTexture("_MainTex", source);

            GL.PushMatrix();
            GL.LoadOrtho();

            blitMaterial.SetPass(0);

            GL.Begin(GL.QUADS);

            GL.MultiTexCoord2(0, 0.0f, 0.0f);
            GL.MultiTexCoord(1, bottomLeft);
            GL.Vertex3(0.0f, 0.0f, 0.0f);

            GL.MultiTexCoord2(0, 1.0f, 0.0f);
            GL.MultiTexCoord(1, bottomRight);
            GL.Vertex3(1.0f, 0.0f, 0.0f);

            GL.MultiTexCoord2(0, 1.0f, 1.0f);
            GL.MultiTexCoord(1, topRight);
            GL.Vertex3(1.0f, 1.0f, 0.0f);

            GL.MultiTexCoord2(0, 0.0f, 1.0f);
            GL.MultiTexCoord(1, topLeft);
            GL.Vertex3(0.0f, 1.0f, 0.0f);

            GL.End();
            GL.PopMatrix();

            Blit(cmd, m_TemporaryColorTexture.id, source);

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

It seems like, the “TEXCOORD1” in the VertIn struct that “ray” get assigned to in the shader is not correct, which leads me to believe that I do something wrong in the Execute function…

The shader is drawing the scan effect, however only when _WorldSpaceScannerPos is exactly that of the cameras, which leads me to believe that the pixel to world position is incorrect. We can see that the effect expands from the lower left corner of the view which also suggests that something fishy is going on…
brisktornammonite

Did you fixed it,I am trying this shader in URP too,and lots of bugs made me crazy

Are you mixing GL codes and commandbuffer codes together?
GL calls are executed almost immediate upon calling but command buffers stack commands and do not execute until the you call for execution, which would explain why you would have lost all your vertex info during the execution because the context where you attempted to blit, do not contain the GL info.

Hint: Try rewriting the GL parts in command buffer terms.

Hi, sorry late answer… I rewrote the shader from scratch and just like weiping-toh is suggesting above this comment is that we can’t use GL commands with command buffer render feature!

1 Like

Hi ko0Zi, I had the same problem. Any update on how to pass the UV value to the shader?

I posted my question here. Resolved. I left a comment about the solution.
https://www.reddit.com/r/Unity3D/comments/ll9h5j/any_idea_on_making_topographical_scanner_effect/

1 Like