Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the Julia set shader example #3467

Merged
merged 13 commits into from
Oct 29, 2023
32 changes: 17 additions & 15 deletions examples/shaders/resources/shaders/glsl100/julia_set.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,30 @@ precision mediump float;
varying vec2 fragTexCoord;
varying vec4 fragColor;

uniform vec2 screenDims; // Dimensions of the screen
uniform vec2 c; // c.x = real, c.y = imaginary component. Equation done is z^2 + c
uniform vec2 offset; // Offset of the scale.
uniform float zoom; // Zoom of the scale.

// NOTE: Maximum number of shader for-loop iterations depend on GPU,
// for example, on RasperryPi for this examply only supports up to 60
const int MAX_ITERATIONS = 48; // Max iterations to do
const int maxIterations = 48; // Max iterations to do.
const float colorCycles = 1.0f; // Number of times the color palette repeats.

// Square a complex number
vec2 ComplexSquare(vec2 z)
{
return vec2(
z.x * z.x - z.y * z.y,
z.x * z.y * 2.0
z.x*z.x - z.y*z.y,
z.x*z.y*2.0f
);
}

// Convert Hue Saturation Value (HSV) color into RGB
vec3 Hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
vec4 K = vec4(1.0f, 2.0f/3.0f, 1.0f/3.0f, 3.0f);
vec3 p = abs(fract(c.xxx + K.xyz)*6.0f - K.www);
return c.z*mix(K.xxx, clamp(p - K.xxx, 0.0f, 1.0f), c.y);
}

void main()
Expand All @@ -45,8 +45,8 @@ void main()

If the number is below 2, we keep iterating.
But when do we stop iterating if the number is always below 2 (it converges)?
That is what MAX_ITERATIONS is for.
Then we can divide the iterations by the MAX_ITERATIONS value to get a normalized value that we can
That is what maxIterations is for.
Then we can divide the iterations by the maxIterations value to get a normalized value that we can
then map to a color.

We use dot product (z.x * z.x + z.y * z.y) to determine the magnitude (length) squared.
Expand All @@ -55,13 +55,15 @@ 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 + offset.x/screenDims.x)*2.5/zoom, (fragTexCoord.y + offset.y/screenDims.y)*1.5/zoom);
vec2 z = vec2((fragTexCoord.x - 0.5f)*2.5f, (fragTexCoord.y - 0.5f)*1.5f)/zoom;
z.x += offset.x;
z.y += offset.y;

int iter = 0;
for (int iterations = 0; iterations < 60; iterations++)
{
z = ComplexSquare(z) + c; // Iterate function
if (dot(z, z) > 4.0) break;
if (dot(z, z) > 4.0f) break;

iter = iterations;
}
Expand All @@ -72,12 +74,12 @@ void main()
z = ComplexSquare(z) + c;

// This last part smooths the color (again see link above).
float smoothVal = float(iter) + 1.0 - (log(log(length(z)))/log(2.0));
float smoothVal = float(iter) + 1.0f - (log(log(length(z)))/log(2.0f));

// Normalize the value so it is between 0 and 1.
float norm = smoothVal/float(MAX_ITERATIONS);
float norm = smoothVal/float(maxIterations);

// If in set, color black. 0.999 allows for some float accuracy error.
if (norm > 0.999) gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
else gl_FragColor = vec4(Hsv2rgb(vec3(norm, 1.0, 1.0)), 1.0);
if (norm > 0.999f) gl_FragColor = vec4(0.0f, 0.0f, 0.0f, 1.0f);
else gl_FragColor = vec4(Hsv2rgb(vec3(norm*colorCycles, 1.0f, 1.0f)), 1.0f);
}
34 changes: 18 additions & 16 deletions examples/shaders/resources/shaders/glsl330/julia_set.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,28 @@ in vec4 fragColor;
// Output fragment color
out vec4 finalColor;

uniform vec2 screenDims; // Dimensions of the screen
uniform vec2 c; // c.x = real, c.y = imaginary component. Equation done is z^2 + c
uniform vec2 offset; // Offset of the scale.
uniform float zoom; // Zoom of the scale.

const int MAX_ITERATIONS = 255; // Max iterations to do.
const int maxIterations = 255; // Max iterations to do.
const float colorCycles = 2.0f; // Number of times the color palette repeats. Can show higher detail for higher iteration numbers.

// Square a complex number
vec2 ComplexSquare(vec2 z)
{
return vec2(
z.x * z.x - z.y * z.y,
z.x * z.y * 2.0
z.x*z.x - z.y*z.y,
z.x*z.y*2.0f
);
}

// Convert Hue Saturation Value (HSV) color into RGB
vec3 Hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
vec4 K = vec4(1.0f, 2.0f/3.0f, 1.0f/3.0f, 3.0f);
vec3 p = abs(fract(c.xxx + K.xyz)*6.0f - K.www);
return c.z*mix(K.xxx, clamp(p - K.xxx, 0.0f, 1.0f), c.y);
}

