Hi Unity shader fans,
I´m trying to implement a shader that allows cutting away parts of a mesh with Signed Distance Fields without converting the mesh into an SDF. My idea is to first render only the front parts that are not cut away in a first pass. Then in a second pass, I change the depth buffer to have the maximum depth wherever I have previously rendered the object and the depth of the SDF in the parts that were not rendered. In a final pass that uses ZTest GEqual it should only render the parts of the cutout, but if the depth of the SDF is still bigger than the backside of the mesh, then nothing will be rendered representing holes that were cut by the SDF.
I have written the following shader to test my idea:
Shader "Unlit/Cutout"
{
SubShader
{
Tags { "RenderType"="Opaque" }
CGINCLUDE
#include "UnityCG.cginc"
#define ITERATIONS 120
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 world : TEXCOORD0;
};
float sphereDistance(float3 p, float3 c, float r)
{
return distance(p, c) - r;
}
float map(float3 p)
{
return sphereDistance(p, float3(0.6, 0, 0), 0.8);
}
int raymarch(inout float3 position, float3 direction)
{
for (int i = 0; i < ITERATIONS; i++)
{
float distance = -map(position);
position += distance * direction;
if (distance < 0.001) return i;
}
return -1;
}
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.world = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
ENDCG
Pass
{
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
fixed4 frag (v2f i) : SV_Target
{
if (map(i.world) <= 0.0) discard;
return fixed4(1, 0, 0, 1);
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float frag(v2f i) : SV_Depth
{
float depth = 0.0;
if (map(i.world) <= 0.0)
{
float3 origin = i.world;
float3 direction = normalize(origin - _WorldSpaceCameraPos.xyz);
int n = raymarch(origin, direction);
if (n >= 0)
{
float3 local = mul(unity_WorldToObject, origin);
float4 clippos = UnityObjectToClipPos(float4(local, 1.0));
depth = clippos.z / clippos.w;
}
}
return depth;
}
ENDCG
}
Pass
{
Cull Off
ZTest GEqual
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
fixed4 frag(v2f i) : SV_Target
{
return fixed4(0,0,1,1);
}
ENDCG
}
}
}
This works quite well as long as the object is at the origin of world space as you can see in these screenshots.
But as soon as I move the object to a different location strange artifacts are rendered. They also change just by changing the view direction.
My first thought was that there is some error with object and world space conversion, but I can´t find any and it also makes no sense that the artifacts change with the view direction. Maybe my depth calculation is wrong?
Can anyone spot an error I made or has any idea what could cause these problems?
Thanks for your help!