How to Normalize and Interpret World-Space Pixel Position in Post-Processing

Hi guys. I’m currently writing a system for implementing mechanics involving shadows in my game. For my shadows, I’m using shadow volumes, which have been working great so far, but I just have one question. Right now, I’m taking areas that are supposed to be in shadow, and, by using the depth buffer, I’m able to reconstruct world-space coordinates for pixels. This allows me to apply textures to my shadows in world space, and not screen space. I use triplanar normals as well to determine the layout of the textures. It should be worth noting that this effect is being done via a quad in post, which is why I have to do all of this work to get the world space pixels.

What I’m trying to do is create a “map” of pixels on the screen that the player can teleport to. Basically, I’m trying to collect all pixels (in world space), and create teleport nodes where players can teleport to anywhere that’s in the shadowed region (hence why I’m collecting these pixels with the stencil buffer to mask out any areas that aren’t in shadow).

My problem lies in trying to actually obtain these coordinates in a script. I’ve heard that you can pack coordinate values via rgb coordinates, but since these are pixel coordinates, I’m not sure how that would work. My reference for how to do the pixel world space conversion came from this article:

https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@11.0/manual/writing-shaders-urp-reconstruct-world-position.html

Also, I believe that I would need to normalize these pixel coordinates, but I’m not sure about that either. To be honest this problem may be way out of scope for anyone, so it’s kind of a long shot if anyone knows a way to solve this problem, but if you do, please reach out :slight_smile:

Some photos for reference:

Also forgot to paste my shader code. Here it is:

Shader "Custom/StencilShow"
{
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _SecondTex ("Second Texture", 2D) = "white" {}
        _ShadowColor ("Shadow Tint Color", Color) = (1,1,1,1) // Default white
        _SizeMultiplier ("Size multiplier", Float) = 1.0
    }
    SubShader {
        // New pass to draw procedural texture where stencil is not 0 (shadowed areas)
        Pass {
            Name "StencilMaskInverted"
            Tags { "LightMode" = "SRPDefaultUnlit" }
            Stencil {
                Ref 0                      // Reference value for the stencil test
                Comp NotEqual                 // Draw only if stencil isn't 0
            }
            ZWrite Off
            CULL OFF

            Blend DstColor Zero
            BlendOp Min
            //Blend SrcAlpha OneMinusSrcAlpha

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment fragStencilMaskInverted

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            TEXTURE2D(_SecondTex);
            SAMPLER(sampler_SecondTex);

            float4 _MainTex_ST;
            float4 _ShadowColor;
            float _SizeMultiplier;

            float3 _WorldMinBounds; // New uniform
            float3 _WorldMaxBounds; // New uniform

            struct VertexInput {
                float4 vertex : POSITION;
            };

            struct VertexOutput {
                float4 pos : SV_POSITION;
            };

            VertexOutput vert(VertexInput v) {
                VertexOutput o;
                o.pos = TransformObjectToHClip(v.vertex);
                return o;
            }

            float4 fragStencilMaskInverted(VertexOutput i) : SV_Target {
                float2 UV = i.pos.xy / _ScaledScreenParams.xy;
                #if UNITY_REVERSED_Z
                    real depth = SampleSceneDepth(UV);
                #else
                    real depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(UV));
                #endif

                float3 worldPos = ComputeWorldSpacePosition(UV, depth, UNITY_MATRIX_I_VP);

                float3 normalizedWorldPos = (worldPos - _WorldMinBounds) / (_WorldMaxBounds - _WorldMinBounds);
                normalizedWorldPos = saturate(normalizedWorldPos); // Ensure values are within [0, 1]

                // Sample the normal in world space
                float3 worldNormal = normalize(SampleSceneNormals(UV));

                // Triplanar projection
                float3 absWorldNormal = abs(worldNormal);
                float totalWeight = absWorldNormal.x + absWorldNormal.y + absWorldNormal.z;

                // XY plane projection
                float2 uvXY = worldPos.xy * _SizeMultiplier;
                half4 texColorXY = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uvXY);

                // XZ plane projection
                float2 uvXZ = worldPos.xz * _SizeMultiplier;
                half4 texColorXZ = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uvXZ);

                // YZ plane projection
                float2 uvYZ = worldPos.yz * _SizeMultiplier;
                half4 texColorYZ = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uvYZ);

                // Blend based on the surface normal
                half4 texColor = (texColorXY * absWorldNormal.z +
                                texColorXZ * absWorldNormal.y +
                                texColorYZ * absWorldNormal.x) / totalWeight;

                // Handle near and far clipping
                #if UNITY_REVERSED_Z
                    if(depth < 0.0001)
                        return half4(0,0,0,1);
                #else
                    if(depth > 0.9999)
                        return half4(0,0,0,1);
                #endif

                return float4(normalizedWorldPos, 1);
            }
            ENDHLSL
        }
    }
}