From e5a730a32dfa3d43d2d55ff3d8b7a1c8bfcab0f7 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sat, 18 Jan 2025 11:05:46 +0000 Subject: [PATCH] Parameterise some bits for cool animations and make fullscreen --- build.sh | 2 +- config.h | 24 ++++++++++++++ frag_shader.glsl | 85 +++++++++++++++++++++++++++++++++++++++++------- main.c | 78 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 config.h diff --git a/build.sh b/build.sh index bd976e6..ffbf1f4 100755 --- a/build.sh +++ b/build.sh @@ -2,4 +2,4 @@ set -e glslc -fshader-stage=vert vert_shader.glsl -o vert_shader.spv glslc -fshader-stage=frag frag_shader.glsl -o frag_shader.spv -clang -std=c11 -pedantic -Wall -Wextra -lglfw -lvulkan main.c -o gpu-fractal +clang -std=c11 -pedantic -Wall -Wextra -lglfw -lm -lvulkan main.c -o gpu-fractal diff --git a/config.h b/config.h new file mode 100644 index 0000000..9e4e4bf --- /dev/null +++ b/config.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Camden Dixie O'Brien + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#define MAXITER 1000 +#define SS 9 + +//#define JULIA +//#define JULIA_C_RE 0.285 +//#define JULIA_C_IM 0.010 + +#define JULIA +#define JULIA_C_RE 0.300 +#define JULIA_DTHETA 0.00001 +#define ZOOMRATE 0.0004 + +//#define CENTRE_RE -0.743643887 +//#define CENTRE_IM 0.131825904 +//#define ZOOMRATE 0.001 + +//#define CENTRE_RE -0.900000000 +//#define POWRATE 0.0001 +//#define ZOOMRATE 0.0001 diff --git a/frag_shader.glsl b/frag_shader.glsl index c3a56db..81dc384 100644 --- a/frag_shader.glsl +++ b/frag_shader.glsl @@ -5,49 +5,110 @@ #version 450 -#define MAXITER 1000 +#include "config.h" + +#define NCOLS 8 layout(location = 0) out vec4 out_colour; layout(push_constant) uniform Constants { vec2 img_size; - vec2 shift; - float zoom; +#ifdef JULIA + vec2 julia; +#endif + vec2 centre; + float scale; +#ifdef POWRATE + float zpow; +#endif } params; -vec2 ss_offsets[4] = vec2[]( +vec3 incol = vec3(0.000000, 0.014444, 0.027321); + +vec3 palette[NCOLS] = vec3[]( + vec3(0.000000, 0.049707, 0.107023), + vec3(0.025187, 0.064803, 0.177888), + vec3(0.254152, 0.080220, 0.274677), + vec3(0.502886, 0.080220, 0.278894), + vec3(1.000000, 0.124772, 0.119538), + vec3(1.000000, 0.234551, 0.030713), + vec3(1.000000, 0.381326, 0.000000), + vec3(1.000000, 0.651406, 0.215861) +); + +#if SS == 4 +vec2 ss_offsets[SS] = vec2[]( vec2(-0.25, -0.25), vec2( 0.25, -0.25), vec2(-0.25, 0.25), vec2( 0.25, 0.25) ); +#elif SS == 9 +vec2 ss_offsets[SS] = vec2[]( + vec2(-0.33, -0.33), vec2(0.0, -0.33), vec2(0.33, -0.33), + vec2(-0.33, 0.0), vec2(0.0, 0.0), vec2(0.33, 0.0), + vec2(-0.33, 0.33), vec2(0.0, 0.33), vec2(0.33, 0.33) +); +#else +#error "Unsupported supersampling count" +#endif vec2 maptoz(vec2 xy) { - return params.zoom * ((xy / params.img_size.xy) * 4.0 - 2.0) + params.shift; + float aspect = params.img_size.x / params.img_size.y; + vec2 z = 4.0 * (xy / params.img_size.xy - 0.5); + z.x *= aspect; + return params.scale * z + params.centre; } -float mandelbrot(vec2 c) { +vec2 zpow(vec2 z, float p) { + float r = length(z); + float theta = atan(z.y, z.x); + float mag = pow(r, p); + float phi = p * theta; + return mag * vec2(cos(phi), sin(phi)); +} + +int fractal(vec2 c) { +#ifdef JULIA + vec2 z = c; + c = params.julia; +#else vec2 z = vec2(0.0, 0.0); +#endif for (int i = 0; i < MAXITER; ++i) { +#ifdef POWRATE + z = zpow(z, params.zpow) + c; +#else z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; +#endif if (length(z) > 2.0) - return float(i) / float(MAXITER); + return i; } - return 0.0; + return -1; } -vec3 colour(float i) { - return vec3(i, i * i, i * i * i); +vec3 colour(int i) { + if (i == -1) { + return incol; + } else { + float c = float(NCOLS) * sqrt(float(i) / float(MAXITER)); + int fc = int(floor(c)); + int cc = int(ceil(c)); + float p = c - float(fc); + p = smoothstep(0.0, 1.0, p); + return mix(palette[fc], palette[cc], p); + } } void main() { vec2 c; vec3 col = vec3(0.0, 0.0, 0.0); - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < SS; ++i) { c = maptoz(gl_FragCoord.xy + ss_offsets[i]); - col += colour(mandelbrot(c)); + col += colour(fractal(c)); } + col /= SS; out_colour = vec4(col, 1.0); } diff --git a/main.c b/main.c index b6007ca..f26a460 100644 --- a/main.c +++ b/main.c @@ -5,8 +5,11 @@ #define GLFW_INCLUDE_VULKAN +#include "config.h" + #include #include +#include #include #include #include @@ -21,14 +24,22 @@ #define PRESENT_MODE_BUFFER_SIZE 8 #define SWAPCHAIN_IMG_BUFFER_SIZE 4 -#define MAX_SHADER_SIZE (4 * 1024) +#define MAX_SHADER_SIZE (8 * 1024) typedef struct { float width, height; +#ifdef JULIA struct { - float x, y; - } shift; - float zoom; + float re, im; + } julia; +#endif + struct { + float re, im; + } centre; + float scale; +#ifdef POWRATE + float zpow; +#endif } params_t; static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; @@ -62,14 +73,18 @@ int main(void) glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - GLFWwindow *window - = glfwCreateWindow(900, 900, "GPU Fractal", NULL, NULL); + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode *mode = glfwGetVideoMode(monitor); + GLFWwindow *window = glfwCreateWindow( + mode->width, mode->height, "GPU Fractal", monitor, NULL); + /* GLFWwindow *window */ + /* = glfwCreateWindow(900, 900, "GPU Fractal", NULL, NULL); */ assert(window); // Initialise Vulkan instance. const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pApplicationName = "Vulkan Test", + .pApplicationName = "GPU Fractal", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), @@ -461,16 +476,41 @@ int main(void) result = vkCreateFence(dev, &fence_config, NULL, &in_flight); assert(result == VK_SUCCESS); + // Prepare parameters params_t params = { .width = (float)swapchain_extent.width, .height = (float)swapchain_extent.height, - .shift = { - .x = -0.743643887037158704752191506114774, - .y = 0.131825904205311970493132056385139, +#ifdef JULIA + .julia = { +#ifdef JULIA_C_RE + .re = JULIA_C_RE, +#endif +#ifdef JULIA_C_IM + .im = JULIA_C_IM, +#endif }, - .zoom = 1.0, +#endif +#if defined(CENTRE_RE) || defined(CENTRE_IM) + .centre = { +#ifdef CENTRE_RE + .re = CENTRE_RE, +#endif +#ifdef CENTRE_IM + .im = CENTRE_IM, +#endif + }, +#endif + .scale = 1.0, +#ifdef POWRATE + .zpow = 2.0, +#endif }; +#ifdef JULIA_DTHETA + const double dtheta = JULIA_DTHETA; + const double complex rotz = cexp(I * dtheta); +#endif + while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -548,8 +588,20 @@ int main(void) result = vkQueuePresentKHR(queue, &present_info); assert(result == VK_SUCCESS); - // Increment zoom - params.zoom *= 0.99; +#ifdef JULIA_DTHETA + double complex julia = params.julia.re + I * params.julia.im; + julia *= rotz; + params.julia.re = (float)creal(julia); + params.julia.im = (float)cimag(julia); +#endif + +#ifdef ZOOMRATE + params.scale *= (1 - ZOOMRATE); +#endif + +#ifdef POWRATE + params.zpow += POWRATE; +#endif } vkDeviceWaitIdle(dev);