Lava lamp
A field of metaballs rendered as a raymarched signed-distance surface.
Each blob keeps a constant radius — so it preserves its own volume —
and bobs on its own y animation; where two blobs come close, the
smooth-union (smoothing:) fuses them into one organic mass, and they
pull apart again as they drift. The glossy red body, orange fresnel rim,
and speculars come from the shader’s lighting; the dark background reads
through the gaps.
The live preview below is the Three.js viewer raymarching the SDF in a fragment shader (the same path the cloud render service uses).
runner "0.0.1";
use std.shapes.*;
use std.scene3d.*;
use std.anim.*;
scene lava(duration: Duration = 12s) -> Frame {
let bg = rect(width: 1920px, height: 1080px, fill: oklch(0.08, 0.03, 285));
let blobs = [
blob(at: vec3( 10px, animate { 0s => 30px, 6s => -70px, 12s => 30px } with { easing: easing.in_out_cubic }, 0px), radius: 250px),
blob(at: vec3(-280px, animate { 0s => 150px, 6s => 270px, 12s => 150px } with { easing: easing.in_out_cubic }, 0px), radius: 160px),
blob(at: vec3( 300px, animate { 0s => -10px, 6s => 110px, 12s => -10px } with { easing: easing.in_out_cubic }, 0px), radius: 170px),
blob(at: vec3(-160px, animate { 0s => -240px, 6s => -110px, 12s => -240px } with { easing: easing.in_out_cubic }, 0px), radius: 150px),
blob(at: vec3( 230px, animate { 0s => -280px, 6s => -150px, 12s => -280px } with { easing: easing.in_out_cubic }, 0px), radius: 145px),
blob(at: vec3(-470px, animate { 0s => 40px, 4s => -90px, 8s => 140px, 12s => 40px } with { easing: easing.in_out_cubic }, 0px), radius: 120px),
blob(at: vec3( 500px, animate { 0s => -90px, 4s => 70px, 8s => -200px, 12s => -90px } with { easing: easing.in_out_cubic }, 0px), radius: 115px),
];
compose [
bg,
metaballs(blobs, smoothing: 90px).material(roughness: 0.18),
]
}