@driftengine/core 3.63.0 → 4.0.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 +111 -0
- package/NOTICE +20 -0
- package/README.md +1 -1
- package/dist/core/loop.js +18 -2
- package/dist/core/storage.js +5 -0
- package/dist/geometry/meshBuilder.d.ts +1 -1
- package/dist/geometry/meshBuilder.js +6 -2
- package/dist/index.d.ts +69 -4
- package/dist/index.js +112 -2
- package/dist/input/input.js +6 -1
- package/dist/math/exact.d.ts +13 -3
- package/dist/math/exact.js +68 -3
- package/dist/math/frustum.d.ts +17 -2
- package/dist/math/frustum.js +24 -0
- package/dist/render/backend/api.d.ts +34 -1
- package/dist/render/backend/api.js +22 -1
- package/dist/render/backend/budget.d.ts +7 -6
- package/dist/render/backend/budget.js +7 -6
- package/dist/render/backend/createRenderer.d.ts +57 -0
- package/dist/render/backend/createRenderer.js +51 -2
- package/dist/render/backend/materialChanges.d.ts +41 -0
- package/dist/render/backend/materialChanges.js +38 -0
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/renderer.d.ts +80 -1
- package/dist/render/backend/webgl2/renderer.js +208 -10
- package/dist/render/backend/webgpu/buffers.d.ts +20 -1
- package/dist/render/backend/webgpu/buffers.js +116 -58
- package/dist/render/backend/webgpu/fieldCompose.d.ts +156 -0
- package/dist/render/backend/webgpu/fieldCompose.js +396 -0
- package/dist/render/backend/webgpu/giFieldPass.d.ts +66 -0
- package/dist/render/backend/webgpu/giFieldPass.js +137 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +728 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.js +2473 -0
- package/dist/render/backend/webgpu/linePass.d.ts +4 -14
- package/dist/render/backend/webgpu/linePass.js +4 -33
- package/dist/render/backend/webgpu/probeBake.d.ts +148 -0
- package/dist/render/backend/webgpu/probeBake.js +493 -0
- package/dist/render/backend/webgpu/renderer.d.ts +379 -24
- package/dist/render/backend/webgpu/renderer.js +1369 -76
- package/dist/render/backend/webgpu/scatterPass.js +15 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +19 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.js +58 -32
- package/dist/render/causticsRenderer.d.ts +3 -1
- package/dist/render/causticsRenderer.js +4 -1
- package/dist/render/depthConvention.d.ts +12 -0
- package/dist/render/depthConvention.js +14 -0
- package/dist/render/deviceFeatures.d.ts +32 -0
- package/dist/render/deviceFeatures.js +41 -0
- package/dist/render/fog.d.ts +79 -0
- package/dist/render/fog.js +138 -0
- package/dist/render/frame/alias.d.ts +30 -0
- package/dist/render/frame/alias.js +61 -0
- package/dist/render/frame/deps.d.ts +37 -0
- package/dist/render/frame/deps.js +122 -0
- package/dist/render/frame/flushGraph.d.ts +43 -0
- package/dist/render/frame/flushGraph.js +84 -0
- package/dist/render/frame/graphSchedule.d.ts +49 -0
- package/dist/render/frame/graphSchedule.js +120 -0
- package/dist/render/frame/index.d.ts +15 -0
- package/dist/render/frame/index.js +8 -0
- package/dist/render/frame/lifetime.d.ts +24 -0
- package/dist/render/frame/lifetime.js +36 -0
- package/dist/render/frame/passTimings.d.ts +42 -0
- package/dist/render/frame/passTimings.js +38 -0
- package/dist/render/frame/validate.d.ts +15 -0
- package/dist/render/frame/validate.js +22 -0
- package/dist/render/frame/virtual.d.ts +44 -0
- package/dist/render/frame/virtual.js +61 -0
- package/dist/render/gi/chain.d.ts +82 -0
- package/dist/render/gi/chain.js +135 -0
- package/dist/render/gi/denoise.d.ts +114 -0
- package/dist/render/gi/denoise.js +169 -0
- package/dist/render/gi/fieldScene.d.ts +125 -0
- package/dist/render/gi/fieldScene.js +219 -0
- package/dist/render/gi/globalField.d.ts +157 -0
- package/dist/render/gi/globalField.js +373 -0
- package/dist/render/gi/probeGridFit.d.ts +24 -0
- package/dist/render/gi/probeGridFit.js +71 -0
- package/dist/render/gi/probeTrace.d.ts +82 -0
- package/dist/render/gi/probeTrace.js +128 -0
- package/dist/render/gi/probeVolume.d.ts +136 -0
- package/dist/render/gi/probeVolume.js +191 -0
- package/dist/render/gi/reflection.d.ts +73 -0
- package/dist/render/gi/reflection.js +109 -0
- package/dist/render/gi/traceField.d.ts +91 -0
- package/dist/render/gi/traceField.js +129 -0
- package/dist/render/gi/traceScreen.d.ts +68 -0
- package/dist/render/gi/traceScreen.js +82 -0
- package/dist/render/gpuScene.d.ts +38 -0
- package/dist/render/gpuScene.js +41 -0
- package/dist/render/gpudriven/alphaTest.d.ts +28 -0
- package/dist/render/gpudriven/alphaTest.js +32 -0
- package/dist/render/gpudriven/clusterUpload.d.ts +59 -0
- package/dist/render/gpudriven/clusterUpload.js +75 -0
- package/dist/render/gpudriven/clusterWorld.d.ts +57 -0
- package/dist/render/gpudriven/clusterWorld.js +155 -0
- package/dist/render/gpudriven/compact.d.ts +51 -0
- package/dist/render/gpudriven/compact.js +68 -0
- package/dist/render/gpudriven/cullClusters.d.ts +129 -0
- package/dist/render/gpudriven/cullClusters.js +237 -0
- package/dist/render/gpudriven/decodeTables.d.ts +64 -0
- package/dist/render/gpudriven/decodeTables.js +219 -0
- package/dist/render/gpudriven/frustum.d.ts +25 -0
- package/dist/render/gpudriven/frustum.js +59 -0
- package/dist/render/gpudriven/geometryHarness.d.ts +15 -0
- package/dist/render/gpudriven/geometryHarness.js +31 -0
- package/dist/render/gpudriven/hzb.d.ts +60 -0
- package/dist/render/gpudriven/hzb.js +97 -0
- package/dist/render/gpudriven/ibl.d.ts +85 -0
- package/dist/render/gpudriven/ibl.js +129 -0
- package/dist/render/gpudriven/indirect.d.ts +67 -0
- package/dist/render/gpudriven/indirect.js +77 -0
- package/dist/render/gpudriven/instances.d.ts +43 -0
- package/dist/render/gpudriven/instances.js +172 -0
- package/dist/render/gpudriven/lit.d.ts +165 -0
- package/dist/render/gpudriven/lit.js +165 -0
- package/dist/render/gpudriven/lodCut.d.ts +28 -0
- package/dist/render/gpudriven/lodCut.js +38 -0
- package/dist/render/gpudriven/materialBin.d.ts +60 -0
- package/dist/render/gpudriven/materialBin.js +108 -0
- package/dist/render/gpudriven/materialTable.d.ts +88 -0
- package/dist/render/gpudriven/materialTable.js +115 -0
- package/dist/render/gpudriven/pipeline.d.ts +135 -0
- package/dist/render/gpudriven/pipeline.js +237 -0
- package/dist/render/gpudriven/rangeAlloc.d.ts +50 -0
- package/dist/render/gpudriven/rangeAlloc.js +112 -0
- package/dist/render/gpudriven/sceneUpload.d.ts +96 -0
- package/dist/render/gpudriven/sceneUpload.js +115 -0
- package/dist/render/gpudriven/shadeBins.d.ts +92 -0
- package/dist/render/gpudriven/shadeBins.js +182 -0
- package/dist/render/gpudriven/shadow.d.ts +136 -0
- package/dist/render/gpudriven/shadow.js +224 -0
- package/dist/render/gpudriven/shadowCamera.d.ts +87 -0
- package/dist/render/gpudriven/shadowCamera.js +345 -0
- package/dist/render/gpudriven/streamScene.d.ts +194 -0
- package/dist/render/gpudriven/streamScene.js +459 -0
- package/dist/render/gpudriven/surfaceFrame.d.ts +25 -0
- package/dist/render/gpudriven/surfaceFrame.js +51 -0
- package/dist/render/gpudriven/twoPhase.d.ts +122 -0
- package/dist/render/gpudriven/twoPhase.js +160 -0
- package/dist/render/gpudriven/visbuffer.d.ts +25 -0
- package/dist/render/gpudriven/visbuffer.js +37 -0
- package/dist/render/halfFloat.d.ts +10 -0
- package/dist/render/halfFloat.js +23 -4
- package/dist/render/inference/attentionTiled.d.ts +27 -0
- package/dist/render/inference/attentionTiled.js +117 -0
- package/dist/render/inference/denseKernels.d.ts +29 -0
- package/dist/render/inference/denseKernels.js +312 -0
- package/dist/render/inference/device.d.ts +21 -0
- package/dist/render/inference/device.js +32 -0
- package/dist/render/inference/deviceGraph.d.ts +34 -0
- package/dist/render/inference/deviceGraph.js +17 -0
- package/dist/render/inference/fuse.d.ts +15 -0
- package/dist/render/inference/fuse.js +37 -0
- package/dist/render/inference/kernelKit.d.ts +52 -0
- package/dist/render/inference/kernelKit.js +70 -0
- package/dist/render/inference/kernels.d.ts +3 -0
- package/dist/render/inference/kernels.js +11 -0
- package/dist/render/inference/plan.d.ts +13 -0
- package/dist/render/inference/plan.js +18 -0
- package/dist/render/inference/reuse.d.ts +39 -0
- package/dist/render/inference/reuse.js +65 -0
- package/dist/render/inference/runner.d.ts +31 -0
- package/dist/render/inference/runner.js +175 -0
- package/dist/render/inference/schedule.d.ts +39 -0
- package/dist/render/inference/schedule.js +59 -0
- package/dist/render/inference/shapeKernels.d.ts +10 -0
- package/dist/render/inference/shapeKernels.js +80 -0
- package/dist/render/inference/spatialKernels.d.ts +21 -0
- package/dist/render/inference/spatialKernels.js +179 -0
- package/dist/render/inference/splatRaster.wgsl.d.ts +75 -0
- package/dist/render/inference/splatRaster.wgsl.js +337 -0
- package/dist/render/inference/splatTiles.d.ts +79 -0
- package/dist/render/inference/splatTiles.js +161 -0
- package/dist/render/lineBatch.d.ts +2 -5
- package/dist/render/lineBatch.js +4 -24
- package/dist/render/mesh.d.ts +4 -0
- package/dist/render/mesh.js +45 -1
- package/dist/render/pass.d.ts +145 -8
- package/dist/render/prefilterEnvMap.d.ts +22 -0
- package/dist/render/prefilterEnvMap.js +31 -0
- package/dist/render/readback.d.ts +30 -0
- package/dist/render/readback.js +47 -0
- package/dist/render/recon/clamp.d.ts +36 -0
- package/dist/render/recon/clamp.js +92 -0
- package/dist/render/recon/disocclusion.d.ts +60 -0
- package/dist/render/recon/disocclusion.js +109 -0
- package/dist/render/recon/frameGen.d.ts +81 -0
- package/dist/render/recon/frameGen.js +150 -0
- package/dist/render/recon/frameSizes.d.ts +34 -0
- package/dist/render/recon/frameSizes.js +58 -0
- package/dist/render/recon/jitter.d.ts +60 -0
- package/dist/render/recon/jitter.js +118 -0
- package/dist/render/recon/motionVectors.d.ts +55 -0
- package/dist/render/recon/motionVectors.js +107 -0
- package/dist/render/recon/reproject.d.ts +41 -0
- package/dist/render/recon/reproject.js +131 -0
- package/dist/render/recon/resolve.d.ts +90 -0
- package/dist/render/recon/resolve.js +313 -0
- package/dist/render/recon/tier.d.ts +24 -0
- package/dist/render/recon/tier.js +8 -0
- package/dist/render/reflectionProbe.d.ts +26 -0
- package/dist/render/reflectionProbe.js +33 -0
- package/dist/render/renderQuality.d.ts +69 -0
- package/dist/render/renderQuality.js +22 -0
- package/dist/render/screenSpaceReflection.d.ts +23 -0
- package/dist/render/screenSpaceReflection.js +22 -3
- package/dist/render/sdfFont.js +2 -0
- package/dist/render/segmentQuads.d.ts +13 -0
- package/dist/render/segmentQuads.js +32 -0
- package/dist/render/shaders/flat/lobes.d.ts +27 -0
- package/dist/render/shaders/flat/lobes.js +33 -3
- package/dist/render/shaders/flat/preamble.d.ts +7 -0
- package/dist/render/shaders/flat/preamble.js +24 -1
- package/dist/render/shaders/flat/probeGrid.d.ts +1 -1
- package/dist/render/shaders/flat/probeGrid.js +71 -2
- package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -8
- package/dist/render/shaders/generated/flat.wgsl.js +64 -24
- package/dist/render/shaders/gi/composeField.wgsl.d.ts +56 -0
- package/dist/render/shaders/gi/composeField.wgsl.js +231 -0
- package/dist/render/shaders/gi/marchField.wgsl.d.ts +30 -0
- package/dist/render/shaders/gi/marchField.wgsl.js +141 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +113 -0
- package/dist/render/shaders/gi/probeBake.wgsl.js +635 -0
- package/dist/render/shaders/gi/probeTrace.wgsl.d.ts +72 -0
- package/dist/render/shaders/gi/probeTrace.wgsl.js +261 -0
- package/dist/render/shaders/gi/probeVolume.wgsl.d.ts +49 -0
- package/dist/render/shaders/gi/probeVolume.wgsl.js +242 -0
- package/dist/render/shaders/gi/sampleField.wgsl.d.ts +32 -0
- package/dist/render/shaders/gi/sampleField.wgsl.js +134 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +26 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +339 -0
- package/dist/render/shaders/gpudriven/compact.wgsl.d.ts +29 -0
- package/dist/render/shaders/gpudriven/compact.wgsl.js +58 -0
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +90 -0
- package/dist/render/shaders/gpudriven/cull.wgsl.js +423 -0
- package/dist/render/shaders/gpudriven/decode.wgsl.d.ts +72 -0
- package/dist/render/shaders/gpudriven/decode.wgsl.js +349 -0
- package/dist/render/shaders/gpudriven/environment.wgsl.d.ts +44 -0
- package/dist/render/shaders/gpudriven/environment.wgsl.js +137 -0
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +28 -0
- package/dist/render/shaders/gpudriven/fog.wgsl.js +107 -0
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +13 -0
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +62 -0
- package/dist/render/shaders/gpudriven/materialBin.wgsl.d.ts +51 -0
- package/dist/render/shaders/gpudriven/materialBin.wgsl.js +112 -0
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +25 -0
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +43 -0
- package/dist/render/shaders/gpudriven/meshTransform.wgsl.d.ts +18 -0
- package/dist/render/shaders/gpudriven/meshTransform.wgsl.js +37 -0
- package/dist/render/shaders/gpudriven/present.wgsl.d.ts +43 -0
- package/dist/render/shaders/gpudriven/present.wgsl.js +127 -0
- package/dist/render/shaders/gpudriven/shade.wgsl.d.ts +127 -0
- package/dist/render/shaders/gpudriven/shade.wgsl.js +874 -0
- package/dist/render/shaders/gpudriven/shadow.wgsl.d.ts +44 -0
- package/dist/render/shaders/gpudriven/shadow.wgsl.js +308 -0
- package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.d.ts +28 -0
- package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.js +245 -0
- package/dist/render/shaders/network.wgsl.d.ts +101 -0
- package/dist/render/shaders/network.wgsl.js +206 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +44 -0
- package/dist/render/shaders/recon/motion.wgsl.js +96 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +51 -0
- package/dist/render/shaders/recon/resolve.wgsl.js +733 -0
- package/dist/render/temporalAa.js +8 -33
- package/dist/render/textLayout.js +12 -6
- package/dist/render/textRenderer.d.ts +3 -1
- package/dist/render/textRenderer.js +4 -1
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +11 -0
- package/dist/render/windStreakRenderer.d.ts +2 -1
- package/dist/render/windStreakRenderer.js +3 -1
- package/dist/world/cell.d.ts +63 -0
- package/dist/world/cell.js +181 -0
- package/dist/world/cellStream.d.ts +103 -0
- package/dist/world/cellStream.js +198 -0
- package/dist/world/freeze.d.ts +81 -0
- package/dist/world/freeze.js +114 -0
- package/dist/world/rebase.d.ts +29 -0
- package/dist/world/rebase.js +41 -0
- package/package.json +8 -3
- package/scripts/frames.mjs +92 -0
- package/scripts/gpuCompute.mjs +323 -0
- package/scripts/png.d.mts +16 -0
- package/scripts/png.mjs +25 -0
- package/src/core/loop.ts +19 -2
- package/src/core/storage.ts +5 -0
- package/src/geometry/meshBuilder.ts +5 -1
- package/src/index.ts +246 -4
- package/src/input/input.ts +6 -1
- package/src/math/exact.ts +69 -3
- package/src/math/frustum.ts +52 -3
- package/src/render/backend/api.ts +42 -1
- package/src/render/backend/budget.ts +7 -6
- package/src/render/backend/createRenderer.ts +92 -3
- package/src/render/backend/materialChanges.ts +54 -0
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/renderer.ts +211 -9
- package/src/render/backend/webgpu/buffers.ts +182 -69
- package/src/render/backend/webgpu/fieldCompose.ts +482 -0
- package/src/render/backend/webgpu/giFieldPass.ts +200 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +2993 -0
- package/src/render/backend/webgpu/linePass.ts +4 -39
- package/src/render/backend/webgpu/probeBake.ts +588 -0
- package/src/render/backend/webgpu/renderer.ts +1481 -76
- package/src/render/backend/webgpu/scatterPass.ts +15 -1
- package/src/render/backend/webgpu/surfaceTexturePass.ts +63 -36
- package/src/render/causticsRenderer.ts +5 -2
- package/src/render/depthConvention.ts +15 -0
- package/src/render/deviceFeatures.ts +77 -0
- package/src/render/fog.ts +185 -0
- package/src/render/frame/alias.ts +97 -0
- package/src/render/frame/deps.ts +155 -0
- package/src/render/frame/flushGraph.ts +135 -0
- package/src/render/frame/graphSchedule.ts +170 -0
- package/src/render/frame/index.ts +52 -0
- package/src/render/frame/lifetime.ts +61 -0
- package/src/render/frame/passTimings.ts +69 -0
- package/src/render/frame/validate.ts +40 -0
- package/src/render/frame/virtual.ts +91 -0
- package/src/render/gi/chain.ts +225 -0
- package/src/render/gi/denoise.ts +276 -0
- package/src/render/gi/fieldScene.ts +264 -0
- package/src/render/gi/globalField.ts +543 -0
- package/src/render/gi/probeGridFit.ts +89 -0
- package/src/render/gi/probeTrace.ts +143 -0
- package/src/render/gi/probeVolume.ts +288 -0
- package/src/render/gi/reflection.ts +163 -0
- package/src/render/gi/traceField.ts +223 -0
- package/src/render/gi/traceScreen.ts +106 -0
- package/src/render/gpuScene.ts +80 -0
- package/src/render/gpudriven/alphaTest.ts +33 -0
- package/src/render/gpudriven/clusterUpload.ts +123 -0
- package/src/render/gpudriven/clusterWorld.ts +239 -0
- package/src/render/gpudriven/compact.ts +80 -0
- package/src/render/gpudriven/cullClusters.ts +333 -0
- package/src/render/gpudriven/decodeTables.ts +332 -0
- package/src/render/gpudriven/frustum.ts +70 -0
- package/src/render/gpudriven/geometryHarness.ts +51 -0
- package/src/render/gpudriven/hzb.ts +113 -0
- package/src/render/gpudriven/ibl.ts +147 -0
- package/src/render/gpudriven/indirect.ts +89 -0
- package/src/render/gpudriven/instances.ts +234 -0
- package/src/render/gpudriven/lit.ts +329 -0
- package/src/render/gpudriven/lodCut.ts +62 -0
- package/src/render/gpudriven/materialBin.ts +131 -0
- package/src/render/gpudriven/materialTable.ts +164 -0
- package/src/render/gpudriven/pipeline.ts +336 -0
- package/src/render/gpudriven/rangeAlloc.ts +122 -0
- package/src/render/gpudriven/sceneUpload.ts +173 -0
- package/src/render/gpudriven/shadeBins.ts +222 -0
- package/src/render/gpudriven/shadow.ts +285 -0
- package/src/render/gpudriven/shadowCamera.ts +406 -0
- package/src/render/gpudriven/streamScene.ts +575 -0
- package/src/render/gpudriven/surfaceFrame.ts +68 -0
- package/src/render/gpudriven/twoPhase.ts +214 -0
- package/src/render/gpudriven/visbuffer.ts +41 -0
- package/src/render/halfFloat.ts +22 -4
- package/src/render/inference/attentionTiled.ts +132 -0
- package/src/render/inference/denseKernels.ts +369 -0
- package/src/render/inference/device.ts +44 -0
- package/src/render/inference/deviceGraph.ts +39 -0
- package/src/render/inference/fuse.ts +47 -0
- package/src/render/inference/kernelKit.ts +120 -0
- package/src/render/inference/kernels.ts +18 -0
- package/src/render/inference/plan.ts +26 -0
- package/src/render/inference/reuse.ts +79 -0
- package/src/render/inference/runner.ts +204 -0
- package/src/render/inference/schedule.ts +80 -0
- package/src/render/inference/shapeKernels.ts +112 -0
- package/src/render/inference/spatialKernels.ts +220 -0
- package/src/render/inference/splatRaster.wgsl.ts +348 -0
- package/src/render/inference/splatTiles.ts +186 -0
- package/src/render/lineBatch.ts +4 -24
- package/src/render/mesh.ts +46 -1
- package/src/render/pass.ts +155 -10
- package/src/render/prefilterEnvMap.ts +33 -0
- package/src/render/readback.ts +73 -0
- package/src/render/recon/clamp.ts +113 -0
- package/src/render/recon/disocclusion.ts +143 -0
- package/src/render/recon/frameGen.ts +191 -0
- package/src/render/recon/frameSizes.ts +64 -0
- package/src/render/recon/jitter.ts +126 -0
- package/src/render/recon/motionVectors.ts +138 -0
- package/src/render/recon/reproject.ts +179 -0
- package/src/render/recon/resolve.ts +444 -0
- package/src/render/recon/tier.ts +31 -0
- package/src/render/reflectionProbe.ts +34 -0
- package/src/render/renderQuality.ts +87 -0
- package/src/render/screenSpaceReflection.ts +37 -3
- package/src/render/sdfFont.ts +2 -0
- package/src/render/segmentQuads.ts +39 -0
- package/src/render/shaders/flat/lobes.ts +35 -3
- package/src/render/shaders/flat/preamble.ts +25 -1
- package/src/render/shaders/flat/probeGrid.ts +77 -2
- package/src/render/shaders/generated/flat.wgsl.ts +64 -24
- package/src/render/shaders/gi/composeField.wgsl.ts +239 -0
- package/src/render/shaders/gi/marchField.wgsl.ts +146 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +649 -0
- package/src/render/shaders/gi/probeTrace.wgsl.ts +269 -0
- package/src/render/shaders/gi/probeVolume.wgsl.ts +246 -0
- package/src/render/shaders/gi/sampleField.wgsl.ts +140 -0
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +343 -0
- package/src/render/shaders/gpudriven/compact.wgsl.ts +59 -0
- package/src/render/shaders/gpudriven/cull.wgsl.ts +433 -0
- package/src/render/shaders/gpudriven/decode.wgsl.ts +373 -0
- package/src/render/shaders/gpudriven/environment.wgsl.ts +141 -0
- package/src/render/shaders/gpudriven/fog.wgsl.ts +110 -0
- package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +62 -0
- package/src/render/shaders/gpudriven/materialBin.wgsl.ts +116 -0
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +46 -0
- package/src/render/shaders/gpudriven/meshTransform.wgsl.ts +37 -0
- package/src/render/shaders/gpudriven/present.wgsl.ts +129 -0
- package/src/render/shaders/gpudriven/shade.wgsl.ts +884 -0
- package/src/render/shaders/gpudriven/shadow.wgsl.ts +322 -0
- package/src/render/shaders/gpudriven/visbufferRaster.wgsl.ts +251 -0
- package/src/render/shaders/network.wgsl.ts +238 -0
- package/src/render/shaders/recon/motion.wgsl.ts +101 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +741 -0
- package/src/render/temporalAa.ts +9 -33
- package/src/render/textLayout.ts +12 -6
- package/src/render/textRenderer.ts +5 -2
- package/src/render/translucentQueue.ts +11 -0
- package/src/render/windStreakRenderer.ts +4 -2
- package/src/world/cell.ts +231 -0
- package/src/world/cellStream.ts +278 -0
- package/src/world/freeze.ts +157 -0
- package/src/world/rebase.ts +56 -0
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
import { GPU_DRIVEN_VERTEX_FLOATS } from '../../gpudriven/sceneUpload.ts';
|
|
2
|
+
import { EMISSIVE_SHADOW_SHARE, MIN_LOBE_ALPHA } from '../flat/lobes.ts';
|
|
3
|
+
import { RELIEF_ROUGHNESS } from '../flat/preamble.ts';
|
|
4
|
+
import { DECODE_WGSL, decodeResourcesWgsl } from './decode.wgsl.ts';
|
|
5
|
+
import { FOG_WGSL } from './fog.wgsl.ts';
|
|
6
|
+
import { FRAME_BLOCK_WGSL } from './frameBlock.wgsl.ts';
|
|
7
|
+
import { MATERIAL_SLOTS, MATERIAL_TABLE_WGSL } from './materialTable.wgsl.ts';
|
|
8
|
+
import { MESH_TRANSFORM_WGSL } from './meshTransform.wgsl.ts';
|
|
9
|
+
import { ENVIRONMENT_WGSL } from './environment.wgsl.ts';
|
|
10
|
+
import { SHADE_SHADOW_WGSL } from './shadow.wgsl.ts';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Rebuilding a pixel's surface from the triangle it recorded, on the device.
|
|
14
|
+
*
|
|
15
|
+
* **The same arithmetic as `gpudriven/shadeBins.ts`, term for term**, and
|
|
16
|
+
* `scripts/gpu-parity.mjs` runs both over generated triangles until they agree. That is the whole
|
|
17
|
+
* safety property of this file: a visibility buffer's interpolation is the only part of the
|
|
18
|
+
* shading that differs between the two pipelines, so it is the only part that can drift, and the
|
|
19
|
+
* check is on the part that can drift.
|
|
20
|
+
*
|
|
21
|
+
* **The lighting is not here and must not arrive here.** What this pass produces is a surface — a
|
|
22
|
+
* position, a normal, texture coordinates and their screen gradients — and the lit expression that
|
|
23
|
+
* consumes it is the forward path's own, wired in when the pipeline is assembled. A second copy of
|
|
24
|
+
* the lighting would drift from the first and the parity gate would be the only thing that
|
|
25
|
+
* noticed; the plan says so in those words and it is right.
|
|
26
|
+
*
|
|
27
|
+
* **Gradients are derived, not differenced.** A fragment shader gets `dpdx` and `dpdy` because it
|
|
28
|
+
* runs in quads of four; a compute invocation has no neighbours and no derivative at all. Without
|
|
29
|
+
* an analytic one a texture read picks a fixed level, which is blurry everywhere, or level zero,
|
|
30
|
+
* which sparkles — and neither reads as a missing derivative.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The interpolation itself, with no bindings and no layout in it.
|
|
35
|
+
*
|
|
36
|
+
* **Its own string because two shaders do this and only one of them can be checked.** The
|
|
37
|
+
* reconstruction below is run against `shadeBins.ts` over generated triangles by
|
|
38
|
+
* `gpu-parity.mjs`; the pipeline's real shading pass reads different buffers and writes a colour,
|
|
39
|
+
* so nothing can compare it to a reference. Sharing the *text* is what makes the checked one
|
|
40
|
+
* speak for both — a second copy of the quotient rule would be the half nobody is watching, which
|
|
41
|
+
* is precisely the arrangement this wave keeps finding and refusing.
|
|
42
|
+
*
|
|
43
|
+
* Takes clip positions and pixel centres and answers weights and their two gradients. `valid` is
|
|
44
|
+
* false for a degenerate triangle or a perspective denominator at zero, which is a visibility
|
|
45
|
+
* identifier for a triangle the rasteriser never drew.
|
|
46
|
+
*/
|
|
47
|
+
export const SHADE_SURFACE_WGSL = `
|
|
48
|
+
struct Screen {
|
|
49
|
+
x: f32,
|
|
50
|
+
y: f32,
|
|
51
|
+
invW: f32,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
struct Weights {
|
|
55
|
+
/** The perspective-correct barycentric weights. */
|
|
56
|
+
c: vec3<f32>,
|
|
57
|
+
/** How each of them changes with screen x, and with screen y. */
|
|
58
|
+
gx: vec3<f32>,
|
|
59
|
+
gy: vec3<f32>,
|
|
60
|
+
valid: bool,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fn screenOf(clip: vec4<f32>, width: f32, height: f32) -> Screen {
|
|
64
|
+
let inv = 1.0 / clip.w;
|
|
65
|
+
var out: Screen;
|
|
66
|
+
out.x = (clip.x * inv * 0.5 + 0.5) * width;
|
|
67
|
+
out.y = (clip.y * inv * 0.5 + 0.5) * height;
|
|
68
|
+
out.invW = inv;
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn surfaceWeights(s0: Screen, s1: Screen, s2: Screen, px: f32, py: f32) -> Weights {
|
|
73
|
+
var out: Weights;
|
|
74
|
+
out.valid = false;
|
|
75
|
+
|
|
76
|
+
let area = (s1.x - s0.x) * (s2.y - s0.y) - (s2.x - s0.x) * (s1.y - s0.y);
|
|
77
|
+
if (abs(area) < 1e-12) { return out; }
|
|
78
|
+
let inverse = 1.0 / area;
|
|
79
|
+
|
|
80
|
+
/* Screen-space weights. A point outside the triangle gets a negative one rather than a clamp:
|
|
81
|
+
clamping shades a pixel beyond an edge as though it were on the triangle. */
|
|
82
|
+
let l1 = ((px - s0.x) * (s2.y - s0.y) - (s2.x - s0.x) * (py - s0.y)) * inverse;
|
|
83
|
+
let l2 = ((s1.x - s0.x) * (py - s0.y) - (px - s0.x) * (s1.y - s0.y)) * inverse;
|
|
84
|
+
let l0 = 1.0 - l1 - l2;
|
|
85
|
+
|
|
86
|
+
let n0 = l0 * s0.invW;
|
|
87
|
+
let n1 = l1 * s1.invW;
|
|
88
|
+
let n2 = l2 * s2.invW;
|
|
89
|
+
let sum = n0 + n1 + n2;
|
|
90
|
+
if (abs(sum) < 1e-20) { return out; }
|
|
91
|
+
let invSum = 1.0 / sum;
|
|
92
|
+
out.c = vec3<f32>(n0 * invSum, n1 * invSum, n2 * invSum);
|
|
93
|
+
|
|
94
|
+
/* The screen weights' gradients, constant over the triangle. */
|
|
95
|
+
let d1dx = (s2.y - s0.y) * inverse;
|
|
96
|
+
let d1dy = (s0.x - s2.x) * inverse;
|
|
97
|
+
let d2dx = (s0.y - s1.y) * inverse;
|
|
98
|
+
let d2dy = (s1.x - s0.x) * inverse;
|
|
99
|
+
let d0dx = -d1dx - d2dx;
|
|
100
|
+
let d0dy = -d1dy - d2dy;
|
|
101
|
+
|
|
102
|
+
let dSumDx = d0dx * s0.invW + d1dx * s1.invW + d2dx * s2.invW;
|
|
103
|
+
let dSumDy = d0dy * s0.invW + d1dy * s1.invW + d2dy * s2.invW;
|
|
104
|
+
|
|
105
|
+
/* Quotient rule, per weight, with the shared denominator factored out. */
|
|
106
|
+
out.gx = vec3<f32>(
|
|
107
|
+
(d0dx * s0.invW - n0 * invSum * dSumDx) * invSum,
|
|
108
|
+
(d1dx * s1.invW - n1 * invSum * dSumDx) * invSum,
|
|
109
|
+
(d2dx * s2.invW - n2 * invSum * dSumDx) * invSum,
|
|
110
|
+
);
|
|
111
|
+
out.gy = vec3<f32>(
|
|
112
|
+
(d0dy * s0.invW - n0 * invSum * dSumDy) * invSum,
|
|
113
|
+
(d1dy * s1.invW - n1 * invSum * dSumDy) * invSum,
|
|
114
|
+
(d2dy * s2.invW - n2 * invSum * dSumDy) * invSum,
|
|
115
|
+
);
|
|
116
|
+
out.valid = true;
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* surfaceFrame.ts surfaceLod: the longer screen-axis UV step, in texels, as a power of two. */
|
|
121
|
+
fn surfaceLod(gradX: vec2<f32>, gradY: vec2<f32>, size: f32) -> f32 {
|
|
122
|
+
let footprint = max(length(gradX), length(gradY)) * size;
|
|
123
|
+
return max(log2(max(footprint, 1e-30)), 0.0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
struct TangentFrame {
|
|
127
|
+
t: vec3<f32>,
|
|
128
|
+
b: vec3<f32>,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* surfaceFrame.ts derivedTangentFrame, which is tangentFrame.ts's derived branch term for term. */
|
|
132
|
+
fn derivedTangentFrame(
|
|
133
|
+
n: vec3<f32>,
|
|
134
|
+
dp1: vec3<f32>,
|
|
135
|
+
dp2: vec3<f32>,
|
|
136
|
+
duv1: vec2<f32>,
|
|
137
|
+
duv2: vec2<f32>,
|
|
138
|
+
) -> TangentFrame {
|
|
139
|
+
let dp2perp = cross(dp2, n);
|
|
140
|
+
let dp1perp = cross(n, dp1);
|
|
141
|
+
let dt = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
142
|
+
let db = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
143
|
+
let det = duv1.x * duv2.y - duv2.x * duv1.y;
|
|
144
|
+
let handed = select(1.0, -1.0, det < 0.0);
|
|
145
|
+
let scale = inverseSqrt(max(max(dot(dt, dt), dot(db, db)), 1e-12)) * handed;
|
|
146
|
+
var frame: TangentFrame;
|
|
147
|
+
frame.t = dt * scale;
|
|
148
|
+
frame.b = db * scale;
|
|
149
|
+
return frame;
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* One invocation a pixel of one material's bin.
|
|
155
|
+
*
|
|
156
|
+
* `settings` is the bin's offset, its length, the screen width and the screen height. A dispatch
|
|
157
|
+
* is issued per material from the blocks `materialBin.wgsl.ts` wrote, so the bin this reads is
|
|
158
|
+
* whichever one the caller bound — the shader does not choose.
|
|
159
|
+
*
|
|
160
|
+
* The outputs here are the reconstruction rather than a colour, because that is what can be
|
|
161
|
+
* compared against a reference. In the assembled pipeline the same values feed the lit expression
|
|
162
|
+
* and the colour is what lands.
|
|
163
|
+
*/
|
|
164
|
+
export const SHADE_RECONSTRUCT_WGSL = `${SHADE_SURFACE_WGSL}
|
|
165
|
+
const VIS_TRIANGLE_BITS: u32 = 7u;
|
|
166
|
+
const VIS_EMPTY: u32 = 0xffffffffu;
|
|
167
|
+
|
|
168
|
+
/** binOffset, binCount, width, height. */
|
|
169
|
+
@group(0) @binding(0) var<storage, read> settings: array<u32, 4>;
|
|
170
|
+
@group(0) @binding(1) var<storage, read> binPixels: array<u32>;
|
|
171
|
+
@group(0) @binding(2) var<storage, read> visibility: array<u32>;
|
|
172
|
+
/** Four words a cluster: index offset, index count, cluster identifier, mesh. */
|
|
173
|
+
@group(0) @binding(3) var<storage, read> clusterMeta: array<u32>;
|
|
174
|
+
@group(0) @binding(4) var<storage, read> indices: array<u32>;
|
|
175
|
+
/** Four floats a vertex: clip x, clip y, clip w, and one attribute to interpolate. */
|
|
176
|
+
@group(0) @binding(5) var<storage, read> vertices: array<f32>;
|
|
177
|
+
/** Six floats a pixel: the interpolated attribute, its two gradients, and the three weights. */
|
|
178
|
+
@group(0) @binding(6) var<storage, read_write> surface: array<f32>;
|
|
179
|
+
|
|
180
|
+
fn clipOf(vertex: u32) -> vec4<f32> {
|
|
181
|
+
let at = vertex * 4u;
|
|
182
|
+
return vec4<f32>(vertices[at], vertices[at + 1u], 0.0, vertices[at + 2u]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@compute @workgroup_size(64)
|
|
186
|
+
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
187
|
+
/* Past the end of the bin, which is the ordinary case: a bin is not a multiple of the group
|
|
188
|
+
size, and the spare lanes must do nothing rather than shade the last pixel again. */
|
|
189
|
+
if (id.x >= settings[1]) { return; }
|
|
190
|
+
let pixel = binPixels[settings[0] + id.x];
|
|
191
|
+
let packed = visibility[pixel];
|
|
192
|
+
if (packed == VIS_EMPTY) { return; }
|
|
193
|
+
|
|
194
|
+
let cluster = packed >> VIS_TRIANGLE_BITS;
|
|
195
|
+
let triangle = packed & 127u;
|
|
196
|
+
let metaAt = cluster * 4u;
|
|
197
|
+
let indexOffset = clusterMeta[metaAt];
|
|
198
|
+
|
|
199
|
+
let i0 = indices[indexOffset + triangle * 3u];
|
|
200
|
+
let i1 = indices[indexOffset + triangle * 3u + 1u];
|
|
201
|
+
let i2 = indices[indexOffset + triangle * 3u + 2u];
|
|
202
|
+
|
|
203
|
+
let width = f32(settings[2]);
|
|
204
|
+
let height = f32(settings[3]);
|
|
205
|
+
let s0 = screenOf(clipOf(i0), width, height);
|
|
206
|
+
let s1 = screenOf(clipOf(i1), width, height);
|
|
207
|
+
let s2 = screenOf(clipOf(i2), width, height);
|
|
208
|
+
|
|
209
|
+
/* Pixel centres, which is where a rasteriser decided this pixel was covered. */
|
|
210
|
+
let px = f32(pixel % settings[2]) + 0.5;
|
|
211
|
+
let py = f32(pixel / settings[2]) + 0.5;
|
|
212
|
+
|
|
213
|
+
let w = surfaceWeights(s0, s1, s2, px, py);
|
|
214
|
+
if (!w.valid) { return; }
|
|
215
|
+
|
|
216
|
+
let a = vec3<f32>(
|
|
217
|
+
vertices[i0 * 4u + 3u],
|
|
218
|
+
vertices[i1 * 4u + 3u],
|
|
219
|
+
vertices[i2 * 4u + 3u],
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
let out = id.x * 6u;
|
|
223
|
+
surface[out] = dot(a, w.c);
|
|
224
|
+
surface[out + 1u] = dot(a, w.gx);
|
|
225
|
+
surface[out + 2u] = dot(a, w.gy);
|
|
226
|
+
surface[out + 3u] = w.c.x;
|
|
227
|
+
surface[out + 4u] = w.c.y;
|
|
228
|
+
surface[out + 5u] = w.c.z;
|
|
229
|
+
}
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* One dispatch a material: every pixel in that material's bin, lit and written to the frame.
|
|
234
|
+
*
|
|
235
|
+
* **This is the assembled half, and it is the one nothing can compare against a reference.** The
|
|
236
|
+
* interpolation it rests on is `SHADE_SURFACE_WGSL` above, which *is* compared — that sharing is
|
|
237
|
+
* the point of the split, because the quotient rule is what drifts and a second copy of it would
|
|
238
|
+
* be the copy nobody watches.
|
|
239
|
+
*
|
|
240
|
+
* **Which material a dispatch is shading arrives in a uniform with a per-material bind group**, not
|
|
241
|
+
* in the shader. There is no builtin for "which dispatch am I", and a shader per material would be
|
|
242
|
+
* a pipeline per material — so one pipeline is bound once and group one moves per dispatch. The
|
|
243
|
+
* bin's own offset and length are read from the buffers `materialBin.wgsl.ts` wrote rather than
|
|
244
|
+
* passed in, so there is one statement of where a bin is.
|
|
245
|
+
*
|
|
246
|
+
* **Eight storage buffers is the budget and this pass spends all of it.**
|
|
247
|
+
* `maxStorageBuffersPerShaderStage` is 8 by default; this adapter offers 16 and most do, and a
|
|
248
|
+
* pipeline that asked for the higher limit would refuse to create a device on the ones that do
|
|
249
|
+
* not. So the vertex attributes are interleaved into one buffer rather than three, and the frame
|
|
250
|
+
* block and the material table are uniforms. Found at pipeline creation with twelve.
|
|
251
|
+
*
|
|
252
|
+
* **What the lighting here is, and what it is not.** Vertex colour times the material's tint, one
|
|
253
|
+
* directional term with its highlight and its shadow, the same hemispheric ambient the forward path
|
|
254
|
+
* uses — `mix(ground, sky, n.y * 0.5 + 0.5)` — and the material's emissive. It is not the forward
|
|
255
|
+
* path's standard material: no image-based term and no texture. Those belong to a pipeline that
|
|
256
|
+
* draws the published scenes, and this one draws drafts. **Stating the gap is the point**;
|
|
257
|
+
* discovering it from a capture is what this repository keeps refusing.
|
|
258
|
+
*
|
|
259
|
+
* **The pixel centre is the flipped row.** WebGPU's framebuffer origin is the top-left, so row zero
|
|
260
|
+
* is where clip y is +1, and `screenOf` maps clip y of +1 to `height`. The flip belongs to the
|
|
261
|
+
* pixel rather than to the shared function, which is checked against a reference and must not
|
|
262
|
+
* learn about a framebuffer; flipping all four points of a barycentric evaluation leaves the
|
|
263
|
+
* weights unchanged, which is why this is the whole of it.
|
|
264
|
+
*
|
|
265
|
+
* **The normal is transformed by the upper three-by-three and renormalised**, which is right for
|
|
266
|
+
* the rigid transforms with uniform scale the draft scenes use and wrong for a non-uniform one —
|
|
267
|
+
* that wants the inverse transpose. A scene that needs it will find this comment rather than a
|
|
268
|
+
* lighting error nobody can place.
|
|
269
|
+
*/
|
|
270
|
+
/**
|
|
271
|
+
* The lit expression, as text, with no bindings in it.
|
|
272
|
+
*
|
|
273
|
+
* **Its own string for the reason the reconstruction above has one, arrived at the same way.** The
|
|
274
|
+
* shading pass writes a colour into a storage texture, so nothing could compare it to a reference —
|
|
275
|
+
* and for as long as the expression was four lines, the shading pass's header argued that nothing
|
|
276
|
+
* needed to. That argument stops the moment the expression grows a term. `gpudriven/lit.ts` is the
|
|
277
|
+
* twin now, `SHADE_LIGHTING_PARITY_WGSL` below is an entry point that does nothing but call this,
|
|
278
|
+
* and `scripts/gpu-parity.mjs` runs both over generated surfaces.
|
|
279
|
+
*
|
|
280
|
+
* Every term matches `flat/main.ts` at the one configuration this pipeline supports — metalness
|
|
281
|
+
* zero, which collapses the Fresnel blend on the highlight to the surface's own reflectance.
|
|
282
|
+
*/
|
|
283
|
+
export const SHADE_LIGHTING_WGSL = `
|
|
284
|
+
/* The normalised GGX distribution, transcribed from flat/lobes.ts. See lit.ts about its peak. */
|
|
285
|
+
/*
|
|
286
|
+
* **Two floors, and only one of them can be seen in the output.** The denominator's is what keeps
|
|
287
|
+
* a near-mirror's peak finite, and deleting it is worth 256 times the highlight at roughness 0.05 —
|
|
288
|
+
* gpu-parity.mjs catches that, now that a fifth of its corpus sits on the peak. The floor on a
|
|
289
|
+
* cannot be caught, because the denominator's already prevents the division by zero it guards
|
|
290
|
+
* against: at roughness zero the whole term is a ten-thousandth with it and zero without, and both
|
|
291
|
+
* round to nothing beside a lit surface. It is kept because it is what flat/lobes.ts has, and two
|
|
292
|
+
* spellings of one lobe differing by a guard is the drift this pair exists to prevent.
|
|
293
|
+
*/
|
|
294
|
+
fn specularLobe(ndh: f32, roughness: f32) -> f32 {
|
|
295
|
+
let a = max(roughness * roughness, ${MIN_LOBE_ALPHA});
|
|
296
|
+
let a2 = a * a;
|
|
297
|
+
let d = ndh * ndh * (a2 - 1.0) + 1.0;
|
|
298
|
+
return (a2 * a2) / max(d * d, 1e-8);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* A dielectric's normal-incidence reflectance, which every material in this pipeline is. */
|
|
302
|
+
const DIELECTRIC_F0: f32 = 0.04;
|
|
303
|
+
|
|
304
|
+
/*
|
|
305
|
+
* The split sum's second half, as Karis's analytic fit: dfg.x scales f0 and dfg.y adds on top.
|
|
306
|
+
* gpudriven/ibl.ts is the twin and carries what the tabulated integral would cost instead.
|
|
307
|
+
*/
|
|
308
|
+
fn envBrdfApprox(ndv: f32, roughness: f32) -> vec2<f32> {
|
|
309
|
+
let c0 = vec4<f32>(-1.0, -0.0275, -0.572, 0.022);
|
|
310
|
+
let c1 = vec4<f32>(1.0, 0.0425, 1.04, -0.04);
|
|
311
|
+
let r = roughness * c0 + c1;
|
|
312
|
+
let a004 = min(r.x * r.x, exp2(-9.28 * ndv)) * r.x + r.y;
|
|
313
|
+
return vec2<f32>(-1.04, 1.04) * a004 + r.zw;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/*
|
|
317
|
+
* The same with the multiply-scattered light put back. At f0 of one this returns exactly 1.0 at
|
|
318
|
+
* every roughness, which is the invariant ibl.test.ts asserts across the range: roughness
|
|
319
|
+
* scatters light and does not absorb it, and the bare split sum claims otherwise by up to 44%.
|
|
320
|
+
*/
|
|
321
|
+
fn envSpecularEnergy(f0: f32, dfg: vec2<f32>) -> f32 {
|
|
322
|
+
let single = f0 * dfg.x + dfg.y;
|
|
323
|
+
let energy = dfg.x + dfg.y;
|
|
324
|
+
let missing = 1.0 - energy;
|
|
325
|
+
let average = f0 + (1.0 - f0) / 21.0;
|
|
326
|
+
let multi = single * average / (1.0 - missing * average);
|
|
327
|
+
return single + multi * missing;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/*
|
|
331
|
+
* How much of what the surface sees in the mirror direction reaches the frame.
|
|
332
|
+
*
|
|
333
|
+
* Two expressions and a selector that is exactly 0 or exactly 1: the split sum is the correct
|
|
334
|
+
* share of a prefiltered environment and asserts an integral that was never performed over
|
|
335
|
+
* anything else. See ibl.ts for what ignoring that costs on the other pipeline.
|
|
336
|
+
*/
|
|
337
|
+
fn surfaceF0(metalness: f32) -> f32 {
|
|
338
|
+
return mix(DIELECTRIC_F0, 1.0, metalness);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
fn environmentWeight(
|
|
342
|
+
ndv: f32,
|
|
343
|
+
roughness: f32,
|
|
344
|
+
reflectivity: f32,
|
|
345
|
+
prefiltered: f32,
|
|
346
|
+
metalness: f32,
|
|
347
|
+
) -> f32 {
|
|
348
|
+
let f0 = surfaceF0(metalness);
|
|
349
|
+
let facing = 1.0 - ndv;
|
|
350
|
+
let fresnel = mix(f0, 1.0, facing * facing * facing * facing * facing);
|
|
351
|
+
/*
|
|
352
|
+
* max(reflectivity * (1 - roughness), metal), and both halves are repairs flat/main.ts records:
|
|
353
|
+
* a metal reflects whether or not the pass asked for reflections, and the one-minus-roughness
|
|
354
|
+
* thinning belongs to the dielectric alone. See ibl.ts.
|
|
355
|
+
*/
|
|
356
|
+
let amount = max(reflectivity * (1.0 - roughness), metalness);
|
|
357
|
+
let sweep = clamp(fresnel * amount, 0.0, 1.0);
|
|
358
|
+
let dfg = envBrdfApprox(ndv, roughness);
|
|
359
|
+
let integrated = clamp(envSpecularEnergy(f0, dfg) * max(reflectivity, metalness), 0.0, 1.0);
|
|
360
|
+
return mix(sweep, integrated, prefiltered);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
struct LitSurface {
|
|
364
|
+
albedo: vec3<f32>,
|
|
365
|
+
normal: vec3<f32>,
|
|
366
|
+
toEye: vec3<f32>,
|
|
367
|
+
roughness: f32,
|
|
368
|
+
specular: f32,
|
|
369
|
+
emissive: f32,
|
|
370
|
+
/*
|
|
371
|
+
* What the glow is coloured by: the albedo times an emissive map, flat/main.ts's
|
|
372
|
+
* emissiveTint * emissiveMapped. The albedo itself where the material binds no map. See lit.ts.
|
|
373
|
+
*/
|
|
374
|
+
emissiveColour: vec3<f32>,
|
|
375
|
+
/* How much of the sun reaches here: 0 in full shadow, 1 in full light. See lit.ts. */
|
|
376
|
+
shade: f32,
|
|
377
|
+
reflectivity: f32,
|
|
378
|
+
/* 0 is a dielectric and 1 is a metal. One number a material here, an ORM texel there. */
|
|
379
|
+
metalness: f32,
|
|
380
|
+
/* A crevice's share of everything below, as flat/main.ts applies ormOcclusion. See lit.ts. */
|
|
381
|
+
occlusion: f32,
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
struct LitEnvironment {
|
|
385
|
+
lightDir: vec3<f32>,
|
|
386
|
+
lightColour: vec3<f32>,
|
|
387
|
+
sky: vec3<f32>,
|
|
388
|
+
ground: vec3<f32>,
|
|
389
|
+
irradiance: vec3<f32>,
|
|
390
|
+
radiance: vec3<f32>,
|
|
391
|
+
irradianceAmount: f32,
|
|
392
|
+
/* How much of the mirror-direction gradient the radiance replaces. Zero keeps the gradient. */
|
|
393
|
+
radianceAmount: f32,
|
|
394
|
+
prefiltered: f32,
|
|
395
|
+
/* The frame's emissive gain times its night factor, which scales every glow. See lit.ts. */
|
|
396
|
+
emission: f32,
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
fn litColour(surface: LitSurface, env: LitEnvironment) -> vec3<f32> {
|
|
400
|
+
let n = surface.normal;
|
|
401
|
+
let albedo = surface.albedo;
|
|
402
|
+
let lightColour = env.lightColour;
|
|
403
|
+
let roughness = surface.roughness;
|
|
404
|
+
let shade = surface.shade;
|
|
405
|
+
let ndl = max(dot(n, env.lightDir), 0.0);
|
|
406
|
+
/*
|
|
407
|
+
* The forward path's own hemispheric term — flat/main.ts, mix(ground, sky, n.y * 0.5 + 0.5) —
|
|
408
|
+
* replaced by the room's irradiance where a caller supplies one rather than lifted toward it.
|
|
409
|
+
*/
|
|
410
|
+
let hemispheric = mix(env.ground, env.sky, n.y * 0.5 + 0.5);
|
|
411
|
+
let ambient = mix(hemispheric, env.irradiance, env.irradianceAmount);
|
|
412
|
+
|
|
413
|
+
/*
|
|
414
|
+
* The half vector between the light and the eye, and the highlight at its peak. Computed whether
|
|
415
|
+
* or not the surface is shiny and multiplied by zero where it is not: a branch on a value read
|
|
416
|
+
* from the material table is not provably uniform control flow, which is the rule this pass and
|
|
417
|
+
* the forward one both keep.
|
|
418
|
+
*/
|
|
419
|
+
let halfway = env.lightDir + surface.toEye;
|
|
420
|
+
let length2 = dot(halfway, halfway);
|
|
421
|
+
var ndh = 0.0;
|
|
422
|
+
if (length2 > 0.0) {
|
|
423
|
+
ndh = max(dot(n, halfway * inverseSqrt(length2)), 0.0);
|
|
424
|
+
}
|
|
425
|
+
let lobe = specularLobe(ndh, roughness);
|
|
426
|
+
let metal = surface.metalness;
|
|
427
|
+
/*
|
|
428
|
+
* A metal's highlight takes its own colour and goes white at the edge; a dielectric's takes the
|
|
429
|
+
* light's and does neither. mix(specular, albedo, 0) is specular exactly, and the Schlick term is
|
|
430
|
+
* scaled by metalness, so at metalness 0 both lines are the expression that shipped before them.
|
|
431
|
+
*/
|
|
432
|
+
let specColour = mix(vec3<f32>(surface.specular), albedo, metal);
|
|
433
|
+
let voh = select(0.0, max(dot(surface.toEye, halfway * inverseSqrt(length2)), 0.0), length2 > 0.0);
|
|
434
|
+
let facing = 1.0 - voh;
|
|
435
|
+
let sunSpec = mix(specColour, vec3<f32>(1.0), facing * facing * facing * facing * facing * metal);
|
|
436
|
+
let highlight = lightColour * lobe * sunSpec * shade;
|
|
437
|
+
|
|
438
|
+
/*
|
|
439
|
+
* Shade multiplies the two direct terms and not the ambient, which is where flat/main.ts puts
|
|
440
|
+
* its own sunShade and what its comment says the wider version costs. A metal keeps its ambient
|
|
441
|
+
* and loses its direct diffuse; the honest two terms cancel into this one.
|
|
442
|
+
*/
|
|
443
|
+
let base = albedo * (ambient + lightColour * ndl * shade * (1.0 - metal)) + highlight * (1.0 - metal);
|
|
444
|
+
/*
|
|
445
|
+
* **The dielectric's highlight goes underneath the blend and the metal's goes on top**, which is
|
|
446
|
+
* flat/main.ts's two lines with the mix between them. Underneath, the blend dims it by the
|
|
447
|
+
* surface's own reflectance — taking it out puts a lobe that was correctly dimmed back at full
|
|
448
|
+
* strength, which on a near-black glass lens carrying specular 1 is a white blob where there was
|
|
449
|
+
* none. On top, it survives a weight of one, which is what a metal always has.
|
|
450
|
+
*/
|
|
451
|
+
let nv = dot(n, surface.toEye);
|
|
452
|
+
let ndv = max(nv, 0.0);
|
|
453
|
+
/*
|
|
454
|
+
* What the surface sees along the mirror direction, and with no probe it is the same two-colour
|
|
455
|
+
* gradient the ambient comes from — which is what flat/main.ts reflects where a scene has no
|
|
456
|
+
* environment. Unclamped nv, because reflect takes the facing term as it is: clamping folds the
|
|
457
|
+
* direction back on a silhouette and reflects the sky where the ground belongs.
|
|
458
|
+
*/
|
|
459
|
+
let mirrored = 2.0 * nv * n - surface.toEye;
|
|
460
|
+
let gradient = mix(env.ground, env.sky, mirrored.y * 0.5 + 0.5);
|
|
461
|
+
let seen = mix(gradient, env.radiance, env.radianceAmount);
|
|
462
|
+
let weight = environmentWeight(ndv, roughness, surface.reflectivity, env.prefiltered, metal);
|
|
463
|
+
/* A metal reflects the room through itself; mix(1, albedo, 0) is exactly 1 for a dielectric. */
|
|
464
|
+
let reflected = seen * mix(vec3<f32>(1.0), albedo, metal);
|
|
465
|
+
/* Scaled by the frame and dimmed by a share of the sun's shadow, as flat/main.ts's glow is. */
|
|
466
|
+
let glow = surface.emissive * env.emission
|
|
467
|
+
* mix(1.0, surface.shade, ${EMISSIVE_SHADOW_SHARE});
|
|
468
|
+
return (mix(base, reflected, weight) + highlight * metal + surface.emissiveColour * glow)
|
|
469
|
+
* surface.occlusion;
|
|
470
|
+
}
|
|
471
|
+
`;
|
|
472
|
+
|
|
473
|
+
export const SHADE_MATERIAL_WGSL = `${SHADE_SURFACE_WGSL}${SHADE_LIGHTING_WGSL}
|
|
474
|
+
const VIS_TRIANGLE_BITS: u32 = 7u;
|
|
475
|
+
const VIS_EMPTY: u32 = 0xffffffffu;
|
|
476
|
+
/** Floats a vertex: position, normal, colour, uv, glow. One buffer, for the storage budget. */
|
|
477
|
+
const VERTEX_FLOATS: u32 = ${GPU_DRIVEN_VERTEX_FLOATS}u;
|
|
478
|
+
|
|
479
|
+
${FRAME_BLOCK_WGSL}
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
${MATERIAL_TABLE_WGSL}
|
|
483
|
+
struct Job {
|
|
484
|
+
material: u32,
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
488
|
+
@group(0) @binding(1) var<storage, read> visibility: array<u32>;
|
|
489
|
+
@group(0) @binding(2) var<storage, read> binPixels: array<u32>;
|
|
490
|
+
@group(0) @binding(3) var<storage, read> binOffsets: array<u32>;
|
|
491
|
+
@group(0) @binding(4) var<storage, read> binCounts: array<u32>;
|
|
492
|
+
/** Four words a cluster: index offset, index count, cluster identifier, mesh. */
|
|
493
|
+
@group(0) @binding(5) var<storage, read> clusterMeta: array<u32>;
|
|
494
|
+
@group(0) @binding(6) var<storage, read> indices: array<u32>;
|
|
495
|
+
@group(0) @binding(7) var<storage, read> vertices: array<f32>;
|
|
496
|
+
/** Sixteen floats a mesh. */
|
|
497
|
+
@group(0) @binding(8) var<storage, read> transforms: array<f32>;
|
|
498
|
+
@group(0) @binding(9) var<uniform> materials: array<Material, ${MATERIAL_SLOTS}>;
|
|
499
|
+
@group(0) @binding(10) var frameColour: texture_storage_2d<rgba16float, write>;
|
|
500
|
+
/*
|
|
501
|
+
* **This pipeline's own map, not the frame's.** A contributed pass is handed a device and a format
|
|
502
|
+
* and no attachments at all, so there is nothing of the forward path's to sample even when one
|
|
503
|
+
* exists — see PassDevice. gpuDrivenPass.ts renders this one from the same clusters, before the
|
|
504
|
+
* frame's own render pass opens.
|
|
505
|
+
*/
|
|
506
|
+
@group(0) @binding(11) var shadowMap: texture_depth_2d;
|
|
507
|
+
/*
|
|
508
|
+
* **The frame's own probe, which is the one thing here the pass could not render for itself.**
|
|
509
|
+
* PrepareContext.environment is the seam and carries why it is offered per frame rather than at
|
|
510
|
+
* registration: a scene bakes when it likes, and a value captured once would be null for the life
|
|
511
|
+
* of a pass that registered first. A stand-in one texel across is bound where there is none, so
|
|
512
|
+
* the layout is the same either way and frame.environment.w is what says which.
|
|
513
|
+
*/
|
|
514
|
+
@group(0) @binding(12) var environmentMap: texture_2d_array<f32>;
|
|
515
|
+
@group(0) @binding(13) var environmentSampler: sampler;
|
|
516
|
+
${decodeResourcesWgsl({ group: 0, latents: 14, clampSampler: 15, repeatSampler: 16, nodes: 17, weights: 18 })}
|
|
517
|
+
|
|
518
|
+
fn decodeLatentSize() -> f32 {
|
|
519
|
+
return frame.size.w;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
${DECODE_WGSL}
|
|
523
|
+
|
|
524
|
+
@group(1) @binding(0) var<uniform> job: Job;
|
|
525
|
+
|
|
526
|
+
/*
|
|
527
|
+
* Nearest, by integer coordinate, which is what the lookup's arithmetic assumes: the fetched depth
|
|
528
|
+
* belongs to the centre of the containing texel and the plane compensation is measured to it. A
|
|
529
|
+
* filtered read would blend four depths into a number no surface is at, which is not a softer
|
|
530
|
+
* shadow — it is a comparison against a depth that does not exist.
|
|
531
|
+
*/
|
|
532
|
+
/*
|
|
533
|
+
* **Layer 0, which is this pipeline reading one probe of a grid.** environment.wgsl.ts carries the
|
|
534
|
+
* argument: a grid is what a large world wants and a single probe pops as a surface crosses a
|
|
535
|
+
* cell, and what this pipeline owns is a fixed cluster set a few metres across.
|
|
536
|
+
*/
|
|
537
|
+
fn environmentSample(uv: vec2<f32>, level: f32) -> vec3<f32> {
|
|
538
|
+
return textureSampleLevel(environmentMap, environmentSampler, uv, 0, level).rgb;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
fn shadowDepthAt(u: f32, v: f32) -> f32 {
|
|
542
|
+
let size = frame.shadow.y;
|
|
543
|
+
let x = i32(clamp(floor(u * size), 0.0, size - 1.0));
|
|
544
|
+
let y = i32(clamp(floor(v * size), 0.0, size - 1.0));
|
|
545
|
+
return textureLoad(shadowMap, vec2<i32>(x, y), 0);
|
|
546
|
+
}
|
|
547
|
+
${SHADE_SHADOW_WGSL}
|
|
548
|
+
${ENVIRONMENT_WGSL}
|
|
549
|
+
${FOG_WGSL}
|
|
550
|
+
|
|
551
|
+
fn positionOf(vertex: u32) -> vec3<f32> {
|
|
552
|
+
let at = vertex * VERTEX_FLOATS;
|
|
553
|
+
return vec3<f32>(vertices[at], vertices[at + 1u], vertices[at + 2u]);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
fn normalOf(vertex: u32) -> vec3<f32> {
|
|
557
|
+
let at = vertex * VERTEX_FLOATS + 3u;
|
|
558
|
+
return vec3<f32>(vertices[at], vertices[at + 1u], vertices[at + 2u]);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fn colourOf(vertex: u32) -> vec3<f32> {
|
|
562
|
+
let at = vertex * VERTEX_FLOATS + 6u;
|
|
563
|
+
return vec3<f32>(vertices[at], vertices[at + 1u], vertices[at + 2u]);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
fn uvOf(vertex: u32) -> vec2<f32> {
|
|
567
|
+
let at = vertex * VERTEX_FLOATS + 9u;
|
|
568
|
+
return vec2<f32>(vertices[at], vertices[at + 1u]);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* The vertex's own glow, added to the material's. Zero wherever a mesh carried none. */
|
|
572
|
+
fn emissiveOf(vertex: u32) -> f32 {
|
|
573
|
+
return vertices[vertex * VERTEX_FLOATS + 11u];
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
${MESH_TRANSFORM_WGSL}
|
|
577
|
+
|
|
578
|
+
@compute @workgroup_size(64)
|
|
579
|
+
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
580
|
+
let material = job.material;
|
|
581
|
+
/* Past the end of this material's bin, which is the ordinary case: a bin is not a multiple of
|
|
582
|
+
the group size, and a spare lane must do nothing rather than shade the last pixel again. */
|
|
583
|
+
if (id.x >= binCounts[material]) { return; }
|
|
584
|
+
let pixel = binPixels[binOffsets[material] + id.x];
|
|
585
|
+
let packed = visibility[pixel];
|
|
586
|
+
if (packed == VIS_EMPTY) { return; }
|
|
587
|
+
|
|
588
|
+
let cluster = packed >> VIS_TRIANGLE_BITS;
|
|
589
|
+
let triangle = packed & 127u;
|
|
590
|
+
let metaAt = cluster * 4u;
|
|
591
|
+
let indexOffset = clusterMeta[metaAt];
|
|
592
|
+
let mesh = clusterMeta[metaAt + 3u];
|
|
593
|
+
|
|
594
|
+
let i0 = indices[indexOffset + triangle * 3u];
|
|
595
|
+
let i1 = indices[indexOffset + triangle * 3u + 1u];
|
|
596
|
+
let i2 = indices[indexOffset + triangle * 3u + 2u];
|
|
597
|
+
|
|
598
|
+
/* The row the buffer stores, which finds the pixel; the corners are placed by the width. */
|
|
599
|
+
let row = u32(frame.pitch.x);
|
|
600
|
+
let w0 = worldOf(mesh, positionOf(i0));
|
|
601
|
+
let w1 = worldOf(mesh, positionOf(i1));
|
|
602
|
+
let w2 = worldOf(mesh, positionOf(i2));
|
|
603
|
+
|
|
604
|
+
let s0 = screenOf(frame.viewProj * vec4<f32>(w0, 1.0), frame.size.x, frame.size.y);
|
|
605
|
+
let s1 = screenOf(frame.viewProj * vec4<f32>(w1, 1.0), frame.size.x, frame.size.y);
|
|
606
|
+
let s2 = screenOf(frame.viewProj * vec4<f32>(w2, 1.0), frame.size.x, frame.size.y);
|
|
607
|
+
|
|
608
|
+
let x = pixel % row;
|
|
609
|
+
let y = pixel / row;
|
|
610
|
+
/* Pixel centres, with the row flipped: see the header. */
|
|
611
|
+
let weights = surfaceWeights(s0, s1, s2, f32(x) + 0.5, frame.size.y - f32(y) - 0.5);
|
|
612
|
+
if (!weights.valid) { return; }
|
|
613
|
+
|
|
614
|
+
let n0 = rotatedOf(mesh, normalOf(i0));
|
|
615
|
+
let n1 = rotatedOf(mesh, normalOf(i1));
|
|
616
|
+
let n2 = rotatedOf(mesh, normalOf(i2));
|
|
617
|
+
let n = normalize(n0 * weights.c.x + n1 * weights.c.y + n2 * weights.c.z);
|
|
618
|
+
let entry = materials[material];
|
|
619
|
+
|
|
620
|
+
/*
|
|
621
|
+
* **The texture coordinate, how fast it moves across this pixel, and the level that asks for.**
|
|
622
|
+
* All three from the weights and gradients the shadow's receiver plane already uses, because a
|
|
623
|
+
* compute invocation has no neighbours to difference against. The scale is applied first, as the
|
|
624
|
+
* forward path's vUv already carries it.
|
|
625
|
+
*/
|
|
626
|
+
let uvScale = entry.maps.xy;
|
|
627
|
+
let uv0 = uvOf(i0) * uvScale;
|
|
628
|
+
let uv1 = uvOf(i1) * uvScale;
|
|
629
|
+
let uv2 = uvOf(i2) * uvScale;
|
|
630
|
+
let uv = uv0 * weights.c.x + uv1 * weights.c.y + uv2 * weights.c.z;
|
|
631
|
+
let duvdx = uv0 * weights.gx.x + uv1 * weights.gx.y + uv2 * weights.gx.z;
|
|
632
|
+
let duvdy = uv0 * weights.gy.x + uv1 * weights.gy.y + uv2 * weights.gy.z;
|
|
633
|
+
let textureLod = surfaceLod(duvdx, duvdy, frame.size.w);
|
|
634
|
+
let time = frame.size.z;
|
|
635
|
+
|
|
636
|
+
/*
|
|
637
|
+
* **The normal map first, and everything after it reads the turned normal**, which is the order
|
|
638
|
+
* flat/main.ts uses: its ndl, its shadow fade, its irradiance and its reflection all take the
|
|
639
|
+
* mapped n. The material table is a uniform, and textureSampleLevel takes no implicit
|
|
640
|
+
* derivative, so the branch is legal whichever way it goes.
|
|
641
|
+
*/
|
|
642
|
+
var shadingNormal = n;
|
|
643
|
+
if (entry.programs.y != NO_PROGRAM && entry.maps.z > 0.0) {
|
|
644
|
+
let mapped = decodeProgram(entry.programs.y, uv, textureLod, time).xyz * 2.0 - 1.0;
|
|
645
|
+
let dp1 = w0 * weights.gx.x + w1 * weights.gx.y + w2 * weights.gx.z;
|
|
646
|
+
let dp2 = w0 * weights.gy.x + w1 * weights.gy.y + w2 * weights.gy.z;
|
|
647
|
+
let tangents = derivedTangentFrame(n, dp1, dp2, duvdx, duvdy);
|
|
648
|
+
let turned = normalize(tangents.t * mapped.x + tangents.b * mapped.y + n * mapped.z);
|
|
649
|
+
shadingNormal = normalize(mix(n, turned, entry.maps.z));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
let vertexColour =
|
|
653
|
+
colourOf(i0) * weights.c.x + colourOf(i1) * weights.c.y + colourOf(i2) * weights.c.z;
|
|
654
|
+
var baseColour = vec3<f32>(1.0);
|
|
655
|
+
if (entry.programs.x != NO_PROGRAM) {
|
|
656
|
+
baseColour = decodeProgram(entry.programs.x, uv, textureLod, time).xyz;
|
|
657
|
+
}
|
|
658
|
+
let albedo = vertexColour * entry.tint.xyz * baseColour;
|
|
659
|
+
/*
|
|
660
|
+
* **Where the surface glows**: the albedo times the emissive map, or the albedo where there is
|
|
661
|
+
* none, so an unmapped surface glows exactly as it did. The map modulates the glow and creates
|
|
662
|
+
* none — a material whose emissive is zero stays dark however bright its map — which is glTF's
|
|
663
|
+
* rule and flat/main.ts's.
|
|
664
|
+
*/
|
|
665
|
+
var emissiveMapped = vec3<f32>(1.0);
|
|
666
|
+
if (entry.programs.w != NO_PROGRAM) {
|
|
667
|
+
emissiveMapped = decodeProgram(entry.programs.w, uv, textureLod, time).xyz;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* The ORM map replaces roughness and metal, and occlusion is a mix from one: SurfaceMaterial's rules. */
|
|
671
|
+
var roughness = entry.surface.x;
|
|
672
|
+
var metalness = entry.surface.w;
|
|
673
|
+
var occlusion = 1.0;
|
|
674
|
+
if (entry.programs.z != NO_PROGRAM) {
|
|
675
|
+
let orm = decodeProgram(entry.programs.z, uv, textureLod, time);
|
|
676
|
+
occlusion = mix(1.0, orm.x, entry.maps.w);
|
|
677
|
+
roughness = clamp(orm.y * entry.orm.x, 0.0, 1.0);
|
|
678
|
+
metalness = clamp(orm.z * entry.orm.y, 0.0, 1.0);
|
|
679
|
+
}
|
|
680
|
+
/*
|
|
681
|
+
* **A mapped normal wanders, so the roughness grows with it**, as flat/main.ts's surfaceRoughness
|
|
682
|
+
* does and for its reason: a normal that varies fast cannot hold a highlight narrower than the
|
|
683
|
+
* variation. The strength is zero for a material with no normal program, so the sum is the
|
|
684
|
+
* material's own roughness there, and the clamp is the forward path's too.
|
|
685
|
+
*/
|
|
686
|
+
let surfaceRoughness = clamp(roughness + entry.maps.z * ${RELIEF_ROUGHNESS}, 0.0, 1.0);
|
|
687
|
+
|
|
688
|
+
/* The surface's world position, for the eye vector the highlight needs. */
|
|
689
|
+
let world = w0 * weights.c.x + w1 * weights.c.y + w2 * weights.c.z;
|
|
690
|
+
let toEyeRaw = frame.eye.xyz - world;
|
|
691
|
+
let toEyeLen2 = dot(toEyeRaw, toEyeRaw);
|
|
692
|
+
let toEye = select(vec3<f32>(0.0, 0.0, 1.0), toEyeRaw * inverseSqrt(toEyeLen2), toEyeLen2 > 0.0);
|
|
693
|
+
|
|
694
|
+
/*
|
|
695
|
+
* **The receiver in the light's clip space, interpolated from the three corners.** Exact rather
|
|
696
|
+
* than approximate: a directional light's projection is orthographic, so w is one and the
|
|
697
|
+
* light-space position is linear in the world position — which is also what lets the plane below
|
|
698
|
+
* come from the barycentric gradients instead of from derivatives this stage cannot take.
|
|
699
|
+
*/
|
|
700
|
+
let l0 = (frame.lightViewProj * vec4<f32>(w0, 1.0)).xyz;
|
|
701
|
+
let l1 = (frame.lightViewProj * vec4<f32>(w1, 1.0)).xyz;
|
|
702
|
+
let l2 = (frame.lightViewProj * vec4<f32>(w2, 1.0)).xyz;
|
|
703
|
+
let lightPos = l0 * weights.c.x + l1 * weights.c.y + l2 * weights.c.z;
|
|
704
|
+
|
|
705
|
+
var settings: ShadowSettings;
|
|
706
|
+
settings.strength = frame.shadow.x;
|
|
707
|
+
settings.mapSize = frame.shadow.y;
|
|
708
|
+
settings.depthSpan = frame.shadow.z;
|
|
709
|
+
settings.maxDistance = frame.shadow.w;
|
|
710
|
+
settings.maxSlope = frame.shadowLimits.x;
|
|
711
|
+
settings.taps = frame.shadowLimits.y;
|
|
712
|
+
settings.lightDir = frame.lightDir;
|
|
713
|
+
/* The same cosine litColour computes, because the lookup fades the shadow through the
|
|
714
|
+
terminator band where that cosine is already collapsing. flat/main.ts shares it too. */
|
|
715
|
+
let ndl = max(dot(shadingNormal, frame.lightDir.xyz), 0.0);
|
|
716
|
+
let shade = shadowFactor(
|
|
717
|
+
vec4<f32>(lightPos, 1.0),
|
|
718
|
+
receiverPlaneFromWeights(l0, l1, l2, weights.gx, weights.gy),
|
|
719
|
+
ndl,
|
|
720
|
+
settings,
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
var litSurface: LitSurface;
|
|
724
|
+
litSurface.albedo = albedo;
|
|
725
|
+
litSurface.normal = shadingNormal;
|
|
726
|
+
litSurface.toEye = toEye;
|
|
727
|
+
litSurface.roughness = surfaceRoughness;
|
|
728
|
+
litSurface.specular = entry.surface.y;
|
|
729
|
+
/* Interpolated as the colour is, and added: the sandbox's block light is a glow a vertex. */
|
|
730
|
+
let glow = emissiveOf(i0) * weights.c.x + emissiveOf(i1) * weights.c.y + emissiveOf(i2) * weights.c.z;
|
|
731
|
+
litSurface.emissive = entry.tint.w + glow;
|
|
732
|
+
litSurface.emissiveColour = albedo * emissiveMapped;
|
|
733
|
+
litSurface.shade = shade;
|
|
734
|
+
litSurface.reflectivity = entry.surface.z;
|
|
735
|
+
litSurface.metalness = metalness;
|
|
736
|
+
litSurface.occlusion = occlusion;
|
|
737
|
+
|
|
738
|
+
var room: LitEnvironment;
|
|
739
|
+
room.lightDir = frame.lightDir.xyz;
|
|
740
|
+
room.lightColour = frame.lightColour.xyz;
|
|
741
|
+
room.sky = frame.sky.xyz;
|
|
742
|
+
room.ground = frame.ground.xyz;
|
|
743
|
+
/*
|
|
744
|
+
* **The mirror direction and the gradient behind it moved into litColour on 2026-09-17**, so the
|
|
745
|
+
* default cannot be forgotten: a metal's environment weight is exactly 1, and a metal handed no
|
|
746
|
+
* radiance renders black. radianceAmount is what says whether a probe was bound, the same shape
|
|
747
|
+
* irradianceAmount already had.
|
|
748
|
+
*/
|
|
749
|
+
let probeBound = frame.environment.w;
|
|
750
|
+
let edge = frame.environment.x;
|
|
751
|
+
let maxLod = frame.environment.y;
|
|
752
|
+
/*
|
|
753
|
+
* **Roughness alone picks the level, and the footprint term is missing rather than folded in.**
|
|
754
|
+
* The forward path widens the level where the mirror direction swings fast across a pixel —
|
|
755
|
+
* fwidth(mirrored), which a compute invocation cannot take — and what its absence costs is
|
|
756
|
+
* sparkle on creases and shut lines at grazing angles, worst on dark surfaces. The derivative is
|
|
757
|
+
* recoverable from the barycentric gradients the same way the shadow's receiver plane is, and
|
|
758
|
+
* that is a term with its own reference rather than one to guess at here.
|
|
759
|
+
*/
|
|
760
|
+
let lod = clamp(surfaceRoughness * maxLod, 0.0, maxLod);
|
|
761
|
+
room.radiance = probeRadianceAt(reflect(-toEye, shadingNormal), edge, lod, maxLod);
|
|
762
|
+
room.radianceAmount = probeBound;
|
|
763
|
+
room.irradiance = probeIrradianceAt(shadingNormal, edge, frame.environment.z);
|
|
764
|
+
room.irradianceAmount = frame.environmentMix.x;
|
|
765
|
+
/* The split sum is the correct share of a prefiltered chain and of nothing else. See ibl.ts. */
|
|
766
|
+
room.prefiltered = probeBound;
|
|
767
|
+
room.emission = frame.environmentMix.y;
|
|
768
|
+
|
|
769
|
+
let lit = litColour(litSurface, room);
|
|
770
|
+
/*
|
|
771
|
+
* **The haze, last, over everything the surface returned.** flat/main.ts applies it at the same
|
|
772
|
+
* point and for the same reason: fog is the air in front of the picture rather than a property
|
|
773
|
+
* of the surface, so it is the last thing between a shaded colour and the frame.
|
|
774
|
+
*/
|
|
775
|
+
let hazed = mix(lit, mediumColour(), mediumFog(distance(world, frame.eye.xyz), world.y));
|
|
776
|
+
|
|
777
|
+
textureStore(frameColour, vec2<u32>(x, y), vec4<f32>(hazed, 1.0));
|
|
778
|
+
}
|
|
779
|
+
`;
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* An entry point that does nothing but light a surface, so `gpu-parity.mjs` can compare the
|
|
783
|
+
* expression above with `gpudriven/lit.ts` directly rather than through a shading pass that writes
|
|
784
|
+
* to a texture and reads eleven buffers.
|
|
785
|
+
*
|
|
786
|
+
* `LIT_SURFACE_FLOATS` a surface: albedo, normal, the direction to the eye, then roughness,
|
|
787
|
+
* specular, emissive, shade, reflectivity, metalness, occlusion and the emitted colour; and
|
|
788
|
+
* `LIT_ENVIRONMENT_FLOATS` a room. The prose said fourteen and twenty for two waves after both had
|
|
789
|
+
* grown, which is why it names the constants now rather than counting them.
|
|
790
|
+
*/
|
|
791
|
+
/**
|
|
792
|
+
* Floats a surface of the lighting check. Matches `LitSurface`: occlusion at fifteen, then the
|
|
793
|
+
* emitted colour, which a surface with no emissive map sets to its albedo.
|
|
794
|
+
*/
|
|
795
|
+
export const LIT_SURFACE_FLOATS = 19;
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Floats an environment of the lighting check. Matches `LitEnvironment`.
|
|
799
|
+
*
|
|
800
|
+
* **One block a surface rather than one for the corpus**, which it was until the image-based term
|
|
801
|
+
* arrived: the room, the chain and the two selectors are per-pixel quantities in the shading pass,
|
|
802
|
+
* and a check that held them constant would run the split sum at exactly one configuration.
|
|
803
|
+
*/
|
|
804
|
+
export const LIT_ENVIRONMENT_FLOATS = 22;
|
|
805
|
+
|
|
806
|
+
export const SHADE_LIGHTING_PARITY_WGSL = `
|
|
807
|
+
@group(0) @binding(0) var<storage, read> params: array<f32>;
|
|
808
|
+
@group(0) @binding(1) var<storage, read> environment: array<f32>;
|
|
809
|
+
@group(0) @binding(2) var<storage, read> surfaces: array<f32>;
|
|
810
|
+
@group(0) @binding(3) var<storage, read_write> colours: array<f32>;
|
|
811
|
+
|
|
812
|
+
${SHADE_LIGHTING_WGSL}
|
|
813
|
+
|
|
814
|
+
@compute @workgroup_size(64)
|
|
815
|
+
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
816
|
+
let count = u32(params[0]);
|
|
817
|
+
if (id.x >= count) {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
let at = id.x * ${LIT_SURFACE_FLOATS}u;
|
|
821
|
+
let env = id.x * ${LIT_ENVIRONMENT_FLOATS}u;
|
|
822
|
+
var surface: LitSurface;
|
|
823
|
+
surface.albedo = vec3<f32>(surfaces[at], surfaces[at + 1u], surfaces[at + 2u]);
|
|
824
|
+
surface.normal = vec3<f32>(surfaces[at + 3u], surfaces[at + 4u], surfaces[at + 5u]);
|
|
825
|
+
surface.toEye = vec3<f32>(surfaces[at + 6u], surfaces[at + 7u], surfaces[at + 8u]);
|
|
826
|
+
surface.roughness = surfaces[at + 9u];
|
|
827
|
+
surface.specular = surfaces[at + 10u];
|
|
828
|
+
surface.emissive = surfaces[at + 11u];
|
|
829
|
+
surface.shade = surfaces[at + 12u];
|
|
830
|
+
surface.reflectivity = surfaces[at + 13u];
|
|
831
|
+
surface.metalness = surfaces[at + 14u];
|
|
832
|
+
surface.occlusion = surfaces[at + 15u];
|
|
833
|
+
surface.emissiveColour = vec3<f32>(surfaces[at + 16u], surfaces[at + 17u], surfaces[at + 18u]);
|
|
834
|
+
|
|
835
|
+
var room: LitEnvironment;
|
|
836
|
+
room.lightDir = vec3<f32>(environment[env], environment[env + 1u], environment[env + 2u]);
|
|
837
|
+
room.lightColour = vec3<f32>(environment[env + 3u], environment[env + 4u], environment[env + 5u]);
|
|
838
|
+
room.sky = vec3<f32>(environment[env + 6u], environment[env + 7u], environment[env + 8u]);
|
|
839
|
+
room.ground = vec3<f32>(environment[env + 9u], environment[env + 10u], environment[env + 11u]);
|
|
840
|
+
room.irradiance =
|
|
841
|
+
vec3<f32>(environment[env + 12u], environment[env + 13u], environment[env + 14u]);
|
|
842
|
+
room.radiance = vec3<f32>(environment[env + 15u], environment[env + 16u], environment[env + 17u]);
|
|
843
|
+
room.irradianceAmount = environment[env + 18u];
|
|
844
|
+
room.radianceAmount = environment[env + 19u];
|
|
845
|
+
room.prefiltered = environment[env + 20u];
|
|
846
|
+
room.emission = environment[env + 21u];
|
|
847
|
+
|
|
848
|
+
let colour = litColour(surface, room);
|
|
849
|
+
colours[id.x * 3u] = colour.x;
|
|
850
|
+
colours[id.x * 3u + 1u] = colour.y;
|
|
851
|
+
colours[id.x * 3u + 2u] = colour.z;
|
|
852
|
+
}
|
|
853
|
+
`;
|
|
854
|
+
|
|
855
|
+
/** Floats a case of the surface-frame check: n, dp1, dp2, duv1, duv2, size. */
|
|
856
|
+
export const SURFACE_FRAME_CASE_FLOATS = 14;
|
|
857
|
+
|
|
858
|
+
export const SURFACE_FRAME_PARITY_WGSL = /* wgsl */ `${SHADE_SURFACE_WGSL}
|
|
859
|
+
@group(0) @binding(0) var<storage, read> params: array<f32>;
|
|
860
|
+
@group(0) @binding(1) var<storage, read> cases: array<f32>;
|
|
861
|
+
@group(0) @binding(2) var<storage, read_write> results: array<f32>;
|
|
862
|
+
|
|
863
|
+
@compute @workgroup_size(64)
|
|
864
|
+
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
865
|
+
if (id.x >= u32(params[0])) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
let at = id.x * ${SURFACE_FRAME_CASE_FLOATS}u;
|
|
869
|
+
let n = vec3<f32>(cases[at], cases[at + 1u], cases[at + 2u]);
|
|
870
|
+
let dp1 = vec3<f32>(cases[at + 3u], cases[at + 4u], cases[at + 5u]);
|
|
871
|
+
let dp2 = vec3<f32>(cases[at + 6u], cases[at + 7u], cases[at + 8u]);
|
|
872
|
+
let duv1 = vec2<f32>(cases[at + 9u], cases[at + 10u]);
|
|
873
|
+
let duv2 = vec2<f32>(cases[at + 11u], cases[at + 12u]);
|
|
874
|
+
let frame = derivedTangentFrame(n, dp1, dp2, duv1, duv2);
|
|
875
|
+
let dst = id.x * 7u;
|
|
876
|
+
results[dst] = surfaceLod(duv1, duv2, cases[at + 13u]);
|
|
877
|
+
results[dst + 1u] = frame.t.x;
|
|
878
|
+
results[dst + 2u] = frame.t.y;
|
|
879
|
+
results[dst + 3u] = frame.t.z;
|
|
880
|
+
results[dst + 4u] = frame.b.x;
|
|
881
|
+
results[dst + 5u] = frame.b.y;
|
|
882
|
+
results[dst + 6u] = frame.b.z;
|
|
883
|
+
}
|
|
884
|
+
`;
|