In my last post I talked about how to convert the koch snowflake fractal into an iterative distance estimator. That can provide some interesting results for a simple 2d fractal, but now let’s ramp this up into three dimensions and introduce Kaleidoscopic IFS fractals! First off, a big bit of recognition to Knighty who came up with this approach on fractalforums a few years back (link in the footer!). To start with we’re going to derive an approximate distance estimator for the sierpinski pyramid.

The sierpinski pyramid is easiest to describe as a 2d shape before extending into 3 dimensions. Take a piece of paper and draw an equilateral triangle. Now mark the midpoint of each edge and connect the midpoints to form another equialateral triangle. This centre triangle is the hole.

Now for each of the three triangles on the edge, mark the midpoints and draw an equilaterial triangle. Again the middle is the hole, and you can iterate to each of the edge triangles.

In three dimensions we do this same operation using a tetrahedron as the basic primitive instead, sneak a peak below if you want to know what this looks like!

For a tetrahedron there are three lines of symmetry required to fold space back to a single point, but in keeping with Knighty’s original algorithm we’ll use a sphere primitive for the final DE calculation. These three lines of symmetry come from the centre point and move along the normals (1,1,0) (1,0,1) and (0,1,1). If we use these three lines of symmetry to fold space back on itself and then do a sphere distance estimate we get the following simple render…

shader tetra { diff = pos diff = singlefold3d(diff, vec(0,0,0), vec(1,1,0)) diff = singlefold3d(diff, vec(0,0,0), vec(1,0,1)) diff = singlefold3d(diff, vec(0,0,0), vec(0,1,1)) distance = sphere(diff, vec(0.5,0.5,0.5), 1) }

Now we need to iterate the fractal by replacing each of the spheres with another instance of the pattern. To do this we scale by 2, and subtract the unit vector to move the shape back into the location of the original sphere above, remembering to divide the distance returned by 2.

shader tetra { diff = pos diff = diff*vec(2,2,2)-vec(1,1,1) diff = singlefold3d(diff, vec(0,0,0), vec(1,1,0)) diff = singlefold3d(diff, vec(0,0,0), vec(1,0,1)) diff = singlefold3d(diff, vec(0,0,0), vec(0,1,1)) distance = sphere(diff, vec(0.5,0.5,0.5), 1)/2 }

Now we can iterate the folds again before the scale, and hey presto, iteration 2 of the sierpinski pyramid.

shader tetra { diff = pos diff = singlefold3d(diff, vec(0,0,0), vec(1,1,0)) diff = singlefold3d(diff, vec(0,0,0), vec(1,0,1)) diff = singlefold3d(diff, vec(0,0,0), vec(0,1,1)) diff = diff*vec(2,2,2)-vec(1,1,1) diff = singlefold3d(diff, vec(0,0,0), vec(1,1,0)) diff = singlefold3d(diff, vec(0,0,0), vec(1,0,1)) diff = singlefold3d(diff, vec(0,0,0), vec(0,1,1)) distance = sphere(diff, vec(0.5,0.5,0.5), 1)/2 }

To save an enormous amount of copying and pasting I’ve constructed a helper function that implements this algorithm many times called kaleido(). The same image above can be constructed using this shader…

shader kal { distance = kaleido(pos, vec(0,0,0), vec(0,0,0), vec(1,1,1), 2, 2) }

And it’s also possible to easily ramp up to 15 iterations of the pyramid without C&P’ing enormous numbers of times, and now you’ll understand why we don’t need to worry about the basic primitive of our fractal being a sphere!

shader kal { distance = kaleido(pos, vec(0,0,0), vec(0,0,0), vec(1,1,1), 15, 2) }

This basic form of the shader above doesn’t use any of the fun tweaks which were originally suggested by Knighty. First up we have the first two vectors. These are rotations which are used before or after the folding. They give slightly different effects, the following two images show pre-rotations.

shader kal { distance = kaleido(pos, vec(1,0,0), vec(0,0,0), vec(1,1,1), 15, 2) }

shader kal { distance = kaleido(pos, vec(0,1,0), vec(0,0,0), vec(1,1,1), 15, 2) }

The third unit vector is the vector that is used to offset each iteration, tweaking here can give some nice effects. The final number is the scale factor, and again you can create some interesting variants using these values.

shader kal { distance = kaleido(pos, vec(0.4,0,0), vec(0.4,0,0), vec(1.2,1,1), 25, 1.8) }

Next up we need to think about how to colour these IFS fractals. Using a plain colour shows the detail of the shape nicely, but it’s a bit boring to look at, particularly if you’ve seen some of the fancier renders that people have produced using other fractal software.

