Folding space

One of the really cool things about distance estimation functions is the ability to instantiate multiple instances of an object.

The basic sphere distance evaluator takes the form of a quick distance check from the current location to the centre of the sphere and looks like this:

distance_estimate = magnitude(pos-centre) - radius

To make this into a tiling sphere you just wrap the x and z coordinates around in a box. A really simple way to do this is using a mod instruction like this

pos.x = mod(x+1, 2)-1;
pos.z = mod(z+1, 2)-1;
distance_estimate = magnitude(pos-centre) - radius
Image of repeating spheres rendered with distance estimation

Repeating sphere distance estimator

The full script if you want to reproduce this object…

rule main {
scale = vec(8,8,8)
distanceminimum=0.001
distanceiterations=200
stepsize=1.0
distancefunction(repspheresingle)
distance
}

shader repspheresingle
{
distance = sphere(vec(rep(getx(pos),0.1),
                      rep(gety(pos),0.25),
                      rep(getz(pos),0.15)),
                  vec(0,0,0),
                  0.1)
}

This approach is the one that Inigo Quilez discusses in his excellent reference article about simple distance estimation functions.

It can be pretty hard to get your head around why this works. Within global space you can exist at any coordinate position i.e. you can go to -1000 on the x axis, or +100000 on the x axis. With a distance estimator there’s not much going on at these locations. A sphere estimator of radius 1 only gives values close to zero in the central cube of space. To make the sphere appear again then we take our coordinates outside of this central cube, and map them back inside it.

When a ray is cast, the ray will step in global space, but the sphere distance estimator will work in a local coordinate system defined by the size of the repeating cube.

This gives very cool results for little cost when instantiating simple objects, but it’s not perfect. Have a look at this screenshot of repeating tori.

Raytraced image of repeating tori using distance estimator

Repeating tori with mod()

I’ve added a rotation to this distance estimator so that each instance of the torus is rotated by it’s global x position. As you can see, we’re starting to get some misses on the distance estimation function near the edges of objects, particularly the torus on the lower edge of the image. What’s going on here?

Well, imagine that the mod operation has effectively added a clipping plane into 3d space. As the distance estimator approaches this clip plane it’s possible that on the nearside, there’s a large distance estimate, but on the other side of the clip plane an object exists almost immediately (i.e. we should have a small distance estimate). When we use the larger value to step the ray we skip over the object. The collision point has been missed!

You can improve the look by reducing your step size and increasing the threshold for the distance estimator, but you’ll never quite be able to get rid of the missing pixels when your instantiated object approaches the clip plane.

It’d be tempting to throw the towel in on multi-object instantiation with more complex global functions, but what we’ve discovered here is an interesting aspect of distance estimation functions. For them to work properly they have to be “smooth”. i.e. the distance estimator shouldn’t go from 0.01 to 1.0 if you’ve moved 0.000001 along the ray.
These discontinuities cause the distance estimator to fail, leading to missing pixels when raytracing.

In the case of a simple mod function the discontinuity occurs at the clip plane of each repeat where the coordinates moves from 1 back to 0. The chart below shows this in 1 dimension…

Discontinuous wrapping function

Discontinuous wrapping function

Fortunately there is a way to fix this. Instead of using a pure mod function we can actually fold space around the clip plane. Folding effectively means “mirroring” space around the clip plane. Mathematically it’s not that dissimilar to the standard repeating function shown above, except that we now reflect it around the origin.

value = abs(smod((value + repeat), repeat*4)
                 - repeat*2)
             - repeat;

And instead of having a graph with obvious discontinuities, you now have a sawtooth graph with no harsh discontinuities.

Continuous wrapping function

Continuous wrapping function

When we change our multiple torus render to use the box fold we get a noticeably different result.

Raytracer image of repeating tori using a fold operation

Repeating tori using fold operation

First off you’ll notice that instead of each torus rotating incrementally, they now alternate between rotate left, and rotate right. This might be unwanted, but the really interesting thing is that the torus on the lower edge of the image is now solid!

If you want to replicate the two tori images above you’ll want the following two shaders (and the latest version of wooscripter)

// repeating torus
distance = torus(roty(vec(rep(getx(pos),0.1),
                          gety(pos),
                          rep(getz(pos),0.15)),mul(180,getx(pos))),
                 0.08,
                 0.02)

// folded torus
distance = torus(roty(vec(fold(getx(pos),0.1),
                          gety(pos),
                          rep(getz(pos),0.15)),mul(180,getx(pos))),
                 0.08,
                 0.02)

Once you start thinking about it, there’s a bunch of other smooth functions in 3d that you could use to transform global space back into a local object space, but I’ll mess about with that another time!

You may also like...

3 Responses

  1. revers says:

    Great series of articles! Very clear explanations and interesting ideas! But unfortunately I cannot test the scripts in WooScripter. When I try to compile any script (even default ones) in WooScripter 1.02 I’ve got errors similar to this:
    *ERROR* Line: 1: Unrecognised expression: “1.0”
    *ERROR* Line: 24: Unrecognised expression: “0.5”
    I’m using Windows 7 x64 and .NET 4.5.1

  2. Dom Penfold says:

    What locale is your machine using? I had some issues with non-standard number formats in 1.01 but I thought I’d fixed them. :( Let me know your locale and I’ll look into it again. .NET is trying to be a bit too clever…

  3. revers says:

    I’m using pl_PL locale where decimal separator is “,” instead of “.”. That indeed might be the cause.

Leave a Reply

Your email address will not be published. Required fields are marked *

Spam Protection *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>