So I made it move. It zooms in on the point (-1.56, 0) but what's interesting is you can really see the fractal nature of the Mandelbrot that you couldn't see from the original. As it zooms in you can start seeing the same shape over and over again.
Zooming was hard at first, but it ended up being simple when I tried not to make it so complicated. The bulk of the work is done here:
vec2 zoom_in_uv(vec2 uv, vec3 S, float zoom_in_point, float zoom) {
uv = translate(uv, vec3(zoom_in_point, vec2(0.0)));
uv = scale(uv, vec3(zoom));
uv = translate(uv, vec3(-zoom_in_point, vec2(0.0)));
return uv;
}
uv = translate(uv, vec3(zoom_in_point, vec2(0.0)));
uv = scale(uv, vec3(zoom));
uv = translate(uv, vec3(-zoom_in_point, vec2(0.0)));
return uv;
}
I translate the image to its original coordinates (I had shifted it a bit to center it on the Mandelbrot), scaled it to the appropriate zoom, and translated it back (explanation on page 25 of these slides).
I encountered an issue while zooming - I was zooming by the same amount every time, but when you zoom in more and more the image seems to zoom much slower (the zoom loses its effectiveness the smaller you get.) So some additional tweaking was needed to speed up the zoom as it went into the image. Taking advantage of the fact that if you take the square root of a positive number less than 1, it gets bigger, then I increased the current zoom by the square root of itself, which does serve to speed up the zoom as it zooms into the image.
My gif recording software sucks so I'll just post pictures.
And here's the source code:
struct complex {
float r;
float i;
};
complex add(complex c1, complex c2) {
return complex(c1.r + c2.r, c1.i + c2.i);
}
complex multiply(complex c1, complex c2) {
complex c_new = complex(0.0, 0.0);
c_new.r = c1.r * c2.r - (c1.i * c2.i);
c_new.i = c1.r * c2.i + (c1.i * c2.r);
return c_new;
}
vec2 translate(vec2 uv, vec3 T) {
return vec2(uv.x + T.x, uv.y + T.y);
}
vec2 scale(vec2 uv, vec3 S) {
return vec2(uv.x * S.x, uv.y * S.y);
}
vec2 create_uv(vec3 S, vec3 T) {
vec2 uv = (gl_FragCoord.xy / iResolution.xy);
uv = scale(uv, S);
uv = translate(uv, -T);
return uv;
}
vec2 zoom_in_uv(vec2 uv, vec3 S, float zoom_in_point, float zoom) {
uv = translate(uv, vec3(zoom_in_point, vec2(0.0)));
uv = scale(uv, vec3(zoom));
uv = translate(uv, vec3(-zoom_in_point, vec2(0.0)));
return uv;
}
bool has_escaped(complex c) {
return abs(c.r) + abs(c.i) >= 4.0;
}
complex mandybrot_eq(complex z, complex c) {
complex z_new = multiply(z, z);
return add(z_new, c);
}
vec3 glow(vec3 col) {
col.z += 0.05*cos(iGlobalTime);
col += pow(cos(iGlobalTime * 0.7),8.0) * 0.1 * vec3(0.8, 0.8, 0.8);
return col;
}
float speed_up_as_we_zoom_in(float zoom) {
float this_gets_bigger_as_zoom_approaches_0 = sqrt(zoom);
float tweak = zoom - this_gets_bigger_as_zoom_approaches_0;
float dont_let_tweak_be_bigger_than_zoom = 2.17;
return dont_let_tweak_be_bigger_than_zoom*zoom + tweak;
}
vec3 create_scaling_vector(float scale, float aspectRatio) {
return vec3(scale*aspectRatio, scale, 0.0);
}
vec3 create_translate_vector(float x_offset, float y_offset) {
return vec3(x_offset, y_offset, 0.0);
}
void main(void)
{
float aspectRatio = iResolution.x / iResolution.y, scale = 2.5;
float x_offset = 0.3 + scale, y_offset = scale/2.0;
vec3 S = create_scaling_vector(scale, aspectRatio);
vec3 T = create_translate_vector(x_offset, y_offset);
vec2 unzoomed_uv = create_uv(S, T);
float zoom = 1.0/mod(iGlobalTime, 10.0);
zoom = speed_up_as_we_zoom_in(zoom);
vec2 uv = zoom_in_uv(unzoomed_uv, S, 1.56, zoom*0.1);
complex z = complex(uv.x, uv.y);
complex c = complex(uv.x, uv.y);
vec3 col = vec3(0.0);
const float iterations = 111.0;
for (float i = 0.0; i < iterations; i++) {
z = mandybrot_eq(z, c);
if (has_escaped(z)) {
col += vec3( i/iterations, 0.0, i/iterations);
col = glow(col);
break;
}
}
gl_FragColor = vec4(col, 1.0);
}
float r;
float i;
};
complex add(complex c1, complex c2) {
return complex(c1.r + c2.r, c1.i + c2.i);
}
complex multiply(complex c1, complex c2) {
complex c_new = complex(0.0, 0.0);
c_new.r = c1.r * c2.r - (c1.i * c2.i);
c_new.i = c1.r * c2.i + (c1.i * c2.r);
return c_new;
}
vec2 translate(vec2 uv, vec3 T) {
return vec2(uv.x + T.x, uv.y + T.y);
}
vec2 scale(vec2 uv, vec3 S) {
return vec2(uv.x * S.x, uv.y * S.y);
}
vec2 create_uv(vec3 S, vec3 T) {
vec2 uv = (gl_FragCoord.xy / iResolution.xy);
uv = scale(uv, S);
uv = translate(uv, -T);
return uv;
}
vec2 zoom_in_uv(vec2 uv, vec3 S, float zoom_in_point, float zoom) {
uv = translate(uv, vec3(zoom_in_point, vec2(0.0)));
uv = scale(uv, vec3(zoom));
uv = translate(uv, vec3(-zoom_in_point, vec2(0.0)));
return uv;
}
bool has_escaped(complex c) {
return abs(c.r) + abs(c.i) >= 4.0;
}
complex mandybrot_eq(complex z, complex c) {
complex z_new = multiply(z, z);
return add(z_new, c);
}
vec3 glow(vec3 col) {
col.z += 0.05*cos(iGlobalTime);
col += pow(cos(iGlobalTime * 0.7),8.0) * 0.1 * vec3(0.8, 0.8, 0.8);
return col;
}
float speed_up_as_we_zoom_in(float zoom) {
float this_gets_bigger_as_zoom_approaches_0 = sqrt(zoom);
float tweak = zoom - this_gets_bigger_as_zoom_approaches_0;
float dont_let_tweak_be_bigger_than_zoom = 2.17;
return dont_let_tweak_be_bigger_than_zoom*zoom + tweak;
}
vec3 create_scaling_vector(float scale, float aspectRatio) {
return vec3(scale*aspectRatio, scale, 0.0);
}
vec3 create_translate_vector(float x_offset, float y_offset) {
return vec3(x_offset, y_offset, 0.0);
}
void main(void)
{
float aspectRatio = iResolution.x / iResolution.y, scale = 2.5;
float x_offset = 0.3 + scale, y_offset = scale/2.0;
vec3 S = create_scaling_vector(scale, aspectRatio);
vec3 T = create_translate_vector(x_offset, y_offset);
vec2 unzoomed_uv = create_uv(S, T);
float zoom = 1.0/mod(iGlobalTime, 10.0);
zoom = speed_up_as_we_zoom_in(zoom);
vec2 uv = zoom_in_uv(unzoomed_uv, S, 1.56, zoom*0.1);
complex z = complex(uv.x, uv.y);
complex c = complex(uv.x, uv.y);
vec3 col = vec3(0.0);
const float iterations = 111.0;
for (float i = 0.0; i < iterations; i++) {
z = mandybrot_eq(z, c);
if (has_escaped(z)) {
col += vec3( i/iterations, 0.0, i/iterations);
col = glow(col);
break;
}
}
gl_FragColor = vec4(col, 1.0);
}
I know some of my variable names are pretty dumb (dont_let_tweak_be_bigger_than_zoom) but they help me keep track of what's happening thankyouverymuch.