From d64af0a39f7147795a200d027666dacbb216a395 Mon Sep 17 00:00:00 2001 From: Jordi Santonja Blanes Date: Sat, 18 Oct 2025 11:51:34 +0200 Subject: [PATCH] Simplified shader code and added comments --- .../shaders/glsl100/mandelbrot_set.fs | 39 ++++++++---------- .../shaders/glsl120/mandelbrot_set.fs | 39 ++++++++---------- .../shaders/glsl330/mandelbrot_set.fs | 41 +++++++++---------- examples/shaders/shaders_mandelbrot_set.c | 4 ++ 4 files changed, 55 insertions(+), 68 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs index 78503024f..6867870c1 100644 --- a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs +++ b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs @@ -15,41 +15,34 @@ uniform float zoom; // Zoom of the scale uniform int maxIterations; // Max iterations per pixel const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity - -float modI(float a, float b) { - float m = a - floor((a + 0.5)/b)*b; - return floor(m + 0.5); -} +const float max2 = max * max; // Square of max to avoid computing square root void main() { // The pixel coordinates are scaled so they are on the mandelbrot scale // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom - vec2 z = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; - z.x += offset.x; - z.y += offset.y; - float a = z.x; - float b = z.y; - float absOld = 0.0; - float convergeNumber = float(maxIterations); + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; + + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i int iter = 0; while (iter < maxIterations) { float aa = a*a; float bb = b*b; - float abs = sqrt(aa + bb); - if (abs > max) - { - convergeNumber = float(iter) + (max - absOld)/(abs - absOld); + if (aa + bb > max2) break; - } float twoab = 2.0*a*b; - a = aa - bb + z.x; - b = twoab + z.y; + a = aa - bb + c.x; + b = twoab + c.y; - absOld = abs; ++iter; } @@ -59,9 +52,9 @@ void main() } else { - float normR = modI(convergeNumber, 55.0)/55.0; - float normG = modI(convergeNumber, 69.0)/69.0; - float normB = modI(convergeNumber, 40.0)/40.0; + float normR = float(iter - (iter/55)*55)/55.0; + float normG = float(iter - (iter/69)*69)/69.0; + float normB = float(iter - (iter/40)*40)/40.0; gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); } diff --git a/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs index 6909c7d2a..8465befb3 100644 --- a/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs +++ b/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs @@ -13,41 +13,34 @@ uniform float zoom; // Zoom of the scale uniform int maxIterations; // Max iterations per pixel const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity - -float modI(float a, float b) { - float m = a - floor((a + 0.5)/b)*b; - return floor(m + 0.5); -} +const float max2 = max * max; // Square of max to avoid computing square root void main() { // The pixel coordinates are scaled so they are on the mandelbrot scale // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom - vec2 z = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; - z.x += offset.x; - z.y += offset.y; - float a = z.x; - float b = z.y; - float absOld = 0.0; - float convergeNumber = float(maxIterations); + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; + + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i int iter = 0; while (iter < maxIterations) { float aa = a*a; float bb = b*b; - float abs = sqrt(aa + bb); - if (abs > max) - { - convergeNumber = float(iter) + (max - absOld)/(abs - absOld); + if (aa + bb > max2) break; - } float twoab = 2.0*a*b; - a = aa - bb + z.x; - b = twoab + z.y; + a = aa - bb + c.x; + b = twoab + c.y; - absOld = abs; ++iter; } @@ -57,9 +50,9 @@ void main() } else { - float normR = modI(convergeNumber, 55.0)/55.0; - float normG = modI(convergeNumber, 69.0)/69.0; - float normB = modI(convergeNumber, 40.0)/40.0; + float normR = float(iter - (iter/55)*55)/55.0; + float normG = float(iter - (iter/69)*69)/69.0; + float normB = float(iter - (iter/40)*40)/40.0; gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); } diff --git a/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs index abb3b85ce..3bbec8701 100644 --- a/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs +++ b/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs @@ -14,47 +14,44 @@ uniform float zoom; // Zoom of the scale uniform int maxIterations; // Max iterations per pixel const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity +const float max2 = max * max; // Square of max to avoid computing square root void main() { // The pixel coordinates are scaled so they are on the mandelbrot scale // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom - vec2 z = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; - z.x += offset.x; - z.y += offset.y; - float a = z.x; - float b = z.y; - float absOld = 0.0; - float convergeNumber = float(maxIterations); + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; - int iterations = 0; - for (iterations = 0; iterations < maxIterations; iterations++) + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i + + int iter = 0; + for (iter = 0; iter < maxIterations; ++iter) { float aa = a*a; float bb = b*b; - float abs = sqrt(aa + bb); - if (abs > max) - { - convergeNumber = float(iterations);// + (max - absOld)/(abs - absOld); + if (aa + bb > max2) break; - } float twoab = 2.0*a*b; - a = aa - bb + z.x; - b = twoab + z.y; - - absOld = abs; + a = aa - bb + c.x; + b = twoab + c.y; } - if (iterations >= maxIterations) + if (iter >= maxIterations) { finalColor = vec4(0.0, 0.0, 0.0, 1.0); } else { - float normR = float(int(convergeNumber)%55)/55.0; - float normG = float(int(convergeNumber)%69)/69.0; - float normB = float(int(convergeNumber)%40)/40.0; + float normR = float(iter%55)/55.0; + float normG = float(iter%69)/69.0; + float normB = float(iter%40)/40.0; finalColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); } diff --git a/examples/shaders/shaders_mandelbrot_set.c b/examples/shaders/shaders_mandelbrot_set.c index 67569c428..39f09101f 100644 --- a/examples/shaders/shaders_mandelbrot_set.c +++ b/examples/shaders/shaders_mandelbrot_set.c @@ -68,6 +68,8 @@ int main(void) // Offset and zoom to draw the mandelbrot set at. (centered on screen and default size) float offset[2] = { startingOffset[0], startingOffset[1] }; float zoom = startingZoom; + // Depending on the zoom the mximum number of iterations must be adapted to get more detail as we zzoom in + // The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys int maxIterations = 333; float maxIterationsMultiplier = 166.5f; @@ -160,9 +162,11 @@ int main(void) updateShader = true; } + // In case a parameter has been changed, update the shader values if (updateShader) { // As we zoom in, increase the number of max iterations to get more detail + // Aproximate formula, but it works-ish maxIterations = (int)(sqrtf(2.0f*sqrtf(fabsf(1.0f - sqrtf(37.5f*zoom))))*maxIterationsMultiplier); // Update the shader uniform values!