I have been working on a simple Sphere Tracer that handles shadows, but it is slightly inaccurate. For most objects like boxes, spheres, Menger Cubes, etc. this inaccuracy does not matter much. However, I have been trying to apply it to the Mandelbox and now that slight inaccuracy is a serious problem, some of the surface looks like noise. I wrote the code originally in Processing and have ported it to WebGL, I am guessing that most people here are more comfortable with GLSL so I'll use that for posting code
Trying to follow along some explanations by Iñigo Quilez and Syntopia, I decided that the general idea of implementing shadows should work something like this:
- March in some direction along a ray from the camera until close enough to a surface (my "close enough" value is called MIN_DIST and is set to 0.0001) and consider that the surface "intersection" even though it may be up to MIN_DIST inaccurate
- For each light source, march from the light source in the direction of the surface intersection until close enough to some surface which also may be up to MIN_DIST inaccurate
The problem with the second part is detecting when the first surface to be hit during the light march is be occluding the surface intersection. My original idea for detecting this was to set a maximum distance for the light march which would be the distance from the light to the surface minus 2.0*MIN_DIST. If the current distance was less than MIN_DIST before the maximum was reached then the surface intersection should be occluded, otherwise if the maximum is reached then the surface intersection is not occluded. This turned out to not work so well but increasing the maximum distance to be the distance from the light to the surface intersection minus 10.0*MIN_DIST worked fine for most objects (but not the Mandelbox)
How is this normally handled? For anyone willing to look, below is a section of my GLSL code that handles the camera march and light marches:
- MAX_DIST is 400.0 (more than enough for my testing scene)
- MIN_DIST is 0.001
- I can't iterate an unknown number of times in WebGL but the limit of 1000 is normally never reached
- MIN_DIST_TEN is MIN_DIST*10.0
float sphereTrace(vec3 inPos, vec3 inDir) {
vec3 march;
float currDist = MAX_DIST;
float t = MIN_DIST;
for (int i = 0; i < 1000; i++) {
march = inPos+inDir*t;
currDist = worldDist(march);
t += currDist;
if (currDist < MIN_DIST || t > MAX_DIST) break;
}
if (currDist < MIN_DIST) {
march = inPos+inDir*t;
float val = POLISHED_SILVER_AMBIENT;
for (int i = 0; i < NUM_LIGHTS; i++)
if (lightTrace(march, lightPos[i])) val += shade(march, lightPos[i]);
return val;
}
return 0.0;
}
bool lightTrace(vec3 inMarch, vec3 inLightPos) {
float shadowMaxDist = length(inMarch-inLightPos)-MIN_DIST_TEN;
vec3 march;
vec3 dir = normalize(inMarch-inLightPos);
float t = MIN_DIST;
for (int i = 0; i < 1000; i++) {
march = inLightPos+dir*t;
float currDist = worldDist(march);
t += currDist;
if (t >= shadowMaxDist) return true;
else if (currDist < MIN_DIST) return false;
}
return true;
}