A squashed field is not a distance any more
The first 3D figure anybody builds is capsules and a sphere, and the first thing anybody does to it is squash an axis, because a body is not a stack of circular cylinders. That single division is enough to make the renderer wrong, and the way it goes wrong looks like a bug in the lighting.
The left figure is eaten away at the grazing angles, the silhouette flickers, and the holes move as the shape turns. Nothing is wrong with the shape or the shading. The marcher is walking straight through the surface.
What sphere tracing assumes
The algorithm is: ask the field how far the nearest surface is, step that far, repeat. It is safe because of one guarantee, which is the whole reason distance fields are interesting: a step of the reported distance can never pass through anything, since by definition there is nothing closer than that.
Divide the sample point by (0.70, 1.0, 1.0) and that guarantee is gone. The
field is now measuring distance in a compressed space, so along that axis it reports up
to 1.43 times the true world distance. Step the full amount and you land inside the
surface, past the point where the sign flipped, and the march sails on into the
background.
The direction of this is worth being careful about, because it is easy to get backwards
and the wrong version is silent. For f(p) = g(p/s), the gradient of
f is the gradient of g divided by s, so it lands
in [1/max(s), 1/min(s)]. Overreporting needs that to exceed 1, which needs
a scale component below 1. Dividing by something larger than 1 widens
the shape and makes the field report SHORT, which is safe and merely slow. The
bound is 1/min(s), not max(s)/min(s).
This is why the holes appear at grazing angles first. A ray hitting head-on has plenty of surface behind the overshoot; a ray skimming the silhouette has almost none, so a modest overshoot takes it clean past.
The fix is one constant
const float LIP = 1.0 / min(min(s.x, s.y), s.z); // 1.43 here
t += map(p) / LIP;
That constant is the field's Lipschitz bound: the largest factor by which it can overreport. Dividing every step by it restores the guarantee, at the cost of taking more steps for the same distance. Here that means 43% more marching, which is the real price and is worth knowing before deciding to reshape something.
Everything else that breaks the same guarantee
Non-uniform scale is the clearest case and far from the only one. Anything on this list needs either a bound or a smaller step:
- Domain warps and noise displacement. The bound is one plus the maximum gradient of the displacement, which is usually estimated rather than derived.
sminwith a large radius, which underreports rather than over, so it is safe for marching but makes it slow. The opposite failure.- Twist and bend deformers, where the bound grows with the twist rate times the distance from the axis. A gentle twist is fine, a strong one is not.
- Subtraction and intersection, which produce a bound rather than a distance near the seam. Usually safe, occasionally not.
- Anything with a
maxin it, which is the general form of the previous point.
A useful habit: track the bound alongside the field as you build. When you write
p / s, write down the factor next to it. Recovering the bound afterwards
from a scene of forty operations is genuinely hard, and guessing 0.5 everywhere is the
usual outcome, which is correct and twice as slow as it needs to be.
Diagnosing it in the wild
The symptoms are specific enough to be recognizable:
- Holes at grazing angles that move when the camera or the object moves.
- A silhouette that shimmers without any temporal effect being on.
- It gets worse further from the camera, because steps are larger there.
- Raising the step count does not fix it, which is the giveaway. More steps of the wrong size are still the wrong size. If more iterations do not help, the problem is the step scale rather than the budget.
Rules of thumb
- Sphere tracing is only safe while the field never overreports. Every operation you apply is a chance to break that.
- A non-uniform scale overreports by
1/min(s), and only when some component is below 1. Divide the step by exactly that. - Holes at grazing angles that move with the camera mean overshoot, not a lighting or normal bug.
- If more steps do not help, the step size is wrong, not the step count.
- Track the Lipschitz bound as you build the field. Reconstructing it later is much harder than writing it down.
- Reporting short is safe and slow. Reporting long is fast and wrong. Only one of those is a bug.
One email when something new goes up. No newsletter, no schedule, nothing else.
Double opt-in, so watch for a confirmation email. Unsubscribe any time.