Isometric 3d Fractals

Isometric 3d Fractals

Let's do something that I don't think anyones done before. Let's draw them isometrically. That is, we are usually trying to make every pixel correct, and frustrated by the fact that there is this space between the pixels that we don't know. So our life is zooming into those gaps to see whats there.

But let's make those pixels bigger. Let's make them 3d cubes (which when we render them will take up more then one pixel). And then let's see what we have. Firstly, we need to change our calculation to instead of generating images, we need to calculate, for a 3d position, what the colour will be. Secondly we need to introduce an alpha value (so that we don't just see the outside of our render). And lastly. Render it.

Introducing a 3rd variable into,  the formula: as per the video on Mandlebrots (here), the basic iterative formula we're using is;

def frac_one(z, c):
    return z*z + c

    
def calc_iterations(location, c limit=10, circle=2):
    if abs(location) > circle:
        return 0
    
    for i in range(limit):
        location = frac_one(location, c)
    
        if abs(location)>circle:
            break
    return(i+1)

Which produces the famous Mandlebrot (or Julia) shape, given a shifting location, and a constant of (0,0) (or (0,1) for the Julia).

Now we could pass in the constant as the 3rd dimension, potentially shifting it slowly away from 0, but, I actually wanted to play with the powers, so a quick alter to make the maths like this:

def frac_one(z, c, h):
    return z**h + c
    
def calc_iterations(location, c, h, limit=10, circle=2):
    if abs(location) > circle:
        return 0
    
    for i in range(limit):
        location = frac_one(location, c, h)
    
        if abs(location)>circle:
            break
    return(i+1)

So this means we're drawing the picture of z2 + c or z3 + c - or ... z2/3 + c... because ... you don't have to raise to an integer power. Now, I'm not going to dive into the mind blowing revelation of that (but its a simple as a raising to a power of 0-1 gives you effectively a root (i.e. 21/2 is actually root(2)).

So, lets do this, lets make a slicing picture of some fractals - now in this I'm going to start at a power of 2 (so the first picture is the default Mandlebrot), Initially I'm slicing by an arbitrary amount, but then finally by the same interval that is between each pixel going left-right or up-down on the image. Then I'm going to lay these next to each other like slices. So what starts as a 4x4 image pixel will become a 16x16 image to give us 256 slices (i.e. the width of the sample image I'm making).

Julia Slices

Julia was the first image that I generated because, I got the formula backwards again.

Mandlebrot slices

With the Mandlebrot slices, there is something slightly Disturbing about non integer powers (in my opinion).

Decreasing the slice interval

To start on 2 ... or not?

So why start on 2? Basically, its just so the first image is recognisable as the Mandlebrot, but theres no reason to. We could just start on 0 - we can't start on any negative number, but we can start on 0, and we get the following output:

Note the numbers 0-1 inclusive give the most boring, non-fractal images, but the moment we breach that 1 value, it goes fractal.

Now we have a way of getting a value for any 3d coordinate (which gives meaningful values at least in the range (0-2, 0-2, +ve)) we can now think about rendering this as an isometric display. Firstly, let's build a static isometric cube, and program that in so we can render just one cube of a value.

To make life easier, I've statically programmed function to generate a cube image in RGBA, which then gets 'pasted' into place.

So we can then start running some quick tests to make sure the render'er makes sense of it, including some alpha value tests: Note these are going to be incredibly blown up because each 'pixel' is 4 pixels wide, before showing a better picture of a randomly coloured 'floor', and then a 3d square.

To work out the location of a isometric block on a 2d picture is done in one of two ways: the hard way, which also gets it wrong because of floating point error and rounding on the sin and cos function, or the easy way.

The easy way is to know that you can treat each position as a vector - so if you know the coordinate of the far back block, the one to its 'right' is going to be down a bit, and right a bit. So if you want to go 5 blocks 'right', you go down 5*bits and right 5*bits.

To go 'down', its always left a bit, and down a bit. So to go 6 blocks down, its 6*left a bit, and 6* down a bit. ... Then to go to a coordinate (i.e. (5,6) you combine both these things.

Stolen from Clint Bellanger... see his website http://clintbellanger.net/articles/isometric_math/

I realise that doesn't make much sense, so go check out: http://clintbellanger.net/articles/isometric_math/ which explains it better, and very accurately. The important thing here is that this will give the right answer every time where as doing sin and cos will always introduce some error. Enough to make weird pixel glitches.

Its worth noting here that the biggest cube on the above render is only 16x16x16 i.e. 4096 sub-images loaded and rendered over. When we move onto the target range of 256x256x256 thats 16.7 million sub-images. Perhaps this could be optimised slightly so as to change the pixels directly and not use the paste (which does the alpha blending for us)...

So. Lets throw in the first fractal ever made, on 16x16 blocks:

Needless to say, I was disappointed. I was expecting to see... spheres, with more translucent on the alpha layering. More stained glass and less fog. After a bit of tinkering, I got rid of the alpha layers and are only now showing escape-or-not at 10 iterations, and I also rotated the direction so that some of the detail could be seen.

The worlds ugliest fractal rendering system

Congratulations. An award just for me. The reality is, this is exactly what it should be. If you look at the 'brain slices' we see that for each number  there is a point on the left, and that point then splits in two as we increase the values (or a new point emerges - and thats what we're seeing here on this render - the point s sweeping up into 180 (height = 3) and then round to the back, and new points forming and then sweeping round again.

The question is, is this because of the formula - does it look 'cool' for other calculations, for formulas actually designed for a 3d coordinate rather then shoehorning it in like I did. I was kind of hoping to see spheres appear, but in retrospect if I had thought about it more, I would have not had that expectation. The second problem is the alpha layering - the number of tiny blocks removes any kind of feeling of 'stained glass' and instead just makes it look like a murky pool:

I think to solve this, it needs the alpha to be non linear. This was an escape-at-10 sort of figure, so the alpha values were multiples of 25.5, and I think having the lower values all be really low alpha, like in the region of 0-10 that would help. Or, as I mentioned above, changing how I stack the images and calculate an individual 'blocks' colour might be better.

Mastodon