aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2026-10-11 01:01:54 +0530
committerAkshay Nair <phenax5@gmail.com>2026-10-11 01:01:54 +0530
commit256a42f2dbbb75227441c53f7259a6672d392659 (patch)
treed597f7a7e8aaa0ba88c2751c3f4c5334e4f39ac6 /src/main
parent262807c799a159c7079b20d95f1ecd7ba4855110 (diff)
downloadart-playground-openrndr-256a42f2dbbb75227441c53f7259a6672d392659.tar.gz
art-playground-openrndr-256a42f2dbbb75227441c53f7259a6672d392659.zip
Roll credits
Diffstat (limited to 'src/main')
-rw-r--r--src/main/kotlin/roll_credits/RollCreditsProgram.kt55
-rw-r--r--src/main/resources/shaders/modules/sdf.glsl6
-rw-r--r--src/main/resources/shaders/roll_credits.glsl198
3 files changed, 259 insertions, 0 deletions
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 <sdf>
+#include <utils>
+#include <noise>
+
+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));
+}