[SOLVED] Porting full-screen raymarching to Post Process V2 stack

UPDATE: I GOT IT! Solution posted below, and I compiled my findings into an article.

Hi, wonderful wizards of the shader world!

I’ve seen a few tutorials featuring the use of Raymarching via the older OnRenderImage function, however that is no longer called in the new stack (what is the correct terminology – is it SRP? Post Processing V2? HDRP?). The intention is to eventually add depth into the rendering so the raymarched objects can be culled/occluded in object space.

Anyways, here is my attempt at rendering a simple sphere. The issue here is the sphere is not being drawn until the camera is physically touching the sphere, at which point the entire viewport is drawing the sphere’s color.

Post Process script

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.PostProcessing;
[Serializable]
[PostProcess(typeof(DebugRenderer), PostProcessEvent.AfterStack, "Custom/Debug")]
public sealed class Debug : PostProcessEffectSettings
{
    public IntParameter maxIterations = new IntParameter { value = 64 };
    public FloatParameter maxDistance = new FloatParameter { value = 100f };
    public FloatParameter minDistance = new FloatParameter { value = 0.01f };
    public DepthTextureMode GetCameraFlags()
    {
        return DepthTextureMode.Depth; //| DepthTextureMode.DepthNormals;
    }
}
public sealed class DebugRenderer : PostProcessEffectRenderer<Debug>
{
    //public override DepthTextureMode GetCameraFlags()
    //{
    //    return DepthTextureMode.Depth;
    //}
    [SerializeField]
    private Shader _shader;
    public Material _raymarchMaterial
    {
        get
        {
            if (!_raymarchMat && _shader)
            {
                _raymarchMat = new Material(_shader);
                _raymarchMat.hideFlags = HideFlags.HideAndDontSave;
            }
            return _raymarchMat;
        }
    }
    private Material _raymarchMat;
    public Camera _camera
    {
        get
        {
            if (!_cam)
            {
                _cam = Camera.main;
            }
            return _cam;
        }
    }
    private Camera _cam;
    public override void Render(PostProcessRenderContext context)
    {
        if (!_cam)
        {
            _cam = Camera.main;
        }
        var sheet = context.propertySheets.Get(Shader.Find("Hidden/Debug"));
        sheet.properties.SetMatrix("_CamFrustum", FrustumCorners(_cam));
        sheet.properties.SetMatrix("_CamToWorld", _cam.cameraToWorldMatrix);
        sheet.properties.SetVector("_CamWorldSpace", _cam.transform.position);
        sheet.properties.SetInt("_MaxIterations", settings.maxIterations);
        sheet.properties.SetFloat("_MaxDistance", settings.maxDistance);
        sheet.properties.SetFloat("_MinDistance", settings.minDistance);
        context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
    }
    private Matrix4x4 FrustumCorners(Camera cam)
    {
        Transform camtr = cam.transform;
        Vector3[] frustumCorners = new Vector3[4];
        cam.CalculateFrustumCorners(new Rect(0, 0, 1, 1), cam.farClipPlane, cam.stereoActiveEye, frustumCorners);
        var bottomLeft = camtr.TransformVector(frustumCorners[0]);
        var topLeft = camtr.TransformVector(frustumCorners[1]);
        var topRight = camtr.TransformVector(frustumCorners[2]);
        var bottomRight = camtr.TransformVector(frustumCorners[3]);
        Matrix4x4 frustumCornersArray = Matrix4x4.identity;
        frustumCornersArray.SetRow(0, bottomLeft);
        frustumCornersArray.SetRow(1, bottomRight);
        frustumCornersArray.SetRow(2, topLeft);
        frustumCornersArray.SetRow(3, topRight);
        return frustumCornersArray;
    }
}

Shader

