Files
deepgrain-foundry/src/render/smooth-shaders.js
T
44r0n7andClaude Opus 5.5 1f8a89db6a Rename screw tubes to screw conveyors (t44)
Player text says Screw conveyor; code uses screw (BP_SCREW, piece id
screw, screwIntake...). No compatibility code for old saves or
blueprints. Suite outputs match before and after apart from names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:59:13 -04:00

370 lines
19 KiB
JavaScript

// Smooth shaders: vertex and fragment programs for the rounded world look.
const SM_VS = `#version 300 es
void main() {
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
}`;
const SM_FS = `#version 300 es
precision highp float;
precision highp int;
uniform sampler2D tCol, tColL, tBack, tTree, tField, tLight, tGlow;
uniform vec2 uSize, uOrigin, uLightSize, uGlowSize;
uniform vec4 uView, uLightMap;
uniform float uGlowK, uTime, uSurf, uRain;
out vec4 o;
float h21(vec2 p) { p = fract(p * vec2(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); }
float vn(vec2 p) {
vec2 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f);
return mix(mix(h21(i), h21(i + vec2(1, 0)), f.x), mix(h21(i + vec2(0, 1)), h21(i + vec2(1, 1)), f.x), f.y);
}
float fbm(vec2 p) { float a = 0.5, s = 0.0; for (int k = 0; k < 4; k++) { s += a * vn(p); p = p * 2.03 + 17.1; a *= 0.5; } return s / 0.9375; }
vec4 lin(sampler2D t, vec2 q) { return textureLod(t, q / uSize, 0.0); }
// cubic B-spline from four bilinear taps: rounds a field of 0s and 1s into smooth shapes
vec4 bsp(sampler2D t, vec2 q) {
vec2 tc = q - 0.5, i = floor(tc), f = tc - i, f2 = f * f, f3 = f2 * f;
vec2 w0 = (1.0 - 3.0 * f + 3.0 * f2 - f3) / 6.0, w1 = (4.0 - 6.0 * f2 + 3.0 * f3) / 6.0;
vec2 w2 = (1.0 + 3.0 * f + 3.0 * f2 - 3.0 * f3) / 6.0, w3 = f3 / 6.0;
vec2 g0 = w0 + w1, g1 = w2 + w3;
vec2 h0 = (i - 0.5 + w1 / g0) / uSize, h1 = (i + 1.5 + w3 / g1) / uSize;
return g0.y * (g0.x * textureLod(t, h0, 0.0) + g1.x * textureLod(t, vec2(h1.x, h0.y), 0.0))
+ g1.y * (g0.x * textureLod(t, vec2(h0.x, h1.y), 0.0) + g1.x * textureLod(t, h1, 0.0));
}
vec4 cellAt(sampler2D t, ivec2 c) { return texelFetch(t, clamp(c, ivec2(0), ivec2(uSize) - 1), 0); }
int cls(vec4 c) { return int(c.a * 255.0 + 0.5); }
float isT(int n) { return (n == 1 || n == 4 || n == 6 || (n >= 7 && n <= 12)) ? 1.0 : 0.0; }
float isM(int n) { return n == 2 ? 1.0 : isT(n); } // matter: ground, built pieces or loose grains
float isF(int n) { return (n == 13 || n == 14) ? 1.0 : 0.0; }
float isBelt(int n) { return (n == 7 || n == 8) ? 1.0 : 0.0; }
float isN(int n, int k) { return n == k ? 1.0 : 0.0; }
// signed distance (in grains) to the outline of one block of belt: a band 3.7 grains tall; where
// the line of belts ends, the end is square with rounded corners. Negative inside.
float beltSD(vec2 lb, bool endL, bool endR) {
float x0 = endL ? 0.15 : -2.0, x1 = endR ? 3.85 : 6.0, r = 0.75;
vec2 c = vec2((x0 + x1) * 0.5, 2.0), hs = vec2((x1 - x0) * 0.5, 1.85);
vec2 q = abs(lb - c) - hs + r;
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
}
// The outline of the belt or filter in cell c. Belts and filters side by side are one belt, so
// the line only rounds off where the whole run ends.
float beltLineSD(ivec2 c, vec2 lb) {
int caps = int(cellAt(tTree, c).a * 255.0 + 0.5);
return beltSD(lb, (caps & 1) != 0, (caps & 2) != 0);
}
// The rubber band of a belt or filter: 1 on a ridge. Ridges travel along the top run one way and
// back along the bottom.
float bandRidge(vec2 w, vec2 lb, float dir) {
float run = lb.y < 2.0 ? 1.0 : -1.0;
return step(0.72, fract((w.x - dir * run * uTime * 30.0) * 0.5));
}
vec3 bandColor(vec2 lb, float rg) { return mix(vec3(0.2, 0.21, 0.24), vec3(0.34, 0.35, 0.39), rg) * (lb.y < 2.0 ? 1.0 : 0.72); }
${SM_SLOPE_HELPERS}
void main() {
float zp = uView.z, px = 1.0 / zp;
vec2 sp = vec2(gl_FragCoord.x, uView.w - gl_FragCoord.y);
vec2 q = sp * px + uView.xy; // position in grains, in the window of grains uploaded
vec2 w = q + uOrigin; // position in the world
vec2 cam = uOrigin + uView.xy; // world position of the screen's top-left corner
vec2 scr = q - uView.xy; // grains from the screen's top-left corner
float shc = uView.w * px; // screen height in grains
ivec2 c = ivec2(floor(q));
vec2 qq = q - 0.5; ivec2 c0 = ivec2(floor(qq)); vec2 t = qq - floor(qq);
vec4 k0 = cellAt(tCol, c0), k1 = cellAt(tCol, c0 + ivec2(1, 0)), k2 = cellAt(tCol, c0 + ivec2(0, 1)), k3 = cellAt(tCol, c0 + ivec2(1, 1));
int n0 = cls(k0), n1 = cls(k1), n2 = cls(k2), n3 = cls(k3);
vec4 own = cellAt(tCol, c); int nc = cls(own);
vec2 s = smoothstep(0.1, 0.9, t);
vec4 wq = vec4((1.0 - s.x) * (1.0 - s.y), s.x * (1.0 - s.y), (1.0 - s.x) * s.y, s.x * s.y);
// fields, and everything that needs screen derivatives (worked out before any branching)
vec4 Fb = bsp(tField, q);
vec4 F = mix(lin(tField, q), Fb, 0.62);
// loose-grain occupancy from the four grains around (bilinear): ground and grains together make "matter"
vec4 wl = vec4((1.0 - t.x) * (1.0 - t.y), t.x * (1.0 - t.y), (1.0 - t.x) * t.y, t.x * t.y);
float pw = dot(wl, vec4(isN(n0, 2), isN(n1, 2), isN(n2, 2), isN(n3, 2)));
float M = clamp(F.r + pw, 0.0, 1.0);
float lf = clamp(mix(F.g, Fb.g, 0.6) + F.r + pw, 0.0, 1.0);
float fwT = max(fwidth(F.r), 1e-4), fwM = max(fwidth(M), 1e-4), fwA = max(fwidth(F.a), 1e-4), fwL = max(fwidth(lf), 1e-4);
vec2 gT = vec2(dFdx(mix(Fb.r, M, 0.5)), dFdy(mix(Fb.r, M, 0.5))), gL = vec2(dFdx(F.g), dFdy(F.g));
// ---- behind everything: sky with clouds and far hills, or the cave's back wall
vec4 back = lin(tBack, q);
vec3 C = back.rgb;
float sky = back.a;
if (sky > 0.01) {
vec3 S = C;
vec2 P0 = scr + cam * 0.08;
// clouds; while it rains they thicken and turn grey
float cl = smoothstep(0.56 - 0.3 * uRain, 0.78 - 0.2 * uRain, fbm(vec2(P0.x * 0.016 + uTime * 0.006, P0.y * 0.05)));
float hi = clamp((uSurf - 40.0 - w.y) / 50.0, 0.0, 1.0);
S = mix(S, mix(vec3(0.96, 0.95, 0.93), vec3(0.5, 0.52, 0.56), uRain), cl * (0.6 + 0.3 * uRain) * hi);
float k1 = 0.16; vec2 P1 = scr + cam * k1;
float base1 = shc * 0.5 + 8.0 + (uSurf - shc * 0.5) * k1;
float top1 = base1 - 36.0 * (0.25 + 0.9 * fbm(vec2(P1.x * 0.009, 1.7)));
float m1 = smoothstep(top1 - px * 1.5, top1 + px * 1.5, P1.y);
vec3 far = mix(vec3(0.50, 0.60, 0.72), S, 0.35) * (0.94 + 0.08 * fbm(P1 * 0.05));
S = mix(S, far, m1);
float k2 = 0.38; vec2 P2 = scr + cam * k2;
float base2 = shc * 0.5 + 14.0 + (uSurf - shc * 0.5) * k2;
float top2 = base2 - 20.0 * (0.2 + 0.9 * fbm(vec2(P2.x * 0.016, 7.3)));
float m2 = smoothstep(top2 - px * 1.5, top2 + px * 1.5, P2.y);
vec3 near = vec3(0.36, 0.5, 0.47) * (0.92 + 0.14 * fbm(P2 * 0.08));
near = mix(near, S, 0.42 - 0.2 * smoothstep(top2, top2 + 30.0, P2.y));
S = mix(S, near, m2);
C = mix(C, S, sky);
}
// the back wall: rough rock, and darker close to the tunnel walls (ambient occlusion)
float notSky = 1.0 - sky;
C *= mix(1.0, 1.08 + 0.4 * fbm(w * 0.21), notSky);
float near = textureLod(tField, q / uSize, 2.2).r;
C *= 1.0 - 0.34 * smoothstep(0.12, 0.85, near) * (1.0 - 0.75 * sky);
// ---- trees (behind the ground and everything on it)
if (F.a > 0.2) {
vec4 a0 = cellAt(tTree, c0), a1 = cellAt(tTree, c0 + ivec2(1, 0)), a2 = cellAt(tTree, c0 + ivec2(0, 1)), a3 = cellAt(tTree, c0 + ivec2(1, 1));
vec4 m = vec4(step(0.4, a0.a), step(0.4, a1.a), step(0.4, a2.a), step(0.4, a3.a)) * wq;
float sm = m.x + m.y + m.z + m.w;
if (sm > 1e-4) {
vec3 tc = (a0.rgb * m.x + a1.rgb * m.y + a2.rgb * m.z + a3.rgb * m.w) / sm;
float leaf = (a0.a * m.x + a1.a * m.y + a2.a * m.z + a3.a * m.w) / sm;
float lv = smoothstep(0.6, 0.9, leaf);
tc *= mix(0.86 + 0.22 * vn(vec2(w.x * 1.4, w.y * 0.25)), 0.72 + 0.5 * fbm(w * 0.42), lv);
float d = (F.a - 0.36) / fwA;
tc *= mix(0.55, 1.0, smoothstep(0.4, 1.6, d));
C = mix(C, tc, smoothstep(-0.7, 0.7, d));
}
}
// ---- smoke, steam and flame: soft, see-through puffs
if (Fb.b > 0.02) {
vec4 m = vec4(isN(n0, 5), isN(n1, 5), isN(n2, 5), isN(n3, 5)) * wq;
float sm = m.x + m.y + m.z + m.w;
vec3 gc = sm > 1e-4 ? (k0.rgb * m.x + k1.rgb * m.y + k2.rgb * m.z + k3.rgb * m.w) / sm : own.rgb;
if (sm > 1e-4 || nc == 5) C = mix(C, gc, smoothstep(0.03, 0.55, Fb.b) * 0.92);
}
// ---- liquids: smooth surfaces that fill right up against the ground, with a glossy top line
float anyL = isN(n0, 3) + isN(n1, 3) + isN(n2, 3) + isN(n3, 3);
if (anyL > 0.0 && lf > 0.3) {
vec4 m = vec4(isN(n0, 3), isN(n1, 3), isN(n2, 3), isN(n3, 3)) * (wq + 0.02);
float sm = m.x + m.y + m.z + m.w;
vec3 lc = (k0.rgb * m.x + k1.rgb * m.y + k2.rgb * m.z + k3.rgb * m.w) / sm;
lc *= 0.94 + 0.1 * vn(w * 0.55 + vec2(uTime * 0.7, uTime * 0.25));
float gl = length(gL);
float topSide = gl > 1e-5 ? clamp(gL.y / gl, 0.0, 1.0) : 0.0;
float band = 1.0 - smoothstep(0.5, 0.75, F.g);
lc += vec3(0.16, 0.18, 0.2) * topSide * band;
C = mix(C, lc, smoothstep(0.5 - fwL, 0.5 + fwL, lf));
}
// ---- the ground: rounded, outlined, with a lit top edge and a shaded underside
if (F.r > 0.3) {
vec4 m = vec4(isT(n0), isT(n1), isT(n2), isT(n3)) * wq;
float sm = m.x + m.y + m.z + m.w;
vec3 blur = bsp(tColL, q).rgb, tc = own.rgb;
float bd = 1e9;
if (m.x > 0.0) { vec3 e = k0.rgb - blur; float dd = dot(e, e); if (dd < bd) { bd = dd; tc = k0.rgb; } }
if (m.y > 0.0) { vec3 e = k1.rgb - blur; float dd = dot(e, e); if (dd < bd) { bd = dd; tc = k1.rgb; } }
if (m.z > 0.0) { vec3 e = k2.rgb - blur; float dd = dot(e, e); if (dd < bd) { bd = dd; tc = k2.rgb; } }
if (m.w > 0.0) { vec3 e = k3.rgb - blur; float dd = dot(e, e); if (dd < bd) { bd = dd; tc = k3.rgb; } }
if (sm < 1e-4) tc = own.rgb;
tc *= 0.84 + 0.3 * fbm(w * 0.28);
tc *= 0.95 + 0.1 * vn(w * 1.6);
float gl = length(gT);
vec2 nrm = gl > 1e-6 ? -gT / gl : vec2(0.0);
float band = 1.0 - smoothstep(0.5, 0.9, M);
float lit = dot(nrm, normalize(vec2(-0.5, -1.0)));
tc *= 1.0 + 0.42 * lit * band;
// Keep the air outline; a softer, screen-width seam also separates rock from loose grains.
float dm = (M - 0.5) / fwM;
tc *= mix(0.4, 1.0, smoothstep(0.5, 1.4 + zp * 0.08, dm));
float dt = (F.r - 0.5) / fwT;
if (nc == 1 && pw > 0.0) tc *= mix(0.65, 1.0, smoothstep(0.0, 1.5, dt));
C = mix(C, tc, smoothstep(-0.6, 0.6, (F.r - 0.5) / fwT));
}
${SM_SLOPE_DRAW}
// ---- loose grains: little round pebbles; a pocket closed in by four grains is filled
int np = int(isN(n0, 2) + isN(n1, 2) + isN(n2, 2) + isN(n3, 2));
bool closed = isM(n0) + isM(n1) + isM(n2) + isM(n3) > 3.5;
if (np > 0) {
float best = 1e9; vec3 gc = vec3(0.0); vec2 gd = vec2(0.0); float gr = 0.6;
for (int k = 0; k < 4; k++) {
ivec2 o4 = ivec2(k & 1, k >> 1);
int nk = k == 0 ? n0 : k == 1 ? n1 : k == 2 ? n2 : n3;
if (nk != 2) continue;
vec4 ck = k == 0 ? k0 : k == 1 ? k1 : k == 2 ? k2 : k3;
vec2 ce = vec2(c0 + o4) + 0.5;
float r = 0.6 + 0.1 * (h21(ce + uOrigin) - 0.5);
vec2 dv = (q - ce) / r;
float dd = length(dv);
if (dd < best) { best = dd; gc = ck.rgb; gd = dv; gr = r; }
}
float cover = np == 4 ? 1.0 : 1.0 - smoothstep(1.0 - px / gr, 1.0 + px / gr, best);
if (closed && F.r < 0.5) cover = 1.0;
// Pebbles stop at the rock face so they cannot paint over its thin seam.
cover *= 1.0 - smoothstep(-0.6, 0.6, (F.r - 0.5) / fwT);
if (cover > 0.0) {
float r2 = min(1.0, dot(gd, gd));
vec3 n3 = normalize(vec3(gd * 0.85, sqrt(1.0 - r2) + 0.25));
float sh = 0.98 + 0.18 * max(0.0, dot(n3, normalize(vec3(-0.45, -0.8, 0.6))));
sh *= mix(1.0, 0.94, smoothstep(0.7 - px * 0.2, 1.15 + px * 0.2, best));
vec3 pc = (np == 4 ? (k0.rgb * wq.x + k1.rgb * wq.y + k2.rgb * wq.z + k3.rgb * wq.w) : gc) * sh;
if (!closed) pc *= mix(1.0, 0.72, smoothstep(0.7, 1.0, best));
C = mix(C, pc, cover);
}
}
// ---- built pieces: crisp, and wall blocks drawn as bevelled tiles with mortar lines
if (nc == 4) C = own.rgb;
else if (nc == 7 || nc == 8) {
// belt: a rubber band round two copper rollers per block; the top run moves the load
float dir = nc == 8 ? 1.0 : -1.0;
vec2 lb = fract(w * 0.25) * 4.0;
// outline: square ends with rounded corners where the line of belts (and filters) ends
float sd = beltLineSD(c, lb);
float inside = 1.0 - smoothstep(-px, px, sd);
if (inside > 0.0) {
float edge = -sd;
vec3 bc;
if (edge < 0.8) {
bc = bandColor(lb, bandRidge(w, lb, dir));
bc *= mix(0.55, 1.0, smoothstep(0.0, max(px * 1.4, 0.12), edge));
} else {
vec2 rc = vec2(lb.x < 2.0 ? 1.0 : 3.0, 2.0), dv = lb - rc;
float rd = length(dv);
bc = vec3(0.15, 0.16, 0.18);
if (rd < 0.95) {
float ang = atan(dv.y, dv.x) - dir * uTime * 30.0 / 0.95;
float spoke = smoothstep(0.55, 0.85, cos(ang * 3.0));
bc = mix(vec3(0.5, 0.36, 0.25), vec3(0.72, 0.53, 0.36), 0.5 + 0.5 * dot(normalize(dv + 1e-4), vec2(-0.6, -0.8)) * smoothstep(0.2, 0.95, rd));
bc *= mix(1.0, 0.72, spoke * smoothstep(0.25, 0.4, rd));
bc *= mix(1.0, 0.55, smoothstep(0.82, 0.95, rd));
}
}
C = mix(C, bc, inside);
}
}
else if ((nc >= 9 && nc <= 12) || (nc >= 19 && nc <= 22)) {
// screw conveyor: a copper pipe; what is inside shows through, and the screw's flights turn
bool vert = nc == 9 || nc == 11;
float sp2 = nc == 9 ? -1.0 : nc == 10 ? 1.0 : nc == 11 ? 1.0 : -1.0;
float a = (vert ? fract(w.x * 0.25) : fract(w.y * 0.25)) * 4.0, along = vert ? w.y : w.x;
float screwSD = -1.0, capRing = 0.0;
if (nc >= 19) {
vec2 d = vec2(1.0, nc == 19 || nc == 21 ? -1.0 : 1.0);
vec4 tag = cellAt(tTree, c);
vec2 local = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w) - 2.0;
int joins = int(tag.a * 255.0 + 0.5);
screwSD = 100.0;
for (int end = 0; end < 2; end++) {
int join = (joins >> (end * 3)) & 7;
float side = end == 0 ? -1.0 : 1.0;
vec2 ray = join == 0 ? vec2(side, side * (nc == 19 || nc == 21 ? -1.0 : 1.0)) * 0.70710678 :
join == 1 ? vec2(0, -1) : join == 2 ? vec2(1, 0) : join == 3 ? vec2(0, 1) : vec2(-1, 0);
float t = dot(local, ray), cross2 = dot(local, vec2(-ray.y, ray.x));
float radius = length(vec2(cross2, min(0.0, t))), cap = t - (join == 0 ? 2.82842712 : 2.0);
float sd = max(radius - 2.0, join != 0 || (joins & (64 << end)) != 0 ? cap : -10.0);
if (sd < screwSD) {
screwSD = sd;
a = 2.0 + (side * cross2 < 0.0 ? -radius : radius);
along = dot(w - local, d) * 0.70710678 + side * t;
capRing = (joins & (64 << end)) != 0 ? smoothstep(-0.55, -0.15, cap) : 0.0;
}
}
sp2 = nc == 19 || nc == 20 ? 1.0 : -1.0;
}
float wall = 0.62;
vec3 tc;
if (a < wall || a > 4.0 - wall || capRing > 0.5) {
float u2 = a < wall ? a / wall : (4.0 - a) / wall; // 0 at the outside, 1 at the bore
float cyl = sin(clamp(u2, 0.0, 1.0) * 1.5708);
tc = vec3(0.62, 0.41, 0.27) * (0.6 + 0.55 * cyl);
if (a < wall) tc *= 1.08;
float ring = 1.0 - smoothstep(0.0, 0.3, min(fract(along * 0.25), 1.0 - fract(along * 0.25)) * 4.0);
if (nc < 19) tc *= 1.0 - 0.3 * ring;
tc *= mix(0.5, 1.0, smoothstep(0.0, max(px * 1.3, 0.1), min(a, 4.0 - a)));
} else {
bool full = own.r + own.g + own.b > 0.0;
float fl = fract((along + a * 0.9 - sp2 * uTime * 14.0) * 0.5);
float flight = smoothstep(0.82, 0.92, fl) * (1.0 - smoothstep(0.92, 1.0, fl));
vec3 bore = vec3(0.11, 0.1, 0.11) * (0.8 + 0.4 * (1.0 - abs(a - 2.0) / 1.4));
tc = full ? own.rgb * (0.9 + 0.1 * vn(w * 1.3)) : bore;
tc = mix(tc, vec3(0.6, 0.48, 0.38), flight * (full ? 0.35 : 0.8));
tc *= 1.0 - 0.25 * smoothstep(0.9, 1.3, abs(a - 2.0));
}
if (nc >= 19) {
bool full = own.r + own.g + own.b > 0.0;
if (full) C = mix(C, own.rgb, 1.0 - smoothstep(0.36, 0.5, length(fract(w) - 0.5)));
C = mix(C, tc, 1.0 - smoothstep(2.0 - px, 2.0 + px, screwSD + 2.0));
} else C = tc;
}
else if (nc == 6 || (nc >= 15 && nc <= 18)) {
vec2 lb = fract(w * 0.25) * 4.0, rb = 4.0 - lb;
float e = min(min(lb.x, lb.y), min(rb.x, rb.y));
vec3 tc = own.rgb * (0.92 + 0.14 * fbm(w * 0.55));
float hl = max(1.0 - smoothstep(0.0, 0.8, lb.x), 1.0 - smoothstep(0.0, 0.8, lb.y));
float shd = max(1.0 - smoothstep(0.0, 0.8, rb.x), 1.0 - smoothstep(0.0, 0.8, rb.y));
tc *= 1.0 + 0.2 * hl - 0.26 * shd;
tc *= mix(0.5, 1.0, smoothstep(px * 0.5, px * 0.5 + max(px * 1.2, 0.14), e));
if (nc >= 15) {
vec2 sl = lb;
if (nc == 16 || nc == 18) sl.x = 4.0 - sl.x;
if (nc >= 17) sl.y = 4.0 - sl.y;
float d = (sl.y - sl.x) * 0.70710678;
tc *= 1.0 + 0.2 * (1.0 - smoothstep(0.0, 0.6, d));
C = mix(C, tc, smoothstep(-px * 0.5, px * 0.5, d));
} else C = tc;
}
else if (nc == 13 || nc == 14) {
// filter: a belt whose middle is a see-through steel mesh. It has the same band as a belt, so a
// row of belts and filters reads as one belt, rounded only where the whole line ends. The
// band is perforated; its colour tag sits on the top run for what rides along, on the
// bottom run for what drops through.
float dir = nc == 14 ? 1.0 : -1.0;
vec2 lb = fract(w * 0.25) * 4.0;
vec4 tag = cellAt(tTree, c);
bool carry = tag.a > 0.16, tinted = tag.r + tag.g + tag.b > 0.0;
float sd = beltLineSD(c, lb);
float inside = 1.0 - smoothstep(-px, px, sd);
if (inside > 0.0) {
float edge = -sd;
vec3 steel = vec3(0.42, 0.44, 0.5);
// through the holes: whatever is behind (water, sky, rock), or what is passing through
vec3 behind = own.r + own.g + own.b > 0.0 ? own.rgb : C * 0.8;
vec2 cg = abs(fract(w) - 0.5);
float bw2 = max(px * 0.9, 0.09);
float hole = 1.0 - smoothstep(0.5 - bw2 - px, 0.5 - bw2, max(cg.x, cg.y));
vec3 fc;
if (edge < 0.8) {
float rg = bandRidge(w, lb, dir);
vec3 band = bandColor(lb, rg);
if (tinted && (carry == (lb.y < 2.0))) band = mix(band, tag.rgb * (lb.y < 2.0 ? 1.0 : 0.8), 0.75 * rg);
fc = mix(band, behind * 0.85, hole * (1.0 - rg) * 0.75);
fc *= mix(0.55, 1.0, smoothstep(0.0, max(px * 1.4, 0.12), edge));
} else {
fc = mix(steel * 0.78, behind, hole);
fc *= mix(0.7, 1.0, smoothstep(0.0, max(px * 1.4, 0.12), edge - 0.8));
}
C = mix(C, fc, inside);
}
}
// ---- light, then the glow of hot things on top
if (uLightMap.w > 0.5) C *= textureLod(tLight, (q - uLightMap.xy) / uLightMap.z / uLightSize, 0.0).rgb;
if (uGlowK > 0.0) {
vec2 gu = q / (2.0 * uGlowSize), d = 1.0 / uGlowSize;
vec3 g = textureLod(tGlow, gu, 0.0).rgb * 0.25;
g += (textureLod(tGlow, gu + vec2(d.x, 0), 0.0).rgb + textureLod(tGlow, gu - vec2(d.x, 0), 0.0).rgb + textureLod(tGlow, gu + vec2(0, d.y), 0.0).rgb + textureLod(tGlow, gu - vec2(0, d.y), 0.0).rgb) * 0.125;
g += (textureLod(tGlow, gu + d, 0.0).rgb + textureLod(tGlow, gu - d, 0.0).rgb + textureLod(tGlow, gu + vec2(d.x, -d.y), 0.0).rgb + textureLod(tGlow, gu + vec2(-d.x, d.y), 0.0).rgb) * 0.0625;
C += g * uGlowK;
}
o = vec4(C, 1.0);
}`;