void main()
Expand All @@ -44,8 +44,8 @@ void main()

If the number is below 2, we keep iterating.
But when do we stop iterating if the number is always below 2 (it converges)?
That is what MAX_ITERATIONS is for.
Then we can divide the iterations by the MAX_ITERATIONS value to get a normalized value that we can
That is what maxIterations is for.
Then we can divide the iterations by the maxIterations value to get a normalized value that we can
then map to a color.

We use dot product (z.x * z.x + z.y * z.y) to determine the magnitude (length) squared.
Expand All @@ -54,14 +54,16 @@ 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 + offset.x/screenDims.x)*2.5/zoom, (fragTexCoord.y + offset.y/screenDims.y)*1.5/zoom);
vec2 z = vec2((fragTexCoord.x - 0.5f)*2.5f, (fragTexCoord.y - 0.5f)*1.5f)/zoom;
z.x += offset.x;
z.y += offset.y;

int iterations = 0;
for (iterations = 0; iterations < MAX_ITERATIONS; iterations++)
for (iterations = 0; iterations < maxIterations; iterations++)
{
z = ComplexSquare(z) + c; // Iterate function

if (dot(z, z) > 4.0) break;
if (dot(z, z) > 4.0f) break;
}

// Another few iterations decreases errors in the smoothing calculation.
Expand All @@ -70,12 +72,12 @@ void main()
z = ComplexSquare(z) + c;

// This last part smooths the color (again see link above).
float smoothVal = float(iterations) + 1.0 - (log(log(length(z)))/log(2.0));
float smoothVal = float(iterations) + 1.0f - (log(log(length(z)))/log(2.0f));

// Normalize the value so it is between 0 and 1.
float norm = smoothVal/float(MAX_ITERATIONS);
float norm = smoothVal/float(maxIterations);

// If in set, color black. 0.999 allows for some float accuracy error.
if (norm > 0.999) finalColor = vec4(0.0, 0.0, 0.0, 1.0);
else finalColor = vec4(Hsv2rgb(vec3(norm, 1.0, 1.0)), 1.0);
if (norm > 0.999f) finalColor = vec4(0.0f, 0.0f, 0.0f, 1.0f);
else finalColor = vec4(Hsv2rgb(vec3(norm*colorCycles, 1.0f, 1.0f)), 1.0f);
}
96 changes: 51 additions & 45 deletions examples/shaders/shaders_julia_set.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
*
* Example originally created with raylib 2.5, last time updated with raylib 4.0
*
* Example contributed by eggmund (@eggmund) and reviewed by Ramon Santamaria (@raysan5)
* Example contributed by Josh Colclough (@joshcol9232) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2019-2023 eggmund (@eggmund) and Ramon Santamaria (@raysan5)
* Copyright (c) 2019-2023 Josh Colclough (@joshcol9232) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/

Expand All @@ -37,17 +37,20 @@ const float pointsOfInterest[6][2] =
{ -0.70176f, -0.3842f },
};

const int screenWidth = 800;
const int screenHeight = 450;
const float zoomSpeed = 1.01f;
const float offsetSpeedMul = 2.0f;

const float startingZoom = 0.75f;

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;

//SetConfigFlags(FLAG_WINDOW_HIGHDPI);
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - julia sets");

// Load julia set shader
Expand All @@ -61,28 +64,22 @@ int main(void)
float c[2] = { pointsOfInterest[0][0], pointsOfInterest[0][1] };

// Offset and zoom to draw the julia set at. (centered on screen and default size)
float offset[2] = { -(float)GetScreenWidth()/2, -(float)GetScreenHeight()/2 };
float zoom = 1.0f;

Vector2 offsetSpeed = { 0.0f, 0.0f };
float offset[2] = { 0.0f, 0.0f };
float zoom = startingZoom;

// Get variable (uniform) locations on the shader to connect with the program
// NOTE: If uniform variable could not be found in the shader, function returns -1
int cLoc = GetShaderLocation(shader, "c");
int zoomLoc = GetShaderLocation(shader, "zoom");
int offsetLoc = GetShaderLocation(shader, "offset");

// Tell the shader what the screen dimensions, zoom, offset and c are
float screenDims[2] = { (float)GetScreenWidth(), (float)GetScreenHeight() };
SetShaderValue(shader, GetShaderLocation(shader, "screenDims"), screenDims, SHADER_UNIFORM_VEC2);

// Upload the shader uniform values!
SetShaderValue(shader, cLoc, c, SHADER_UNIFORM_VEC2);
SetShaderValue(shader, zoomLoc, &zoom, SHADER_UNIFORM_FLOAT);
SetShaderValue(shader, offsetLoc, offset, SHADER_UNIFORM_VEC2);

