@driftengine/core 4.4.2 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +46 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +79 -8
- package/dist/render/backend/webgl2/renderer.js +360 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +2 -12
- package/dist/render/backend/webgpu/renderer.d.ts +171 -54
- package/dist/render/backend/webgpu/renderer.js +944 -390
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +7 -1
- package/dist/render/sceneTarget.js +58 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +401 -108
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/renderer.ts +1108 -421
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +58 -11
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/translucentQueue.ts +20 -1
|
@@ -80,6 +80,27 @@ fn oitResolved(accum: vec4<f32>, reveal: f32) -> vec4<f32> {
|
|
|
80
80
|
return vec4<f32>(average * covered, covered);
|
|
81
81
|
}
|
|
82
82
|
`;
|
|
83
|
+
/**
|
|
84
|
+
* Glass over its own shading, as the forward path draws it: `gpudriven/lit.ts`'s `glassShade`,
|
|
85
|
+
* which `scripts/gpu-parity.mjs` holds this to. Schlick at 0.04 with the angle unsigned — a pane
|
|
86
|
+
* passes the same light seen from either side — and floored at the forward path's grazing 0.05.
|
|
87
|
+
*/
|
|
88
|
+
const GLASS_SHADE_WGSL = /* wgsl */ `
|
|
89
|
+
fn glassShade(
|
|
90
|
+
own: vec3<f32>,
|
|
91
|
+
seen: vec3<f32>,
|
|
92
|
+
transmission: f32,
|
|
93
|
+
frost: f32,
|
|
94
|
+
tint: vec3<f32>,
|
|
95
|
+
cosView: f32,
|
|
96
|
+
sunGlow: vec3<f32>,
|
|
97
|
+
) -> vec3<f32> {
|
|
98
|
+
let c = clamp(abs(cosView), 0.05, 1.0);
|
|
99
|
+
let fresnel = 0.04 + 0.96 * pow(1.0 - c, 5.0);
|
|
100
|
+
let shown = transmission * (1.0 - fresnel);
|
|
101
|
+
return own + (seen * tint - own) * shown + sunGlow * tint * (frost * transmission);
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
83
104
|
export const BLEND_RASTER_WGSL = /* wgsl */ `
|
|
84
105
|
struct BlendVarying {
|
|
85
106
|
@builtin(position) position: vec4<f32>,
|
|
@@ -106,6 +127,12 @@ ${FRAME_BLOCK_WGSL}
|
|
|
106
127
|
@group(0) @binding(9) var environmentMap: texture_2d_array<f32>;
|
|
107
128
|
@group(0) @binding(10) var environmentSampler: sampler;
|
|
108
129
|
${decodeResourcesWgsl({ group: 0, latents: 11, clampSampler: 12, repeatSampler: 13, nodes: 14, weights: 15 })}
|
|
130
|
+
/**
|
|
131
|
+
* What the opaque half drew, with a mip chain on a frame whose scene holds glass: a pane shows it,
|
|
132
|
+
* a frosted one a coarser level of it. Read only by a material whose transmission is not zero.
|
|
133
|
+
*/
|
|
134
|
+
@group(0) @binding(16) var opaqueColour: texture_2d<f32>;
|
|
135
|
+
@group(0) @binding(17) var opaqueSampler: sampler;
|
|
109
136
|
fn decodeLatentSize() -> f32 {
|
|
110
137
|
return frame.size.w;
|
|
111
138
|
}
|
|
@@ -124,6 +151,7 @@ ${MESH_TRANSFORM_WGSL}
|
|
|
124
151
|
${SHADE_LIGHTING_WGSL}
|
|
125
152
|
${FOG_WGSL}
|
|
126
153
|
${OIT_WGSL}
|
|
154
|
+
${GLASS_SHADE_WGSL}
|
|
127
155
|
|
|
128
156
|
const NO_PROGRAM_HERE: u32 = ${0xffffffff}u;
|
|
129
157
|
|
|
@@ -239,7 +267,25 @@ fn blendFrag(in: BlendVarying) -> BlendTargets {
|
|
|
239
267
|
* distance — which is the whole reason this pipeline gained a fog.
|
|
240
268
|
*/
|
|
241
269
|
let toward = distance(in.world, frame.eye.xyz);
|
|
242
|
-
let
|
|
270
|
+
let fog = mediumFog(toward, in.world.y);
|
|
271
|
+
var colour = mix(litColour(surface, room), mediumColour(), fog);
|
|
272
|
+
/*
|
|
273
|
+
* **Glass shows what is behind it**, as the forward path's glass branch does: the opaque half's
|
|
274
|
+
* picture at this pixel, at the level frost picks, two short of the chain's end so a fully frosted
|
|
275
|
+
* pane keeps the large light and dark behind it. The sun glows through from the pane's far side,
|
|
276
|
+
* after the haze as the forward path adds it. A pane is seen from both sides, so its facing is
|
|
277
|
+
* the side toward the eye.
|
|
278
|
+
*/
|
|
279
|
+
let transmission = entry.extra.y;
|
|
280
|
+
if (transmission > 0.0) {
|
|
281
|
+
let size = vec2<f32>(textureDimensions(opaqueColour, 0));
|
|
282
|
+
let levels = max(floor(log2(max(size.x, size.y))) - 2.0, 0.0);
|
|
283
|
+
let seen = textureSampleLevel(opaqueColour, opaqueSampler, in.position.xy / size, entry.extra.z * levels).xyz;
|
|
284
|
+
let cosView = dot(surface.normal, toEye);
|
|
285
|
+
let facing = select(surface.normal, -surface.normal, cosView < 0.0);
|
|
286
|
+
let sunGlow = frame.lightColour.xyz * max(-dot(facing, frame.lightDir.xyz), 0.0) * (1.0 - fog);
|
|
287
|
+
colour = glassShade(colour, seen, transmission, entry.extra.z, entry.glassTint.xyz, cosView, sunGlow);
|
|
288
|
+
}
|
|
243
289
|
|
|
244
290
|
/*
|
|
245
291
|
* **The distance to the eye, not the clip-space depth**, because that is what oitWeight's falloff
|
|
@@ -253,6 +299,20 @@ fn blendFrag(in: BlendVarying) -> BlendTargets {
|
|
|
253
299
|
out.reveal = vec4<f32>(alpha, 0.0, 0.0, alpha);
|
|
254
300
|
return out;
|
|
255
301
|
}
|
|
302
|
+
|
|
303
|
+
/*
|
|
304
|
+
* **What a pane lets through, drawn from the sun into the tint map**: \`blendVert\` again, handed
|
|
305
|
+
* the light's raster matrix and the glass list, and this for the colour — \`glassShadow.ts\`'s
|
|
306
|
+
* paneTexel, the forward path's GLASS_TINT_FRAG in this language. Written against a white target
|
|
307
|
+
* with a multiplying blend and no depth, so every pane on a ray counts in any order.
|
|
308
|
+
*/
|
|
309
|
+
@fragment
|
|
310
|
+
fn glassTintFrag(in: BlendVarying) -> @location(0) vec4<f32> {
|
|
311
|
+
let entry = materials[min(in.material, ${MATERIAL_SLOTS - 1}u)];
|
|
312
|
+
let c = clamp(abs(dot(normalize(in.normal), frame.lightDir.xyz)), 0.05, 1.0);
|
|
313
|
+
let passes = entry.extra.y * (1.0 - (0.04 + 0.96 * pow(1.0 - c, 5.0)));
|
|
314
|
+
return vec4<f32>(passes * entry.glassTint.xyz, 1.0 - entry.extra.z);
|
|
315
|
+
}
|
|
256
316
|
`;
|
|
257
317
|
/**
|
|
258
318
|
* The two targets composited over what the opaque half drew.
|
|
@@ -337,3 +397,28 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
337
397
|
results[out + 3u] = resolved.z;
|
|
338
398
|
}
|
|
339
399
|
`;
|
|
400
|
+
/**
|
|
401
|
+
* `glassShade` alone, as a dispatch, so it can be checked against `gpudriven/lit.ts`.
|
|
402
|
+
*
|
|
403
|
+
* Sixteen floats a case — own, seen, transmission, frost, tint, the view cosine, the glow, one
|
|
404
|
+
* spare — and four out, the colour and one spare.
|
|
405
|
+
*/
|
|
406
|
+
export const GLASS_SHADE_PARITY_WGSL = /* wgsl */ `
|
|
407
|
+
@group(0) @binding(0) var<storage, read> cases: array<f32>;
|
|
408
|
+
@group(0) @binding(1) var<storage, read_write> results: array<f32>;
|
|
409
|
+
${GLASS_SHADE_WGSL}
|
|
410
|
+
@compute @workgroup_size(64)
|
|
411
|
+
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
412
|
+
if (id.x * 16u + 15u >= arrayLength(&cases)) { return; }
|
|
413
|
+
let at = id.x * 16u;
|
|
414
|
+
let own = vec3<f32>(cases[at], cases[at + 1u], cases[at + 2u]);
|
|
415
|
+
let seen = vec3<f32>(cases[at + 3u], cases[at + 4u], cases[at + 5u]);
|
|
416
|
+
let tint = vec3<f32>(cases[at + 8u], cases[at + 9u], cases[at + 10u]);
|
|
417
|
+
let glow = vec3<f32>(cases[at + 12u], cases[at + 13u], cases[at + 14u]);
|
|
418
|
+
let shaded = glassShade(own, seen, cases[at + 6u], cases[at + 7u], tint, cases[at + 11u], glow);
|
|
419
|
+
let out = id.x * 4u;
|
|
420
|
+
results[out] = shaded.x;
|
|
421
|
+
results[out + 1u] = shaded.y;
|
|
422
|
+
results[out + 2u] = shaded.z;
|
|
423
|
+
}
|
|
424
|
+
`;
|
|
@@ -43,7 +43,7 @@ export declare const CULL_INSTANCES_WGSL = "\n@group(0) @binding(0) var<storage,
|
|
|
43
43
|
* wall and is the whole reason the front half of the pipeline is reusable. A scene with nothing
|
|
44
44
|
* blended runs this once and the filter costs it one comparison against a uniform.
|
|
45
45
|
*/
|
|
46
|
-
export declare const LOD_CUT_WGSL = "\nstruct Params {\n screenHeight: f32,\n fovY: f32,\n thresholdPixels: f32,\n eyeX: f32,\n eyeY: f32,\n eyeZ: f32,\n /** Non-zero to select the blended clusters, zero to select everything else. */\n wantBlend: f32,\n};\n\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, and
|
|
46
|
+
export declare const LOD_CUT_WGSL = "\nstruct Params {\n screenHeight: f32,\n fovY: f32,\n thresholdPixels: f32,\n eyeX: f32,\n eyeY: f32,\n eyeZ: f32,\n /** Non-zero to select the blended clusters, zero to select everything else. */\n wantBlend: f32,\n /** Non-zero to keep, of those, only glass: what the light's glass pass draws. */\n wantGlass: f32,\n};\n\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, the glass's transmission and frost, and one spare. */\n extra: vec4<f32>,\n /** The glass's tint, white where the material is not glass, and one spare. */\n glassTint: vec4<f32>,\n}\n\nconst NO_PROGRAM: u32 = 4294967295u;\n\n@group(0) @binding(0) var<storage, read> params: array<f32, 8>;\n/** Six floats a cluster: centre, radius, own error, parent error. */\n@group(0) @binding(1) var<storage, read> clusters: array<f32>;\n@group(0) @binding(2) var<storage, read_write> selected: array<u32>;\n/** Four words a cluster: index offset, index count, identifier, mesh. */\n@group(0) @binding(3) var<storage, read> clusterMeta: array<u32>;\n/** One word a mesh, nonzero where CULL_INSTANCES_WGSL found it wholly outside the view. */\n@group(0) @binding(4) var<storage, read> instanceHidden: array<u32>;\n/** One material a cluster, and the table it indexes. Both only to read one flag. */\n@group(0) @binding(5) var<storage, read> materialOf: array<u32>;\n@group(0) @binding(6) var<uniform> materials: array<Material, 64>;\n\nfn projectedError(error: f32, distance: f32, radius: f32, screenHeight: f32, fovY: f32) -> f32 {\n let safe = max(max(distance, radius), 1e-4);\n let scale = screenHeight / (2.0 * tan(fovY * 0.5));\n return (error * scale) / safe;\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3<u32>) {\n let i = id.x;\n if (i * 6u + 5u >= arrayLength(&clusters)) { return; }\n /* A hidden mesh contributes nothing, so none of its clusters is in the cut at any level. */\n if (instanceHidden[clusterMeta[i * 4u + 3u]] != 0u) {\n selected[i] = 0u;\n return;\n }\n /*\n * **Which half of the frame this cluster belongs to, asked once.** The two halves are exclusive:\n * a blended cluster is not in the opaque cut and an opaque one is not in the blended cut, so\n * neither is drawn twice and neither is dropped. The flag is read through a uniform by an index\n * that is the same for every cluster of a mesh, so the branch is as coherent as this shader gets.\n */\n let material = materials[min(materialOf[i], 63u)];\n let blended = material.orm.w != 0.0;\n if (blended != (params[6] != 0.0)) {\n selected[i] = 0u;\n return;\n }\n /* The light's glass pass: a blended surface that is not glass casts nothing, as on the forward\n path, so only a material letting light through is kept. */\n if (params[7] != 0.0 && material.extra.y <= 0.0) {\n selected[i] = 0u;\n return;\n }\n let screenHeight = params[0];\n let fovY = params[1];\n let threshold = params[2];\n let eye = vec3<f32>(params[3], params[4], params[5]);\n\n let centre = vec3<f32>(clusters[i * 6u], clusters[i * 6u + 1u], clusters[i * 6u + 2u]);\n let radius = clusters[i * 6u + 3u];\n let ownError = clusters[i * 6u + 4u];\n let parentError = clusters[i * 6u + 5u];\n let distance = length(centre - eye);\n\n let own = projectedError(ownError, distance, radius, screenHeight, fovY);\n let parent = projectedError(parentError, distance, radius, screenHeight, fovY);\n /* Both halves: the first alone draws every ancestor too, the second alone draws nothing. */\n selected[i] = select(0u, 1u, own <= threshold && parent > threshold);\n}\n";
|
|
47
47
|
/**
|
|
48
48
|
* The pyramid's base level, copied out of the depth attachment a phase drew into.
|
|
49
49
|
*
|
|
@@ -100,9 +100,11 @@ struct Params {
|
|
|
100
100
|
eyeZ: f32,
|
|
101
101
|
/** Non-zero to select the blended clusters, zero to select everything else. */
|
|
102
102
|
wantBlend: f32,
|
|
103
|
+
/** Non-zero to keep, of those, only glass: what the light's glass pass draws. */
|
|
104
|
+
wantGlass: f32,
|
|
103
105
|
};
|
|
104
106
|
${MATERIAL_TABLE_WGSL}
|
|
105
|
-
@group(0) @binding(0) var<storage, read> params: array<f32,
|
|
107
|
+
@group(0) @binding(0) var<storage, read> params: array<f32, 8>;
|
|
106
108
|
/** Six floats a cluster: centre, radius, own error, parent error. */
|
|
107
109
|
@group(0) @binding(1) var<storage, read> clusters: array<f32>;
|
|
108
110
|
@group(0) @binding(2) var<storage, read_write> selected: array<u32>;
|
|
@@ -135,11 +137,18 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
135
137
|
* neither is drawn twice and neither is dropped. The flag is read through a uniform by an index
|
|
136
138
|
* that is the same for every cluster of a mesh, so the branch is as coherent as this shader gets.
|
|
137
139
|
*/
|
|
138
|
-
let
|
|
140
|
+
let material = materials[min(materialOf[i], ${MATERIAL_SLOTS - 1}u)];
|
|
141
|
+
let blended = material.orm.w != 0.0;
|
|
139
142
|
if (blended != (params[6] != 0.0)) {
|
|
140
143
|
selected[i] = 0u;
|
|
141
144
|
return;
|
|
142
145
|
}
|
|
146
|
+
/* The light's glass pass: a blended surface that is not glass casts nothing, as on the forward
|
|
147
|
+
path, so only a material letting light through is kept. */
|
|
148
|
+
if (params[7] != 0.0 && material.extra.y <= 0.0) {
|
|
149
|
+
selected[i] = 0u;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
143
152
|
let screenHeight = params[0];
|
|
144
153
|
let fovY = params[1];
|
|
145
154
|
let threshold = params[2];
|
|
@@ -22,4 +22,4 @@
|
|
|
22
22
|
*/
|
|
23
23
|
export declare const MATERIAL_SLOTS = 64;
|
|
24
24
|
/** The struct and the sentinel, for any module that binds the table. */
|
|
25
|
-
export declare const MATERIAL_TABLE_WGSL = "\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, and
|
|
25
|
+
export declare const MATERIAL_TABLE_WGSL = "\nstruct Material {\n /** rgb is the tint, w is the emissive gain. */\n tint: vec4<f32>,\n /** x is the roughness, y the specular reflectance, z the environment's and w the metalness. */\n surface: vec4<f32>,\n /** Base colour, normal, ORM and emissive programs; NO_PROGRAM where there is none. */\n programs: vec4<u32>,\n /** u scale, v scale, normal strength, occlusion strength. */\n maps: vec4<f32>,\n /** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */\n orm: vec4<f32>,\n /** The blended opacity, the glass's transmission and frost, and one spare. */\n extra: vec4<f32>,\n /** The glass's tint, white where the material is not glass, and one spare. */\n glassTint: vec4<f32>,\n}\n\nconst NO_PROGRAM: u32 = 4294967295u;\n";
|
|
@@ -35,8 +35,10 @@ struct Material {
|
|
|
35
35
|
maps: vec4<f32>,
|
|
36
36
|
/** Roughness scale, metallic scale, the alpha cutoff, and non-zero where this one blends. */
|
|
37
37
|
orm: vec4<f32>,
|
|
38
|
-
/** The blended opacity, and
|
|
38
|
+
/** The blended opacity, the glass's transmission and frost, and one spare. */
|
|
39
39
|
extra: vec4<f32>,
|
|
40
|
+
/** The glass's tint, white where the material is not glass, and one spare. */
|
|
41
|
+
glassTint: vec4<f32>,
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
const NO_PROGRAM: u32 = ${DECODE_NO_PROGRAM}u;
|
|
@@ -8,6 +8,7 @@ import { MATERIAL_SLOTS, MATERIAL_TABLE_WGSL } from './materialTable.wgsl.js';
|
|
|
8
8
|
import { MESH_TRANSFORM_WGSL } from './meshTransform.wgsl.js';
|
|
9
9
|
import { ENVIRONMENT_WGSL } from './environment.wgsl.js';
|
|
10
10
|
import { SHADE_SHADOW_WGSL } from './shadow.wgsl.js';
|
|
11
|
+
import { SUN_GLASS_WGSL } from './sunGlass.wgsl.js';
|
|
11
12
|
/**
|
|
12
13
|
* Rebuilding a pixel's surface from the triangle it recorded, on the device.
|
|
13
14
|
*
|
|
@@ -509,6 +510,14 @@ struct Job {
|
|
|
509
510
|
@group(0) @binding(12) var environmentMap: texture_2d_array<f32>;
|
|
510
511
|
@group(0) @binding(13) var environmentSampler: sampler;
|
|
511
512
|
${decodeResourcesWgsl({ group: 0, latents: 14, clampSampler: 15, repeatSampler: 16, nodes: 17, weights: 18 })}
|
|
513
|
+
/*
|
|
514
|
+
* **What the sun keeps through glass: the nearest pane's depth and what the panes let through**,
|
|
515
|
+
* which the pass draws from the sun after its opaque map. Where no material is glass the depth is
|
|
516
|
+
* the opaque map again and the tint a single white texel, which sunGlassAt reads as none.
|
|
517
|
+
*/
|
|
518
|
+
@group(0) @binding(19) var glassDepthMap: texture_depth_2d;
|
|
519
|
+
@group(0) @binding(20) var glassTints: texture_2d<f32>;
|
|
520
|
+
@group(0) @binding(21) var glassSampler: sampler;
|
|
512
521
|
|
|
513
522
|
fn decodeLatentSize() -> f32 {
|
|
514
523
|
return frame.size.w;
|
|
@@ -540,6 +549,45 @@ fn shadowDepthAt(u: f32, v: f32) -> f32 {
|
|
|
540
549
|
return textureLoad(shadowMap, vec2<i32>(x, y), 0);
|
|
541
550
|
}
|
|
542
551
|
${SHADE_SHADOW_WGSL}
|
|
552
|
+
|
|
553
|
+
/* sunGlass.wgsl.ts's three reads, as the opaque map is read: nearest by integer coordinate. */
|
|
554
|
+
fn glassDepthAt(u: f32, v: f32) -> f32 {
|
|
555
|
+
let size = frame.shadow.y;
|
|
556
|
+
let x = i32(clamp(floor(u * size), 0.0, size - 1.0));
|
|
557
|
+
let y = i32(clamp(floor(v * size), 0.0, size - 1.0));
|
|
558
|
+
return textureLoad(glassDepthMap, vec2<i32>(x, y), 0);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fn glassTintTexel(u: f32, v: f32) -> vec4<f32> {
|
|
562
|
+
let size = f32(textureDimensions(glassTints, 0).x);
|
|
563
|
+
let x = i32(clamp(floor(u * size), 0.0, size - 1.0));
|
|
564
|
+
let y = i32(clamp(floor(v * size), 0.0, size - 1.0));
|
|
565
|
+
return textureLoad(glassTints, vec2<i32>(x, y), 0);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* The tint over a spread, from the chain: the level whose texel is the spread's width. */
|
|
569
|
+
fn glassTintAround(u: f32, v: f32, radius: f32) -> vec4<f32> {
|
|
570
|
+
let size = f32(textureDimensions(glassTints, 0).x);
|
|
571
|
+
return textureSampleLevel(glassTints, glassSampler, vec2<f32>(u, v), log2(max(radius * size, 1.0)));
|
|
572
|
+
}
|
|
573
|
+
${SUN_GLASS_WGSL}
|
|
574
|
+
|
|
575
|
+
/*
|
|
576
|
+
* The sun's colour at this receiver after the panes between it and the sun: white where no glass
|
|
577
|
+
* cast, and faded with the opaque shadow's own strength. The map's scale in metres comes off the
|
|
578
|
+
* light's matrix, which is orthographic: a compute invocation has no derivatives to take it from.
|
|
579
|
+
*/
|
|
580
|
+
fn sunGlassAt(lightPos: vec3<f32>) -> vec3<f32> {
|
|
581
|
+
if (textureDimensions(glassTints, 0).x <= 1u || frame.shadow.x <= 0.0) { return vec3<f32>(1.0); }
|
|
582
|
+
let m = frame.lightViewProj;
|
|
583
|
+
var settings: SunGlassSettings;
|
|
584
|
+
settings.mapSize = frame.shadow.y;
|
|
585
|
+
settings.depthSpan = frame.shadow.z;
|
|
586
|
+
settings.uvPerMetre = 0.5 * length(vec3<f32>(m[0][0], m[1][0], m[2][0]));
|
|
587
|
+
settings.taps = frame.shadowLimits.y;
|
|
588
|
+
let tint = sunGlass(lightPos.x * 0.5 + 0.5, lightPos.y * 0.5 + 0.5, lightPos.z, settings);
|
|
589
|
+
return mix(vec3<f32>(1.0), tint, frame.shadow.x);
|
|
590
|
+
}
|
|
543
591
|
${ENVIRONMENT_WGSL}
|
|
544
592
|
${FOG_WGSL}
|
|
545
593
|
|
|
@@ -732,7 +780,8 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
732
780
|
|
|
733
781
|
var room: LitEnvironment;
|
|
734
782
|
room.lightDir = frame.lightDir.xyz;
|
|
735
|
-
|
|
783
|
+
/* The sun's colour through whatever glass stands between it and here, as flat/main.ts's is. */
|
|
784
|
+
room.lightColour = frame.lightColour.xyz * sunGlassAt(lightPos);
|
|
736
785
|
room.sky = frame.sky.xyz;
|
|
737
786
|
room.ground = frame.ground.xyz;
|
|
738
787
|
/*
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const SUN_GLASS_WGSL: string;
|
|
2
|
+
/**
|
|
3
|
+
* `sunGlass` over maps held in storage buffers, read as the reference reads them, so the whole
|
|
4
|
+
* lookup can be compared with `glassTintAt` rather than only the arithmetic between the reads.
|
|
5
|
+
*
|
|
6
|
+
* Four floats a case in — u, v, the receiver's depth, one spare — and four out, the tint and one
|
|
7
|
+
* spare. The config is the map's size, the depth span, map units a metre, and the tap count.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SUN_GLASS_PARITY_WGSL: string;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sun through glass for the device: `gpudriven/sunGlass.ts`'s `glassTintAt`, term for term, as
|
|
3
|
+
* the text the shading pass and the parity entry point both include.
|
|
4
|
+
*
|
|
5
|
+
* **The reads are the including module's**, as `SHADE_SHADOW_WGSL`'s are: `glassDepthAt(u, v)` the
|
|
6
|
+
* nearest pane's depth, `glassTintTexel(u, v)` the tint at the finest level, and
|
|
7
|
+
* `glassTintAround(u, v, radius)` the tint averaged over a spread of that radius. The shading pass
|
|
8
|
+
* answers the last from the tint's mip chain; the parity entry answers it with the reference's own
|
|
9
|
+
* box average, so everything around the reads is checked as it stands. Every constant is the
|
|
10
|
+
* reference's, interpolated rather than restated.
|
|
11
|
+
*/
|
|
12
|
+
import { FROST_RADIUS_CAP, FROST_SPREAD } from '../../glassShadow.js';
|
|
13
|
+
import { SHADOW_DEPTH_BIAS, SHADOW_PCF_OFFSETS, SHADOW_TEXEL_SCALE, } from '../../gpudriven/shadow.js';
|
|
14
|
+
import { SHADE_SHADOW_WGSL } from './shadow.wgsl.js';
|
|
15
|
+
export const SUN_GLASS_WGSL = /* wgsl */ `
|
|
16
|
+
struct SunGlassSettings {
|
|
17
|
+
mapSize: f32,
|
|
18
|
+
depthSpan: f32,
|
|
19
|
+
uvPerMetre: f32,
|
|
20
|
+
taps: f32,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* A coarse texel with the open ground's white taken back out, by how milky the pane at the
|
|
25
|
+
* receiver is: the pane's colour and its share of the texel. glassShadow.ts's unmixTint.
|
|
26
|
+
*/
|
|
27
|
+
fn sunGlassUnmix(mip: vec4<f32>, centreClarity: f32) -> vec4<f32> {
|
|
28
|
+
if (centreClarity >= 1.0) { return vec4<f32>(mip.xyz, 1.0); }
|
|
29
|
+
let share = clamp((1.0 - mip.w) / (1.0 - centreClarity), 0.0, 1.0);
|
|
30
|
+
if (share <= 0.0) { return vec4<f32>(1.0, 1.0, 1.0, 0.0); }
|
|
31
|
+
let colour = clamp((mip.xyz - vec3<f32>(1.0 - share)) / share, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
32
|
+
return vec4<f32>(colour, share);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* What the sun keeps through glass at map position (u, v) and light depth "depth". */
|
|
36
|
+
fn sunGlass(u: f32, v: f32, depth: f32, settings: SunGlassSettings) -> vec3<f32> {
|
|
37
|
+
if (u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0) { return vec3<f32>(1.0); }
|
|
38
|
+
let texel = ${SHADOW_TEXEL_SCALE.toFixed(4)} / settings.mapSize;
|
|
39
|
+
let bias = ${SHADOW_DEPTH_BIAS.toFixed(4)} / settings.depthSpan;
|
|
40
|
+
let taps = min(u32(settings.taps), ${SHADOW_PCF_OFFSETS.length}u);
|
|
41
|
+
let clarity = glassTintTexel(u, v).w;
|
|
42
|
+
let behind = max(depth - glassDepthAt(u, v), 0.0) * settings.depthSpan;
|
|
43
|
+
let spread = (1.0 - clarity) * ${FROST_SPREAD.toFixed(6)} * behind * settings.uvPerMetre;
|
|
44
|
+
let radius = min(max(texel, spread), texel * ${FROST_RADIUS_CAP.toFixed(1)});
|
|
45
|
+
|
|
46
|
+
var cover = 0.0;
|
|
47
|
+
var colour = vec3<f32>(0.0);
|
|
48
|
+
var found = 0.0;
|
|
49
|
+
for (var i = 0u; i < taps; i = i + 1u) {
|
|
50
|
+
let offset = SHADOW_TAPS[i];
|
|
51
|
+
/* The outline: a tap counts the pane only where the receiver is behind it, by the bias. */
|
|
52
|
+
if (depth > glassDepthAt(u + offset.x * texel, v + offset.y * texel) + bias) {
|
|
53
|
+
cover = cover + 1.0;
|
|
54
|
+
}
|
|
55
|
+
/* The colour: the spread's taps, each an average, unmixed and weighed by the pane's share. */
|
|
56
|
+
let pane = sunGlassUnmix(glassTintAround(u + offset.x * radius, v + offset.y * radius, radius), clarity);
|
|
57
|
+
colour = colour + pane.xyz * pane.w;
|
|
58
|
+
found = found + pane.w;
|
|
59
|
+
}
|
|
60
|
+
let mixed = select(vec3<f32>(1.0), colour / max(found, 1e-6), found > 0.0);
|
|
61
|
+
return mix(vec3<f32>(1.0), mixed, cover / f32(max(taps, 1u)));
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
/**
|
|
65
|
+
* `sunGlass` over maps held in storage buffers, read as the reference reads them, so the whole
|
|
66
|
+
* lookup can be compared with `glassTintAt` rather than only the arithmetic between the reads.
|
|
67
|
+
*
|
|
68
|
+
* Four floats a case in — u, v, the receiver's depth, one spare — and four out, the tint and one
|
|
69
|
+
* spare. The config is the map's size, the depth span, map units a metre, and the tap count.
|
|
70
|
+
*/
|
|
71
|
+
export const SUN_GLASS_PARITY_WGSL = /* wgsl */ `
|
|
72
|
+
@group(0) @binding(0) var<storage, read> depths: array<f32>;
|
|
73
|
+
@group(0) @binding(1) var<storage, read> tints: array<f32>;
|
|
74
|
+
@group(0) @binding(2) var<storage, read> cases: array<f32>;
|
|
75
|
+
@group(0) @binding(3) var<storage, read> config: array<f32>;
|
|
76
|
+
@group(0) @binding(4) var<storage, read_write> results: array<f32>;
|
|
77
|
+
|
|
78
|
+
/* The opaque lookup's own read, which this entry never calls: included only for its tap table. */
|
|
79
|
+
fn shadowDepthAt(u: f32, v: f32) -> f32 { return 1.0; }
|
|
80
|
+
${SHADE_SHADOW_WGSL}
|
|
81
|
+
|
|
82
|
+
fn glassIndex(u: f32, v: f32) -> u32 {
|
|
83
|
+
let size = config[0];
|
|
84
|
+
let x = u32(clamp(floor(u * size), 0.0, size - 1.0));
|
|
85
|
+
let y = u32(clamp(floor(v * size), 0.0, size - 1.0));
|
|
86
|
+
return y * u32(size) + x;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn glassDepthAt(u: f32, v: f32) -> f32 {
|
|
90
|
+
return depths[glassIndex(u, v)];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn glassTintTexel(u: f32, v: f32) -> vec4<f32> {
|
|
94
|
+
let at = glassIndex(u, v) * 4u;
|
|
95
|
+
return vec4<f32>(tints[at], tints[at + 1u], tints[at + 2u], tints[at + 3u]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* The reference's coarse read: the average of every texel within the radius. */
|
|
99
|
+
fn glassTintAround(u: f32, v: f32, radius: f32) -> vec4<f32> {
|
|
100
|
+
let size = config[0];
|
|
101
|
+
let reach = radius * size;
|
|
102
|
+
let x0 = u32(max(0.0, floor(u * size - reach)));
|
|
103
|
+
let x1 = u32(min(size - 1.0, floor(u * size + reach)));
|
|
104
|
+
let y0 = u32(max(0.0, floor(v * size - reach)));
|
|
105
|
+
let y1 = u32(min(size - 1.0, floor(v * size + reach)));
|
|
106
|
+
var sum = vec4<f32>(0.0);
|
|
107
|
+
var n = 0.0;
|
|
108
|
+
for (var y = y0; y <= y1; y = y + 1u) {
|
|
109
|
+
for (var x = x0; x <= x1; x = x + 1u) {
|
|
110
|
+
let at = (y * u32(size) + x) * 4u;
|
|
111
|
+
sum = sum + vec4<f32>(tints[at], tints[at + 1u], tints[at + 2u], tints[at + 3u]);
|
|
112
|
+
n = n + 1.0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return sum / max(n, 1.0);
|
|
116
|
+
}
|
|
117
|
+
${SUN_GLASS_WGSL}
|
|
118
|
+
|
|
119
|
+
@compute @workgroup_size(64)
|
|
120
|
+
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
121
|
+
if (id.x * 4u + 3u >= arrayLength(&cases)) { return; }
|
|
122
|
+
var settings: SunGlassSettings;
|
|
123
|
+
settings.mapSize = config[0];
|
|
124
|
+
settings.depthSpan = config[1];
|
|
125
|
+
settings.uvPerMetre = config[2];
|
|
126
|
+
settings.taps = config[3];
|
|
127
|
+
let at = id.x * 4u;
|
|
128
|
+
let tint = sunGlass(cases[at], cases[at + 1u], cases[at + 2u], settings);
|
|
129
|
+
results[at] = tint.x;
|
|
130
|
+
results[at + 1u] = tint.y;
|
|
131
|
+
results[at + 2u] = tint.z;
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
* the window it came through, and without it a barred floor under a smooth shaft is two
|
|
47
47
|
* halves of one frame disagreeing about the same light.
|
|
48
48
|
*/
|
|
49
|
+
import { SUN_DYNAMIC_LAYER, SUN_PEELED_LAYER, SUN_STATIC_LAYER } from '../shadowMap.js';
|
|
49
50
|
import { resolveConditionals } from './conditionals.js';
|
|
50
51
|
export const LIGHT_VOLUME_VERT = `#version 300 es
|
|
51
52
|
precision highp float;
|
|
@@ -143,9 +144,8 @@ uniform vec3 uDustOffset;
|
|
|
143
144
|
uniform float uSunShadow;
|
|
144
145
|
uniform mat4 uLightViewProj;
|
|
145
146
|
uniform float uShadowMapSize;
|
|
146
|
-
|
|
147
|
-
uniform
|
|
148
|
-
uniform sampler2D uDynamicShadowMap;
|
|
147
|
+
/* The sun's static, moving and peeled maps, as layers of one array: see shadowMap.ts. */
|
|
148
|
+
uniform highp sampler2DArray uSunShadows;
|
|
149
149
|
uniform int uPeeledShadowEnabled;
|
|
150
150
|
#endif
|
|
151
151
|
|
|
@@ -308,11 +308,11 @@ float sunReach(vec3 worldPos) {
|
|
|
308
308
|
if (edgeFade <= 0.0) return 1.0;
|
|
309
309
|
|
|
310
310
|
float compare = p.z - BIAS;
|
|
311
|
-
float blocked = occlusion(textureLod(
|
|
311
|
+
float blocked = occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_STATIC_LAYER}.0), 0.0).r, compare);
|
|
312
312
|
if (uPeeledShadowEnabled != 0) {
|
|
313
|
-
blocked = max(blocked, occlusion(textureLod(
|
|
313
|
+
blocked = max(blocked, occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_PEELED_LAYER}.0), 0.0).r, compare));
|
|
314
314
|
}
|
|
315
|
-
blocked = max(blocked, occlusion(textureLod(
|
|
315
|
+
blocked = max(blocked, occlusion(textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r, compare));
|
|
316
316
|
return mix(1.0, 1.0 - blocked, uSunShadow * edgeFade);
|
|
317
317
|
}
|
|
318
318
|
#endif
|
|
@@ -23,3 +23,11 @@ export declare const OCTAHEDRAL_RESOLVE_VERT = "#version 300 es\nvoid main() {\n
|
|
|
23
23
|
* redeclaration and GLSL rejects the whole shader.
|
|
24
24
|
*/
|
|
25
25
|
export declare const OCTAHEDRAL_RESOLVE_FRAG = "#version 300 es\nprecision highp float;\n\n/** The face the bake just rendered, as a depth texture. */\nuniform highp sampler2D uFace;\n/** That face's world-to-face rotation. See pointShadowFaceRotation. */\nuniform mat3 uFaceRotation;\n/** Which of the six this pass is resolving. Every other texel discards. */\nuniform int uFaceIndex;\nuniform float uFar;\nuniform float uNear;\n/** The octahedral map's edge, so this needs no viewport query. */\nuniform float uEdge;\n\nvec2 octEncode(vec3 d) {\n vec3 n = d / (abs(d.x) + abs(d.y) + abs(d.z));\n vec2 p = n.z >= 0.0\n ? n.xy\n : (1.0 - abs(vec2(n.y, n.x))) * vec2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);\n return p * 0.5 + 0.5;\n}\n\nvec3 octDecode(vec2 uv) {\n vec2 f = uv * 2.0 - 1.0;\n vec3 n = vec3(f.x, f.y, 1.0 - abs(f.x) - abs(f.y));\n float t = max(-n.z, 0.0);\n n.x += n.x >= 0.0 ? -t : t;\n n.y += n.y >= 0.0 ? -t : t;\n return normalize(n);\n}\n\nvec2 octWrapUv(vec2 uv) {\n vec2 f = uv * 2.0 - 1.0;\n if (f.x < -1.0 || f.x > 1.0) {\n f.x = (f.x > 0.0 ? 2.0 : -2.0) - f.x;\n f.y = -f.y;\n }\n if (f.y < -1.0 || f.y > 1.0) {\n f.y = (f.y > 0.0 ? 2.0 : -2.0) - f.y;\n f.x = -f.x;\n }\n return f * 0.5 + 0.5;\n}\n\nvec2 octInsetUv(vec3 d, float edge) {\n return (octEncode(d) * (edge - 2.0) + 1.0) / edge;\n}\n\nvec3 octInsetDir(vec2 uv, float edge) {\n return octDecode(octWrapUv((uv * edge - 1.0) / (edge - 2.0)));\n}\n\nvoid main() {\n vec3 d = octDecode(gl_FragCoord.xy / uEdge);\n\n /*\n * Which face owns this texel. The tie-breaking is x, then y, then z, and it has to match the\n * CPU's exactly or a sliver of texels along a boundary belongs to two faces or to none.\n */\n vec3 a = abs(d);\n int face;\n if (a.x >= a.y && a.x >= a.z) face = d.x >= 0.0 ? 0 : 1;\n else if (a.y >= a.z) face = d.y >= 0.0 ? 2 : 3;\n else face = d.z >= 0.0 ? 4 : 5;\n if (face != uFaceIndex) discard;\n\n /* lookAt looks down -z, so a direction inside this face has a negative z here. */\n vec3 v = uFaceRotation * d;\n float forward = -v.z;\n vec2 faceUv = (v.xy / forward) * 0.5 + 0.5;\n\n /*\n * textureLod at level zero rather than plain texture. The scratch carries one storage level and\n * NEAREST filtering so the fetch is identical, and this call sits after a discard \u2014 which is\n * non-uniform control flow, where an implicit derivative is undefined in GLSL and rejected\n * outright in WGSL. AGENTS.md, 2026-08-07.\n */\n float stored = textureLod(uFace, faceUv, 0.0).r;\n if (stored >= 0.9999) {\n /* Nothing was rendered along this direction, so the stored radius is the far plane. */\n gl_FragDepth = 1.0;\n return;\n }\n float ndc = stored * 2.0 - 1.0;\n float faceLocalZ = (2.0 * uFar * uNear) / (uFar + uNear - ndc * (uFar - uNear));\n /* d is a unit vector, so forward is its cosine to the axis and this is the true radius. */\n gl_FragDepth = clamp((faceLocalZ / forward) / uFar, 0.0, 1.0);\n}";
|
|
26
|
+
/**
|
|
27
|
+
* One face of a lamp's glass tint into its region of the tint layer: the depth resolve's region,
|
|
28
|
+
* and a colour copied rather than a depth decoded.
|
|
29
|
+
*
|
|
30
|
+
* **Nearest, at level zero**, like the depth beside it: the tint's outline has to land on the
|
|
31
|
+
* glass depth's texels, and a filtered copy would smear a pane's colour a texel past its edge.
|
|
32
|
+
*/
|
|
33
|
+
export declare const OCTAHEDRAL_RESOLVE_TINT_FRAG = "#version 300 es\nprecision highp float;\n\n/** The face the bake just tinted, as colour. */\nuniform highp sampler2D uFace;\nuniform mat3 uFaceRotation;\nuniform int uFaceIndex;\nuniform float uEdge;\n\nout vec4 outTint;\n\nvec2 octEncode(vec3 d) {\n vec3 n = d / (abs(d.x) + abs(d.y) + abs(d.z));\n vec2 p = n.z >= 0.0\n ? n.xy\n : (1.0 - abs(vec2(n.y, n.x))) * vec2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);\n return p * 0.5 + 0.5;\n}\n\nvec3 octDecode(vec2 uv) {\n vec2 f = uv * 2.0 - 1.0;\n vec3 n = vec3(f.x, f.y, 1.0 - abs(f.x) - abs(f.y));\n float t = max(-n.z, 0.0);\n n.x += n.x >= 0.0 ? -t : t;\n n.y += n.y >= 0.0 ? -t : t;\n return normalize(n);\n}\n\nvec2 octWrapUv(vec2 uv) {\n vec2 f = uv * 2.0 - 1.0;\n if (f.x < -1.0 || f.x > 1.0) {\n f.x = (f.x > 0.0 ? 2.0 : -2.0) - f.x;\n f.y = -f.y;\n }\n if (f.y < -1.0 || f.y > 1.0) {\n f.y = (f.y > 0.0 ? 2.0 : -2.0) - f.y;\n f.x = -f.x;\n }\n return f * 0.5 + 0.5;\n}\n\nvec2 octInsetUv(vec3 d, float edge) {\n return (octEncode(d) * (edge - 2.0) + 1.0) / edge;\n}\n\nvec3 octInsetDir(vec2 uv, float edge) {\n return octDecode(octWrapUv((uv * edge - 1.0) / (edge - 2.0)));\n}\n\nvoid main() {\n vec3 d = octDecode(gl_FragCoord.xy / uEdge);\n\n /*\n * Which face owns this texel. The tie-breaking is x, then y, then z, and it has to match the\n * CPU's exactly or a sliver of texels along a boundary belongs to two faces or to none.\n */\n vec3 a = abs(d);\n int face;\n if (a.x >= a.y && a.x >= a.z) face = d.x >= 0.0 ? 0 : 1;\n else if (a.y >= a.z) face = d.y >= 0.0 ? 2 : 3;\n else face = d.z >= 0.0 ? 4 : 5;\n if (face != uFaceIndex) discard;\n\n /* lookAt looks down -z, so a direction inside this face has a negative z here. */\n vec3 v = uFaceRotation * d;\n float forward = -v.z;\n vec2 faceUv = (v.xy / forward) * 0.5 + 0.5;\n outTint = textureLod(uFace, faceUv, 0.0);\n}";
|
|
@@ -10,6 +10,29 @@ void main() {
|
|
|
10
10
|
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
11
11
|
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
|
12
12
|
}`;
|
|
13
|
+
/**
|
|
14
|
+
* Which cube face owns this octahedral texel, and where in that face it looks: `d`, `forward` and
|
|
15
|
+
* `faceUv`, with every texel another face owns discarded. **One text for both resolves**, depth
|
|
16
|
+
* and tint, because the tie-breaking has to agree with itself as well as with the CPU: a sliver
|
|
17
|
+
* of texels given to one face's depth and another face's colour is a tinted seam.
|
|
18
|
+
*/
|
|
19
|
+
const FACE_REGION = ` vec3 d = octDecode(gl_FragCoord.xy / uEdge);
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Which face owns this texel. The tie-breaking is x, then y, then z, and it has to match the
|
|
23
|
+
* CPU's exactly or a sliver of texels along a boundary belongs to two faces or to none.
|
|
24
|
+
*/
|
|
25
|
+
vec3 a = abs(d);
|
|
26
|
+
int face;
|
|
27
|
+
if (a.x >= a.y && a.x >= a.z) face = d.x >= 0.0 ? 0 : 1;
|
|
28
|
+
else if (a.y >= a.z) face = d.y >= 0.0 ? 2 : 3;
|
|
29
|
+
else face = d.z >= 0.0 ? 4 : 5;
|
|
30
|
+
if (face != uFaceIndex) discard;
|
|
31
|
+
|
|
32
|
+
/* lookAt looks down -z, so a direction inside this face has a negative z here. */
|
|
33
|
+
vec3 v = uFaceRotation * d;
|
|
34
|
+
float forward = -v.z;
|
|
35
|
+
vec2 faceUv = (v.xy / forward) * 0.5 + 0.5;`;
|
|
13
36
|
/**
|
|
14
37
|
* One cube face into its region of an octahedral map, as linear radial distance.
|
|
15
38
|
*
|
|
@@ -44,23 +67,7 @@ uniform float uEdge;
|
|
|
44
67
|
${OCTAHEDRAL_GLSL}
|
|
45
68
|
|
|
46
69
|
void main() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
/*
|
|
50
|
-
* Which face owns this texel. The tie-breaking is x, then y, then z, and it has to match the
|
|
51
|
-
* CPU's exactly or a sliver of texels along a boundary belongs to two faces or to none.
|
|
52
|
-
*/
|
|
53
|
-
vec3 a = abs(d);
|
|
54
|
-
int face;
|
|
55
|
-
if (a.x >= a.y && a.x >= a.z) face = d.x >= 0.0 ? 0 : 1;
|
|
56
|
-
else if (a.y >= a.z) face = d.y >= 0.0 ? 2 : 3;
|
|
57
|
-
else face = d.z >= 0.0 ? 4 : 5;
|
|
58
|
-
if (face != uFaceIndex) discard;
|
|
59
|
-
|
|
60
|
-
/* lookAt looks down -z, so a direction inside this face has a negative z here. */
|
|
61
|
-
vec3 v = uFaceRotation * d;
|
|
62
|
-
float forward = -v.z;
|
|
63
|
-
vec2 faceUv = (v.xy / forward) * 0.5 + 0.5;
|
|
70
|
+
${FACE_REGION}
|
|
64
71
|
|
|
65
72
|
/*
|
|
66
73
|
* textureLod at level zero rather than plain texture. The scratch carries one storage level and
|
|
@@ -79,3 +86,27 @@ void main() {
|
|
|
79
86
|
/* d is a unit vector, so forward is its cosine to the axis and this is the true radius. */
|
|
80
87
|
gl_FragDepth = clamp((faceLocalZ / forward) / uFar, 0.0, 1.0);
|
|
81
88
|
}`;
|
|
89
|
+
/**
|
|
90
|
+
* One face of a lamp's glass tint into its region of the tint layer: the depth resolve's region,
|
|
91
|
+
* and a colour copied rather than a depth decoded.
|
|
92
|
+
*
|
|
93
|
+
* **Nearest, at level zero**, like the depth beside it: the tint's outline has to land on the
|
|
94
|
+
* glass depth's texels, and a filtered copy would smear a pane's colour a texel past its edge.
|
|
95
|
+
*/
|
|
96
|
+
export const OCTAHEDRAL_RESOLVE_TINT_FRAG = `#version 300 es
|
|
97
|
+
precision highp float;
|
|
98
|
+
|
|
99
|
+
/** The face the bake just tinted, as colour. */
|
|
100
|
+
uniform highp sampler2D uFace;
|
|
101
|
+
uniform mat3 uFaceRotation;
|
|
102
|
+
uniform int uFaceIndex;
|
|
103
|
+
uniform float uEdge;
|
|
104
|
+
|
|
105
|
+
out vec4 outTint;
|
|
106
|
+
|
|
107
|
+
${OCTAHEDRAL_GLSL}
|
|
108
|
+
|
|
109
|
+
void main() {
|
|
110
|
+
${FACE_REGION}
|
|
111
|
+
outTint = textureLod(uFace, faceUv, 0.0);
|
|
112
|
+
}`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const LATE_DEPTH_WGSL: string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The render's depth, taken up to the output size for the draws that land after the upscale.
|
|
3
|
+
*
|
|
4
|
+
* **The nearest of the four render texels around each output pixel**, so a translucent draw is
|
|
5
|
+
* never let through an opaque edge the render did cover. What it gives up: where a translucent
|
|
6
|
+
* surface crosses an opaque silhouette, the edge steps at the render's resolution.
|
|
7
|
+
*
|
|
8
|
+
* Hand-written, and built by concatenation rather than a template literal: the reconstruction's
|
|
9
|
+
* passes have no WebGL2 twin (the AGENTS.md exception for them), and a `${}` inside WGSL source is
|
|
10
|
+
* the thing that rule forbids.
|
|
11
|
+
*/
|
|
12
|
+
import { REVERSED_DEPTH } from '../../depthConvention.js';
|
|
13
|
+
/** The nearer of two stored depths: the larger in a reversed buffer. */
|
|
14
|
+
const NEARER = REVERSED_DEPTH ? 'max' : 'min';
|
|
15
|
+
/** Where the search for the nearest starts: the far plane. */
|
|
16
|
+
const FARTHEST = REVERSED_DEPTH ? '0.0' : '1.0';
|
|
17
|
+
export const LATE_DEPTH_WGSL = '@group(0) @binding(0) var source: texture_depth_2d;\n' +
|
|
18
|
+
'@group(0) @binding(1) var<uniform> sizes: vec4<f32>;\n' +
|
|
19
|
+
'\n' +
|
|
20
|
+
'@vertex\n' +
|
|
21
|
+
'fn lateDepthVert(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> {\n' +
|
|
22
|
+
' let x = f32((index << 1u) & 2u);\n' +
|
|
23
|
+
' let y = f32(index & 2u);\n' +
|
|
24
|
+
' return vec4<f32>(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);\n' +
|
|
25
|
+
'}\n' +
|
|
26
|
+
'\n' +
|
|
27
|
+
'@fragment\n' +
|
|
28
|
+
'fn lateDepthFrag(@builtin(position) at: vec4<f32>) -> @builtin(frag_depth) f32 {\n' +
|
|
29
|
+
' let centre = at.xy * sizes.xy / sizes.zw - vec2<f32>(0.5);\n' +
|
|
30
|
+
' let base = vec2<i32>(floor(centre));\n' +
|
|
31
|
+
' let top = vec2<i32>(sizes.xy) - vec2<i32>(1);\n' +
|
|
32
|
+
' var nearest = ' +
|
|
33
|
+
FARTHEST +
|
|
34
|
+
';\n' +
|
|
35
|
+
' for (var y = 0; y < 2; y += 1) {\n' +
|
|
36
|
+
' for (var x = 0; x < 2; x += 1) {\n' +
|
|
37
|
+
' let texel = clamp(base + vec2<i32>(x, y), vec2<i32>(0), top);\n' +
|
|
38
|
+
' nearest = ' +
|
|
39
|
+
NEARER +
|
|
40
|
+
'(nearest, textureLoad(source, texel, 0));\n' +
|
|
41
|
+
' }\n' +
|
|
42
|
+
' }\n' +
|
|
43
|
+
' return nearest;\n' +
|
|
44
|
+
'}\n';
|
|
@@ -41,4 +41,4 @@ export declare const MOTION_DRAW_STRIDE = 256;
|
|
|
41
41
|
export declare const MOTION_DRAW_FLOATS = 32;
|
|
42
42
|
/** The motion target's format. Half float holds a uv to a hundredth of a pixel and a view depth. */
|
|
43
43
|
export declare const MOTION_TARGET_FORMAT: GPUTextureFormat;
|
|
44
|
-
export declare const RECON_MOTION_WGSL = "\nstruct MotionFrame {\n /* The scene's own clip transform, with the generated shaders' y negation already folded in. */\n raster: mat4x4<f32>,\n /* This frame's and last frame's view-projections, unjittered, in the matrices' own convention. */\n viewProj: mat4x4<f32>,\n previousViewProj: mat4x4<f32>,\n}\n\nstruct MotionDraw {\n model: mat4x4<f32>,\n previousModel: mat4x4<f32>,\n}\n\n@group(0) @binding(0) var<uniform> frame: MotionFrame;\n@group(0) @binding(1) var<uniform> draw: MotionDraw;\n\nstruct Varyings {\n @builtin(position) clip: vec4<f32>,\n /* Clip positions rather than uv: an interpolator divides by w for us, so dividing these by their\n own interpolated w in the fragment stage is the perspective-correct answer at every pixel. */\n @location(0) now: vec4<f32>,\n @location(1) was: vec4<f32>,\n}\n\n@vertex\nfn motionVert(@location(0) position: vec3<f32>) -> Varyings {\n let world = draw.model * vec4<f32>(position, 1.0);\n let wasWorld = draw.previousModel * vec4<f32>(position, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/* Normalised device coordinates to the uv the resolve measures in: v increases upward, as the\n references' images do, and the accessor that reads this target is what flips the texture's rows. */\nfn motionUv(clip: vec4<f32>) -> vec2<f32> {\n return clip.xy / clip.w * 0.5 + vec2<f32>(0.5);\n}\n\n@fragment\nfn motionFrag(in: Varyings) -> @location(0) vec4<f32> {\n /*\n * A surface behind last frame's eye has a w that is not positive, and there is no uv for it. The\n * flag stays set: the disocclusion refuses a depth that is not positive, so the resolve drops the\n * history rather than reading it from wherever a divide by a negative w would have pointed.\n */\n let offset = select(vec2<f32>(0.0), motionUv(in.was) - motionUv(in.now), in.was.w > 0.0);\n return vec4<f32>(offset, in.was.w, 1.0);\n}\n";
|
|
44
|
+
export declare const RECON_MOTION_WGSL = "\nstruct MotionFrame {\n /* The scene's own clip transform, with the generated shaders' y negation already folded in. */\n raster: mat4x4<f32>,\n /* This frame's and last frame's view-projections, unjittered, in the matrices' own convention. */\n viewProj: mat4x4<f32>,\n previousViewProj: mat4x4<f32>,\n}\n\nstruct MotionDraw {\n model: mat4x4<f32>,\n previousModel: mat4x4<f32>,\n}\n\n@group(0) @binding(0) var<uniform> frame: MotionFrame;\n@group(0) @binding(1) var<uniform> draw: MotionDraw;\n\nstruct Varyings {\n @builtin(position) clip: vec4<f32>,\n /* Clip positions rather than uv: an interpolator divides by w for us, so dividing these by their\n own interpolated w in the fragment stage is the perspective-correct answer at every pixel. */\n @location(0) now: vec4<f32>,\n @location(1) was: vec4<f32>,\n}\n\n@vertex\nfn motionVert(@location(0) position: vec3<f32>) -> Varyings {\n let world = draw.model * vec4<f32>(position, 1.0);\n let wasWorld = draw.previousModel * vec4<f32>(position, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/* Normalised device coordinates to the uv the resolve measures in: v increases upward, as the\n references' images do, and the accessor that reads this target is what flips the texture's rows. */\nfn motionUv(clip: vec4<f32>) -> vec2<f32> {\n return clip.xy / clip.w * 0.5 + vec2<f32>(0.5);\n}\n\n/*\n * The skinned stage: the joints this frame and last frame, read from two palette textures of the\n * frame's ring. The same four weighted products in the same order as the scene's own skinning, so\n * the depth this stage produces is the depth the scene wrote.\n */\n@group(1) @binding(0) var palette: texture_2d<f32>;\n@group(1) @binding(1) var previousPalette: texture_2d<f32>;\n\nfn jointOf(source: texture_2d<f32>, index: i32) -> mat4x4<f32> {\n let x = index * 4;\n return mat4x4<f32>(\n textureLoad(source, vec2<i32>(x, 0), 0),\n textureLoad(source, vec2<i32>(x + 1, 0), 0),\n textureLoad(source, vec2<i32>(x + 2, 0), 0),\n textureLoad(source, vec2<i32>(x + 3, 0), 0));\n}\n\nfn skinOf(source: texture_2d<f32>, joints: vec4<f32>, weights: vec4<f32>) -> mat4x4<f32> {\n return jointOf(source, i32(joints.x)) * weights.x\n + jointOf(source, i32(joints.y)) * weights.y\n + jointOf(source, i32(joints.z)) * weights.z\n + jointOf(source, i32(joints.w)) * weights.w;\n}\n\n@vertex\nfn motionSkinnedVert(\n @location(0) position: vec3<f32>,\n @location(11) joints: vec4<f32>,\n @location(12) weights: vec4<f32>,\n) -> Varyings {\n let world = draw.model * (skinOf(palette, joints, weights) * vec4<f32>(position, 1.0));\n let wasWorld =\n draw.previousModel * (skinOf(previousPalette, joints, weights) * vec4<f32>(position, 1.0));\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/*\n * The rewritten stage: a mesh whose vertices were rewritten this frame reads last frame's rows from\n * a second buffer of the same stride, so each vertex carries both of its positions.\n */\n@vertex\nfn motionDynamicVert(\n @location(0) position: vec3<f32>,\n @location(1) previousPosition: vec3<f32>,\n) -> Varyings {\n let world = draw.model * vec4<f32>(position, 1.0);\n let wasWorld = draw.previousModel * vec4<f32>(previousPosition, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n/*\n * The instanced stage: each instance's matrix this frame and last frame, read from the batch's\n * placement and the copy of last frame's it kept. Slot for slot, so an instance is paired with\n * itself; the per-draw matrices are not read.\n */\n@vertex\nfn motionInstancedVert(\n @location(0) position: vec3<f32>,\n @location(2) m0: vec4<f32>,\n @location(3) m1: vec4<f32>,\n @location(4) m2: vec4<f32>,\n @location(5) m3: vec4<f32>,\n @location(6) p0: vec4<f32>,\n @location(7) p1: vec4<f32>,\n @location(8) p2: vec4<f32>,\n @location(9) p3: vec4<f32>,\n) -> Varyings {\n let world = mat4x4<f32>(m0, m1, m2, m3) * vec4<f32>(position, 1.0);\n let wasWorld = mat4x4<f32>(p0, p1, p2, p3) * vec4<f32>(position, 1.0);\n var out: Varyings;\n out.clip = frame.raster * world;\n out.now = frame.viewProj * world;\n out.was = frame.previousViewProj * wasWorld;\n return out;\n}\n\n@fragment\nfn motionFrag(in: Varyings) -> @location(0) vec4<f32> {\n /*\n * A surface behind last frame's eye has a w that is not positive, and there is no uv for it. The\n * flag stays set: the disocclusion refuses a depth that is not positive, so the resolve drops the\n * history rather than reading it from wherever a divide by a negative w would have pointed.\n */\n let offset = select(vec2<f32>(0.0), motionUv(in.was) - motionUv(in.now), in.was.w > 0.0);\n return vec4<f32>(offset, in.was.w, 1.0);\n}\n";
|