Shader "Hidden/Debug"
{

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

            #pragma target 3.5

            #pragma vertex vert
            #pragma fragment frag

            //#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
            //#include "HLSLSupport.cginc"
            #include "UnityCG.cginc"

            //TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
            uniform sampler2D _MainTex;
            uniform sampler2D_float _CameraDepthTexture;
            half4 _MainTex_ST;
            uniform float4 _CamWorldSpace;
            uniform float4x4 _CamFrustum,  _CamToWorld;
            uniform int _MaxIterations;
            uniform float _MaxDistance;
            uniform float _MinDistance;
            float4 _Tint;

            uniform float4 _MainTex_TexelSize;

            struct AttributesDefault
            {
                float3 vertex : POSITION;
                half2 texcoord : TEXCOORD0;
            };
            struct VaryingsDefault
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                //float2 uvStereo : TEXCOORD1;
                float2 uv_depth : TEXCOORD1;
                float4 interpolatedRay : TEXCOORD2;
            };

            float LinearEyeDepth135( float z )
            {
                return LinearEyeDepth( z );
            }
            // Vertex manipulation
            float2 TransformTriangleVertexToUV(float2 vertex)
            {
                float2 uv = (vertex + 1.0) * 0.5;
                return uv;
            }

            VaryingsDefault vert(AttributesDefault v  )
            {
                VaryingsDefault o;
                v.vertex.z = 0.1;
                o.pos = float4(v.vertex.xy, 0.0, 1.0);
                o.uv = TransformTriangleVertexToUV(v.vertex.xy);
                o.uv_depth = v.texcoord.xy;
                #if UNITY_UV_STARTS_AT_TOP
                    o.uv = o.uv * float2(1.0, -1.0) + float2(0.0, 1.0);
                #endif
  
                //o.uvStereo = TransformStereoScreenSpaceTex(o.uv, 1.0);
                #if UNITY_UV_STARTS_AT_TOP
                    if (_MainTex_TexelSize.y < 0)
                        o.uv.y = 1 - o.uv.y;
                #endif
                int frustumIndex = v.texcoord.x + (2 * o.uv.y);
                o.interpolatedRay = _CamFrustum[frustumIndex];
                o.interpolatedRay.w = frustumIndex;
                return o;
            }

            float sdSphere(float3 position, float3 origin, float radius)
            {
                return distance(position, origin) - radius;
            }

            fixed4 raymarching(float3 ro, float3 rd) {
                fixed4 result = float4(1, 1, 1, 1);
                float t = 0; // Distance Traveled from ray origin (ro) along the ray direction (rd)

                for (int i = 0; i < _MaxIterations; i++)
                {
                    if (t > _MaxDistance)
                    {
                        result = float4(rd, 1); // color backround from ray direction for debugging
                        break;
                    }

                    float3 p = ro + rd * t;    // This is our current position
                    float d = sdSphere(ro, float3(0, 0, 0), 1); // should be a sphere at (0, 0, 0) with a radius of 1
                    if (d <= _MinDistance) // We have hit something
                    {
                        // shading
                        result = float4(1, 1, 0, 1); // yellow sphere should be drawn at (0, 0, 0)
                        break;
                    }

                    t += d;
                }

                return result;
            }

            float4 frag(VaryingsDefault i) : SV_Target
            {
                float4 wsDir = normalize(i.interpolatedRay);
                float4 wsPos = _CamWorldSpace;
                fixed4 result = raymarching(wsPos, wsDir);
                return result;
            }

            ENDHLSL
        }
    }
}

For reference, I am converting the C# script and shader from Peer Play’s tutorial video to the SRP.

Anyways, thanks for reading, and please let me know if I can provide any more information.

Cheers!

Edit I think the issue may be in the vert function as I am not handling v.vertex and o.vertex the same as in the video. This clashes with my limited understanding of what is going on in the VertDefault function found in PostProcessing/StdLib.hlsl

