Skip to content

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).

rendering…
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),
  ]
}