The key to colouring an object is to come up with a value which is in the range 0->1. Once we have that value we can use it to index into a colour ramp and produce some pretty renders. With a kaleidoscopic IFS fractal there is no possibility of using something like an iteration count because all elements of the object will have been iterated the same amount. Instead we can use the number of folds that were used to get to each point of the fractal.

Note that we have 3 folds for the tetrahedron. We would expect the average random point in space to be either side of the fold 50% of the time as it seperates space into two equal halves.

So for each sample, we take the number of folds on each axis and subtract half the number of iterations. This gives us a number between -iterations/2 and iterations/2. We now take the absolute value of this number as we don’t care if you had less than half or more than half, it’s the total deviation from the mean that we’ll use to colour the fractal. Once we’ve done this we scale the value by dividing through by the iteration count, leaving us with a value in the range 0 to 0.5. Next we can sum the three values to give a number in the range 0 to 1.5. We now divide by 1.5 to get a value between 0 and 1. Or in code…

// acc is the number of folds on each axis // i is the number of iterations // fabsf returns the absolute value of the arg acc[0] = fabsf(acc[0]-float(i)*0.5f)/float(i); acc[1] = fabsf(acc[1]-float(i)*0.5f)/float(i); acc[2] = fabsf(acc[2]-float(i)*0.5f)/float(i); float ret = (acc[0] + acc[1] + acc[2]) / 1.5f;

With flat ambient lighting we can now see that this is adding some detail into the fractal, even with a flat white->black colour ramp

To use this colour algorithm in wooscripter I’ve added a second version of the kaleido function which returns only the floating point colour. Once a collision point is found we then run this alternate version as a materialfunction to calculate the colour. The code for this rule now looks like this…

rule main { distancefunction(kal) materialfunction(kalcol) distance } shader kal { distance = kaleido(pos, vec(-0.3,0.1,-0.4), vec(0.3,0.2,0.1), vec(1.2,1,1), 18, 1.8) } shader kalcol { diff = kaleidocol(opos, vec(-0.3,0.1,-0.4), vec(0.3,0.2,0.1), vec(1.2,1,1), 18, 1.8) diff = lerp(vec(1,1,1), vec(0,0,0), diff.x) }

The only thing with the render above that’s not great is it’s general greyness. This is due to the number of folds being normally distributed around the median value. So as we add up the total number of folds it’s very unlikely we’ll get 0, or 1, on the output number. It’s far more likely we’ll get 0.5. To increase the contrast of the colours it’s helpful to remap the 0->1 space so that we get more detail from the 0.5 range. The following line of code will do that nicely…

diff = lerp(vec(1,1,1), vec(0,0,0), pow(1-abs(2*(diff.x-0.5)),4))

Now we can turn the lights back on and start to have a bit more fun with our colouring!

And with that I’m going to wrap up!

For more information about KIFS fractals (and more inspiration) check out the original thread on fractal forums.

For updates and all the latest news on my own progress, and the progress of wooscripter, follow my twitter feed at @dom767.

Awesome! Amazing!

http://i58.fastpic.ru/big/2015/0510/23/1e2dca4d8a5f844bc4b255474f556923.jpg

The next step will coloring orbit Trap?

Glad you like it! I don’t think orbit traps work with IFS functions, but I will be doing orbit trap colouring for the mandelbox and mandelbulb. Next on the list!

Your render looks great btw, particularly with the slice through the fractal, although if you want to remove the slice then set distanceextents=vec(2,2,2).

Hi, Dom!

orbitTrap for IFS fractals works great for fragmentarium, inherent in most raytracer, why not work?

Thanks for the help – now I will cut fractals into pieces))))))

http://i57.fastpic.ru/big/2015/0511/67/3876894758b0e44b43b4f4670d6acf67.jpg

Interesting. I clearly need to do a bit more reading on orbit traps then! My understanding was that orbit traps were used to evaluate fractals which “escape” a certain orbit (i.e. mandel**** fractals, julia sets, etc..). I’ll look into how this works.

Excellent article and excellent website. Thanks for this very clear, straight forward explanation of kaleidoscope fractals and other related topics. I’ve been trying to get my head around these things lately with limited success – most of the information on the web is relatively high-level and seems to require a strong grounding in mathematics/physics and of course 3D programming, whereas you manage to take things back to first principles. Now I just have to read through the details a few more times.. Cheers

Thanks Ben, I’m glad you found it useful. These things are best understood by experimentation to be honest. You can do some of that with wooscripter, but you should also take a look at things like shadertoy. If you try and change an existing script on shadertoy it should help you to understand what the different things do. I’ve done a quick KIFS shader here… https://www.shadertoy.com/view/ltS3W3

Looks great, I’ll have a fiddle with it. Thanks again