Hey All,
I made a cutout shader that raytraces the intersection with a sphere and discards all fragments that lie within the sphere. In order to render a cap on the cutout part I check in the shader if a fragment is backfacing or not. If it is backfacing I calculate the normal at that point by intersecting a ray with the sphere and use that normal for shading. This works fine and gives the illusion of a clean boolean between my object and a sphere.
However, since the zbuffer only stores the original depth of the other side of the object inside the cutout part the cap doesn’t actually occlude geometry behind it. I would like to fix this by updating the zbuffer to the correct, calculated, depth value inside of the cutout. I thought I could simply do this by writing the distance from the camera to the intersection with the sphere. That doesn’t work though.
I also tried 1/depth and some stuff in the BufferDepth function in the code below. Any combination of these doesn’t make the object sort properly. Can anyone help me with this?
Shader "Custom/CutOut" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Color ("Color", color) = (1,1,1,1)
_Sphere ("Sphere", vector) = (1,1,1,1)
}
SubShader {
Tags {"LightMode" = "ForwardBase"}
LOD 200
cull off
Pass {
CGPROGRAM
#pragma exclude_renderers gles flash xbox360
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
float2 uv_MainTex : TEXCOORD0;
float3 N : TEXCOORD1;
float3 P : TEXCOORD2;
float3 E : TEXCOORD3;
float3 C : TEXCOORD4;
float3 L : TEXCOORD5;
};
float4 _MainTex_ST;
float4 _Sphere;
float iSphere( float3 ro, float3 rd, float4 sph ) {
float3 oc = ro - sph.xyz; // looks like we are going place sphere from an offset from ray origin, which is = camera
float b = 2.0 * dot( oc, rd );
float c = dot(oc, oc) - sph.w * sph.w; // w should be size
float h = b*b - 4.0 *c;
//if (h<0.0) { return -1.0; }
float t = (-b + sqrt(h)) / 2.0;
return t;
}
v2f vert(appdata_full v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv_MainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
o.N = mul((float3x3)_Object2World, v.normal);
o.P = mul((float3x3)_Object2World, v.vertex.xyz);
o.E = WorldSpaceViewDir(v.vertex);
o.C = _WorldSpaceCameraPos;
o.L = float3(unity_4LightPosX0[0], unity_4LightPosY0[0], unity_4LightPosZ0[0]);//_WorldSpaceLightPos0;
return o;
}
sampler2D _MainTex;
float BufferDepth(float z) {
// returns a zbuffer value (which is depth non-linearly encoded
float zNear = _ProjectionParams.y;
float zFar = _ProjectionParams.z;
float a = zFar / ( zFar - zNear );
float b = zFar * zNear / ( zNear - zFar );
return a + b / z;
}
struct C2E2f_Output {
float4 col:COLOR;
float dep:smile:EPTH;
};
C2E2f_Output frag(v2f IN) : COLOR{
// float4 frag(v2f IN) : COLOR {
C2E2f_Output o;
float dCenter = length(IN.P-_Sphere.xyz);
if(dCenter < _Sphere.w)
discard;
float3 N = normalize(IN.N);
float3 L = IN.L-IN.P;
float3 P = IN.P;
half4 c = tex2D (_MainTex, IN.uv_MainTex);
if(dot(N, IN.E) < 0) {
// fragment is backfacing, so render cap instead
//Get intersection of ray from C through P with sphere
float3 E = normalize(IN.P-IN.C);
float t = iSphere(IN.C, E, _Sphere);
P = IN.C + t*E;
N = normalize(_Sphere.xyz-P);
L = IN.L-P;
}
float depth = length(P-IN.C);
L = normalize(L);
float lambert = dot(N, L);
o.col = lambert; // SHow diffuse lighting
//o.col = float4(N, 1); // Show normals
//o.col = 1/depth; // Show depth, there should be no sharp edge at the cut edges
o.dep = BufferDepth(1/depth);
return o;
//return o.col;
}
ENDCG
}
}
}