WebGPU pixel look: GPU colours the world, CPU only rare cells (t88 phase 2a)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 19:49:36 -04:00
co-authored by Claude Opus 5.5
parent b2c9ad9533
commit fc05146887
29 changed files with 949 additions and 386 deletions
+82
View File
@@ -0,0 +1,82 @@
// The plain-cell rules mirror cellPixel; built geometry stays in the CPU override.
const GPU_COLOUR = `
struct Params { view: vec4f, world: vec4f, style: vec4f, effects: vec4f, camera: vec4f, light: vec4f, screen: vec4f, extra: vec4f }
@group(0) @binding(0) var<uniform> u: Params;
@group(0) @binding(1) var<storage, read> lut: array<vec4f>;
@group(0) @binding(2) var<storage, read> land: array<vec4f>;
${['mat','flags','life','part','bgm','digp','noise','fog'].map((n,i)=>`@group(0) @binding(${i+3}) var ${n}T: texture_2d<u32>;`).join('\n')}
@group(0) @binding(11) var tempT: texture_2d<f32>;
@group(0) @binding(12) var overrideT: texture_2d<f32>;
@group(0) @binding(13) var overrideGlow: texture_2d<f32>;
@group(0) @binding(14) var outputT: texture_storage_2d<rgba8unorm, write>;
@group(0) @binding(15) var glowT: texture_storage_2d<rgba8unorm, write>;
fn phase(p: vec2i) -> i32 { return i32(lut[textureLoad(matT,p,0).r*3u+1u].x); }
fn powder(p: vec2i) -> bool {
return all(p >= vec2i(0)) && all(p < vec2i(u.world.xy)) && phase(p)==${PH_POWDER} && textureLoad(partT,p,0).r==0u;
}
fn quant(c: vec3f) -> vec3f { return floor(clamp(c,vec3f(0),vec3f(255)))/255.; }
@compute @workgroup_size(8,8)
fn colour(@builtin(global_invocation_id) id: vec3u) {
let q=vec2i(id.xy); if (any(q>=vec2i(u.view.zw))) { return; }
let p=q+vec2i(u.view.xy); let x=p.x; let y=p.y;
var rgb=vec3f(0); var bloom=vec3f(0);
if (all(p>=vec2i(0)) && all(p<vec2i(u.world.xy))) {
let m=textureLoad(matT,p,0).r; let nz=f32(textureLoad(noiseT,p,0).r)-128.;
let T=textureLoad(tempT,p,0).r; let mc=lut[m*3u]; let props=lut[m*3u+1u];
var fv=255.;
if (u.style.x>0.) {
let f=vec2f(p)/${FOG}.; let a=vec2i(f); let b=min(a+1,vec2i(u.world.zw)-1); let t=fract(f);
fv=mix(mix(f32(textureLoad(fogT,a,0).r),f32(textureLoad(fogT,vec2i(b.x,a.y),0).r),t.x),mix(f32(textureLoad(fogT,vec2i(a.x,b.y),0).r),f32(textureLoad(fogT,b,0).r),t.x),t.y);
if (fv>0. && fv<255.) { var s=fv/255.; s=clamp(s+nz*(0.6/128.)*s*(1.-s),0.,1.); fv=max(1.,255.*s*s*(3.-2.*s)); }
}
if (fv>0.) {
var shade=1.;
let built=props.x==${PH_SOLID}. && (textureLoad(flagsT,p,0).r & ${F_NAT}u)==0u;
if (m!=${EMPTY}u && !built) {
if (props.x==${PH_POWDER}.) { shade=1.1+nz*0.00065; }
else if (props.x==${PH_SOLID}. && (powder(p+vec2i(-1,0)) || powder(p+vec2i(1,0)) || powder(p+vec2i(0,-1)) || powder(p+vec2i(0,1)))) { shade=0.72; }
}
if (u.effects.x>0.) {
let ti=u32(clamp(i32(T/4.),0,550)); let thermal=lut[${NMAT*3+700}u+u32(u.effects.y)*551u+ti].rgb;
rgb=select(thermal*0.78+vec3f((mc.r+mc.g+mc.b)/3.*0.25),thermal*0.55,m==${EMPTY}u)*shade;
} else {
let tb=textureLoad(bgmT,p,0).r; let dp=f32(textureLoad(digpT,p,0).r);
let column=land[x]; let sky=land[i32(u.world.x)+y].rgb;
var back: vec3f;
if (tb!=0u) { back=(lut[tb*3u+2u].rgb+vec3f(nz*lut[tb*3u].a/128.))*(1.+dp/700.); }
else if (f32(y)<column.x) {
back=sky;
if (u.style.y>0.01) { let gy=(sky.r+sky.g+sky.b)/3.*0.82; back+= (vec3f(gy)-sky)*u.style.y*vec3f(0.7,0.7,0.65); }
} else {
back=vec3f(${BG.basalt.join(',')});
if (f32(y)<column.y) { back=vec3f(${BG.dirt.join(',')}); }
else if (f32(y)<column.z) { back=vec3f(${BG.stone.join(',')}); }
else if (f32(y)<column.w) { back=vec3f(${BG.granite.join(',')}); }
back+=vec3f(nz*0.06);
}
rgb=back;
if (m!=${EMPTY}u) {
rgb=(mc.rgb+vec3f(nz*mc.a*u.style.z/128.))*shade;
if (m==${FIRE}u) { let f=f32(textureLoad(lifeT,p,0).r & 15u)*6.; rgb=vec3f(255.,120.+f,30.+f*0.5); }
else if (m==${WATER}u && ((x*3+y*5+(i32(u.style.w)>>2)) & 15)==0) { rgb+=vec3f(14.); }
rgb=back+(rgb-back)*props.y;
if (dp>0.) { let lift=dp/255.*(30.+sin(u.style.w*1.9+f32(x)*1.3+f32(y)*0.7)*6.); rgb+=lift*vec3f(1,1,1.08); }
}
if (T>450. && m!=${EMPTY}u && u.effects.z>0.) {
var gt=T;
if (props.x==${PH_SOLID}.) { gt+=nz*0.9; }
else if (m==${MAGMA}u) { gt+=sin(f32(x)*0.19+f32(y)*0.11+u.style.w*0.03+nz*0.04)*55.-30.; }
let gi=u32(clamp(i32((gt-400.)/4.),0,699)); let glow=lut[${NMAT*3}u+gi];
rgb=rgb*(1.-glow.a)+glow.rgb*glow.a;
if (u.effects.w>0. && fv>=128. && glow.a>0.3) { bloom=quant(glow.rgb*(glow.a-0.3)*1.4); }
}
}
rgb=quant(rgb*fv/255.);
}
}
// Flying grains replace colour, while the glow behind them remains.
let over=textureLoad(overrideT,q,0);
if (over.a>0.) { rgb=over.rgb; bloom=max(bloom,textureLoad(overrideGlow,q/2,0).rgb); }
textureStore(outputT,q,vec4f(rgb,1));
if (((q.x | q.y) & 1)==0) { textureStore(glowT,q/2,vec4f(bloom,1)); }
}`;