// RD on Sphere
//
// A Turing-like spotted/striped pattern wrapped on a 3D sphere,
// procedurally generated from 3D fBm noise rather than iterative simulation.
// Raymarches a unit sphere, samples fBm at the surface point, then thresholds
// into one of three pattern modes: Spots, Stripes, or Maze.
export const PARAMS = {
pattern: { value: "Spots", options: ["Spots", "Stripes", "Maze"], label: "Pattern Type", folder: "Structure" },
scale: { value: 8.0, min: 2, max: 30, step: 0.1, label: "Pattern Scale", folder: "Structure" },
threshold: { value: 0.4, min: 0.1, max: 0.9, step: 0.01, label: "Threshold", folder: "Structure" },
speed: { value: 0.1, min: 0, max: 1.5, step: 0.01, label: "Animation Speed", folder: "Behavior" },
hueA: { value: 180, min: 0, max: 360, step: 1, label: "Hue A", folder: "Appearance" },
hueB: { value: 30, min: 0, max: 360, step: 1, label: "Hue B", folder: "Appearance" },
bg: { value: "#080a0f", type: "color", label: "Background", folder: "Appearance" },
};
function hexToVec3(hex) {
const h = hex.replace(/^#/, '');
return [
parseInt(h.slice(0, 2), 16) / 255,
parseInt(h.slice(2, 4), 16) / 255,
parseInt(h.slice(4, 6), 16) / 255,
];
}
export function shaderUniforms(params) {
const map = { Spots: 0, Stripes: 1, Maze: 2 };
return {
u_scale: { value: params.scale },
u_threshold: { value: params.threshold },
u_speed: { value: params.speed },
u_hueA: { value: params.hueA },
u_hueB: { value: params.hueB },
u_bg: { value: hexToVec3(params.bg) },
u_pattern: { value: map[params.pattern] ?? 0 },
};
}
export function shaderAnimate(uniforms, params) {
const map = { Spots: 0, Stripes: 1, Maze: 2 };
uniforms.u_scale.value = params.scale;
uniforms.u_threshold.value = params.threshold;
uniforms.u_speed.value = params.speed;
uniforms.u_hueA.value = params.hueA;
uniforms.u_hueB.value = params.hueB;
uniforms.u_bg.value = hexToVec3(params.bg);
uniforms.u_pattern.value = map[params.pattern] ?? 0;
}
export function fragmentShader() {
return `
uniform float u_time;
uniform vec2 u_resolution;
uniform float u_scale;
uniform float u_threshold;
uniform float u_speed;
uniform float u_hueA;
uniform float u_hueB;
uniform vec3 u_bg;
uniform int u_pattern;
// Hash for 3D gradient noise
vec3 hash33(vec3 p) {
p = vec3(
dot(p, vec3(127.1, 311.7, 74.7)),
dot(p, vec3(269.5, 183.3, 246.1)),
dot(p, vec3(113.5, 271.9, 124.6))
);
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}
// Smooth Perlin-style 3D noise
float noise3(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(
mix(
mix(dot(hash33(i + vec3(0,0,0)), f - vec3(0,0,0)),
dot(hash33(i + vec3(1,0,0)), f - vec3(1,0,0)), f.x),
mix(dot(hash33(i + vec3(0,1,0)), f - vec3(0,1,0)),
dot(hash33(i + vec3(1,1,0)), f - vec3(1,1,0)), f.x), f.y),
mix(
mix(dot(hash33(i + vec3(0,0,1)), f - vec3(0,0,1)),
dot(hash33(i + vec3(1,0,1)), f - vec3(1,0,1)), f.x),
mix(dot(hash33(i + vec3(0,1,1)), f - vec3(0,1,1)),
dot(hash33(i + vec3(1,1,1)), f - vec3(1,1,1)), f.x), f.y),
f.z
);
}
// Fractional Brownian Motion — 4 octaves, result in [0,1]
float fbm(vec3 p) {
float v = 0.0, a = 0.5;
for (int i = 0; i < 4; i++) {
v += a * noise3(p);
p *= 2.03;
a *= 0.5;
}
return v + 0.5;
}
// Cosine-based color palette from two hue values
vec3 hueColor(float h) {
vec3 a = vec3(0.5);
vec3 b = vec3(0.5);
vec3 c = vec3(1.0);
return a + b * cos(6.28318 * (c * 0.5 + vec3(h / 360.0)));
}
void main() {
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
// Camera ray — simple orthographic-style perspective
vec3 ro = vec3(0.0, 0.0, -3.0);
vec3 rd = normalize(vec3(uv, 1.5));
// Analytic sphere intersection (unit sphere at origin)
float b = dot(ro, rd);
float c = dot(ro, ro) - 1.0;
float disc = b * b - c;
if (disc < 0.0) {
gl_FragColor = vec4(u_bg, 1.0);
return;
}
float t = -b - sqrt(disc);
vec3 pos = ro + rd * t;
vec3 nor = normalize(pos);
// Sample fBm at surface point, animated by time
vec3 sp = pos * u_scale + vec3(u_time * u_speed);
float v = fbm(sp);
// Pattern modes
float pat = 0.0;
if (u_pattern == 0) {
// Spots: hard threshold
pat = step(u_threshold, v);
} else if (u_pattern == 1) {
// Stripes: repeated banding
pat = step(0.5, fract(v * 4.0));
} else {
// Maze: thin corridors at contour lines
float band = abs(v - 0.5);
pat = 1.0 - smoothstep(0.0, 0.06, band);
}
vec3 colA = hueColor(u_hueA);
vec3 colB = hueColor(u_hueB);
vec3 col = mix(colA, colB, pat);
// Lambertian shading
vec3 lightDir = normalize(vec3(0.6, 0.7, -0.5));
float diff = max(dot(nor, lightDir), 0.0);
col *= (0.3 + 0.7 * diff);
// Specular highlight
vec3 viewDir = normalize(-rd);
vec3 halfDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(nor, halfDir), 0.0), 32.0);
col += 0.15 * spec;
// Subtle rim light
float rim = 1.0 - max(dot(nor, viewDir), 0.0);
col += 0.08 * rim * rim * mix(colA, colB, 0.5);
// Gamma correction
col = pow(clamp(col, 0.0, 1.0), vec3(1.0 / 2.2));
gl_FragColor = vec4(col, 1.0);
}
`;
}