# Baroque fractal divide lines

## April 30, 2023

Julia sets make an interesting visual building block that can be combined with other kinds of transforms to create different shapes.

Here they are used to create some baroqueish page divide lines.

These are all going to be images the full width of the page, but not tall.

(Note: all the images here are rendered by your browser using WebGL shader programs. If you want to see the code for those then view the page source)

Here is a basic Julia set at c = -0.7 + 0.0 i

void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); a.x *= 2.0; a.y *= 4.0; vec2 c = vec2(-0.7,0.00); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } float g = clamp(i-90.0,0.0,1.0); gl_FragColor=vec4(vec3(0.0),g); }

This one reminds strongly of the Mandelbrot set (which is like the map of all the Julia sets). The y axis has been squeezed about 2x compared to the x axis, to make it wide. Because c is at zero on the i axis, it is symmetrical rather than skewed.

Moving in the negative direction on the real axis to -1.3 gives a more spread out image.

void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); a.x *= 2.0; a.y *= 4.0; vec2 c = vec2(-1.3,0.00); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } float g = clamp(i-90.0,0.0,1.0); gl_FragColor=vec4(vec3(0.0),g); }

Now we try a trick - transforming the x-axis with an offset and some reflection. This gives us two copies of the main bulge which makes the “line” less heavy in the middle.

void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); a.x *= 3.8; a.y *= 4.0; a.x = 1.5 - abs(a.x); vec2 c = vec2(-1.3,0.00); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } float g = clamp(i-90.0,0.0,1.0); gl_FragColor=vec4(vec3(0.0),g); }

If we move away from zero in the i axis to 0.05, we can break some of the symmetry.

void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); a.x *= 3.8; a.y *= 4.0; a.x = 1.5 - abs(a.x); vec2 c = vec2(-1.3,0.05); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } float g = clamp(i-90.0,0.0,1.0); gl_FragColor=vec4(vec3(0.0),g); }

Solid black is ok, but it might be nice to more contrast and expose more of the edge details. The previous images were showing only the last iteration of the Julia set calculation. However, if we pick a range of iterations to use as the image - here from 13 to 90 - we can create an outline instead of a solid black image.

void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); a.x *= 3.8; a.y *= 4.0; a.x = 1.5 - abs(a.x); vec2 c = vec2(-1.3,0.05); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } float g = clamp(i-13.0,0.0,1.0); g *= clamp(90.0-i,0.0,1.0); gl_FragColor=vec4(vec3(0.0),g); }

This still looks quite similar to a regular Julia set.

If we reflect the y-axis in a similar way to the x-axis earlier, that creates a double line. Note the y-axis is also squeezed again to keep the height similar.

void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); a.x *= 3.8; a.y *= 6.0; a.x = 1.5 - abs(a.x); a.y = 0.2 - abs(a.y); vec2 c = vec2(-1.3,0.05); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } float g = clamp(i-13.0,0.0,1.0); g *= clamp(90.0-i,0.0,1.0); gl_FragColor=vec4(vec3(0.0),g); }

The iterations contain also gradient information. If we analyse that at each point, we can use it to create shading, giving a 3D effect, here with some “gold”.

float julia(vec2 a) { a.x *= 3.8; a.y *= 6.0; a.x = 1.5 - abs(a.x); a.y = 0.2 - abs(a.y); vec2 c = vec2(-1.3,0.05); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } return i; } vec2 normal(vec2 a) { vec2 eps = vec2(0.001,0.0); return (vec2(julia(a+eps.xy)-julia(a+eps-eps.xy), julia(a+eps.yx)-julia(a+eps-eps.yx))); } void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); float i = julia(a); vec2 n = clamp(normal(a),vec2(0.0),vec2(2.0)); float g = clamp(i-10.0,0.0,1.0); vec2 light = vec2(0.7,-0.7); vec3 col = (0.5+0.5*dot(light,n))*mix(vec3(1.0,0.8,0.0),vec3(1.0,0.9,0.3),1.0-g); gl_FragColor=vec4(col*g,g); }

Tweaking the c value just a little finds us some whirly fractal loops.

float julia(vec2 a) { a.x *= 3.8; a.y *= 6.0; a.x = 1.5 - abs(a.x); a.y = 0.2 - abs(a.y); vec2 c = vec2(-1.263,0.05); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } return i; } vec2 normal(vec2 a) { vec2 eps = vec2(0.001,0.0); return (vec2(julia(a+eps.xy)-julia(a+eps-eps.xy), julia(a+eps.yx)-julia(a+eps-eps.yx))); } void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); float i = julia(a); vec2 n = clamp(normal(a),vec2(0.0),vec2(2.0)); float g = clamp(i-10.0,0.0,1.0); vec2 light = vec2(0.7,-0.7); vec3 col = (0.5+0.5*dot(light,n))*mix(vec3(1.0,0.8,0.0),vec3(1.0,0.9,0.3),1.0-g); gl_FragColor=vec4(col*g,g); }

Finally, we can animate c or any other parameters.

float julia(vec2 a) { a.x *= 3.8; a.y *= 6.0; a.x = 1.0 + 0.5*sin(time*0.7) - abs(a.x); a.y = 0.3 + 0.3*sin(time*0.8) - abs(a.y); vec2 c = vec2(-1.1+0.35*cos(time*0.9),0.05*cos(time)); // Calculate julia sets float R = 100.0; vec2 z = vec2(a.x,a.y); float i = 0.0; float m_old = 0.0; float m_new = 0.0; float deep = 1.0; for (int iter=0;iter<100;iter++) { vec2 z2 = vec2(z.x*z.x-z.y*z.y+c.x, +2.0*z.x*z.y+c.y); z = z2; m_new = (z.x*z.x)+(z.y*z.y); if (m_new>(R*R)) { deep = 0.0; float add = log2(log(sqrt(m_new))/log(R))-1.; i -= add; break; } m_old = m_new; i += 1.0; } return i; } vec2 normal(vec2 a) { vec2 eps = vec2(0.001,0.0); return (vec2(julia(a+eps.xy)-julia(a+eps-eps.xy), julia(a+eps.yx)-julia(a+eps-eps.yx))); } void main() { vec2 a = (gl_FragCoord.xy/512. - vec2(1.,.25)); float i = julia(a); vec2 n = clamp(normal(a),vec2(0.0),vec2(2.0)); float g = clamp(i-10.0,0.0,1.0); vec2 light = vec2(0.7,-0.7); vec3 col = (0.5+0.5*dot(light,n))*mix(vec3(1.0,0.8,0.0),vec3(1.0,0.9,0.3),1.0-g); gl_FragColor=vec4(col*g,g); }