2
\$\begingroup\$

Hello 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 Processing 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

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

Update: more info, including object space culling, found here.

YES!!! After SO many days, so much time spent feeling defeated, I GOT IT!

Many thanks to this blog post and this forum post for giving me clues for the right direction.

Here is the completely hideous working solution to raymarch a sphere in Unity's PostFX V2 stack!

Debug.cs

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;
    }
}

Debug.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
        }
    }
}

raymarch sphere in Unity's PostFX V2 stack

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .