diff --git a/examples/Makefile b/examples/Makefile index 083afd92..6b9f5229 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -423,6 +423,7 @@ EXAMPLES = \ shaders/shaders_postprocessing \ shaders/shaders_raymarching \ shaders/shaders_palette_switch \ + shaders/shaders_julia_set \ audio/audio_sound_loading \ audio/audio_music_stream \ audio/audio_module_playing \ diff --git a/examples/shaders/resources/shaders/glsl330/julia_shader.fs b/examples/shaders/resources/shaders/glsl330/julia_shader.fs new file mode 100644 index 00000000..b1331d84 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/julia_shader.fs @@ -0,0 +1,86 @@ +#version 330 + +// Input vertex attributes (from vertex shader) + +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. + +// Output fragment color +out vec4 finalColor; + +const int MAX_ITERATIONS = 255; // Max iterations to do. + +// Square a complex number +vec2 complexSquare(vec2 z) +{ + return vec2( + z.x * z.x - z.y * z.y, + z.x * z.y * 2.0 + ); +} + +// Convert Hue Saturation Value 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); +} + + +void main() +{ + // The pixel coordinates scaled so they are on the mandelbrot scale. + vec2 z = vec2(((gl_FragCoord.x + offset.x)/screenDims.x) * 2.5 * zoom, + ((screenDims.y - gl_FragCoord.y + offset.y)/screenDims.y) * 1.5 * zoom); // y also flipped due to opengl + int iterations = 0; + + /* + Julia sets use a function z^2 + c, where c is a constant. + This function is iterated until the nature of the point is determined. + + If the magnitude of the number becomes greater than 2, then from that point onward + the number will get bigger and bigger, and will never get smaller (tends towards infinity). + 2^2 = 4, 4^2 = 8 and so on. + So at 2 we stop iterating. + + 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 + then map to a color. + + We use dot product (z.x * z.x + z.y * z.y) to determine the magnitude (length) squared. + And once the magnitude squared is > 4, then magnitude > 2 is also true (saves computational power). + */ + for (iterations = 0; iterations < MAX_ITERATIONS; iterations++) + { + z = complexSquare(z) + c; // Iterate function + if (dot(z, z) > 4.0) + { + break; + } + } + + // Another few iterations decreases errors in the smoothing calculation. + // See http://linas.org/art-gallery/escape/escape.html for more information. + z = complexSquare(z) + c; + 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)); + + // Normalize the value so it is between 0 and 1. + float norm = smoothVal/float(MAX_ITERATIONS); + + // 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); + } +} diff --git a/examples/shaders/shaders_julia_set.c b/examples/shaders/shaders_julia_set.c new file mode 100644 index 00000000..381cd33e --- /dev/null +++ b/examples/shaders/shaders_julia_set.c @@ -0,0 +1,213 @@ +/******************************************************************************************* +* +* raylib [shaders] example - Render julia sets using a shader. +* +* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support, +* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version. +* +* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3). +* +* This example has been created using raylib 2.5 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Author: eggmund (https://github.com/eggmund) +* +********************************************************************************************/ + +#include "raylib.h" +#include // For memcpy + +// Speed when using auto +const float AUTO_SPEED = 0.0005; + +// A few good julia sets +const float POINTS_OF_INTEREST[6][2] = +{ + {-0.348827, 0.607167}, + {-0.786268, 0.169728}, + {-0.8, 0.156}, + {0.285, 0.0}, + {-0.835, -0.2321}, + {-0.70176, -0.3842}, +}; + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 1280; + int screenHeight = 720; + + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - julia set renderer"); + + // If julia set is rendered for this frame. + bool rendered = false; + + bool showControls = true; + + // Multiplier of speed to change c value. Set to 3 to start off with. + int incrementSpeed = 3; + + // Offset and zoom to draw the julia set at. (centered on screen and 1.6 times smaller) + float offset[2] = { -(float)screenWidth/2, -(float)screenHeight/2 }; + float zoom = 1.6; + + // c constant to use in z^2 + c + float c[2]; + // Copy a point of interest into the c variable. 4 bytes per float (32 bits). + memcpy(c, &POINTS_OF_INTEREST[0], 8); + + // Load julia set shader + // NOTE: Defining 0 (NULL) for vertex shader forces usage of internal default vertex shader + Shader shader = LoadShader(0, "resources/shaders/glsl330/julia_shader.fs"); + + // Get variable (uniform) location on the shader to connect with the program + // NOTE: If uniform variable could not be found in the shader, function returns -1 + // The location of c will be stored since we will need to change this whenever c changes + int cLoc = GetShaderLocation(shader, "c"); + + // Tell the shader what the screen dimensions, zoom, offset and c are + float screenDims[2] = { (float)screenWidth, (float)screenHeight }; + SetShaderValue(shader, GetShaderLocation(shader, "screenDims"), screenDims, UNIFORM_VEC2); + SetShaderValue(shader, GetShaderLocation(shader, "zoom"), &zoom, UNIFORM_FLOAT); + SetShaderValue(shader, GetShaderLocation(shader, "offset"), offset, UNIFORM_VEC2); + + SetShaderValue(shader, cLoc, c, UNIFORM_VEC2); + + // Create a RenderTexture2D to be used for render to texture + RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight); + + SetTargetFPS(60); // Set the window to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + + // Get input + //---------------------------------------------------------------------------------- + + // Press 1 - 6 to reset c to a point of interest. + if (IsKeyPressed(KEY_ONE) || IsKeyPressed(KEY_TWO) || IsKeyPressed(KEY_THREE) || IsKeyPressed(KEY_FOUR) || IsKeyPressed(KEY_FIVE) || IsKeyPressed(KEY_SIX)) + { + if (IsKeyPressed(KEY_ONE)) + { + memcpy(c, &POINTS_OF_INTEREST[0], 8); + } + else if (IsKeyPressed(KEY_TWO)) + { + memcpy(c, &POINTS_OF_INTEREST[1], 8); + } + else if (IsKeyPressed(KEY_THREE)) + { + memcpy(c, &POINTS_OF_INTEREST[2], 8); + } + else if (IsKeyPressed(KEY_FOUR)) + { + memcpy(c, &POINTS_OF_INTEREST[3], 8); + } + else if (IsKeyPressed(KEY_FIVE)) + { + memcpy(c, &POINTS_OF_INTEREST[4], 8); + } + else if (IsKeyPressed(KEY_SIX)) + { + memcpy(c, &POINTS_OF_INTEREST[5], 8); + } + SetShaderValue(shader, cLoc, c, UNIFORM_VEC2); + rendered = false; // c value has changed, so render the set again. + } + + // Press "r" to stop changing c + if (IsKeyPressed(KEY_R)) + { + incrementSpeed = 0; + } + + // Toggle whether or not to show controls + if (IsKeyPressed(KEY_H)) + { + showControls = !showControls; + } + + // Scroll to change c increment speed. + int mouseMv = GetMouseWheelMove(); // Get the amount the mouse has moved this frame + if (mouseMv != 0) + { + if (IsKeyDown(KEY_LEFT_SHIFT)) + { + incrementSpeed += mouseMv * 10; + } + else + { + incrementSpeed += mouseMv; + } + rendered = false; + } + + if (incrementSpeed != 0) + { + float amount = GetFrameTime() * incrementSpeed * AUTO_SPEED; + c[0] += amount; + c[1] += amount; + + // Update the c value in the shader. + SetShaderValue(shader, cLoc, c, UNIFORM_VEC2); + rendered = false; + } + + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); // Clear the screen of the previous frame. + + // If the c value has changed, redraw the julia set using the shader, onto the render texture. + if (!rendered) + { + BeginTextureMode(target); // Enable drawing to texture + + ClearBackground(BLACK); // Clear the last frame drawn on the texture. + + // Draw a rectangle in shader mode. This acts as a canvas for the shader to draw on. + BeginShaderMode(shader); + DrawRectangle(0, 0, screenWidth, screenHeight, BLACK); + EndShaderMode(); + + EndTextureMode(); + + rendered = true; // The set is now rendered, so do not compute it again until it next changes. + } + + // Draw the saved texture (rendered julia set). + DrawTextureRec(target.texture, (Rectangle){ 0, 0, target.texture.width, target.texture.height }, (Vector2){ 0, 0 }, WHITE); + + // Print information. + DrawText( FormatText("cx: %f\ncy: %f\nspeed: %d", c[0], c[1], incrementSpeed), 10, 10, 20, RAYWHITE ); + + if (showControls) + { + DrawText("Press keys 1 - 6 to change point of interest.", 10, screenHeight - 88, 20, RAYWHITE); + DrawText("Use the scroll wheel to auto increment the c value. Hold shift while scrolling to increase speed by 10.", 10, screenHeight - 66, 20, RAYWHITE); + DrawText("Press 'r' to reset speed.", 10, screenHeight - 44, 20, RAYWHITE); + DrawText("Press 'h' to hide these controls.", 10, screenHeight - 22, 20, RAYWHITE); + } + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadShader(shader); // Unload shader + UnloadRenderTexture(target); // Unload render texture + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/shaders/shaders_julia_set.png b/examples/shaders/shaders_julia_set.png new file mode 100644 index 00000000..5117ed0d Binary files /dev/null and b/examples/shaders/shaders_julia_set.png differ