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:
@@ -53,6 +53,9 @@
|
||||
"render/gpu/colour.js",
|
||||
"render/gpu/shaders.js",
|
||||
"render/gpu/light.js",
|
||||
"render/gpu/smooth-slope.js",
|
||||
"render/gpu/smooth-shader.js",
|
||||
"render/gpu/smooth.js",
|
||||
"render/gpu/device.js",
|
||||
"render/gpu/textures.js",
|
||||
"render/gpu/draw.js",
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
function cellPixel(x, y) {
|
||||
const { bw, bh, px, vx0, vy0, smoothOn: sm, rain: rk } = R;
|
||||
const bx = x - vx0, by = y - vy0, row = by * bw;
|
||||
if (R.gpuOn && !px[row + bx]) GPU.overrideCount++;
|
||||
if (R.gpuOn) { if (!gpuOverrideHas(row + bx)) GPU.overrideCount++; if (sm) GPU.overrideMask[row + bx] = 255; }
|
||||
const bpx = R.backPx, tpx = R.treePx, fpx = R.fieldPx, mmask = R.moldMask;
|
||||
const OUT = sm ? 0x04000000 : 0xff000000;
|
||||
if (x < 0 || y < 0 || x >= W || y >= H) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// The plain-cell rules mirror cellPixel; built geometry stays in the CPU override.
|
||||
const GPU_COLOUR = `
|
||||
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<uniform> u: Params;
|
||||
@group(0) @binding(1) var<storage, read> lut: array<vec4f>;
|
||||
@@ -9,6 +9,14 @@ ${['mat','flags','life','part','bgm','digp','noise','fog'].map((n,i)=>`@group(0)
|
||||
@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>;
|
||||
${smooth ? `@group(0) @binding(16) var backOut: texture_storage_2d<rgba8unorm, write>;
|
||||
@group(0) @binding(17) var treeOut: texture_storage_2d<rgba8unorm, write>;
|
||||
@group(0) @binding(18) var fieldOut: texture_storage_2d<rgba8unorm, write>;
|
||||
@group(0) @binding(19) var backOver: texture_2d<f32>;
|
||||
@group(0) @binding(20) var treeOver: texture_2d<f32>;
|
||||
@group(0) @binding(21) var fieldOver: texture_2d<f32>;
|
||||
@group(0) @binding(22) var maskOver: texture_2d<u32>;
|
||||
` : ""}
|
||||
@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 {
|
||||
@@ -20,6 +28,7 @@ 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(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];
|
||||
@@ -32,7 +41,7 @@ fn colour(@builtin(global_invocation_id) id: vec3u) {
|
||||
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 (${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; }
|
||||
}
|
||||
@@ -43,7 +52,7 @@ fn colour(@builtin(global_invocation_id) id: vec3u) {
|
||||
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.); }
|
||||
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)<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); }
|
||||
@@ -52,13 +61,18 @@ fn colour(@builtin(global_invocation_id) id: vec3u) {
|
||||
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);
|
||||
${smooth ? "" : "back+=vec3f(nz*0.06);"}
|
||||
}
|
||||
${smooth ? `backValue=vec4f(quant(back),select(0.,1.,f32(y)<column.x));
|
||||
if (tb!=0u) { treeValue=vec4f(quant((lut[tb*3u+2u].rgb+vec3f(nz*lut[tb*3u].a/256.))*(1.+dp/700.)),select(128./255.,1.,tb==${LEAVES}u)); }
|
||||
cl=0.; if (m!=${EMPTY}u) { cl=5.; if(props.x==${PH_SOLID}.) { cl=1.; } else if(props.x==${PH_POWDER}.) { cl=2.; } else if(props.x==${PH_LIQUID}.) { cl=3.; } }
|
||||
fieldValue=vec4f(select(0.,1.,cl==1.),select(0.,1.,cl==3.),select(0.,1.,cl==5.),select(0.,1.,tb!=0u));
|
||||
` : ""}
|
||||
rgb=back;
|
||||
if (m!=${EMPTY}u) {
|
||||
rgb=(mc.rgb+vec3f(nz*mc.a*u.style.z/128.))*shade;
|
||||
rgb=(mc.rgb+vec3f(nz*mc.a*${smooth ? `select(u.style.z*0.35,0.,props.x==${PH_SOLID}.)` : "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.); }
|
||||
else if (${smooth ? "false && " : ""}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); }
|
||||
}
|
||||
@@ -72,11 +86,16 @@ fn colour(@builtin(global_invocation_id) id: vec3u) {
|
||||
}
|
||||
}
|
||||
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 (over.a>0.) { rgb=over.rgb; bloom=max(bloom,textureLoad(overrideGlow,q/2,0).rgb); }
|
||||
textureStore(outputT,q,vec4f(rgb,1));
|
||||
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"}));
|
||||
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);
|
||||
|
||||
@@ -3,7 +3,7 @@ const GPU = { ok: false, canvas: document.getElementById('gpu'), error: '', gpuM
|
||||
async function gpuInit(adapter) {
|
||||
try {
|
||||
const features = adapter.features.has('timestamp-query') ? ['timestamp-query'] : [];
|
||||
const d = GPU.device = await adapter.requestDevice({ requiredFeatures: features });
|
||||
const d = GPU.device = await adapter.requestDevice({ requiredFeatures: features, requiredLimits: { maxStorageTexturesPerShaderStage: 5 } });
|
||||
d.addEventListener('uncapturederror', e => gpuFail(e.error.message));
|
||||
d.lost.then(info => gpuFail(info.message || 'Graphics device lost'));
|
||||
GPU.context = GPU.canvas.getContext('webgpu');
|
||||
@@ -11,6 +11,8 @@ async function gpuInit(adapter) {
|
||||
GPU.context.configure({ device: d, format: GPU.format, alphaMode: 'opaque' });
|
||||
d.pushErrorScope('validation');
|
||||
GPU.colour = await d.createComputePipelineAsync({ layout: 'auto', compute: { module: d.createShaderModule({ code: GPU_COLOUR }), entryPoint: 'colour' } });
|
||||
GPU.colourSmooth = await d.createComputePipelineAsync({ layout: 'auto', compute: { module: d.createShaderModule({ code: GPU_COLOUR_SMOOTH }), entryPoint: 'colour' } });
|
||||
gpuSmoothInit();
|
||||
for (const [name, code, entryPoint] of [['lightRoof',GPU_LIGHT,'roof'],['lightSources',GPU_LIGHT,'sources'],['lightSpread',GPU_LIGHT_SPREAD,'main'],['lightPack',GPU_LIGHT_PACK,'main']]) {
|
||||
GPU[name] = await d.createComputePipelineAsync({layout:'auto',compute:{module:d.createShaderModule({code}),entryPoint}});
|
||||
}
|
||||
@@ -19,7 +21,7 @@ async function gpuInit(adapter) {
|
||||
const error = await d.popErrorScope(); if (error) throw error;
|
||||
GPU.uniform = d.createBuffer({ size: 128, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
||||
GPU.params = new Float32Array(32);
|
||||
GPU.sampler = d.createSampler({ magFilter: 'linear', minFilter: 'linear' });
|
||||
GPU.sampler = d.createSampler({ magFilter: 'linear', minFilter: 'linear', mipmapFilter: 'linear' });
|
||||
if (features.length) {
|
||||
GPU.queries = d.createQuerySet({ type: 'timestamp', count: 4 });
|
||||
GPU.resolve = d.createBuffer({ size: 256, usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC });
|
||||
|
||||
+14
-4
@@ -1,8 +1,13 @@
|
||||
// Only exceptional blocks and their geometric neighbours run the CPU colour rules.
|
||||
const GPU_PATTERN = new Uint8Array(NMAT);
|
||||
for (const m of [BELTL, BELTR, PIPE, SHAFT, GATE, CUTST, GRINDER, SAWB, PRESSB, RACKB, WOOD, LWOOD, HULL, SPRING]) GPU_PATTERN[m] = 1;
|
||||
for (const m of [BELTL, BELTR, PIPE, SHAFT, GATE, CUTST, GRINDER, SAWB, PRESSB, RACKB, WOOD, LWOOD, HULL, SPRING, DOCKW]) GPU_PATTERN[m] = 1;
|
||||
function gpuOverrideHas(k) { return R.smoothOn ? GPU.overrideMask[k] : R.px[k]; }
|
||||
function gpuOverrides() {
|
||||
R.px.fill(0);
|
||||
if (R.smoothOn) {
|
||||
if (!GPU.overrideMask || GPU.overrideMask.length !== R.px.length) GPU.overrideMask = new Uint8Array(R.px.length);
|
||||
GPU.overrideMask.fill(0);
|
||||
}
|
||||
if (!GPU.blocks || GPU.blocks.length !== NB) GPU.blocks = new Uint8Array(NB);
|
||||
const marked = GPU.blocks; marked.fill(0);
|
||||
const bx0 = Math.max(0, (R.vx0 >> 2) - 3), by0 = Math.max(0, (R.vy0 >> 2) - 3);
|
||||
@@ -22,6 +27,10 @@ function gpuOverrides() {
|
||||
for (let xx = Math.max(0, bx - 2); xx <= Math.min(BW - 1, bx + 2); xx++) marked[yy * BW + xx] = 1;
|
||||
}
|
||||
}
|
||||
if (R.smoothOn) for (const mo of molds) {
|
||||
if (mo.x + 40 < R.vx0 || mo.x - 40 > R.vx0 + R.bw || mo.y + 40 < R.vy0 || mo.y - 40 > R.vy0 + R.bh) continue;
|
||||
for (const i of moldCellsAt(mo.kind, mo.x, mo.y).walls) marked[(((i / W) | 0) >> 2) * BW + ((i % W) >> 2)] = 1;
|
||||
}
|
||||
GPU.overrideCount = 0;
|
||||
for (let by = by0; by <= by1; by++) for (let bx = bx0; bx <= bx1; bx++) {
|
||||
if (!marked[by * BW + bx]) continue;
|
||||
@@ -39,14 +48,15 @@ function gpuDraw(lighting, glowOn, lightUpdate) {
|
||||
const blit = performance.now(), d = GPU.device, encoder = d.createCommandEncoder();
|
||||
const timed = GPU.queries && !GPU.timingBusy && (PERF_MARKS || SET.showPerf);
|
||||
const compute = encoder.beginComputePass(timed ? { timestampWrites: { querySet: GPU.queries, beginningOfPassWriteIndex: 0 } } : {});
|
||||
compute.setPipeline(GPU.colour); compute.setBindGroup(0, GPU.bindColour);
|
||||
compute.setPipeline(R.smoothOn ? GPU.colourSmooth : GPU.colour); compute.setBindGroup(0, R.smoothOn ? GPU.bindColourSmooth : GPU.bindColour);
|
||||
compute.dispatchWorkgroups(Math.ceil(R.bw / 8), Math.ceil(R.bh / 8)); compute.end();
|
||||
if (glowOn) for (let i = 0; i < 2; i++) {
|
||||
if (glowOn && !R.smoothOn) for (let i = 0; i < 2; i++) {
|
||||
const pass = encoder.beginComputePass(); pass.setPipeline(GPU.blur); pass.setBindGroup(0, GPU.bindBlur[i]);
|
||||
pass.dispatchWorkgroups(Math.ceil(R.glowC.width / 8), Math.ceil(R.glowC.height / 8)); pass.end();
|
||||
}
|
||||
const lightTimed = lighting && (lightUpdate || GPU.lightFrame === undefined);
|
||||
if (lightTimed) { gpuLight(encoder, timed); GPU.lightFrame = R.frame; }
|
||||
if (R.smoothOn) gpuSmoothMips(encoder);
|
||||
gpuPresent(encoder, timed);
|
||||
if (timed) { encoder.resolveQuerySet(GPU.queries, 0, lightTimed ? 4 : 2, GPU.resolve, 0); encoder.copyBufferToBuffer(GPU.resolve, 0, GPU.readback, 0, lightTimed ? 32 : 16); }
|
||||
d.queue.submit([encoder.finish()]);
|
||||
@@ -69,5 +79,5 @@ function gpuDraw(lighting, glowOn, lightUpdate) {
|
||||
function gpuPresent(encoder, timed = false) {
|
||||
const pass = encoder.beginRenderPass({ colorAttachments: [{ view: GPU.context.getCurrentTexture().createView(), loadOp: 'clear', storeOp: 'store', clearValue: [0, 0, 0, 1] }],
|
||||
...(timed ? { timestampWrites: { querySet: GPU.queries, endOfPassWriteIndex: 1 } } : {}) });
|
||||
pass.setPipeline(GPU.draw); pass.setBindGroup(0, GPU.bindDraw); pass.draw(3); pass.end();
|
||||
pass.setPipeline(R.smoothOn ? GPU.smooth : GPU.draw); pass.setBindGroup(0, R.smoothOn ? GPU.bindSmooth : GPU.bindDraw); pass.draw(3); pass.end();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
// Literal port of the legacy smooth shader; dpdy is reversed for WebGPU's top-left origin.
|
||||
const GPU_SMOOTH = `
|
||||
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 tCol: texture_2d<f32>;
|
||||
@group(0) @binding(2) var tBack: texture_2d<f32>;
|
||||
@group(0) @binding(3) var tTree: texture_2d<f32>;
|
||||
@group(0) @binding(4) var tField: texture_2d<f32>;
|
||||
@group(0) @binding(5) var tLight: texture_2d<f32>;
|
||||
@group(0) @binding(6) var tGlow: texture_2d<f32>;
|
||||
@group(0) @binding(7) var linear: sampler;
|
||||
@group(0) @binding(8) var<uniform> su: vec4f;
|
||||
fn sampleLevel(t: texture_2d<f32>, uv: vec2f, level: f32) -> vec4f { return textureSampleLevel(t,linear,uv,level); }
|
||||
fn modulo(x: f32,y: f32) -> f32 { return x-y*floor(x/y); }
|
||||
fn h21(inputP: vec2f) -> f32 { var p=inputP; p = fract(p * vec2f(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); }
|
||||
fn vn(p: vec2f) -> f32 {
|
||||
var i: vec2f = floor(p); var f: vec2f = fract(p); f = f * f * (3.0 - 2.0 * f);
|
||||
return mix(mix(h21(i), h21(i + vec2f(1, 0)), f.x), mix(h21(i + vec2f(0, 1)), h21(i + vec2f(1, 1)), f.x), f.y);
|
||||
}
|
||||
fn fbm(inputP: vec2f) -> f32 { var p=inputP; var a: f32 = 0.5; var s: f32 = 0.0; for (var k: i32 = 0; k < 4; k++) { s += a * vn(p); p = p * 2.03 + 17.1; a *= 0.5; } return s / 0.9375; }
|
||||
|
||||
fn lin(t: texture_2d<f32>, q: vec2f) -> vec4f { return sampleLevel(t, q / (u.view.zw), 0.0); }
|
||||
|
||||
fn bsp(t: texture_2d<f32>, q: vec2f) -> vec4f {
|
||||
var tc: vec2f = q - 0.5; var i: vec2f = floor(tc); var f: vec2f = tc - i; var f2: vec2f = f * f; var f3: vec2f = f2 * f;
|
||||
var w0: vec2f = (1.0 - 3.0 * f + 3.0 * f2 - f3) / 6.0; var w1: vec2f = (4.0 - 6.0 * f2 + 3.0 * f3) / 6.0;
|
||||
var w2: vec2f = (1.0 + 3.0 * f + 3.0 * f2 - 3.0 * f3) / 6.0; var w3: vec2f = f3 / 6.0;
|
||||
var g0: vec2f = w0 + w1; var g1: vec2f = w2 + w3;
|
||||
var h0: vec2f = (i - 0.5 + w1 / g0) / (u.view.zw); var h1: vec2f = (i + 1.5 + w3 / g1) / (u.view.zw);
|
||||
return g0.y * (g0.x * sampleLevel(t, h0, 0.0) + g1.x * sampleLevel(t, vec2f(h1.x, h0.y), 0.0))
|
||||
+ g1.y * (g0.x * sampleLevel(t, vec2f(h0.x, h1.y), 0.0) + g1.x * sampleLevel(t, h1, 0.0));
|
||||
}
|
||||
fn cellAt(t: texture_2d<f32>, c: vec2i) -> vec4f { return textureLoad(t, clamp(c, vec2i(0), vec2i((u.view.zw)) - 1), 0); }
|
||||
fn cls(c: vec4f) -> i32 { return i32(c.a * 255.0 + 0.5); }
|
||||
fn isT(n: i32) -> f32 { return select( 0.0, 1.0 ,(n == 1 || n == 4 || n == 6 || (n >= 7 && n <= 12)) ); }
|
||||
fn isM(n: i32) -> f32 { return select( isT(n), 1.0 ,n == 2 ); }
|
||||
fn isF(n: i32) -> f32 { return select( 0.0, 1.0 ,(n == 13 || n == 14) ); }
|
||||
fn isBelt(n: i32) -> f32 { return select( 0.0, 1.0 ,(n == 7 || n == 8) ); }
|
||||
fn isN(n: i32, k: i32) -> f32 { return select( 0.0, 1.0 ,n == k ); }
|
||||
|
||||
fn beltSD(lb: vec2f, endL: bool, endR: bool) -> f32 {
|
||||
var x0: f32 = select( -2.0, 0.15 , endL ); var x1: f32 = select( 6.0, 3.85 , endR ); var r: f32 = 0.75;
|
||||
var c: vec2f = vec2f((x0 + x1) * 0.5, 2.0); var hs: vec2f = vec2f((x1 - x0) * 0.5, 1.85);
|
||||
var q: vec2f = abs(lb - c) - hs + r;
|
||||
return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - r;
|
||||
}
|
||||
|
||||
fn beltLineSD(c: vec2i, lb: vec2f) -> f32 {
|
||||
var caps: i32 = i32(cellAt(tTree, c).a * 255.0 + 0.5);
|
||||
return beltSD(lb, (caps & 1) != 0, (caps & 2) != 0);
|
||||
}
|
||||
|
||||
fn bandRidge(w: vec2f, lb: vec2f, dir: f32) -> f32 {
|
||||
var run: f32 = select( -1.0, 1.0 , lb.y < 2.0 );
|
||||
return step(0.72, fract((w.x - dir * run * (su.x) * 30.0) * 0.5));
|
||||
}
|
||||
fn bandColor(lb: vec2f, rg: f32) -> vec3f { return mix(vec3f(0.2, 0.21, 0.24), vec3f(0.34, 0.35, 0.39), rg) * (select( 0.72, 1.0 ,lb.y < 2.0 )); }
|
||||
|
||||
${GPU_SMOOTH_SLOPE_HELPERS}
|
||||
@fragment fn fs(@builtin(position) position: vec4f) -> @location(0) vec4f {
|
||||
let uView=vec4f(u.extra.xy-u.view.xy,u.camera.z,u.screen.y);
|
||||
let uLightMap=vec4f(u.light.xy*u.light.z-u.view.xy,u.light.z,u.light.w);
|
||||
var zp: f32 = uView.z; var px: f32 = 1.0 / zp;
|
||||
var sp: vec2f = vec2f(position.x, position.y);
|
||||
var q: vec2f = sp * px + uView.xy;
|
||||
var w: vec2f = q + (u.view.xy);
|
||||
var cam: vec2f = (u.view.xy) + uView.xy;
|
||||
var scr: vec2f = q - uView.xy;
|
||||
var shc: f32 = uView.w * px;
|
||||
var c: vec2i = vec2i(floor(q));
|
||||
var qq: vec2f = q - 0.5; var c0: vec2i = vec2i(floor(qq)); var t: vec2f = qq - floor(qq);
|
||||
var k0: vec4f = cellAt(tCol, c0); var k1: vec4f = cellAt(tCol, c0 + vec2i(1, 0)); var k2: vec4f = cellAt(tCol, c0 + vec2i(0, 1)); var k3: vec4f = cellAt(tCol, c0 + vec2i(1, 1));
|
||||
var n0: i32 = cls(k0); var n1: i32 = cls(k1); var n2: i32 = cls(k2); var n3: i32 = cls(k3);
|
||||
var own: vec4f = cellAt(tCol, c); var nc: i32 = cls(own);
|
||||
var s: vec2f = smoothstep(vec2f(0.1), vec2f(0.9), t);
|
||||
var wq: vec4f = vec4f((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 screen derivatives before branching.
|
||||
var Fb: vec4f = bsp(tField, q);
|
||||
var F: vec4f = mix(lin(tField, q), Fb, 0.62);
|
||||
|
||||
var wl: vec4f = vec4f((1.0 - t.x) * (1.0 - t.y), t.x * (1.0 - t.y), (1.0 - t.x) * t.y, t.x * t.y);
|
||||
var pw: f32 = dot(wl, vec4f(isN(n0, 2), isN(n1, 2), isN(n2, 2), isN(n3, 2)));
|
||||
var M: f32 = clamp(F.r + pw, 0.0, 1.0);
|
||||
var lf: f32 = clamp(mix(F.g, Fb.g, 0.6) + F.r + pw, 0.0, 1.0);
|
||||
var fwT: f32 = max(fwidth(F.r), 1e-4); var fwM: f32 = max(fwidth(M), 1e-4); var fwA: f32 = max(fwidth(F.a), 1e-4); var fwL: f32 = max(fwidth(lf), 1e-4);
|
||||
var gT: vec2f = vec2f(dpdx(mix(Fb.r, M, 0.5)), -dpdy(mix(Fb.r, M, 0.5))); var gL: vec2f = vec2f(dpdx(F.g), -dpdy(F.g));
|
||||
|
||||
// Sky, clouds, parallax hills and back-wall AO.
|
||||
var back: vec4f = lin(tBack, q);
|
||||
var C: vec3f = back.rgb;
|
||||
var sky: f32 = back.a;
|
||||
if (sky > 0.01) {
|
||||
var S: vec3f = C;
|
||||
var P0: vec2f = scr + cam * 0.08;
|
||||
|
||||
var cl: f32 = smoothstep(0.56 - 0.3 * (u.style.y), 0.78 - 0.2 * (u.style.y), fbm(vec2f(P0.x * 0.016 + (su.x) * 0.006, P0.y * 0.05)));
|
||||
var hi: f32 = clamp(((su.y) - 40.0 - w.y) / 50.0, 0.0, 1.0);
|
||||
S = mix(S, mix(vec3f(0.96, 0.95, 0.93), vec3f(0.5, 0.52, 0.56), (u.style.y)), cl * (0.6 + 0.3 * (u.style.y)) * hi);
|
||||
var k1: f32 = 0.16; var P1: vec2f = scr + cam * k1;
|
||||
var base1: f32 = shc * 0.5 + 8.0 + ((su.y) - shc * 0.5) * k1;
|
||||
var top1: f32 = base1 - 36.0 * (0.25 + 0.9 * fbm(vec2f(P1.x * 0.009, 1.7)));
|
||||
var m1: f32 = smoothstep(top1 - px * 1.5, top1 + px * 1.5, P1.y);
|
||||
var far: vec3f = mix(vec3f(0.50, 0.60, 0.72), S, 0.35) * (0.94 + 0.08 * fbm(P1 * 0.05));
|
||||
S = mix(S, far, m1);
|
||||
var k2: f32 = 0.38; var P2: vec2f = scr + cam * k2;
|
||||
var base2: f32 = shc * 0.5 + 14.0 + ((su.y) - shc * 0.5) * k2;
|
||||
var top2: f32 = base2 - 20.0 * (0.2 + 0.9 * fbm(vec2f(P2.x * 0.016, 7.3)));
|
||||
var m2: f32 = smoothstep(top2 - px * 1.5, top2 + px * 1.5, P2.y);
|
||||
var near: vec3f = vec3f(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);
|
||||
}
|
||||
|
||||
var notSky: f32 = 1.0 - sky;
|
||||
C *= mix(1.0, 1.08 + 0.4 * fbm(w * 0.21), notSky);
|
||||
var near: f32 = sampleLevel(tField, q / (u.view.zw), 2.2).r;
|
||||
C *= 1.0 - 0.34 * smoothstep(0.12, 0.85, near) * (1.0 - 0.75 * sky);
|
||||
|
||||
// Trees behind terrain and built pieces.
|
||||
if (F.a > 0.2) {
|
||||
var a0: vec4f = cellAt(tTree, c0); var a1: vec4f = cellAt(tTree, c0 + vec2i(1, 0)); var a2: vec4f = cellAt(tTree, c0 + vec2i(0, 1)); var a3: vec4f = cellAt(tTree, c0 + vec2i(1, 1));
|
||||
var m: vec4f = vec4f(step(0.4, a0.a), step(0.4, a1.a), step(0.4, a2.a), step(0.4, a3.a)) * wq;
|
||||
var sm: f32 = m.x + m.y + m.z + m.w;
|
||||
if (sm > 1e-4) {
|
||||
var tc: vec3f = (a0.rgb * m.x + a1.rgb * m.y + a2.rgb * m.z + a3.rgb * m.w) / sm;
|
||||
var leaf: f32 = (a0.a * m.x + a1.a * m.y + a2.a * m.z + a3.a * m.w) / sm;
|
||||
var lv: f32 = smoothstep(0.6, 0.9, leaf);
|
||||
tc *= mix(0.86 + 0.22 * vn(vec2f(w.x * 1.4, w.y * 0.25)), 0.72 + 0.5 * fbm(w * 0.42), lv);
|
||||
var d: f32 = (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.
|
||||
if (Fb.b > 0.02) {
|
||||
var m: vec4f = vec4f(isN(n0, 5), isN(n1, 5), isN(n2, 5), isN(n3, 5)) * wq;
|
||||
var sm: f32 = m.x + m.y + m.z + m.w;
|
||||
var gc: vec3f = select( own.rgb, (k0.rgb * m.x + k1.rgb * m.y + k2.rgb * m.z + k3.rgb * m.w) / sm , sm > 1e-4 );
|
||||
if (sm > 1e-4 || nc == 5) { C = mix(C, gc, smoothstep(0.03, 0.55, Fb.b) * 0.92); }
|
||||
}
|
||||
|
||||
// Liquids with a glossy top edge.
|
||||
var anyL: f32 = isN(n0, 3) + isN(n1, 3) + isN(n2, 3) + isN(n3, 3);
|
||||
if (anyL > 0.0 && lf > 0.3) {
|
||||
var m: vec4f = vec4f(isN(n0, 3), isN(n1, 3), isN(n2, 3), isN(n3, 3)) * (wq + 0.02);
|
||||
var sm: f32 = m.x + m.y + m.z + m.w;
|
||||
var lc: vec3f = (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 + vec2f((su.x) * 0.7, (su.x) * 0.25));
|
||||
var gl: f32 = length(gL);
|
||||
var topSide: f32 = select( 0.0, clamp(gL.y / gl, 0.0, 1.0) , gl > 1e-5 );
|
||||
var band: f32 = 1.0 - smoothstep(0.5, 0.75, F.g);
|
||||
lc += vec3f(0.16, 0.18, 0.2) * topSide * band;
|
||||
C = mix(C, lc, smoothstep(0.5 - fwL, 0.5 + fwL, lf));
|
||||
}
|
||||
|
||||
// Rounded ground and the seam against loose grains.
|
||||
if (F.r > 0.3) {
|
||||
var m: vec4f = vec4f(isT(n0), isT(n1), isT(n2), isT(n3)) * wq;
|
||||
var sm: f32 = m.x + m.y + m.z + m.w;
|
||||
var blur: vec3f = bsp(tCol, q).rgb; var tc: vec3f = own.rgb;
|
||||
var bd: f32 = 1e9;
|
||||
if (m.x > 0.0) { var e: vec3f = k0.rgb - blur; var dd: f32 = dot(e, e); if (dd < bd) { bd = dd; tc = k0.rgb; } }
|
||||
if (m.y > 0.0) { var e: vec3f = k1.rgb - blur; var dd: f32 = dot(e, e); if (dd < bd) { bd = dd; tc = k1.rgb; } }
|
||||
if (m.z > 0.0) { var e: vec3f = k2.rgb - blur; var dd: f32 = dot(e, e); if (dd < bd) { bd = dd; tc = k2.rgb; } }
|
||||
if (m.w > 0.0) { var e: vec3f = k3.rgb - blur; var dd: f32 = 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);
|
||||
var gl: f32 = length(gT);
|
||||
var nrm: vec2f = select( vec2f(0.0), -gT / gl , gl > 1e-6 );
|
||||
var band: f32 = 1.0 - smoothstep(0.5, 0.9, M);
|
||||
var lit: f32 = dot(nrm, normalize(vec2f(-0.5, -1.0)));
|
||||
tc *= 1.0 + 0.42 * lit * band;
|
||||
|
||||
var dm: f32 = (M - 0.5) / fwM;
|
||||
tc *= mix(0.4, 1.0, smoothstep(0.5, 1.4 + zp * 0.08, dm));
|
||||
var dt: f32 = (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));
|
||||
}
|
||||
|
||||
${GPU_SMOOTH_SLOPE_DRAW}
|
||||
// Round loose grains.
|
||||
var np: i32 = i32(isN(n0, 2) + isN(n1, 2) + isN(n2, 2) + isN(n3, 2));
|
||||
var closed: bool = isM(n0) + isM(n1) + isM(n2) + isM(n3) > 3.5;
|
||||
if (np > 0) {
|
||||
var best: f32 = 1e9; var gc: vec3f = vec3f(0.0); var gd: vec2f = vec2f(0.0); var gr: f32 = 0.6;
|
||||
for (var k: i32 = 0; k < 4; k++) {
|
||||
var o4: vec2i = vec2i(k & 1, k >> 1);
|
||||
var nk: i32 = select(select(select( n3, n2 , k == 2 ), n1 , k == 1 ), n0 , k == 0 );
|
||||
if (nk != 2) { continue; }
|
||||
var ck: vec4f = select(select(select( k3, k2 , k == 2 ), k1 , k == 1 ), k0 , k == 0 );
|
||||
var ce: vec2f = vec2f(c0 + o4) + 0.5;
|
||||
var r: f32 = 0.6 + 0.1 * (h21(ce + (u.view.xy)) - 0.5);
|
||||
var dv: vec2f = (q - ce) / r;
|
||||
var dd: f32 = length(dv);
|
||||
if (dd < best) { best = dd; gc = ck.rgb; gd = dv; gr = r; }
|
||||
}
|
||||
var cover: f32 = select( 1.0 - smoothstep(1.0 - px / gr, 1.0 + px / gr, best), 1.0 , np == 4 );
|
||||
if (closed && F.r < 0.5) { cover = 1.0; }
|
||||
|
||||
cover *= 1.0 - smoothstep(-0.6, 0.6, (F.r - 0.5) / fwT);
|
||||
if (cover > 0.0) {
|
||||
var r2: f32 = min(1.0, dot(gd, gd));
|
||||
var n3: vec3f = normalize(vec3f(gd * 0.85, sqrt(1.0 - r2) + 0.25));
|
||||
var sh: f32 = 0.98 + 0.18 * max(0.0, dot(n3, normalize(vec3f(-0.45, -0.8, 0.6))));
|
||||
sh *= mix(1.0, 0.94, smoothstep(0.7 - px * 0.2, 1.15 + px * 0.2, best));
|
||||
var pc: vec3f = (select( gc, (k0.rgb * wq.x + k1.rgb * wq.y + k2.rgb * wq.z + k3.rgb * wq.w) ,np == 4 )) * sh;
|
||||
if (!closed) { pc *= mix(1.0, 0.72, smoothstep(0.7, 1.0, best)); }
|
||||
C = mix(C, pc, cover);
|
||||
}
|
||||
}
|
||||
|
||||
// Built pieces: belts, screws, wall tiles and filters.
|
||||
if (nc == 4) { C = own.rgb; }
|
||||
else if (nc == 7 || nc == 8) {
|
||||
|
||||
var dir: f32 = select( -1.0, 1.0 , nc == 8 );
|
||||
var lb: vec2f = fract(w * 0.25) * 4.0;
|
||||
|
||||
var sd: f32 = beltLineSD(c, lb);
|
||||
var inside: f32 = 1.0 - smoothstep(-px, px, sd);
|
||||
if (inside > 0.0) {
|
||||
var edge: f32 = -sd;
|
||||
var bc: vec3f;
|
||||
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 {
|
||||
var rc: vec2f = vec2f(select( 3.0, 1.0 ,lb.x < 2.0 ), 2.0); var dv: vec2f = lb - rc;
|
||||
var rd: f32 = length(dv);
|
||||
bc = vec3f(0.15, 0.16, 0.18);
|
||||
if (rd < 0.95) {
|
||||
var ang: f32 = atan2(dv.y, dv.x) - dir * (su.x) * 30.0 / 0.95;
|
||||
var spoke: f32 = smoothstep(0.55, 0.85, cos(ang * 3.0));
|
||||
bc = 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));
|
||||
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)) {
|
||||
|
||||
var vert: bool = nc == 9 || nc == 11;
|
||||
var sp2: f32 = select(select(select( -1.0, 1.0 , nc == 11 ), 1.0 , nc == 10 ), -1.0 , nc == 9 );
|
||||
var a: f32 = (select( fract(w.y * 0.25), fract(w.x * 0.25) ,vert )) * 4.0; var along: f32 = select( w.x, w.y , vert );
|
||||
var screwSD: f32 = -1.0; var capRing: f32 = 0.0;
|
||||
var screwTag: vec4f = cellAt(tTree, c);
|
||||
var screwJoins: i32 = i32(screwTag.a * 255.0 + 0.5);
|
||||
|
||||
var shaped: bool = nc >= 19 || (screwJoins > 0 && screwJoins < 64);
|
||||
if (shaped) {
|
||||
var d: vec2f = vec2f(1.0,select( 1.0, -1.0 , nc == 19 || nc == 21 ));
|
||||
var tag: vec4f = screwTag;
|
||||
var local: vec2f = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w) - 2.0;
|
||||
var joins: i32 = screwJoins;
|
||||
screwSD = 100.0;
|
||||
for (var end: i32 = 0; end < 2; end++) {
|
||||
var join: i32 = (joins >> u32(end * 3)) & 7;
|
||||
var side: f32 = select( 1.0, -1.0 , end == 0 );
|
||||
var diagonal: bool = select( join != 0, join == 0 || join >= 5 , nc >= 19 );
|
||||
var grade: f32 = select(select( 1.0, -1.0 , join == 1 ), d.y , nc >= 19 );
|
||||
var ray: vec2f = select(select(select(select(select( vec2f(-1, 0), vec2f(0, 1) , join == 3 ), vec2f(1, 0) , join == 2 ), vec2f(0, -1) ,
|
||||
join == 1 ), (select( vec2f(side, 0), vec2f(0, side) ,vert )) ,
|
||||
nc < 19 ), (select( vec2f(side, side * grade), vec2f(side * grade, side) ,vert )) * 0.70710678 , diagonal );
|
||||
var t: f32 = dot(local, ray); var cross2: f32 = dot(local, vec2f(-ray.y, ray.x));
|
||||
var radius: f32 = length(vec2f(cross2, min(0.0, t))); var cap: f32 = t - (select( 2.0, 2.82842712 ,diagonal ));
|
||||
|
||||
var sd: f32 = max(radius - 2.0,select( -10.0, cap , (joins & (64 << u32(end))) != 0 ));
|
||||
if (sd < screwSD) {
|
||||
screwSD = sd;
|
||||
a = 2.0 + (select( radius, -radius ,side * cross2 < 0.0 ));
|
||||
along = (select(select( w.x - local.x, w.y - local.y , vert ), dot(w - local, d) * 0.70710678 ,nc >= 19 )) + side * t;
|
||||
capRing = select( 0.0, smoothstep(-0.55, -0.15, cap) ,(joins & (64 << u32(end))) != 0 );
|
||||
}
|
||||
}
|
||||
if (nc >= 19) { sp2 = select( -1.0, 1.0 ,nc == 19 || nc == 20 ); }
|
||||
}
|
||||
var wall: f32 = 0.62;
|
||||
var tc: vec3f;
|
||||
if (a < wall || a > 4.0 - wall || capRing > 0.5) {
|
||||
var u2: f32 = select( (4.0 - a) / wall, a / wall , a < wall );
|
||||
var cyl: f32 = sin(clamp(u2, 0.0, 1.0) * 1.5708);
|
||||
tc = vec3f(0.62, 0.41, 0.27) * (0.6 + 0.55 * cyl);
|
||||
if (a < wall) { tc *= 1.08; }
|
||||
var ring: f32 = 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 {
|
||||
var full: bool = own.r + own.g + own.b > 0.0;
|
||||
var fl: f32 = fract((along + a * 0.9 - sp2 * (su.x) * 14.0) * 0.5);
|
||||
var flight: f32 = smoothstep(0.82, 0.92, fl) * (1.0 - smoothstep(0.92, 1.0, fl));
|
||||
var bore: vec3f = vec3f(0.11, 0.1, 0.11) * (0.8 + 0.4 * (1.0 - abs(a - 2.0) / 1.4));
|
||||
tc = select( bore, own.rgb * (0.9 + 0.1 * vn(w * 1.3)) ,full );
|
||||
tc = mix(tc, vec3f(0.6, 0.48, 0.38), flight * (select( 0.8, 0.35 ,full )));
|
||||
tc *= 1.0 - 0.25 * smoothstep(0.9, 1.3, abs(a - 2.0));
|
||||
}
|
||||
if (shaped) {
|
||||
var full: bool = 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)) {
|
||||
var lb: vec2f = fract(w * 0.25) * 4.0; var rb: vec2f = 4.0 - lb;
|
||||
var e: f32 = min(min(lb.x, lb.y), min(rb.x, rb.y));
|
||||
var tc: vec3f = own.rgb * (0.92 + 0.14 * fbm(w * 0.55));
|
||||
var hl: f32 = max(1.0 - smoothstep(0.0, 0.8, lb.x), 1.0 - smoothstep(0.0, 0.8, lb.y));
|
||||
var shd: f32 = 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) {
|
||||
var sl: vec2f = lb;
|
||||
if (nc == 16 || nc == 18) { sl.x = 4.0 - sl.x; }
|
||||
if (nc >= 17) { sl.y = 4.0 - sl.y; }
|
||||
var d: f32 = (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) {
|
||||
|
||||
var dir: f32 = select( -1.0, 1.0 , nc == 14 );
|
||||
var lb: vec2f = fract(w * 0.25) * 4.0;
|
||||
var tag: vec4f = cellAt(tTree, c);
|
||||
var carry: bool = tag.a > 0.16; var tinted: bool = tag.r + tag.g + tag.b > 0.0;
|
||||
var sd: f32 = beltLineSD(c, lb);
|
||||
var inside: f32 = 1.0 - smoothstep(-px, px, sd);
|
||||
if (inside > 0.0) {
|
||||
var edge: f32 = -sd;
|
||||
var steel: vec3f = vec3f(0.42, 0.44, 0.5);
|
||||
|
||||
var behind: vec3f = select( C * 0.8, own.rgb , own.r + own.g + own.b > 0.0 );
|
||||
var cg: vec2f = abs(fract(w) - 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 fc: vec3f;
|
||||
if (edge < 0.8) {
|
||||
var rg: f32 = bandRidge(w, lb, dir);
|
||||
var band: vec3f = bandColor(lb, rg);
|
||||
if (tinted && (carry == (lb.y < 2.0))) { band = mix(band, tag.rgb * (select( 0.8, 1.0 ,lb.y < 2.0 )), 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 legacy nine-tap glow filter.
|
||||
if (uLightMap.w > 0.5) { C *= sampleLevel(tLight, (q - uLightMap.xy) / uLightMap.z / (vec2f(textureDimensions(tLight))), 0.0).rgb; }
|
||||
if ((su.z) > 0.0) {
|
||||
var gu: vec2f = q / (2.0 * (vec2f(textureDimensions(tGlow)))); var d: vec2f = 1.0 / (vec2f(textureDimensions(tGlow)));
|
||||
var g: vec3f = sampleLevel(tGlow, gu, 0.0).rgb * 0.25;
|
||||
g += (sampleLevel(tGlow, gu + vec2f(d.x, 0), 0.0).rgb + sampleLevel(tGlow, gu - vec2f(d.x, 0), 0.0).rgb + sampleLevel(tGlow, gu + vec2f(0, d.y), 0.0).rgb + sampleLevel(tGlow, gu - vec2f(0, d.y), 0.0).rgb) * 0.125;
|
||||
g += (sampleLevel(tGlow, gu + d, 0.0).rgb + sampleLevel(tGlow, gu - d, 0.0).rgb + sampleLevel(tGlow, gu + vec2f(d.x, -d.y), 0.0).rgb + sampleLevel(tGlow, gu + vec2f(-d.x, d.y), 0.0).rgb) * 0.0625;
|
||||
C += g * (su.z);
|
||||
}
|
||||
return vec4f(C, 1.0);
|
||||
}`;
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
`;
|
||||
@@ -0,0 +1,47 @@
|
||||
// Field mip levels preserve the legacy trilinear AO sample at level 2.2.
|
||||
const GPU_FIELD_MIP = `
|
||||
@group(0) @binding(0) var source: texture_2d<f32>;
|
||||
@group(0) @binding(1) var linear: sampler;
|
||||
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
|
||||
let pos=array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3)); return vec4f(pos[i],0,1);
|
||||
}
|
||||
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
||||
let size=max(vec2u(1),textureDimensions(source)/2u);
|
||||
return textureSampleLevel(source,linear,p.xy/vec2f(size),0);
|
||||
}`;
|
||||
function gpuSmoothInit() {
|
||||
const d=GPU.device;
|
||||
const pipeline=(code,vertex,format)=>d.createRenderPipeline({layout:'auto',vertex:{module:d.createShaderModule({code:vertex}),entryPoint:'vs'},fragment:{module:d.createShaderModule({code}),entryPoint:'fs',targets:[{format}]},primitive:{topology:'triangle-list'}});
|
||||
GPU.smooth=pipeline(GPU_SMOOTH,GPU_DRAW,GPU.format);
|
||||
GPU.fieldMip=pipeline(GPU_FIELD_MIP,GPU_FIELD_MIP,'rgba8unorm');
|
||||
GPU.smoothUniform=d.createBuffer({size:16,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST});
|
||||
}
|
||||
function gpuSmoothTextures() {
|
||||
const w=R.bw,h=R.bh;
|
||||
GPU.smoothOver=Array.from({length:3},()=>gpuTexture(w,h));
|
||||
GPU.smoothMask=gpuTexture(w,h,'r8uint');
|
||||
GPU.backTex=gpuTexture(w,h,'rgba8unorm',true);
|
||||
GPU.treeTex=gpuTexture(w,h,'rgba8unorm',true);
|
||||
GPU.fieldTex=GPU.device.createTexture({size:[w,h],format:'rgba8unorm',mipLevelCount:Math.min(4,1+Math.floor(Math.log2(Math.max(w,h)))),usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.STORAGE_BINDING|GPUTextureUsage.RENDER_ATTACHMENT});
|
||||
GPU.viewTextures.push(...GPU.smoothOver,GPU.smoothMask,GPU.backTex,GPU.treeTex,GPU.fieldTex);
|
||||
}
|
||||
function gpuSmoothBindings(group,view,buffer) {
|
||||
GPU.bindColourSmooth=group(GPU.colourSmooth,[buffer(GPU.uniform),buffer(GPU.lut),buffer(GPU.land),...GPU.worldTextures.slice(0,9).map(view),view(GPU.override),view(GPU.overrideGlow),view(GPU.colourTex),view(GPU.glowTex),view(GPU.backTex),view(GPU.treeTex),GPU.fieldTex.createView({baseMipLevel:0,mipLevelCount:1}),...GPU.smoothOver.map(view),view(GPU.smoothMask)]);
|
||||
GPU.bindSmooth=group(GPU.smooth,[buffer(GPU.uniform),view(GPU.colourTex),view(GPU.backTex),view(GPU.treeTex),view(GPU.fieldTex),view(GPU.lightTex),view(GPU.glowTex),GPU.sampler,buffer(GPU.smoothUniform)]);
|
||||
GPU.fieldMips=[];
|
||||
for(let i=1;i<GPU.fieldTex.mipLevelCount;i++) GPU.fieldMips.push({
|
||||
bind:group(GPU.fieldMip,[GPU.fieldTex.createView({baseMipLevel:i-1,mipLevelCount:1}),GPU.sampler]),
|
||||
view:GPU.fieldTex.createView({baseMipLevel:i,mipLevelCount:1})
|
||||
});
|
||||
}
|
||||
function gpuSmoothUpload() {
|
||||
for(const [i,a] of [R.backPx,R.treePx,R.fieldPx].entries()) gpuWrite(GPU.smoothOver[i],a,R.bw,0,0,R.bw,R.bh);
|
||||
gpuWrite(GPU.smoothMask,GPU.overrideMask,R.bw,0,0,R.bw,R.bh);
|
||||
GPU.device.queue.writeBuffer(GPU.smoothUniform,0,new Float32Array([(performance.now()-SM.t0)/1000,smoothSurface(),SET.glow==='off'?0:SET.glow==='high'?0.9:0.55,0]));
|
||||
}
|
||||
function gpuSmoothMips(encoder) {
|
||||
for(const m of GPU.fieldMips) {
|
||||
const pass=encoder.beginRenderPass({colorAttachments:[{view:m.view,loadOp:'clear',storeOp:'store'}]});
|
||||
pass.setPipeline(GPU.fieldMip);pass.setBindGroup(0,m.bind);pass.draw(3);pass.end();
|
||||
}
|
||||
}
|
||||
@@ -35,9 +35,9 @@ function gpuWorldTextures() {
|
||||
GPU.bindColour = null;
|
||||
}
|
||||
function gpuViewTextures() {
|
||||
const key = `${R.bw}/${R.bh}/${R.lw}/${R.lh}`;
|
||||
const key = `${R.bw}/${R.bh}/${R.lw}/${R.lh}/${+R.smoothOn}`;
|
||||
if (GPU.viewKey === key) return;
|
||||
GPU.viewKey = key;
|
||||
GPU.viewKey = key; GPU.lightFrame = undefined;
|
||||
if (GPU.viewTextures) for (const t of GPU.viewTextures) t.destroy();
|
||||
const bw = R.bw, bh = R.bh, gw = R.glowC.width, gh = R.glowC.height;
|
||||
GPU.override = gpuTexture(bw, bh); GPU.overrideGlow = gpuTexture(gw, gh);
|
||||
@@ -47,6 +47,7 @@ function gpuViewTextures() {
|
||||
GPU.lightTex = gpuTexture(Math.max(1, R.lw), Math.max(1, R.lh), 'rgba8unorm', true);
|
||||
GPU.lightWork = [0,1].map(() => gpuTexture(Math.max(1,R.lw),Math.max(1,R.lh),'rgba16float',true));
|
||||
GPU.viewTextures = [GPU.override, GPU.overrideGlow, GPU.colourTex, GPU.glowTex, GPU.blurX, GPU.blurY, GPU.lightTex, ...GPU.lightWork];
|
||||
if (R.smoothOn) gpuSmoothTextures();
|
||||
GPU.bindColour = null;
|
||||
}
|
||||
function gpuBindings() {
|
||||
@@ -54,6 +55,7 @@ function gpuBindings() {
|
||||
const d = GPU.device, buffer = b => ({ buffer: b }), view = t => t.createView();
|
||||
const group = (pipeline, resources) => d.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: resources.map((resource, binding) => ({ binding, resource })) });
|
||||
GPU.bindColour = group(GPU.colour, [buffer(GPU.uniform), buffer(GPU.lut), buffer(GPU.land), ...GPU.worldTextures.slice(0,9).map(view), view(GPU.override), view(GPU.overrideGlow), view(GPU.colourTex), view(GPU.glowTex)]);
|
||||
if (R.smoothOn) gpuSmoothBindings(group, view, buffer);
|
||||
gpuLightBindings(group, view, buffer);
|
||||
GPU.bindDraw = group(GPU.draw, [buffer(GPU.uniform), view(GPU.colourTex), view(GPU.lightTex), view(GPU.blurY), GPU.sampler]);
|
||||
if (GPU.blurUniforms) for (const b of GPU.blurUniforms) b.destroy();
|
||||
@@ -89,6 +91,7 @@ function gpuUpload(lighting, glowOn) {
|
||||
gpuWrite(GPU.overrideGlow, R.glowPx, R.glowC.width, 0, 0, R.glowC.width, R.glowC.height);
|
||||
for(let i=0;i<W;i++) { GPU.columnData[i*4]=surfY[i]; GPU.columnData[i*4+1]=roofY[i]; }
|
||||
q.writeBuffer(GPU.columns,0,GPU.columnData);
|
||||
if (R.smoothOn) gpuSmoothUpload();
|
||||
const zp = R.zp, ox = Math.round(-(R.camX - R.vx0) * zp), oy = Math.round(-(R.camY - R.vy0) * zp);
|
||||
GPU.params.set([R.vx0, R.vy0, R.bw, R.bh, W, H, FW, FH,
|
||||
+SET.fog, R.rain, SET.grain, R.frame, +R.lens, Math.max(0, Object.keys(THERM).indexOf(SET.thermalPalette)), +SET.incandescence, +glowOn,
|
||||
|
||||
@@ -16,7 +16,7 @@ function worldObjects() {
|
||||
if (dy === 0) { r += end ? 10 : 34; g += end ? 8 : 28; b += end ? 6 : 24; } else { r -= end ? 34 : 18; g -= end ? 30 : 16; b -= end ? 26 : 14; }
|
||||
if (e.T > 450 && incand) { const gi = glowIndex(e.T), w = GLOW_W[gi]; r = r * (1 - w) + GLOW_R[gi] * w; g = g * (1 - w) + GLOW_G[gi] * w; b = b * (1 - w) + GLOW_B[gi] * w; }
|
||||
if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0;
|
||||
if (R.gpuOn && !px[sy * bw + sx]) GPU.overrideCount++;
|
||||
if (R.gpuOn && !gpuOverrideHas(sy * bw + sx)) { if (sm) cellPixel(sx + vx0, sy + vy0); else GPU.overrideCount++; }
|
||||
px[sy * bw + sx] = (sm ? 0x04000000 : 0xff000000) | ((b > 255 ? 255 : b | 0) << 16) | ((g > 255 ? 255 : g | 0) << 8) | (r > 255 ? 255 : r | 0);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ function worldObjects() {
|
||||
const sx = Math.floor(f.x) - vx0, sy = Math.floor(f.y) - vy0;
|
||||
if (sx < 0 || sy < 0 || sx >= bw || sy >= bh) continue;
|
||||
const m = f.m, v = ((sx * 7 + sy * 13) & 15) - 8;
|
||||
if (R.gpuOn && !px[sy * bw + sx]) GPU.overrideCount++;
|
||||
if (R.gpuOn && !gpuOverrideHas(sy * bw + sx)) { if (sm) cellPixel(sx + vx0, sy + vy0); else GPU.overrideCount++; }
|
||||
px[sy * bw + sx] = (sm ? 0x02000000 : 0xff000000) | (Math.max(0, Math.min(255, COLB[m] + v)) << 16) | (Math.max(0, Math.min(255, COLG[m] + v)) << 8) | Math.max(0, Math.min(255, COLR[m] + v));
|
||||
}
|
||||
// blueprint ghosts: blocks waiting to be built
|
||||
@@ -68,7 +68,7 @@ function worldObjects() {
|
||||
const sx = gbx * BS + dx - vx0, sy = gby * BS + dy - vy0;
|
||||
if (sx < 0 || sy < 0 || sx >= bw || sy >= bh) continue;
|
||||
const k = sy * bw + sx;
|
||||
if (R.gpuOn && !px[k]) cellPixel(sx + vx0, sy + vy0);
|
||||
if (R.gpuOn && !gpuOverrideHas(k)) cellPixel(sx + vx0, sy + vy0);
|
||||
const c = px[k];
|
||||
const edge = slope || diagonal ? sd > -0.8 : (dx === 0 && (caps & 1)) || dy === 0 || (dx === BS - 1 && (caps & 2)) || dy === BS - 1;
|
||||
const a = edge ? (((dx + dy + pulse) & 3) === 0 ? 0.85 : 0.6) : 0.28;
|
||||
|
||||
@@ -55,12 +55,17 @@ function smUpload(name, w, h, data) {
|
||||
if (T.mip) gl.generateMipmap(gl.TEXTURE_2D);
|
||||
}
|
||||
const smBytes = a => new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
|
||||
// Both renderers share the same cached parallax horizon.
|
||||
function smoothSurface() {
|
||||
if (SM.surf < 0 || SM.surfSeed !== worldSeed || SM.surfW !== W) { SM.surf = meanSurface(); SM.surfW = W; SM.surfSeed = worldSeed; }
|
||||
return SM.surf;
|
||||
}
|
||||
function smoothDraw(lighting, glowOn) {
|
||||
const t0 = performance.now();
|
||||
// the GPU pass can run below full resolution on a slow graphics card; it is scaled up smoothly
|
||||
const gl = SM.gl, cv = SM.canvas, sc = SM.scale, cw = Math.max(1, Math.round(R.canvas.width * sc)), ch = Math.max(1, Math.round(R.canvas.height * sc)), u = SM.u;
|
||||
if (cv.width !== cw || cv.height !== ch) { cv.width = cw; cv.height = ch; }
|
||||
if (SM.surf < 0 || SM.surfSeed !== worldSeed || SM.surfW !== W) { SM.surf = meanSurface(); SM.surfW = W; SM.surfSeed = worldSeed; }
|
||||
const surf = smoothSurface();
|
||||
gl.viewport(0, 0, cw, ch);
|
||||
gl.useProgram(SM.prog);
|
||||
const bw = R.bw, bh = R.bh;
|
||||
@@ -82,7 +87,7 @@ function smoothDraw(lighting, glowOn) {
|
||||
gl.uniform2f(u.uGlowSize, R.glowC.width || 1, R.glowC.height || 1);
|
||||
gl.uniform1f(u.uGlowK, glowOn ? (SET.glow === 'high' ? 0.9 : 0.55) : 0);
|
||||
gl.uniform1f(u.uTime, (performance.now() - SM.t0) / 1000);
|
||||
gl.uniform1f(u.uSurf, SM.surf);
|
||||
gl.uniform1f(u.uSurf, surf);
|
||||
gl.uniform1f(u.uRain, R.rain || 0);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
||||
const ctx = R.ctx;
|
||||
|
||||
+3
-4
@@ -6,7 +6,7 @@ function drawWorld() {
|
||||
const perfStart = (PERF_MARKS || SET.showPerf) ? performance.now() : 0;
|
||||
if (PERF_MARKS || SET.showPerf) { perfMarks.light = 0; perfMarks.override = 0; delete perfMarks.gpu; }
|
||||
const wasGpu = R.gpuOn;
|
||||
R.gpuOn = SET.renderer === 'webgpu' && SET.look === 'pixel' && GPU.ok;
|
||||
R.gpuOn = SET.renderer === 'webgpu' && GPU.ok;
|
||||
if (R.gpuOn && !wasGpu) GPU.lightFrame = undefined;
|
||||
GPU.canvas.hidden = !R.gpuOn;
|
||||
if (R.gpuOn) R.ctx.clearRect(0, 0, R.canvas.width, R.canvas.height);
|
||||
@@ -15,9 +15,8 @@ function drawWorld() {
|
||||
const vx0 = R.vx0, vy0 = R.vy0;
|
||||
// how hard it is raining around the camera, eased so the sky greys over gently
|
||||
R.rain = (R.rain || 0) * 0.98 + rainAt(Math.max(0, Math.min(W - 1, Math.floor(R.camX + bw / 2)))) * 0.02;
|
||||
// Smooth look: the colours below are still worked out per grain, but each grain also gets a
|
||||
// class (alpha byte) and the GPU draws rounded shapes from them (see smooth-shaders.js)
|
||||
const sm = R.smoothOn = SET.look === 'smooth' && !R.lens && typeof smoothAvailable === 'function' && smoothAvailable();
|
||||
// Smooth inputs carry a class in alpha; only exceptional cells use the CPU in WebGPU mode.
|
||||
const sm = R.smoothOn = SET.look === 'smooth' && !R.lens && (R.gpuOn || smoothAvailable());
|
||||
const mmask = R.moldMask;
|
||||
if (sm) {
|
||||
mmask.fill(0);
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ function frame(now) {
|
||||
weather.seen = true;
|
||||
showCard({ kind: 'Weather', title: 'Rain', swatch: WATER, body: ['Steam that rises into the open sky gathers into clouds and comes back down as rain nearby. On freezing heights it falls as snow.', 'Water is never used up, only moved around. If some leaves the world (dropped into the dock, say), a shower from beyond the claim makes it up.'] });
|
||||
}
|
||||
if (SET.look === 'smooth' && SM.tried && !SM.ok) {
|
||||
if (SET.look === 'smooth' && !R.gpuOn && SM.tried && !SM.ok) {
|
||||
SET.look = 'pixel'; saveSettings();
|
||||
toast(SM.why === 'slow' ? 'The smooth look was too slow on this computer, so the pixel look is on. You can try it again in Settings.' : SM.why === 'software' ? 'This browser is drawing without the graphics card, so the smooth look would crawl. The pixel look is on.' : 'This browser cannot draw the smooth look, so the pixel look is on.');
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ const SET_TABS = {
|
||||
{ k: 'shake', label: 'Screen shake', type: 'toggle' },
|
||||
],
|
||||
Graphics: [
|
||||
{ k: 'renderer', label: 'Renderer', d: 'WebGPU colours the pixel look on the graphics card. Smooth uses the original renderer.', type: 'select', opts: [['webgpu', 'WebGPU'], ['legacy', 'Legacy']] },
|
||||
{ k: 'renderer', label: 'Renderer', d: 'WebGPU draws both looks on the graphics card. Legacy uses the original renderer.', type: 'select', opts: [['webgpu', 'WebGPU'], ['legacy', 'Legacy']] },
|
||||
{ k: 'look', label: 'Look', d: 'Smooth draws rounded, outlined ground, round grains and glossy liquids on the graphics card. Pixel is the original look, one square per grain.', type: 'select', opts: [['smooth', 'Smooth'], ['pixel', 'Pixel']] },
|
||||
{ k: 'lighting', label: 'Lighting', d: 'Full is colored light at grain resolution. Fast uses half resolution.', type: 'select', opts: [['off', 'Off'], ['fast', 'Fast'], ['full', 'Full']] },
|
||||
{ k: 'lightEvery', label: 'Light updates', type: 'select', opts: [[1, 'Every frame'], [2, 'Every 2nd frame'], [3, 'Every 3rd frame']] },
|
||||
|
||||
Reference in New Issue
Block a user