I would like to know the magic behind this tiny fractal shader; I didn't really understand the code and the mentioned thread...

Is there an accessible explaination?



Here is a line by line breakdown of my shader:

#define mainImage(o,p)\
    for( vec2 r = iResolution.xy, q = 2.*(p+p-r)/r.y; o.a++ < 30.; )\
        o += length( q = abs(q)/dot(q,q) - iMouse.xy/r) / 2e2

First, it is important to know that the “\” at the end of the first two lines do not count towards the character count and merely serve to make the code more readable by splitting it onto multiple lines.

#define mainImage(o,p)

Begins the definition of the function which would normally be written void mainImage(out fragColor, in fragCoord){...} This saves a few characters.

The first part of the loop:

vec2 r = iResolution.xy, q = 2.*(p+p-r)/r.y;

Establishes two variables of type Vector2:

r is the resolution which allows me to refer to iResolution several times later in fewer characters.

q is the coordinate in the fractal which is calculated from the fragCoord p and is then iterated in each loop.

Instead of fragCoord starting at (0,0) in the bottom left corner and extending to the (r.x,r.y) in the top right, (p+p-r) transforms each fragCoord so that the center of the screen is (0,0). This doubles the fragCoord value and then subtracts the resolution of the window. The division by r.y fits the coordinates such that the top has a y value of 1 and the bottom has a y value of -1. With this, the x is scaled accordingly without stretching. Finally, the multiplication by 2 just zooms out by a factor of 2.

Then for the iterator, I use o.a (the alpha channel of o). o is the output color which is a vec4. As defined by Shadertoy, o begins with the value vec4(0,0,0,1). I use this so that I do not need to use characters declaring a new float as an iterator. o.a++<30 combines the test expression and the iterator in the for loop.

The content of the loop is one line where a number gets added to each channel of the output color. This line:

o += length( q = abs(q)/dot(q,q) - iMouse.xy/r) / 2e2

Can be broken down into a few parts. Each iteration of the coordinates in the fractal occurs here:

q = abs(q)/dot(q,q) - iMouse.xy/r

Where iMouse.xy/r is a way to transform mouse coordinates from pixel to the range (0,0) to (1,1)

Each iteration of this function changes the value of q to be used.

What produces this fractal is the idea that some initial values for q will escape to infinity and others will converge or cycle around a few small values. Each iteration I increase the lightness of the pixel based how far q is from (0,0). This length of q is then divided by the constant 2e2 (200) which is to prevent o.rgb from increasing too rapidly.

The contents of the loop could be understood as two steps:

q = abs(q)/dot(q,q) - iMouse.xy/r;
o += length(q) / 2e2;

In the end, even if o contains a value in one of its channels that is greater than 1, it gets clamped by Shadertoy automatically, and a trailing semi-colon is not needed on the last line.

  • $\begingroup$ Great! How did you find this formula abs(q)/dot(q,q) - iMouse.xy/r? Is it empirical research or do you have a method to find formulas which have good properties? $\endgroup$ – arthur.sw Oct 23 '18 at 21:20
  • 1
    $\begingroup$ @arthur.sw I found this formula here: link, and then adapted it to work for my shader. $\endgroup$ – mathmasterzach Oct 24 '18 at 1:57
  • 1
    $\begingroup$ If you're interested in code golfing techniques, we also have a site for that... They post challenges for competition, but also accept questions tagged "tips" for advice on golfing in general or reducing a specific piece of code. $\endgroup$ – trichoplax Oct 24 '18 at 22:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.