Back to shaders
Shader test bench
20260518
runnable fragment
Complete GLSL fragment shader. Stronghold runs it directly when the browser can compile it.
Code
precision mediump float;
#define TDOutputSwizzle(c) (c)
// ===============================================================
// CAMBRIAN RELIC — Marrella Model v3.1 Stable
// TouchDesigner GLSL TOP
// Stable Marrella silhouette:
// slender carapace + four horn spines + ventral legs + shorter antennae
// Keeps original color constants exactly.
// ===============================================================
#define fragColor gl_FragColor
uniform float iTime;
#define MAX_STEPS 112
#define MAX_DIST 10.5
#define SURF_DIST 0.00105
#define TAU 6.28318530718
#define PI 3.14159265359
#define INTENSITY 1.82
#define GLOW_POWER 2.35
#define VEIL_POWER 1.70
const vec3 ACID = vec3(0.0, 1.0, 0.624);
const vec3 CYAN = vec3(0.0, 0.812,1.0);
const vec3 VIOLET = vec3(0.545,0.0, 1.0);
const vec3 PINK = vec3(1.0, 0.0, 0.431);
// ---------- helpers ----------
float hash11(float n) {
return fract(sin(n * 12.9898) * 43758.5453);
}
float hash13(vec3 p) {
return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);
}
float noise3(vec3 x) {
vec3 p = floor(x);
vec3 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
return mix(
mix(
mix(hash13(p + vec3(0, 0, 0)), hash13(p + vec3(1, 0, 0)), f.x),
mix(hash13(p + vec3(0, 1, 0)), hash13(p + vec3(1, 1, 0)), f.x),
f.y
),
mix(
mix(hash13(p + vec3(0, 0, 1)), hash13(p + vec3(1, 0, 1)), f.x),
mix(hash13(p + vec3(0, 1, 1)), hash13(p + vec3(1, 1, 1)), f.x),
f.y
),
f.z
);
}
float fbm(vec3 p) {
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 5; i++) {
v += a * noise3(p);
p *= 2.04;
a *= 0.52;
}
return v;
}
float sdSphere(vec3 p, float r) {
return length(p) - r;
}
float sdEllipsoid(vec3 p, vec3 r) {
float k0 = length(p / r);
float k1 = length(p / (r * r));
return k0 * (k0 - 1.0) / k1;
}
float sdCapsule(vec3 p, vec3 a, vec3 b, float r) {
vec3 pa = p - a;
vec3 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}
float softUnion(float a, float b, float k) {
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
mat2 rot(float a) {
float s = sin(a);
float c = cos(a);
return mat2(c, -s, s, c);
}
// ---------- camera ----------
struct Cam {
vec3 ro;
vec3 fwd;
vec3 rgt;
vec3 up;
};
vec3 creatureOffset(float t) {
float swimT = t * 0.32;
return vec3(
sin(swimT * 0.52) * 0.10 + sin(t * 1.05) * 0.016,
sin(swimT * 0.42 + 1.2) * 0.06 + cos(t * 0.86) * 0.012,
cos(swimT * 0.36) * 0.08 + sin(t * 0.67) * 0.016
);
}
Cam makeCam(float t) {
Cam c;
float orbitT = t * 0.22; // faster orbit
vec3 center = creatureOffset(t);
// Full orbit including vertical sweep so head/face side becomes visible.
float yaw = orbitT;
float dist = 4.50 + 0.12 * sin(t * 0.21 + 0.7);
float height = 0.80 * sin(t * 0.14); // swings above and below to show all sides
vec3 orbitPos = vec3(
sin(yaw) * dist,
height,
cos(yaw) * dist
);
vec3 target = center + vec3(
0.02 * sin(t * 0.27),
0.05 + 0.02 * sin(t * 0.23),
0.02 * cos(t * 0.19)
);
c.ro = center + orbitPos;
c.ro += normalize(target - c.ro) * (0.025 + 0.016 * sin(t * 0.34));
c.fwd = normalize(target - c.ro);
c.rgt = normalize(cross(vec3(0, 1, 0), c.fwd));
c.up = cross(c.fwd, c.rgt);
return c;
}
// Local Marrella coordinate:
// -Y = anterior / head / antenna direction
// +Y = posterior / abdomen direction
// X = lateral width
// Z = dorsal/ventral depth
vec3 marrellaPose(vec3 p) {
p.yz *= rot(PI * 0.5); // body horizontal — swims sideways
return p;
}
vec3 bendSpace(vec3 p, float t) {
p -= creatureOffset(t);
p = marrellaPose(p);
// Reduced swimming deformation. Marrella should not behave like a jellyfish.
p.xz *= rot(0.020 * sin(t * 0.46));
p.yz *= rot(0.018 * sin(t * 0.38 + 1.1));
p.xy *= rot(0.012 * sin(t * 0.52));
// Restrained body wave.
float wave = sin(p.y * 2.35 - t * 1.45);
float fineWave = sin(p.y * 4.8 - t * 2.35) * 0.34;
float bodyMask = smoothstep(-0.55, 1.05, p.y);
p.x += (wave + fineWave) * 0.014 * bodyMask;
p.z += cos(p.y * 1.8 - t * 1.22) * 0.018 * bodyMask;
return p;
}
// ---------- Marrella v3 anatomy ----------
float headSDF(vec3 q, float t) {
// Smaller, shield-like head.
vec3 h = q - vec3(0.0, -0.48, 0.02);
float shield = sdEllipsoid(h, vec3(0.48, 0.34, 0.27));
// Taper sides to form a shield / wedge.
float sideCutL = h.x + 0.30 + h.y * 0.46;
float sideCutR = -h.x + 0.30 + h.y * 0.46;
float rearCut = h.y - 0.28;
shield = max(shield, -sideCutL);
shield = max(shield, -sideCutR);
shield = max(shield, rearCut * 0.52);
// Small frontal lobe.
float front = sdEllipsoid(q - vec3(0.0, -0.78, 0.03), vec3(0.26, 0.18, 0.18));
shield = softUnion(shield, front, 0.12);
return shield;
}
float carapaceSDF(vec3 q, float t) {
float d = 10.0;
// Long slender dorsal body / carapace.
for (int i = 0; i < 17; i++) {
float fi = float(i);
float u = fi / 16.0;
float y = mix(-0.08, 1.42, u);
float taper = 1.0 - u * 0.42;
float phase = sin(t * 1.12 - fi * 0.42) * 0.012;
vec3 c = vec3(
sin(fi * 0.45 + t * 0.40) * 0.018 * (1.0 - u),
y + phase,
0.04 * sin(fi * 0.35 - t * 0.5)
);
vec3 r = vec3(
0.28 * taper,
0.078,
0.155 * taper
);
float seg = sdEllipsoid(q - c, r);
// Segment ridges.
float groove = abs(fract((q.y + 0.06) * 12.0) - 0.5) - 0.42;
seg += groove * 0.009;
d = softUnion(d, seg, 0.045);
}
// Thin dorsal plate line.
float dorsal = sdCapsule(
q,
vec3(0.0, -0.02, 0.18),
vec3(0.0, 1.48, 0.18),
0.035
);
d = softUnion(d, dorsal, 0.035);
return d;
}
float hornSpinesSDF(vec3 q, float t) {
float d = 10.0;
// Four horn-like spines: two pairs sweeping outward-backward from head shield.
// References show them curving laterally and slightly posteriorly, not upward.
for (int i = 0; i < 4; i++) {
float fi = float(i);
float side = fi < 2.0 ? -1.0 : 1.0;
float pair = mod(fi, 2.0); // 0 = inner/shorter, 1 = outer/longer
// Root at head shield edge
vec3 a = vec3(
side * mix(0.14, 0.22, pair),
mix(-0.52, -0.38, pair),
mix(0.12, 0.20, pair)
);
// Mid sweeps outward and slightly upward (dorsal)
vec3 mid = vec3(
side * (0.68 + pair * 0.28),
mix(-0.18, 0.08, pair),
0.32 + pair * 0.18
);
// Tip curves gently backward (posterior) — matches the curved horns in references
vec3 b = vec3(
side * (1.18 + pair * 0.42),
mix(0.32, 0.62, pair) + 0.05 * sin(t * 0.62 + fi),
0.18 + pair * 0.10 + side * 0.06
);
float rRoot = mix(0.048, 0.036, pair);
float s1 = sdCapsule(q, a, mid, rRoot);
float s2 = sdCapsule(q, mid, b, rRoot * 0.48);
d = softUnion(d, s1, 0.038);
d = softUnion(d, s2, 0.028);
}
// Two long posterior sweeping spines (the backward-curving lower pair in references)
for (int i = 0; i < 2; i++) {
float side = i == 0 ? -1.0 : 1.0;
vec3 a = vec3(side * 0.20, -0.28, 0.06);
vec3 mid = vec3(side * 0.55, 0.48, -0.08);
vec3 b = vec3(side * 0.82, 1.38, -0.18 + 0.04 * sin(t * 0.65 + side));
float s1 = sdCapsule(q, a, mid, 0.040);
float s2 = sdCapsule(q, mid, b, 0.018);
d = softUnion(d, s1, 0.032);
d = softUnion(d, s2, 0.022);
}
return d;
}
float ventralLegsSDF(vec3 q, float t) {
float d = 10.0;
// Dense but restrained ventral appendages.
for (int i = 0; i < 26; i++) {
float fi = float(i);
float u = fi / 25.0;
float y = mix(-0.05, 1.32, u);
for (int s = 0; s < 2; s++) {
float side = s == 0 ? -1.0 : 1.0;
float gait = sin(t * 2.15 - fi * 0.48 + side);
float taper = 1.0 - u * 0.50;
vec3 root = vec3(
side * (0.16 * taper),
y,
-0.03
);
vec3 mid = root + vec3(
side * (0.20 + 0.030 * gait),
-0.05,
-0.18 + 0.045 * gait
);
vec3 tip = root + vec3(
side * (0.32 + 0.050 * gait),
-0.14,
-0.36 + 0.075 * gait
);
float r0 = mix(0.014, 0.0036, u);
d = softUnion(d, sdCapsule(q, root, mid, r0), 0.013);
d = softUnion(d, sdCapsule(q, mid, tip, r0 * 0.55), 0.009);
}
}
return d;
}
float anteriorAntennaeSDF(vec3 q, float t) {
float d = 10.0;
// Antennae: two long whips from head, sweeping forward then drooping down.
for (int s = 0; s < 2; s++) {
float side = s == 0 ? -1.0 : 1.0;
vec3 prev = vec3(side * 0.12, -0.72, 0.06);
for (int j = 1; j <= 20; j++) {
float u = float(j) / 20.0;
float wave = sin(t * 0.95 - u * 4.2 + side * 0.7) * 0.055 * u;
float droop = u * u * 0.55; // strong droop — hangs forward and down
vec3 next = vec3(
side * (0.10 + u * 0.18), // minimal lateral spread
-0.70 - u * 1.20, // forward along head axis
0.05 + droop + wave // droop forward-downward
);
float r = mix(0.020, 0.0024, u);
d = softUnion(d, sdCapsule(q, prev, next, r), 0.012);
prev = next;
}
}
return d;
}
float smallPalpsSDF(vec3 q, float t) {
float d = 10.0;
// Smaller front appendages under the head.
for (int i = 0; i < 6; i++) {
float fi = float(i);
float side = mod(fi, 2.0) < 1.0 ? -1.0 : 1.0;
float row = floor(fi * 0.5);
vec3 a = vec3(side * (0.10 + row * 0.05), -0.62 + row * 0.08, -0.04);
vec3 b = a + vec3(side * (0.18 + row * 0.04), -0.22, -0.18 + 0.035 * sin(t + fi));
float palp = sdCapsule(q, a, b, mix(0.016, 0.009, row / 3.0));
d = softUnion(d, palp, 0.013);
}
return d;
}
float creatureSDF(vec3 p, float t) {
vec3 q = bendSpace(p, t);
float head = headSDF(q, t);
float body = carapaceSDF(q, t);
float spines = hornSpinesSDF(q, t);
float legs = ventralLegsSDF(q, t);
float antennae = anteriorAntennaeSDF(q, t);
float palps = smallPalpsSDF(q, t);
float d = head;
d = softUnion(d, body, 0.105);
d = softUnion(d, spines, 0.052);
d = softUnion(d, legs, 0.030);
d = softUnion(d, antennae, 0.032);
d = softUnion(d, palps, 0.022);
float grain = (fbm(q * 6.4 + vec3(0.0, t * 0.15, 0.0)) - 0.5) * 0.010;
return d + grain;
}
vec3 getNormal(vec3 p, float t) {
float e = 0.002;
vec2 k = vec2(1.0, -1.0);
return normalize(
k.xyy * creatureSDF(p + k.xyy * e, t) +
k.yyx * creatureSDF(p + k.yyx * e, t) +
k.yxy * creatureSDF(p + k.yxy * e, t) +
k.xxx * creatureSDF(p + k.xxx * e, t)
);
}
float rayMarch(vec3 ro, vec3 rd, float t) {
float d = 0.0;
for (int i = 0; i < MAX_STEPS; i++) {
float s = creatureSDF(ro + rd * d, t);
if (s < SURF_DIST || d > MAX_DIST) {
break;
}
d += max(s * 0.66, 0.004);
}
return d;
}
// ---------- 2D projection helpers ----------
float distSeg(vec2 p, vec2 a, vec2 b) {
vec2 pa = p - a;
vec2 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}
vec2 project(vec3 p, Cam c) {
vec3 rel = p - c.ro;
float zc = max(dot(rel, c.fwd), 0.06);
return vec2(
dot(rel, c.rgt) / zc,
dot(rel, c.up) / zc
);
}
// ---------- atmosphere ----------
float caustic(vec2 p, float t) {
vec2 q = p * 1.55;
float c = 0.0;
for (int i = 0; i < 4; i++) {
float fi = float(i);
q += 0.24 * vec2(
cos(t * 0.22 + fi * 2.0),
sin(t * 0.18 + fi * 1.6)
);
c += sin(q.x * (2.2 + fi) + t * (0.42 + fi * 0.08))
* cos(q.y * (2.6 + fi) - t * 0.32);
}
return pow(0.5 + 0.5 * c / 4.0, 3.4);
}
float marineSnow(vec2 p, float t) {
float s = 0.0;
for (int i = 0; i < 7; i++) {
float fi = float(i);
vec2 drift = p * (1.0 + fi * 0.56)
+ vec2(
t * 0.022 * (fi + 1.0),
-t * 0.016 * (fi + 0.5)
);
vec2 cell = floor(drift);
vec2 f = fract(drift) - 0.5;
float h = hash11(dot(cell, vec2(41.7, 289.3)) + fi * 19.1);
if (h > 0.984) {
s += smoothstep(0.05, 0.0, length(f)) * (1.0 - fi * 0.09);
}
}
return s;
}
// ---------- gill lamellae glow lines ----------
// Marrella's most iconic feature: dense stacked lateral gill flaps
// extending from each body segment, fanning outward and slightly downward.
// Reference: expansion_marrella.jpg (blue flap stacks), download.jpg (rainbow iridescence).
vec3 renderGills(vec2 st, Cam cam, float t) {
vec3 col = vec3(0.0);
vec3 off = creatureOffset(t);
// TODO(human): implement the gill lamellae glow lines.
// Each body segment (use the same ~17 segment positions as carapaceSDF, y in [-0.05, 1.42])
// should emit ~5-8 gill rays per side, fanning outward in X and slightly downward in Z.
// Each ray = one projected line segment from root (near body centerline) to tip (lateral/down).
// Animate each lamella with a gentle wave (use sin(t + segment phase)).
// Color: mix between CYAN and VIOLET for the inner lamellae, ACID for tips.
// Glow: exp(-d * 110.0) * 0.09 per segment, plus smoothstep line core.
// Tip: project() and distSeg() work the same as in renderFilaments below.
return col;
}
// ---------- projected glow appendage lines ----------
vec3 renderFilaments(vec2 st, Cam cam, float t) {
vec3 col = vec3(0.0);
vec3 off = creatureOffset(t);
// Ventral gill-filaments: dense fan-like appendages in PINK <-> CYAN,
// matching Marrella.png — inner filaments blue, outer tips pink.
for (int i = 0; i < 26; i++) {
float fi = float(i);
float side = mod(fi, 2.0) < 1.0 ? -1.0 : 1.0;
float row = floor(fi * 0.5);
float urow = row / 12.0;
float segY = mix(-0.04, 1.32, urow);
vec3 rootLocal = vec3(
side * (0.14 - urow * 0.04),
segY,
-0.02
);
rootLocal = marrellaPose(rootLocal);
vec3 prev = off + rootLocal;
// More filaments per segment (8 instead of 6) for the dense fan look
for (int j = 1; j <= 8; j++) {
float u = float(j) / 8.0;
float wave = sin(t * 1.85 - u * 5.2 + fi * 0.55) * 0.10 * u;
// Fan outward in X and downward in Z — matches the splayed filaments in Marrella.png
vec3 nextLocal = vec3(
side * (0.14 + u * 0.42 - urow * 0.03),
segY - u * 0.10,
-0.05 - u * 0.38 + wave
);
nextLocal = marrellaPose(nextLocal);
vec3 next = off + nextLocal;
vec2 a2 = project(prev, cam);
vec2 b2 = project(next, cam);
float d = distSeg(st, a2, b2);
float line = smoothstep(0.0015, 0.0, d);
float glow = exp(-d * 80.0) * 0.085;
float pulse = pow(max(sin(t * 1.8 - u * TAU + fi * 0.72), 0.0), 2.5);
// Inner (u=0) = CYAN blue, outer tip (u=1) = PINK — exactly like the reference
vec3 c = mix(CYAN, PINK, u * u);
c = mix(c, VIOLET, 0.18 * (1.0 - u)); // slight violet tint at root
col += c
* (line * 0.38 + glow * (0.45 + pulse * 0.35))
* (1.0 - urow * 0.30)
* VEIL_POWER;
prev = next;
}
}
// Abdomen rib glow.
for (int i = 0; i < 17; i++) {
float fi = float(i);
float u = fi / 16.0;
float y = mix(-0.06, 1.42, u);
float taper = 1.0 - u * 0.42;
vec3 p0Local = vec3(-0.25 * taper, y, 0.05 * sin(fi));
vec3 p1Local = vec3( 0.25 * taper, y, 0.05 * sin(fi));
p0Local = marrellaPose(p0Local);
p1Local = marrellaPose(p1Local);
vec3 p0 = off + p0Local;
vec3 p1 = off + p1Local;
vec2 a2 = project(p0, cam);
vec2 b2 = project(p1, cam);
float d = distSeg(st, a2, b2);
float rib = exp(-d * 138.0) * 0.105;
col += mix(VIOLET, CYAN, 0.56) * rib * GLOW_POWER;
}
// Long anterior antenna glow — matches updated SDF (sweep forward/anterior).
for (int s = 0; s < 2; s++) {
float side = s == 0 ? -1.0 : 1.0;
vec3 prevLocal = vec3(side * 0.12, -0.72, 0.06);
prevLocal = marrellaPose(prevLocal);
vec3 prev = off + prevLocal;
for (int j = 1; j <= 20; j++) {
float u = float(j) / 20.0;
float wave = sin(t * 0.95 - u * 4.2 + side * 0.7) * 0.055 * u;
float droop = u * u * 0.55;
vec3 nextLocal = vec3(
side * (0.10 + u * 0.18),
-0.70 - u * 1.20,
0.05 + droop + wave
);
nextLocal = marrellaPose(nextLocal);
vec3 next = off + nextLocal;
vec2 a2 = project(prev, cam);
vec2 b2 = project(next, cam);
float d = distSeg(st, a2, b2);
// Antenna: single thin glowing line, ACID green — simple whip shape
col += ACID
* (smoothstep(0.0012, 0.0, d) * 0.45 + exp(-d * 120.0) * 0.038)
* (1.0 - u * 0.55)
* GLOW_POWER;
prev = next;
}
}
return col;
}
// ---------- main ----------
void main() {
vec2 uv = vUV.st;
vec2 st = uv * 2.0 - 1.0;
st.x *= uTDOutputInfo.res.z / uTDOutputInfo.res.w;
float t = iTime;
Cam cam = makeCam(t);
float lens = 1.18 + 0.018 * sin(t * 0.18 + 0.6);
vec3 rd = normalize(
cam.fwd +
cam.rgt * st.x * lens * 0.94 +
cam.up * st.y * lens
);
// ---------- background ----------
vec3 col = vec3(0.002, 0.004, 0.014);
col += vec3(0.028, 0.030, 0.086)
* smoothstep(-0.88, 1.0, st.y);
col += vec3(0.028, 0.088, 0.145)
* caustic(st * 0.74 + vec2(t * 0.026, 0.0), t)
* smoothstep(-0.58, 0.95, st.y);
// ---------- raymarch ----------
float d = rayMarch(cam.ro, rd, t);
bool hit = d < MAX_DIST;
vec3 p = cam.ro + rd * d;
if (hit) {
vec3 n = getNormal(p, t);
vec3 L1 = normalize(vec3(-0.42, 0.82, -0.28));
vec3 L2 = normalize(vec3(0.62, -0.10, 0.70));
float diff = max(dot(n, L1), 0.0);
float rim = pow(1.0 - max(dot(-rd, n), 0.0), 2.70);
float back = pow(max(dot(L2, rd), 0.0), 2.1);
vec3 local = bendSpace(p, t);
float headRegion = smoothstep(0.10, -0.58, local.y);
float dorsalRegion = smoothstep(-0.04, 0.24, local.z);
float stripes = 0.5 + 0.5 * sin(local.y * 28.0 - t * 1.5);
float livingNoise = fbm(local * 5.8 + vec3(0.0, t * 0.18, 0.0));
vec3 headCol = mix(ACID, CYAN, 0.22);
vec3 abdomenCol = mix(VIOLET, CYAN, 0.50);
vec3 spineCol = mix(PINK, ACID, 0.35);
vec3 body = mix(abdomenCol, headCol, headRegion) * 0.22;
body = mix(body, spineCol * 0.26, dorsalRegion * 0.24);
body += CYAN * diff * 0.28;
body += VIOLET * 0.15;
body += ACID * back * 0.52;
body += PINK * stripes * rim * 0.28;
body += CYAN * rim * (0.88 + livingNoise * 0.54);
float alpha = 0.74 + rim * 0.64 + back * 0.32;
col = mix(col, body, clamp(alpha, 0.0, 0.92));
float core = exp(
-length((local - vec3(0.0, -0.34, 0.0)) * vec3(1.5, 1.7, 1.2)) * 2.35
);
col += mix(
VIOLET,
ACID,
0.34 + 0.32 * sin(t * 1.25)
) * core * 0.38 * GLOW_POWER;
}
// ---------- projected glow ----------
col += renderGills(st, cam, t) * (1.35 + 0.10 * sin(t * 0.85));
col += renderFilaments(st, cam, t) * (1.28 + 0.08 * sin(t * 1.2));
// ---------- particles ----------
float snow = marineSnow(st * 1.22, t);
col += vec3(0.70, 0.82, 1.0) * snow * 0.66;
for (int i = 0; i < 11; i++) {
float fi = float(i);
vec2 center = vec2(
sin(fi * 12.7 + t * 0.10),
cos(fi * 9.1 - t * 0.075)
);
center.x *= 0.74;
center.y = fract(center.y * 0.5 + 0.5 + t * 0.021 + fi * 0.13) * 2.4 - 1.2;
float dd = length(st - center);
vec3 cc = mix(VIOLET, CYAN, hash11(fi * 4.7));
col += cc * exp(-dd * 25.0) * 0.050;
}
// ---------- post ----------
col *= INTENSITY * 1.06;
col += pow(max(col, 0.0), vec3(1.28)) * 0.38;
float vignette = 1.0 - 0.18 * smoothstep(0.22, 1.62, length(st));
col *= vignette;
col = col / (0.84 + col);
col = pow(col, vec3(1.0 / 0.96));
float grain = (hash11(uv.x * 1234.5 + uv.y * 987.6 + t) - 0.5) * 0.018;
col += grain;
fragColor = TDOutputSwizzle(vec4(col, 1.0));
}