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 same figure, narrowed to 0.74 on one axis. Left: stepping the full reported distance. Right: stepping 1/1.35 of it.

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:

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:

Rules of thumb

  1. Sphere tracing is only safe while the field never overreports. Every operation you apply is a chance to break that.
  2. A non-uniform scale overreports by 1/min(s), and only when some component is below 1. Divide the step by exactly that.
  3. Holes at grazing angles that move with the camera mean overshoot, not a lighting or normal bug.
  4. If more steps do not help, the step size is wrong, not the step count.
  5. Track the Lipschitz bound as you build the field. Reconstructing it later is much harder than writing it down.
  6. Reporting short is safe and slow. Reporting long is fast and wrong. Only one of those is a bug.

All 61 notes How to use them Credits