mesh-gradient@0.1.0
Mesh gradient
Two SDF blobs under a smooth union, domain-warped by simplex noise, blended through OKLab so the gradient stays vivid instead of graying out in the middle. The backdrop of this page is this exact effect, running live.
Controls
#7928ca
#ff0080
Imports
What this entry shader composes; every module resolves from npm.
import { sdCircle } from "@vshaders/sdf/2d";
import { opSmoothUnion } from "@vshaders/sdf/ops";
import { mixOklab } from "@vshaders/color/oklab";
import { cosinePalette } from "@vshaders/color/palette";
import { simplex2d } from "@vgpu/wgsl-std/noise/simplex";
import { linearToSrgb3 } from "@vgpu/wgsl-std/color";
Source
The full entry shader, mesh-gradient.wgsl. Copy it and it is yours.
// effect 001 · mesh-gradient — the animated hero background.
// Two SDF blobs smooth-unioned, domain-warped by simplex noise, blended
// through OKLab so the gradient stays vivid instead of graying out.
// Works in linear light; encodes to sRGB once at the end.
import { sdCircle } from "@vshaders/sdf/2d";
import { opSmoothUnion } from "@vshaders/sdf/ops";
import { mixOklab } from "@vshaders/color/oklab";
import { cosinePalette } from "@vshaders/color/palette";
import { simplex2d } from "@vgpu/wgsl-std/noise/simplex";
import { linearToSrgb3 } from "@vgpu/wgsl-std/color";
// Tunable members follow resolution/time. Defaults live in lib/effects.ts and
// must be set by the runner: an unset uniform member reads as zero. Members are
// ordered so each vec3f lands on a 16-byte boundary with an f32 in its pad.
struct Uniforms {
resolution: vec2f,
time: f32,
// Domain-warp displacement, in centered field units.
warpStrength: f32,
// Gradient endpoints, linear light (defaults: #7928CA purple, #FF0080 pink).
colorA: vec3f,
// Multiplies time in every animation term.
speed: f32,
colorB: vec3f,
// opSmoothUnion band between the two blobs, in field units.
smoothness: f32,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@fragment
fn main(@builtin(position) position: vec4f) -> @location(0) vec4f {
let res = max(uniforms.resolution, vec2f(1.0));
let uv = position.xy / res;
let aspect = res.x / res.y;
var p = (uv - vec2f(0.5)) * vec2f(aspect, 1.0);
let t = uniforms.time * uniforms.speed;
// Domain warp so the blobs drift organically instead of orbiting.
let warp = simplex2d(p * 2.0 + vec2f(t * 0.05, 0.0));
p += vec2f(warp) * uniforms.warpStrength;
let blobA = sdCircle(p - vec2f(0.22 * cos(t * 0.3), 0.1), 0.28);
let blobB = sdCircle(p + vec2f(0.25 * sin(t * 0.2), 0.18), 0.22);
let field = opSmoothUnion(blobA, blobB, uniforms.smoothness);
// Perceptual gradient between the two linear-light endpoint colors.
let blend = smoothstep(-0.3, 0.25, field);
var color = mixOklab(uniforms.colorA, uniforms.colorB, blend);
// A faint animated accent from a cosine palette, feathered by the field.
let accent = cosinePalette(
t * 0.02 + uv.y * 0.3,
vec3f(0.5),
vec3f(0.5),
vec3f(1.0),
vec3f(0.0, 0.33, 0.67)
);
color = mix(color, accent, 0.08 * (1.0 - smoothstep(-0.05, 0.05, field)));
return vec4f(linearToSrgb3(max(color, vec3f(0.0))), 1.0);
}