From 256a42f2dbbb75227441c53f7259a6672d392659 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sun, 11 Oct 2026 01:01:54 +0530 Subject: Roll credits --- src/main/kotlin/roll_credits/RollCreditsProgram.kt | 55 ++++++ src/main/resources/shaders/modules/sdf.glsl | 6 + src/main/resources/shaders/roll_credits.glsl | 198 +++++++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 src/main/kotlin/roll_credits/RollCreditsProgram.kt create mode 100644 src/main/resources/shaders/roll_credits.glsl diff --git a/src/main/kotlin/roll_credits/RollCreditsProgram.kt b/src/main/kotlin/roll_credits/RollCreditsProgram.kt new file mode 100644 index 0000000..b07d6fc --- /dev/null +++ b/src/main/kotlin/roll_credits/RollCreditsProgram.kt @@ -0,0 +1,55 @@ +package roll_credits + +import org.openrndr.application +import org.openrndr.color.* +import org.openrndr.draw.* +import org.openrndr.extra.noise.filters.SimplexNoise3D +import org.openrndr.math.Vector3 +import phenax.utils.* + +fun main() = application { + recordDuration(5.seconds()) + + if (captureEnabled) { + configure { + width = 1920 + height = 1080 + windowResizable = false + } + } else { + configure { + width = 640 + height = 360 + windowResizable = false + } + } + + oliveProgram { + val cs by liveComputeShader("roll_credits") + + val outputBuffer = colorBuffer(width, height, type = ColorType.UINT8) + + val size = Math.max(width, height) * 2 + val noiseBuffer = colorBuffer(size, size, type = ColorType.UINT8) + val noiseFilter = SimplexNoise3D() + noiseFilter.scale = Vector3.ONE * 2.4 + noiseFilter.octaves = 8 + noiseFilter.premultipliedAlpha = true + + // val camera_manager = CameraManager() + // extend(camera_manager) + + extend { + noiseFilter.seed = Vector3(0.01, 0.01, 0.001) + noiseFilter.apply(emptyArray(), noiseBuffer) + cs.image("noise_texture", 1, noiseBuffer.imageBinding(0, ImageAccess.READ)) + + // camera_manager.setUniforms(cs) + + cs.uniform("seconds", seconds) + cs.image("output_image", 0, outputBuffer.imageBinding(0, ImageAccess.WRITE)) + cs.execute(outputBuffer.width, outputBuffer.height, 1) + drawer.image(outputBuffer) + } + } +} diff --git a/src/main/resources/shaders/modules/sdf.glsl b/src/main/resources/shaders/modules/sdf.glsl index c6787c9..e7222b4 100644 --- a/src/main/resources/shaders/modules/sdf.glsl +++ b/src/main/resources/shaders/modules/sdf.glsl @@ -25,3 +25,9 @@ float sd_ellipsoid(vec3 p, vec3 r) { float k1 = length(p / (r * r)); return k0 * (k0 - 1.0) / k1; } + +float sd_cone(vec3 p, vec2 c) { + vec2 q = vec2(length(p.xz), -p.y); + float d = length(q - c * max(dot(q, c), 0.0)); + return d * ((q.x * c.y - q.y * c.x < 0.0) ? -1.0 : 1.0); +} diff --git a/src/main/resources/shaders/roll_credits.glsl b/src/main/resources/shaders/roll_credits.glsl new file mode 100644 index 0000000..ae3fdd5 --- /dev/null +++ b/src/main/resources/shaders/roll_credits.glsl @@ -0,0 +1,198 @@ +#version 430 +layout(local_size_x = 1, local_size_y = 1) in; + +#include +#include +#include + +const float MAX_DISTANCE = 1000.; +const int MAX_ITERATIONS = 500; +const float MIN_GAP = 0.001; + +uniform float seconds = 0; + +layout(rgba8) uniform readonly image2D noise_texture; +layout(rgba8) uniform writeonly image2D output_image; + +uniform float antialiasing = 0.28; +uniform bool enable_antialiasing = false; + +// uniform vec3 camera_position = vec3(0, 1, -2); +// uniform vec3 camera_position = vec3(-9.5, 2.0, 30.0); // character focus +uniform vec3 camera_position = vec3(12., 16, 60); // death focus + +const vec3 character_offset = vec3(-9.5, 2.6, 36.0); +const vec3 death_offset = vec3(12., 16, 80); + +vec3 pink = srgb(0.82, 0.33, 0.51); +vec3 teal = srgb(0.28, 0.74, 0.75); +vec3 skyblue = srgb(0.73, 0.93, 0.95); +vec3 skygreen = srgb(0.53, 0.72, 0.54); +vec3 violet = srgb(0.38, 0.21, 0.51); +vec3 sandred = srgb(0.82, 0.44, 0.42); +vec3 sandyellow = srgb(0.81, 0.66, 0.43); // 0.79,0.60,0.34 +vec3 red1 = srgb(0.69, 0.09, 0.06); +vec3 gold = srgb(0.89, 0.77, 0.26); + +float sample_noise(in vec2 p) { + vec4 noise = imageLoad(noise_texture, ivec2(p * imageSize(noise_texture))); + return noise.x; +} + +const int O_DUNE1 = 1; +const int O_CAPE = 2; +const int O_LEGS = 3; +const int O_DEATH = 4; +const int O_DEATHCONE = 5; + +float scene(in vec3 p, out int obj_id) { + float dune; + { + float dune_elevation = 100.0 * fbm((p.xz + vec2(10.0, -40)) / 360) - 0.1 * fbm(p.xz / 1.2); + dune_elevation = mix(dune_elevation, dune_elevation - 2.0 - 0.3 * fbm((p.xz - 1) / 1.1), smoothstep(-50, 100, p.z) * step(p.z, 200)); + dune_elevation = mix(dune_elevation, dune_elevation * 1.4 - 86, smoothstep(740, 920, p.z)); + dune_elevation = mix(dune_elevation, 0, 1 - step(p.z, 1200)); + dune_elevation = mix(dune_elevation, dune_elevation + 44, step(p.z, 740) * smoothstep(100, 740, p.z)); + dune = sdf_plane(p, vec3(0, 1, 0), dune_elevation); + } + + float cape, legs; + { + float ctheta = PI / 9; + vec3 charp = p - character_offset; + cape = sd_cone(charp, vec2(sin(ctheta), cos(ctheta))); + cape = smin(cape, sdf_circle(charp + vec3(0, 0.12, 0), 0.21), 0.26); + cape = max(cape, -sdf_plane(charp, vec3(0, 1, 0), 1.20 + 0.04 * sin(16 * charp.x) + 0.6 * (0.76 - fbm(charp.xz / 1.5)))); + cape = max(cape, -sdf_circle(charp + vec3(0, 1.42, -0.12), 0.51)); + legs = sdf_box(charp + vec3(0.1, 1.6, 0.1), vec3(0.08, 0.3, 0.08), 0.1); + legs = min(legs, sdf_box(charp + vec3(-0.2, 1.6, 0.1), vec3(0.08, 0.3, 0.08), 0.1)); + } + + float death, deathcone; + { + vec3 dp = p - death_offset; + death = sdf_circle(dp, 2.0 - 1.4 * fbm(p, 2)); + float ctheta = PI / 21; + vec3 conep = dp; + float deaththeta = PI / 3; + conep.xy *= mat2x2(cos(deaththeta), -sin(deaththeta), sin(deaththeta), cos(deaththeta)); + conep -= vec3(0, 11.0, 0); + conep -= vec3(0, sin(conep.y * 10), 0); + deathcone = sd_cone(conep, vec2(sin(ctheta), cos(ctheta))); + deathcone = max(deathcone, -sdf_plane(conep, vec3(0, 1, 0), 10)); + } + + float dist = dune; + dist = min(dist, cape); + dist = min(dist, legs); + dist = min(dist, death); + dist = min(dist, deathcone); + if (dist == dune) obj_id = O_DUNE1; + else if (dist == cape) obj_id = O_CAPE; + else if (dist == legs) obj_id = O_LEGS; + else if (dist == death) obj_id = O_DEATH; + else if (dist == deathcone) obj_id = O_DEATHCONE; + return dist; +} +vec3 get_material_properties(in int object_id, in vec3 surface_point, in vec2 screen_point, out float ambient) { + vec3 color = skyblue; + color = mix(color, skygreen, 1 - smoothstep(-0.2, 0.5, screen_point.y)); + + ambient = 0.06; + + if (object_id == O_DUNE1) { + color = sandred; + color = mix(color, sandyellow, 1 - smoothstep(-0.2, 0.2, fbm(surface_point.xz * 2))); + color = mix(color, red1, 1 - smoothstep(-0.2, 0.2, fbm((surface_point.xz + 2) * 4))); + } else if (object_id == O_CAPE) { + color = srgb(0.3, 0.3, 0.5); + float x = surface_point.x * 10; + float y = surface_point.y * 32; + color = mix(color, sandyellow, step(surface_point.y, character_offset.y - 0.38) * step(abs(sin(x) + cos(y) - cos(x) * sin(y)) - 0.18, 0)); + color = mix(color, sandyellow, + (1 - step(surface_point.y, character_offset.y - 0.40)) + * step(surface_point.y, character_offset.y - 0.35) + * step(sin(surface_point.y * 50), 0.38)); + ambient = 0.2; + } else if (object_id == O_LEGS) { + color = vec3(0.04); + color = mix(color, srgb(0.3, 0.3, 0.5), step(sin(surface_point.y * 90), 0)); + ambient = 0.01; + } else if (object_id == O_DEATH) { + color = srgb(0.6, 0.5, 0.6); + ambient = 0.2; + } else if (object_id == O_DEATHCONE) { + color = srgb(1.0, 0.5, 0.6); + ambient = 1; + } + return color; +} + +float raymarch_scene(vec3 ray_origin, vec3 ray_direction, out int obj_id) { + float dist = 0.; + for (int i = 0; i < MAX_ITERATIONS && dist <= MAX_DISTANCE; i++) { + vec3 p = ray_origin + ray_direction * dist; + float gap = scene(p, obj_id); + dist += gap; + if (gap <= MIN_GAP) + return dist; + } + obj_id = 0; + return -1; +} + +vec3 surface_normal_scene(in vec3 p) { + vec2 e = vec2(MIN_GAP, 0); + int obj_id; + return normalize(vec3( + scene(p + e.xyy, obj_id) - scene(p - e.xyy, obj_id), + scene(p + e.yxy, obj_id) - scene(p - e.yxy, obj_id), + scene(p + e.yyx, obj_id) - scene(p - e.yyx, obj_id))); +} + +vec3 image(vec2 uv) { + int object_id = 0; + + vec3 uv_direction = normalize(vec3(uv, 1)); + float dist = raymarch_scene(camera_position, uv_direction, object_id); + + vec3 p = camera_position + uv_direction * dist; + vec3 norm = surface_normal_scene(p); + + vec3 light = vec3(8, 300, -4); + light = mix(light, vec3(0, light.y, -100), smoothstep(-20, 50, p.z)); + light = mix(light, vec3(0, light.y, 480), smoothstep(200, 400, p.z)); + light = mix(light, vec3(-8, 280, 1000), smoothstep(800, 1000, p.z)); + vec3 light_dir = normalize(light - p); + + float ambient; + vec3 color = get_material_properties(object_id, p, uv, ambient); + + if (object_id > 0) { + float contrast = clamp(dot(norm, light_dir), 0, 1); + color *= clamp((1 - ambient) * contrast + ambient, 0, 1); + } + + return color; +} + +vec3 antialiased_image(vec2 uv) { + vec2 resolution = vec2(imageSize(output_image).xy); + float s = antialiasing / max(resolution.x, resolution.y); + vec3 sum = vec3(0); + int count = 0; + for (int i = -2; i < 2; i++) + for (int j = -2; j < 2; j++, count++) + sum += image(uv + vec2(i, j) * s); + return sum / count; +} + +void main() { + vec2 resolution = vec2(imageSize(output_image).xy); + float aspect_ratio = resolution.y / resolution.x; + vec2 uv = (gl_GlobalInvocationID.xy - resolution / 2) * vec2(1.0, aspect_ratio) / resolution; + + vec3 color = enable_antialiasing ? antialiased_image(uv) : image(uv); + + imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(color, 1)); +} -- cgit v1.3.1