diff options
| author | Akshay Nair <phenax5@gmail.com> | 2026-10-25 14:52:36 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2026-10-25 14:52:36 +0530 |
| commit | d41559270e57752cc16f97f7e820f3781ed03a49 (patch) | |
| tree | e92d55544f78966bd3283b39269a34fa61fb57d8 | |
| parent | 5e51938952b5275debce06e5cee4a5f71eacaeb1 (diff) | |
| download | art-playground-openrndr-d41559270e57752cc16f97f7e820f3781ed03a49.tar.gz art-playground-openrndr-d41559270e57752cc16f97f7e820f3781ed03a49.zip | |
Ice cream sandwichmaster
Diffstat (limited to '')
| -rw-r--r-- | src/main/kotlin/icecream/IcecreamProgram.kt | 48 | ||||
| -rw-r--r-- | src/main/kotlin/phenax/utils/program.kt | 6 | ||||
| -rw-r--r-- | src/main/resources/shaders/icecream.glsl | 268 |
3 files changed, 319 insertions, 3 deletions
diff --git a/src/main/kotlin/icecream/IcecreamProgram.kt b/src/main/kotlin/icecream/IcecreamProgram.kt new file mode 100644 index 0000000..69e8297 --- /dev/null +++ b/src/main/kotlin/icecream/IcecreamProgram.kt @@ -0,0 +1,48 @@ +package icecream + +import org.openrndr.application +import org.openrndr.color.* +import org.openrndr.draw.* +import org.openrndr.math.IntVector2 +import phenax.utils.* + +fun main() = application { + recordDuration(20.seconds()) + + if (captureEnabled) { + configure { + width = 1920 + height = 1440 + windowResizable = false + } + } else { + configure { + width = 640 + height = 480 + windowResizable = false + } + } + + oliveProgram { + val enableAntialiasing = captureEnabled + val shaderLocalSize = IntVector2(8, 8) + val cs by liveComputeShader("icecream") + + val outputBuffer = colorBuffer(width, height, type = ColorType.UINT8) + + // if (captureEnabled) + // repeat(1.0) { println("${Math.round((seconds / recordDuration()) * 10000)/100}%") } + + extend { + cs.uniform("enable_antialiasing", enableAntialiasing) + cs.uniform("seconds", seconds) + cs.image("output_image", 0, outputBuffer.imageBinding(0, ImageAccess.WRITE)) + cs.execute( + (outputBuffer.width + shaderLocalSize.x - 1) / shaderLocalSize.x, + (outputBuffer.height + shaderLocalSize.y - 1) / shaderLocalSize.y, + 1 + ) + drawer.image(outputBuffer, 0.0, 0.0, width.toDouble(), height.toDouble()) + } + } +} diff --git a/src/main/kotlin/phenax/utils/program.kt b/src/main/kotlin/phenax/utils/program.kt index fdb6197..821324a 100644 --- a/src/main/kotlin/phenax/utils/program.kt +++ b/src/main/kotlin/phenax/utils/program.kt @@ -11,11 +11,11 @@ import org.openrndr.extra.olive.oliveProgram as oliveProgramOriginal fun Number.seconds(): Number = this -fun recordDuration(millis: Number) { - System.setProperty("phenax.record.recordDuration", millis.toDouble().toString()) +fun recordDuration(seconds: Number) { + System.setProperty("phenax.record.recordDuration", seconds.toDouble().toString()) } -fun recordDuration(): Double = System.getProperty("phenax.record.recordDuration")?.toDoubleOrNull() ?: 5000.0 +fun recordDuration(): Double = System.getProperty("phenax.record.recordDuration")?.toDoubleOrNull() ?: 5.0 val recordingEnabled: Boolean by lazy { System.getenv("PHENAX_RECORD") != null } val screenshotEnabled: Boolean by lazy { System.getenv("PHENAX_SCREENSHOT") != null } diff --git a/src/main/resources/shaders/icecream.glsl b/src/main/resources/shaders/icecream.glsl new file mode 100644 index 0000000..38cfb23 --- /dev/null +++ b/src/main/resources/shaders/icecream.glsl @@ -0,0 +1,268 @@ +#version 430 +layout(local_size_x = 8, local_size_y = 8) in; + +#include <sdf> +#include <utils> +#include <noise> + +const float MAX_DISTANCE = 1000.; // 1000. +const int MAX_ITERATIONS = 800; // 600+ +const float MIN_GAP = 0.001; + +uniform float seconds = 0; +layout(rgba8) uniform writeonly image2D output_image; + +uniform float antialiasing = 0.28; +uniform bool enable_antialiasing = false; + +uniform vec3 camera_position = vec3(0, 0, -3); + +const int O_SANDWICH_FILL = 1; +const int O_SANDWICH_WAFER = 2; + +float time = seconds * 2; +int break_sym = int(int(time) % 40 == 14 || int(time) % 40 == 18 || int(time) % 40 == 36); +int break_sym_2 = int(int(time) % 40 == 20 || int(time) % 40 == 3); + +float sdf_icecream_sandwich(in vec3 p, in bool show_bite, inout int obj_id) { + float fill = sdf_box(p, vec3(0.18, 0.04, 0.1), 0.02); + float wafernoise = 0.00062 * noise(p * 900); + vec3 wafersize = vec3(0.20, 0.016, 0.12) + wafernoise; + float wafer = sdf_box(p - vec3(0, 0.056, 0), wafersize, 0.014); + wafer = min(wafer, sdf_box(p - vec3(0, -0.056, 0), wafersize, 0.014)); + + { + vec3 dotp = p - vec3(0, -0.07, 0); + float gap = 0.07; + dotp.xz -= round(dotp.xz / gap) * gap; + float dots = sdf_circle(dotp, 0.006); + wafer = max(wafer, -dots); + } + { + vec3 dotp = p - vec3(0, 0.07, 0); + float gap = 0.07; + dotp.xz -= round(dotp.xz / gap) * gap; + float dots = sdf_circle(dotp, 0.006); + wafer = max(wafer, -dots); + } + + if (show_bite) { + vec3 teethp = p - vec3(-0.19, -0.07, -0.11); + float theta = atan(teethp.z, teethp.x); + float biteradius = 0.08 + 0.003 * sin(theta * 18); + fill = max(fill, -sdf_capsule(teethp, 0.12, biteradius)); + wafer = max(wafer, -sdf_capsule(teethp, 0.12, biteradius + 8.0 * wafernoise)); + } + + float dist = min(fill, wafer); + if (dist == wafer) obj_id = O_SANDWICH_WAFER; + else obj_id = O_SANDWICH_FILL; + return dist; +} + +void rotate_all_axes(inout vec3 p, float theta) { + mat2x2 rot = mat2x2(cos(theta), -sin(theta), sin(theta), cos(theta)); + p.xy *= rot; + p.yz *= rot; + p.xz *= rot; +} + +float scene(in vec3 p, out int obj_id) { + float sw; + int sw_id; + float rotation_time = seconds; + { + vec3 swp = p; + { + float theta = rotation_time / 26 + 0.3 * break_sym + 0.17 * break_sym_2; + swp.xy *= mat2x2(cos(theta), -sin(theta), sin(theta), cos(theta)); + } + vec2 p1rot, p2rot; + float id = rotational_repetition(swp.xy, 20, p1rot, p2rot); + int odd = int(id) % 2; + vec3 offset = vec3(3.5 - 0.06 * odd * break_sym, 0, 4.5); + float theta = -rotation_time / 6 + 0.1 + 0.5 * break_sym; + int sw1_id, sw2_id; + vec3 p1 = vec3(p1rot, swp.z) - offset; + vec3 p2 = vec3(p2rot, swp.z) - offset; + rotate_all_axes(p1, theta); + rotate_all_axes(p2, theta); + float b1 = sdf_icecream_sandwich(p1, bool(break_sym), sw1_id); + float b2 = sdf_icecream_sandwich(p2, bool(break_sym), sw2_id); + sw = min(b1, b2); + if (sw == b1) sw_id = sw1_id; + else if (sw == b2) sw_id = sw2_id; + } + { + vec3 swp = p; + { + float theta = rotation_time / 20 + 0.3 * break_sym; + swp.xy *= mat2x2(cos(theta), -sin(theta), sin(theta), cos(theta)); + } + vec2 p1rot, p2rot; + float id = rotational_repetition(swp.xy, 20, p1rot, p2rot); + int odd = int(id) % 2; + vec3 offset = vec3(1 - 0.06 * odd * break_sym, 0, 0); + float theta = rotation_time / 4 + 0.3 * break_sym; + int sw1_id, sw2_id; + vec3 p1 = vec3(p1rot, swp.z) - offset; + vec3 p2 = vec3(p2rot, swp.z) - offset; + rotate_all_axes(p1, theta); + rotate_all_axes(p2, theta); + float b1 = sdf_icecream_sandwich(p1, bool(break_sym), sw1_id); + float b2 = sdf_icecream_sandwich(p2, bool(break_sym), sw2_id); + sw = min(sw, min(b1, b2)); + if (sw == b1) sw_id = sw1_id; + else if (sw == b2) sw_id = sw2_id; + } + { + vec3 swp = p - vec3(0, 0, 0.5); + { + float theta = rotation_time / 12 + 0.3 * break_sym; + swp.xy *= mat2x2(cos(theta), -sin(theta), sin(theta), cos(theta)); + } + vec2 p1rot, p2rot; + float id = rotational_repetition(swp.xy, 36, p1rot, p2rot); + int odd = int(id) % 2; + vec3 offset = vec3(0.8 - 0.06 * odd * break_sym, 0, 0); + float theta = -rotation_time / 5 + 0.3 + 0.3 * break_sym; + int sw1_id, sw2_id; + vec3 p1 = vec3(p1rot, swp.z) - offset; + vec3 p2 = vec3(p2rot, swp.z) - offset; + rotate_all_axes(p1, theta); + rotate_all_axes(p2, theta); + float b1 = sdf_icecream_sandwich(p1, bool(break_sym), sw1_id); + float b2 = sdf_icecream_sandwich(p2, bool(break_sym), sw2_id); + sw = min(sw, min(b1, b2)); + if (sw == b1) sw_id = sw1_id; + else if (sw == b2) sw_id = sw2_id; + } + + { + vec3 sp = p * 0.8 - vec3(0, 0, -1); + sp.yz *= mat2x2(cos(0.42), -sin(0.42), sin(0.42), cos(0.42)); + sp.xz *= mat2x2(cos(-0.42), -sin(-0.42), sin(-0.42), cos(-0.42)); + int sw1_id; + float sando = sdf_icecream_sandwich(sp, true, sw1_id); + sw = min(sw, sando); + if (sw == sando) sw_id = sw1_id; + } + + float dist = sw; + if (dist == sw) obj_id = sw_id; + return dist; +} + +void apply_material( + in int obj_id, + in float dist, + inout vec3 color, + inout float ambient, + inout float shine +) { + if (obj_id == O_SANDWICH_FILL) { + color = srgb(1); + shine = 2.0; + } else if (obj_id == O_SANDWICH_WAFER) { + color = srgb(0.44, 0.27, 0.17); + shine = 0.06; + } +} + +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 = -1; + return dist; +} + +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) { + vec3 campos = camera_position; + vec3 uv_direction = normalize(vec3(uv, 1)); + + int obj_id = 0; + float dist = raymarch_scene(campos, uv_direction, obj_id); + + vec3 p = campos + uv_direction * dist; + vec3 norm = surface_normal_scene(p); + + vec3 light = vec3(6, 8, -20); + vec3 light_dir = normalize(light - p); + + vec3 c1 = srgb(0.74, 0.93, 0.80); + vec3 c2 = srgb(0.98, 0.75, 0.83); + vec3 c3 = srgb(0.59, 0.88, 0.94); + vec3 c4 = srgb(0.98, 0.78, 0.51); + + vec2 patp = uv * 30; + float pat = sin(patp.x) * cos(patp.y - time) - sin(3 * patp.x) - cos(3 * patp.y); + vec3 bg_color = c1; + bg_color = mix(bg_color, c2, step(pat, 1.0)); + bg_color = mix(bg_color, c3, step(pat, 0)); + bg_color = mix(bg_color, c4, step(pat, -1.0)); + bg_color = mix(bg_color, srgb(0.8, 0, 0), 0.25 * break_sym); + + vec3 color = bg_color; + float ambient = 0.05; + float shine = 1.; + apply_material(obj_id, dist, color, ambient, shine); + + if (obj_id > 0) { + float contrast = clamp(dot(norm, light_dir), 0, 1); + color *= clamp((1 - ambient) * contrast + ambient, 0, 1); + + vec3 hal = normalize(light_dir - uv_direction); + float spec = + pow(clamp(dot(norm, hal), 0.0, 1.0), 16.0) * contrast * + (0.04 + + 0.96 * pow(clamp(1.0 + dot(hal, uv_direction), 0.0, 1.0), 5.0)); + color += shine * 10. * spec * vec3(1.00, 0.70, 0.5); + } + + 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); + + float t = time; + vec2 offset = vec2(mod(t * sin(t * 8 + fbm(vec2(t)) * 0.5), 0.02), 0); + if (break_sym == 1) { + color.r = (color.r + image(uv + offset).r + image(uv - offset).r) / 3.; + color.g = (color.g + image(uv + vec2(offset.y, offset.x)).g + image(uv + offset).g) / 3.; + color.b = (color.b + image(uv + vec2(offset.y, offset.x)).b + image(uv - offset).b) / 3.; + } + + color = clamp(color, 0, 1); + imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(color, 1)); +} |
