Smooth look on WebGPU: WGSL port fed by GPU inputs and CPU overrides (t88 phase 2c)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 22:42:12 -04:00
co-authored by Claude Opus 5.5
parent 65054e691b
commit 452b6aed32
26 changed files with 780 additions and 80 deletions
+107
View File
@@ -0,0 +1,107 @@
// Smooth slope geometry and cleats, ported from slope-belt-shader.js.
const GPU_SMOOTH_SLOPE_HELPERS = `fn slopeStroke(p: vec2f, a: vec2f, b: vec2f, origin: f32) -> vec3f {
var ray: vec2f = b - a; var len: f32 = length(ray); ray /= len;
var along: f32 = dot(p - a, ray); var across: f32 = dot(p - a, vec2f(-ray.y, ray.x));
var q: vec2f = abs(vec2f(along - len * 0.5, across)) - vec2f(len * 0.5 - 0.75, 1.1);
var sd: f32 = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - 0.75;
return vec3f(sd, origin + along, across + 2.0);
}
fn slopeCorner(p: vec2f, a: vec2f, origin: f32) -> vec3f {
var d: vec2f = p - a; var radius: f32 = length(d);
return vec3f(radius - 1.85, origin + d.x, 2.0 + d.y);
}
fn slopeShape(inputP: vec2f, worldOrigin: vec2f, grade: f32, joins: i32) -> vec3f { var p=inputP;
if ((joins & 64) != 0) { p.y -= 0.45; }
var sy: f32 = select( 0.0, 4.0 , grade < 0.0 );
var a: vec2f = vec2f(-grade * 1.30814755, sy + 1.30814755); var b: vec2f = vec2f(4.0 - grade * 1.30814755, 4.0 - sy + 1.30814755);
if ((joins & 1) != 0) { a.y = sy + 2.0; a.x = (a.y - sy - 2.61629509) / grade; }
else if ((joins & 4) != 0) { a -= vec2f(4.0, grade * 4.0); }
if ((joins & 2) != 0) { b.y = 6.0 - sy; b.x = (b.y - sy - 2.61629509) / grade; }
else if ((joins & 8) != 0) { b += vec2f(4.0, grade * 4.0); }
var axis: vec2f = vec2f(1.0, grade) * 0.70710678;
var best: vec3f = slopeStroke(p, a, b, dot(worldOrigin + a, axis));
if ((joins & 1) != 0) {
var h: vec3f = slopeStroke(p, vec2f(-2.0, a.y), a, worldOrigin.x - 2.0);
if (h.x < best.x) { best = h; }
h = slopeCorner(p, a, worldOrigin.x + a.x);
if (h.x < best.x) { best = h; }
}
if ((joins & 2) != 0) {
var h: vec3f = slopeStroke(p, b, vec2f(6, b.y), worldOrigin.x + b.x);
if (h.x < best.x) { best = h; }
h = slopeCorner(p, b, worldOrigin.x + b.x);
if (h.x < best.x) { best = h; }
}
return best;
}
`;
const GPU_SMOOTH_SLOPE_DRAW = ` if (nc == 31 || nc == 32) {
var tag: vec4f = cellAt(tTree, c);
var grade: f32 = select( 1.0, -1.0 , nc == 31 );
var local: vec2f = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w);
var sy: f32 = select( 0.0, 4.0 , grade < 0.0 );
var foot: f32 = (local.x + grade * (local.y - sy)) * 0.5;
var depth: f32 = (grade * local.x - local.y + sy) * 0.70710678;
var wallFlags: i32 = i32(tag.a * 255.0 + 0.5);
var ends: f32 = max(select( -100.0, -foot ,(wallFlags & 8) == 0 ),select( -100.0, foot - 4.0 , (wallFlags & 16) == 0 ));
var sd: f32 = max(ends * 1.41421356, max(-depth, depth - ${CLEAT_HEIGHT.toFixed(1)}));
var phase: f32 = f32(wallFlags & 7);
var wave: f32 = 0.5 + 0.5 * sin((foot + w.x - local.x - phase) * 3.14159265);
var rubber: vec3f = mix(vec3f(0.20,0.22,0.25), vec3f(0.25,0.27,0.31), wave);
C = mix(C, rubber, 1.0 - smoothstep(-px, px, sd));
var dirS: f32 = select( -1.0, 1.0 , (wallFlags & 32) != 0 );
if (grade * dirS < 0.0) {
var rc: f32 = (w.x - local.x) + foot - (select( 0.0, 1.0 ,grade < 0.0 )) - dirS * phase;
var off: f32 = (modulo(rc + ${(CLEAT_SPACING / 2).toFixed(1)}, ${CLEAT_SPACING.toFixed(1)}) - ${(CLEAT_SPACING / 2).toFixed(1)}) * 1.41421356;
var ribTop: f32 = ${((2 * CLEAT_REACH + 1) * Math.SQRT1_2).toFixed(3)};
var rd: f32 = length(vec2f(off, depth - clamp(depth, 0.0, ribTop))) - 0.5;
var rib: vec3f = mix(vec3f(0.50,0.52,0.56), vec3f(0.70,0.72,0.76), smoothstep(ribTop - 1.2, ribTop, depth));
rib *= mix(0.78, 1.0, smoothstep(-0.5, 0.2, off * -dirS * grade));
C = mix(C, rib, 1.0 - smoothstep(-px, px, rd));
}
}
if ((nc == 2 || (nc >= 23 && nc <= 30)) &&
(i32(cellAt(tTree, c).a * 255.0 + 0.5) & 128) != 0 &&
cellAt(tTree, c).r * 255.0 >= 22.5 && cellAt(tTree, c).r * 255.0 <= 30.5) {
var slopeClass: i32 = i32(cellAt(tTree, c).r * 255.0 + 0.5);
var shape: i32 = (slopeClass - 23) / 2;
var mesh: bool = shape >= 2;
var grade: f32 = select( 1.0, -1.0 , (shape == 0 || shape == 2) );
var dir: f32 = select( -1.0, 1.0 , ((slopeClass - 23) % 2) == 1 );
var tag: vec4f = cellAt(tTree, c);
var joins: i32 = i32(tag.a * 255.0 + 0.5);
var local: vec2f = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w);
var shapePos: vec3f = slopeShape(local, w - local, grade, joins);
var sd: f32 = shapePos.x; var axis: f32 = shapePos.y; var depth: f32 = shapePos.z;
var time: f32 = select( 0.0, (su.x) , !mesh && (joins & 16) != 0 );
var lb: vec2f = vec2f(axis, depth); var alongWorld: vec2f = vec2f(axis, depth);
var rg: f32 = step(0.72, fract((axis - dir * (select( -1.0, 1.0 ,depth < 2.0 )) * time * 30.0) * 0.5));
var belt: vec3f;
if (-sd < select(0.8,1.1,mesh)) {
belt = select( bandColor(lb, rg), mix(vec3f(0.22,0.25,0.29), vec3f(0.48,0.53,0.59),select( 0.35, 0.85 , depth < 2.0 )) ,mesh );
belt *= mix(0.55, 1.0, smoothstep(0.0, max(px * 1.4, 0.12), -sd));
} else if (mesh) {
var cg: vec2f = abs(fract(alongWorld) - 0.5);
var bw2: f32 = max(px * 0.9, 0.09);
var hole: f32 = 1.0 - smoothstep(0.5 - bw2 - px, 0.5 - bw2, max(cg.x, cg.y));
var steel: vec3f = select( vec3f(0.42, 0.44, 0.5), mix(vec3f(0.42, 0.44, 0.5), own.rgb, 0.4) , dot(own.rgb, vec3f(1)) > 0.01 );
belt = mix(steel * 0.78, C * 0.8, hole);
} else {
var dv: vec2f = vec2f(modulo(axis, 2.0) - 1.0, depth - 2.0);
var rd: f32 = length(dv);
belt = vec3f(0.15, 0.16, 0.18);
if (rd < 0.95) {
var ang: f32 = atan2(dv.y, dv.x) - dir * time * 30.0 / 0.95;
var spoke: f32 = smoothstep(0.55, 0.85, cos(ang * 3.0));
belt = mix(vec3f(0.5, 0.36, 0.25), vec3f(0.72, 0.53, 0.36), 0.5 + 0.5 * dot(normalize(dv + 1e-4), vec2f(-0.6, -0.8)) * smoothstep(0.2, 0.95, rd));
belt *= mix(1.0, 0.72, spoke * smoothstep(0.25, 0.4, rd));
belt *= mix(1.0, 0.55, smoothstep(0.82, 0.95, rd));
}
}
C = mix(C, belt, 1.0 - smoothstep(-px, px, sd));
}
`;