int incrementSpeed = 0; // Multiplier of speed to change c value
bool showControls = true; // Show controls
bool pause = false; // Pause animation

SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
Expand Down Expand Up @@ -110,42 +107,50 @@ int main(void)
SetShaderValue(shader, cLoc, c, SHADER_UNIFORM_VEC2);
}

if (IsKeyPressed(KEY_SPACE)) pause = !pause; // Pause animation (c change)
if (IsKeyPressed(KEY_F1)) showControls = !showControls; // Toggle whether or not to show controls

if (!pause)
// If "R" is pressed, reset zoom and offset.
if (IsKeyPressed(KEY_R))
{
if (IsKeyPressed(KEY_RIGHT)) incrementSpeed++;
else if (IsKeyPressed(KEY_LEFT)) incrementSpeed--;

// TODO: The idea is to zoom and move around with mouse
// Probably offset movement should be proportional to zoom level
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsMouseButtonDown(MOUSE_BUTTON_RIGHT))
{
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) zoom += zoom*0.003f;
if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) zoom -= zoom*0.003f;

Vector2 mousePos = GetMousePosition();
zoom = startingZoom;
offset[0] = 0.0f;
offset[1] = 0.0f;
SetShaderValue(shader, zoomLoc, &zoom, SHADER_UNIFORM_FLOAT);
SetShaderValue(shader, offsetLoc, offset, SHADER_UNIFORM_VEC2);
}

offsetSpeed.x = mousePos.x -(float)screenWidth/2;
offsetSpeed.y = mousePos.y -(float)screenHeight/2;
if (IsKeyPressed(KEY_SPACE)) incrementSpeed = 0; // Pause animation (c change)
if (IsKeyPressed(KEY_F1)) showControls = !showControls; // Toggle whether or not to show controls

// Slowly move camera to targetOffset
offset[0] += GetFrameTime()*offsetSpeed.x*0.8f;
offset[1] += GetFrameTime()*offsetSpeed.y*0.8f;
}
else offsetSpeed = (Vector2){ 0.0f, 0.0f };
if (IsKeyPressed(KEY_RIGHT)) incrementSpeed++;
else if (IsKeyPressed(KEY_LEFT)) incrementSpeed--;

// If either left or right button is pressed, zoom in/out.
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsMouseButtonDown(MOUSE_BUTTON_RIGHT))
{
// Change zoom. If Mouse left -> zoom in. Mouse right -> zoom out.
zoom *= IsMouseButtonDown(MOUSE_BUTTON_LEFT)? zoomSpeed : 1.0f/zoomSpeed;

const Vector2 mousePos = GetMousePosition();
Vector2 offsetVelocity;
// Find the velocity at which to change the camera. Take the distance of the mouse
// from the center of the screen as the direction, and adjust magnitude based on
// the current zoom.
offsetVelocity.x = (mousePos.x/(float)screenWidth - 0.5f)*offsetSpeedMul/zoom;
offsetVelocity.y = (mousePos.y/(float)screenHeight - 0.5f)*offsetSpeedMul/zoom;

// Apply move velocity to camera
offset[0] += GetFrameTime()*offsetVelocity.x;
offset[1] += GetFrameTime()*offsetVelocity.y;

// Update the shader uniform values!
SetShaderValue(shader, zoomLoc, &zoom, SHADER_UNIFORM_FLOAT);
SetShaderValue(shader, offsetLoc, offset, SHADER_UNIFORM_VEC2);

// Increment c value with time
float amount = GetFrameTime()*incrementSpeed*0.0005f;
c[0] += amount;
c[1] += amount;

SetShaderValue(shader, cLoc, c, SHADER_UNIFORM_VEC2);
}

// Increment c value with time
const float dc = GetFrameTime()*(float)incrementSpeed*0.0005f;
c[0] += dc;
c[1] += dc;
SetShaderValue(shader, cLoc, c, SHADER_UNIFORM_VEC2);
//----------------------------------------------------------------------------------

// Draw
Expand Down Expand Up @@ -178,7 +183,8 @@ int main(void)
DrawText("Press KEY_F1 to toggle these controls", 10, 30, 10, RAYWHITE);
DrawText("Press KEYS [1 - 6] to change point of interest", 10, 45, 10, RAYWHITE);
DrawText("Press KEY_LEFT | KEY_RIGHT to change speed", 10, 60, 10, RAYWHITE);
DrawText("Press KEY_SPACE to pause movement animation", 10, 75, 10, RAYWHITE);
DrawText("Press KEY_SPACE to stop movement animation", 10, 75, 10, RAYWHITE);
DrawText("Press KEY_R to recenter the camera", 10, 90, 10, RAYWHITE);
}
EndDrawing();
//----------------------------------------------------------------------------------
Expand Down