Very often you find youself in the need to compute the distance to an isosurface that is defined through an implicit scalar field f(x). This happens in just
too many situations, like in raymarching mandelbulbs or julia sets or
any sort of regular distance fields, rasterizing functions or rendering
2d fractals, just to mention a few. In this article I'm going to explain the usual way to estimate a distance
to the isosurface, and how that can help avoid one particular annoying issue that often arises when rendering procedural graphics - the compression and shrinking of
your pattern.The problemSay you are using some random implicit function, and imagine we are using this function to draw some shape (defined by it's f = 0 isosurface). Let's take one simple formula as an example: ![]() where, as usual, r = sqrt(x˛+y˛) and a = atan(y,x), meaning f is in the end of the day a function of regular x and y cartesian coordiantes. This formula produces a simple 3-lobe fan shape, and it could easily be one of the many elements making a bigger procedural image/texture or something. Now, if you were to do nothing special but just assign a color to each pixel based on the value of f, in a way similar to the code below does, then you would be producing an image like the one here in the left
Interesting as it is, imagine that it didn't look as you wanted, and that in particular the non constant thicknes of the shape was a problem. Perhaps you would be more interested in a constant thickness outline image, probably based on a more homogeneous scalar field. In fact, if you could compute some sort of distance to the zero-isoline of f, that would be great, wouldn't it? (now you probably see how this is related to doing distance based raymarching and you try to find intersections with the zero-isosurface). Perhaps, you would be interested in being able to compute an image like the one on the right. Let's see how we can achieve this! The maths
The implementationOf course, most of the times we won't have an analytical gradient vector for our distance field, so we are forced to do a numerical approximation with the regular central differences method vec2 grad( in vec2 x )
{
vec2 h = vec2( 0.01, 0.0 );
return vec2( f(p+h.xy) - f(p-h.xy),
f(p+h.yx) - f(p-h.yx) )/(2.0*h.x);
}
and then use the formula above to compute the distance estimation (de in the code) to compute the color as before: float color( in vec2 x )
{
float v = f( x );
float g = grad( x );
float de = abs(f)/length(g)
return smoothstep( 0.19, 0.20, de );
}
This method evaluates f four extra times, which makes it more expensive. Besides, choosing the right value for h might be difficult sometimes.
But more often than not, it simply works pretty well. When analytical gradients can be computed, it is strongly recommended to use them instead, like when rasterizing
2D fractals or raymarching mandelbulbs or
julia sets.This method shines for computing distances to 1D functions. In fact, the gradient of f is perpendicular to it's tangent/derivative, so it simplifies to (1, f'), making the distance estimation even simpler: ![]() This is particulary nice to graph 1D functions in tools which scan the xy plane and compute a color from x and y, like Shadertoy.
|