Updates from [this post]( Screen Space Multiple Scattering page-3#post-3316350) have been applied to the OP. Thank you @FuzzkingCool for sharing your port!

Update: To anyone else stumbling upon this thread, I found another source of information about BlitFullscreenTriangle & working with the camera frustum in PostFX V2: AVA: More foggy adventures

I GOT IT. I FREAKING GOT IT. HERE IS THE WORKING, NOT-GLORIOUS CODE:

C# script

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.PostProcessing;

[Serializable]
[PostProcess(typeof(DebugRenderer), PostProcessEvent.AfterStack, "Custom/Debug")]
public sealed class Debug : PostProcessEffectSettings
{
    public IntParameter maxIterations = new IntParameter { value = 64 };
    public FloatParameter maxDistance = new FloatParameter { value = 100f };
    public FloatParameter minDistance = new FloatParameter { value = 0.01f };

    public DepthTextureMode GetCameraFlags()
    {
        return DepthTextureMode.Depth; //| DepthTextureMode.DepthNormals;
    }
}

public sealed class DebugRenderer : PostProcessEffectRenderer<Debug>
{
    public override void Render(PostProcessRenderContext context)
    {
        Camera _cam = context.camera;

        var sheet = context.propertySheets.Get(Shader.Find("Hidden/Debug"));
        sheet.properties.SetMatrix("_CamFrustum", FrustumCorners2(_cam));
        sheet.properties.SetMatrix("_CamToWorld", _cam.cameraToWorldMatrix);
        sheet.properties.SetVector("_CamWorldSpace", _cam.transform.position);
        sheet.properties.SetInt("_MaxIterations", settings.maxIterations);
        sheet.properties.SetFloat("_MaxDistance", settings.maxDistance);
        sheet.properties.SetFloat("_MinDistance", settings.minDistance);

        context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
    }

    private Matrix4x4 CamFrustum(Camera cam)
    {
        Matrix4x4 frustum = Matrix4x4.identity;
        float fov = Mathf.Tan((cam.fieldOfView * 0.5f) * Mathf.Deg2Rad);

        Vector3 goUp = Vector3.up * fov;
        Vector3 goRight = Vector3.right * fov * cam.aspect;

        Vector3 TL = (-Vector3.forward - goRight + goUp);
        Vector3 TR = (-Vector3.forward + goRight + goUp);
        Vector3 BR = (-Vector3.forward + goRight - goUp);
        Vector3 BL = (-Vector3.forward - goRight - goUp);

        frustum.SetRow(0, TL);
        frustum.SetRow(1, TR);
        frustum.SetRow(2, BR);
        frustum.SetRow(3, BL);

        return frustum;
    }

    private Matrix4x4 GetFrustumCorners(Camera cam)
    {
        float camFov = cam.fieldOfView;
        float camAspect = cam.aspect;

        Matrix4x4 frustumCorners = Matrix4x4.identity;

        float fovWHalf = camFov * 0.5f;

        float tan_fov = Mathf.Tan(fovWHalf * Mathf.Deg2Rad);

        Vector3 toRight = Vector3.right * tan_fov * camAspect;
        Vector3 toTop = Vector3.up * tan_fov;

        Vector3 topLeft = (-Vector3.forward - toRight + toTop);
        Vector3 topRight = (-Vector3.forward + toRight + toTop);
        Vector3 bottomRight = (-Vector3.forward + toRight - toTop);
        Vector3 bottomLeft = (-Vector3.forward - toRight - toTop);

        frustumCorners.SetRow(0, topLeft);
        frustumCorners.SetRow(1, topRight);
        frustumCorners.SetRow(2, bottomRight);
        frustumCorners.SetRow(3, bottomLeft);

        return frustumCorners;
    }

    private Matrix4x4 FrustumCorners(Camera cam)
    {
        Transform camtr = cam.transform;
        Vector3[] frustumCorners = new Vector3[4];
        cam.CalculateFrustumCorners(new Rect(0, 0, 1, 1), cam.farClipPlane, cam.stereoActiveEye, frustumCorners);
        var bottomLeft = camtr.TransformVector(frustumCorners[0]);
        var topLeft = camtr.TransformVector(frustumCorners[1]);
        var topRight = camtr.TransformVector(frustumCorners[2]);
        var bottomRight = camtr.TransformVector(frustumCorners[3]);

        Matrix4x4 frustumCornersArray = Matrix4x4.identity;
        frustumCornersArray.SetRow(0, bottomLeft);
        frustumCornersArray.SetRow(1, bottomRight);
        frustumCornersArray.SetRow(2, topLeft);
        frustumCornersArray.SetRow(3, topRight);

        return frustumCornersArray;
    }

    private Matrix4x4 FrustumCorners2(Camera cam)
    {
        Transform camtr = cam.transform;

        Vector3[] frustumCorners = new Vector3[4];
        cam.CalculateFrustumCorners(new Rect(0, 0, 1, 1),
         cam.farClipPlane, cam.stereoActiveEye, frustumCorners);
        var bottomLeft = camtr.TransformVector(frustumCorners[1]);
        var topLeft = camtr.TransformVector(frustumCorners[0]);
        var bottomRight = camtr.TransformVector(frustumCorners[2]);

        Matrix4x4 frustumVectorsArray = Matrix4x4.identity;
        frustumVectorsArray.SetRow(0, bottomLeft);
        frustumVectorsArray.SetRow(1, bottomLeft + (bottomRight - bottomLeft) * 2);
        frustumVectorsArray.SetRow(2, bottomLeft + (topLeft - bottomLeft) * 2);

        return frustumVectorsArray;
    }
}

Shader

Shader "Hidden/Debug"
{

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

            #pragma target 3.5

            #pragma vertex vert
            #pragma fragment frag

            //#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
            //#include "HLSLSupport.cginc"
            #include "UnityCG.cginc"

            //TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
            uniform sampler2D _MainTex;
            uniform sampler2D_float _CameraDepthTexture, sampler_CameraDepthTexture;
            half4 _MainTex_ST;
            uniform float4 _CamWorldSpace;
            uniform float4x4 _CamFrustum,  _CamToWorld;
            uniform int _MaxIterations;
            uniform float _MaxDistance;
            uniform float _MinDistance;
            float4 _Tint;

            uniform float4 _MainTex_TexelSize;

            struct AttributesDefault
            {
                float3 vertex : POSITION;
                half2 texcoord : TEXCOORD0;
            };
            struct VaryingsDefault
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float2 uvStereo : TEXCOORD4;
                //float2 uvStereo : TEXCOORD1;
                float2 uv_depth : TEXCOORD1;
                float4 interpolatedRay : TEXCOORD2;
                float3 wPos : TEXCOORD3;
            };

            struct v2f
            {
             float4 vertex : SV_POSITION;
             float2 texcoord : TEXCOORD0;
             float2 texcoordStereo : TEXCOORD1;
             float4 ray : TEXCOORD2;
            };

            float LinearEyeDepth135( float z )
            {
                return LinearEyeDepth( z );
            }
            // Vertex manipulation
            float2 TransformTriangleVertexToUV(float2 vertex)
            {
                float2 uv = (vertex + 1.0) * 0.5;
                return uv;
            }

            //VaryingsDefault vert(AttributesDefault v  )
            //{
            //    VaryingsDefault o;
            //    v.vertex.z = 0.1;
            //    o.wPos =  mul(UNITY_MATRIX_VP, v.vertex);
            //    o.pos = float4(v.vertex.xy, 0.0, 1.0);
            //    o.uv = TransformTriangleVertexToUV(v.vertex.xy);
            //    o.uv_depth = v.texcoord.xy;
            //    #if UNITY_UV_STARTS_AT_TOP
            //        o.uv = o.uv * float2(1.0, -1.0) + float2(0.0, 1.0);
            //    #endif
        
            //    o.uvStereo = TransformStereoScreenSpaceTex(o.uv, 1.0);
            //    #if UNITY_UV_STARTS_AT_TOP
            //        if (_MainTex_TexelSize.y < 0)
            //            o.uv.y = 1 - o.uv.y;
            //    #endif
            //    //int frustumIndex = v.texcoord.x + (2 * o.uv.y);
            //    //o.interpolatedRay = _CamFrustum[frustumIndex];
            //    //o.interpolatedRay.w = frustumIndex;

            //    int index = (o.uv.x / 2) + o.uv.y;
            //    o.interpolatedRay = _CamFrustum[index];
            //    //o.interpolatedRay /= abs(o.interpolatedRay.z);
            //    //o.interpolatedRay = mul(_CamToWorld, o.interpolatedRay);
            //    return o;
            //}

            v2f vert(AttributesDefault v  )
            {
                v2f o;
                v.vertex.z = 0.1;
                //o.vertex = mul(UNITY_MATRIX_VP, v.vertex);
                o.vertex = float4(v.vertex.xy, 0.0, 1.0);
                o.texcoord = TransformTriangleVertexToUV(v.vertex.xy);
                //#if UNITY_UV_STARTS_AT_TOP
                //    o.texcoord = o.texcoord * float2(1.0, -1.0) + float2(0.0, 1.0);
                //#endif
        
                o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0);
                //#if UNITY_UV_STARTS_AT_TOP
                //    if (_MainTex_TexelSize.y < 0)
                //        o.texcoord.y = 1 - o.texcoord.y;
                //#endif
                //int index = v.texcoord.x + (2 * o.texcoord.y);
                int index = (o.texcoord.x / 2) + o.texcoord.y;
                o.ray = _CamFrustum[index];
                return o;
            }

            float sdSphere(float3 position, float3 origin, float radius)
            {
                return distance(position, origin) - radius;
            }

            float sdf_sphere(float3 p, float3 c, float r)
             {
                 return distance(p, c) - r;
             }

            fixed4 raymarching(float3 rayOrigin, float3 rayDirection) {
                fixed4 result = float4(1, 1, 1, 1);
                float t = 0.01; // Distance Traveled from ray origin (ro) along the ray direction (rd)

                for (int i = 0; i < _MaxIterations; i++)
                {
                    if (t > _MaxDistance)
                    {
                        result = float4(rayDirection, 1); // color backround from ray direction for debugging
                        break;
                    }

                    float3 p = rayOrigin + rayDirection * t;    // This is our current position
                    //float3 p = float3(5, 0, 0) + (float3(1, 0, 0) * t);    // This is our current position
                    //float d = sdSphere(p, float3(1, 0, 0), 2); // should be a sphere at (0, 0, 0) with a radius of 1
                    float d = sdf_sphere(p, float3(0, 0, 0), 1);
                    if (d <= _MinDistance) // We have hit something
                    {
                        // shading
                        result = float4(1, 1, 1, 1); // yellow sphere should be drawn at (0, 0, 0)
                        break;
                    }

                    //t += max(0.01, 0.02 * 2);
                    t += d;
                }

                return result;
            }

            fixed4 rm (float3 ro, float3 rd) {
                fixed4 result = float4(0, 0, 0, 1);
                float t = 0;

                for (int i = 0; i < 100; i++) {
                    float3 p = ro + rd * t;
                    float d = sdf_sphere(p, float3(0, 0, 0), 0.8);
                    if (d <= _MinDistance) // We have hit something
                    {
                        // shading
                        result = float4(1, 1, 0, 1); // yellow sphere should be drawn at (0, 0, 0)
                        break;
                    }

                    t += d;
                }

                return result;
            }

            float4 frag(v2f i) : SV_Target
            {
                //float rawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoTransformScreenSpaceTex(i.uv ));
                //float dpth = Linear01Depth(rawDepth);

                //float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.uvStereo);
                //depth = Linear01Depth(depth);

                //float rawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uvStereo);
                //float dpth = Linear01Depth(rawDepth);

                ////float4 wsDir = normalize(i.interpolatedRay);
                //float3 wsDir = normalize(i.interpolatedRay);
                //float4 wsPos = _CamWorldSpace;
                ////fixed4 result = raymarching(_CamWorldSpace, normalize(i.interpolatedRay.xyz));
                //fixed4 result = rm(_CamWorldSpace, normalize(i.interpolatedRay.xyz));
                //return result;

                //float dpth = Linear01Depth(rawDepth);
                //return raymarching(_WorldSpaceCameraPos, normalize(i.interpolatedRay));

                return raymarching(_CamWorldSpace, normalize(i.ray));

                //half4 sceneColor = tex2D(_MainTex, UnityStereoTransformScreenSpaceTex(i.uv));
                //// Reconstruct world space position & direction
                //// towards this screen pixel.
                //float rawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoTransformScreenSpaceTex(i.uv ));
                //float dpth = Linear01Depth(rawDepth);
                //float4 wsDir = dpth * i.interpolatedRay;
                //float4 wsPos = _CamWorldSpace + wsDir;

                //return wsPos;
                //return raymarching(wsPos, wsDir);
            }

            ENDHLSL
        }
    }
}
1 Like

Have you got an example project with this in? I cannot get this to work in either the LWRP or HDRP in Unity 2019.2.0f1 or 2019.3.0b3 with the HDRP 6.9.1 or 7.0.1.

I did get an image effect raymarching shader working with the old render system following the same tutorials as you linked, and I’ve read everything I can from all the links you posted, but haven’t been able to get any raymarching rendering working at all under HDRP. I wonder if it’s a project setting or HDRP Asset setting maybe?
Tried everything I can think of but nothing seems to be working so far :frowning:

Sadly PPv2 is for 2018 only. The new Volume has replaced it and there’s currently not a simple way to do custom PP with that. (although blitting to a quad that’s parented to the camera would work)

However - we should see a custom PP solution in the next release of HDRP which is already available on Github master and should be released to the package manager any day now.

1 Like

Was there a resolution to this problem? Latest HDRP in Unity 2020 and custom raymarching shaders