// The plain-cell rules mirror cellPixel; built geometry stays in the CPU override. function gpuColourShader(smooth) { return ` struct Params { view: vec4f, world: vec4f, style: vec4f, effects: vec4f, camera: vec4f, light: vec4f, screen: vec4f, extra: vec4f } @group(0) @binding(0) var u: Params; @group(0) @binding(1) var lut: array; @group(0) @binding(2) var land: array; ${['mat','flags','life','part','bgm','digp','noise','fog'].map((n,i)=>`@group(0) @binding(${i+3}) var ${n}T: texture_2d;`).join('\n')} @group(0) @binding(11) var tempT: texture_2d; @group(0) @binding(12) var overrideT: texture_2d; @group(0) @binding(13) var overrideGlow: texture_2d; @group(0) @binding(14) var outputT: texture_storage_2d; ${smooth ? `@group(0) @binding(16) var backOut: texture_storage_2d; @group(0) @binding(17) var treeOut: texture_storage_2d; @group(0) @binding(18) var fieldOut: texture_storage_2d; @group(0) @binding(19) var backOver: texture_2d; @group(0) @binding(20) var treeOver: texture_2d; @group(0) @binding(21) var fieldOver: texture_2d; @group(0) @binding(22) var maskOver: texture_2d; ` : ""} ${smooth ? "" : "@group(0) @binding(15) var glowT: texture_storage_2d;"} 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); ${smooth ? "var cl=4.; var backValue=vec4f(0); var treeValue=vec4f(0); var fieldValue=vec4f(1,0,0,0);" : ""} if (all(p>=vec2i(0)) && all(p0.) { 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 (${smooth ? "false" : `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 (${smooth ? "false" : "tb!=0u"}) { back=(lut[tb*3u+2u].rgb+vec3f(nz*lut[tb*3u].a/128.))*(1.+dp/700.); } else if (f32(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)>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.); ${smooth ? "backValue=vec4f(quant(backValue.rgb*fv),backValue.a); treeValue=vec4f(quant(treeValue.rgb*fv),treeValue.a);" : ""} } } // Flying grains replace colour, while the glow behind them remains. let over=textureLoad(overrideT,q,0); if (${smooth ? "textureLoad(maskOver,q,0).r>0u" : "over.a>0."}) { rgb=over.rgb; ${smooth ? "cl=over.a*255.; backValue=textureLoad(backOver,q,0); treeValue=textureLoad(treeOver,q,0); fieldValue=textureLoad(fieldOver,q,0);" : ""} bloom=max(bloom,textureLoad(overrideGlow,q/2,0).rgb); } ${smooth ? "textureStore(backOut,q,backValue); textureStore(treeOut,q,treeValue); textureStore(fieldOut,q,fieldValue);" : ""} textureStore(outputT,q,vec4f(rgb,${smooth ? "cl/255." : "1"})); ${smooth ? "" : "if (((q.x | q.y) & 1)==0) { textureStore(glowT,q/2,vec4f(bloom,1)); }"} }`; } const GPU_COLOUR = gpuColourShader(false); const GPU_COLOUR_SMOOTH = gpuColourShader(true);