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