@driftengine/core 3.63.0 → 4.1.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 +122 -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,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transient resources a frame declares, addressed by identifier rather than by bit.
|
|
3
|
+
*
|
|
4
|
+
* **`resources.ts` is not replaced and is not wrong.** It is a fixed table of fourteen
|
|
5
|
+
* attachments held in a bit mask, and it says in its own header that the transient allocator a
|
|
6
|
+
* frame graph is famous for would be machinery with no case to serve — which was true while
|
|
7
|
+
* this frame's composition was fixed. A GPU-driven frame's composition is not: hierarchical
|
|
8
|
+
* depth chains, visibility buffers, per-material bin lists and indirect argument buffers are
|
|
9
|
+
* allocated against what the frame contains. Thirty-one bits cannot hold hundreds of them and
|
|
10
|
+
* a thirty-second bit is not the fix.
|
|
11
|
+
*
|
|
12
|
+
* So there are two representations and each is used where it is right: the mask for a fixed
|
|
13
|
+
* composition, identifiers for a dynamic one. Nothing converts between them, because nothing
|
|
14
|
+
* needs to — a frame is scheduled by one or the other.
|
|
15
|
+
*
|
|
16
|
+
* Structure of arrays, growable by doubling, and nothing here holds an object per resource.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** A transient texture, sized in texels. */
|
|
20
|
+
export interface VirtualTextureDesc {
|
|
21
|
+
kind: 'texture';
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
bytesPerTexel: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A transient buffer, sized in bytes. */
|
|
28
|
+
export interface VirtualBufferDesc {
|
|
29
|
+
kind: 'buffer';
|
|
30
|
+
bytes: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type VirtualDesc = VirtualTextureDesc | VirtualBufferDesc;
|
|
34
|
+
|
|
35
|
+
export interface VirtualTable {
|
|
36
|
+
/** 0 for a texture, 1 for a buffer, one entry per identifier. */
|
|
37
|
+
kinds: Uint8Array;
|
|
38
|
+
/** Byte footprint per identifier, which is what the aliasing allocator packs. */
|
|
39
|
+
bytes: Uint32Array;
|
|
40
|
+
/** How many identifiers have been declared this frame. */
|
|
41
|
+
count: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createVirtualTable(capacity: number): VirtualTable {
|
|
45
|
+
return { kinds: new Uint8Array(capacity), bytes: new Uint32Array(capacity), count: 0 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resetVirtualTable(table: VirtualTable): void {
|
|
49
|
+
table.count = 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function virtualCount(table: VirtualTable): number {
|
|
53
|
+
return table.count;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* Doubling rather than growing by one, because a frame that declares one more resource than
|
|
58
|
+
* last frame will declare one more again, and a table that reallocates per declaration turns a
|
|
59
|
+
* linear frame into a quadratic one.
|
|
60
|
+
*/
|
|
61
|
+
function grow(table: VirtualTable): void {
|
|
62
|
+
const size = table.kinds.length === 0 ? 8 : table.kinds.length * 2;
|
|
63
|
+
const kinds = new Uint8Array(size);
|
|
64
|
+
kinds.set(table.kinds);
|
|
65
|
+
const bytes = new Uint32Array(size);
|
|
66
|
+
bytes.set(table.bytes);
|
|
67
|
+
table.kinds = kinds;
|
|
68
|
+
table.bytes = bytes;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function declareVirtual(table: VirtualTable, desc: VirtualDesc): number {
|
|
72
|
+
if (table.count === table.kinds.length) grow(table);
|
|
73
|
+
const id = table.count;
|
|
74
|
+
if (desc.kind === 'texture') {
|
|
75
|
+
table.kinds[id] = 0;
|
|
76
|
+
table.bytes[id] = desc.width * desc.height * desc.bytesPerTexel;
|
|
77
|
+
} else {
|
|
78
|
+
table.kinds[id] = 1;
|
|
79
|
+
table.bytes[id] = desc.bytes;
|
|
80
|
+
}
|
|
81
|
+
table.count = id + 1;
|
|
82
|
+
return id;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function virtualKind(table: VirtualTable, id: number): number {
|
|
86
|
+
return table.kinds[id] ?? 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function virtualBytes(table: VirtualTable, id: number): number {
|
|
90
|
+
return table.bytes[id] ?? 0;
|
|
91
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/** The three levels in one place, and the rule that there is never a fourth. */
|
|
2
|
+
|
|
3
|
+
import { createProbeBlend } from '../probeGrid.ts';
|
|
4
|
+
import { REFLECTION_EDGE_FADE, edgeFade, newScreenSpaceHit } from '../screenSpaceReflection.ts';
|
|
5
|
+
import { GLOBAL_FIELD_BLEND } from './globalField.ts';
|
|
6
|
+
import { sampleProbeVolume, visibleProbes } from './probeVolume.ts';
|
|
7
|
+
import { FIELD_MARCH, newFieldHit, traceField } from './traceField.ts';
|
|
8
|
+
import { GI_SCREEN_MARCH, traceScreen } from './traceScreen.ts';
|
|
9
|
+
|
|
10
|
+
import type { ReadonlyVec3 } from 'gl-matrix';
|
|
11
|
+
import type { ProbeGrid } from '../probeGrid.ts';
|
|
12
|
+
import type { ScreenSpaceMarch } from '../screenSpaceReflection.ts';
|
|
13
|
+
import type { GlobalField, GlobalFieldCascade } from './globalField.ts';
|
|
14
|
+
import type { ProbeVisibility } from './probeVolume.ts';
|
|
15
|
+
import type { FieldMarch } from './traceField.ts';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* **Three levels, and the architecture is the fallback rather than any one of them.**
|
|
19
|
+
*
|
|
20
|
+
* `ROADMAP.md` refused screen-space global illumination, and this does not reverse that refusal —
|
|
21
|
+
* it satisfies the requirement the refusal was about. Screen space is exact wherever the screen has
|
|
22
|
+
* the answer and has *no* answer where it does not, which as a terminal technique means inventing
|
|
23
|
+
* one from whatever happened to be in frame. Here it is an accelerator with something behind it:
|
|
24
|
+
* a world-space distance field whose error is bounded by its own resolution, and behind that a
|
|
25
|
+
* probe volume which is never wrong and only ever coarse.
|
|
26
|
+
*
|
|
27
|
+
* **The last level cannot fail, and that is the load-bearing sentence.** A ray with no answer
|
|
28
|
+
* leaves a shader nothing to write but what the buffer held, which is the previous frame and reads
|
|
29
|
+
* as a flicker. A probe volume is an interpolation between measurements — no ray to escape, no
|
|
30
|
+
* frame to leave — and `visibleProbes` falls back to the nearest probe even when every visibility
|
|
31
|
+
* weight has crushed to zero. So there are exactly three cases.
|
|
32
|
+
*
|
|
33
|
+
* **And the levels blend rather than switch.** Every boundary between them moves with the camera:
|
|
34
|
+
* the frame's edge does by definition, and the cascades are centred on the camera so their faces do
|
|
35
|
+
* too. A hard switch at a boundary that moves is a seam that sweeps across the picture whenever the
|
|
36
|
+
* camera turns, which is worse than either level's own error — an error that is wrong everywhere
|
|
37
|
+
* reads as a coarse solution, and one that changes abruptly along a surface reads as a crack.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export const GI_SOURCE_SCREEN = 0;
|
|
41
|
+
export const GI_SOURCE_FIELD = 1;
|
|
42
|
+
export const GI_SOURCE_PROBES = 2;
|
|
43
|
+
|
|
44
|
+
export type GiSource = 0 | 1 | 2;
|
|
45
|
+
|
|
46
|
+
/** One indirect ray, as the shading path hands it over. */
|
|
47
|
+
export interface IndirectRay {
|
|
48
|
+
/** Where it starts, on the surface being shaded. */
|
|
49
|
+
readonly point: ReadonlyVec3;
|
|
50
|
+
/** That surface's normal, which both traces lift off along. */
|
|
51
|
+
readonly normal: ReadonlyVec3;
|
|
52
|
+
readonly direction: ReadonlyVec3;
|
|
53
|
+
/** Cone half-angle for the world trace. Zero is a mirror; wider is a rougher surface. */
|
|
54
|
+
readonly coneAngle: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Everything the three levels need, gathered so a per-ray call takes one object. */
|
|
58
|
+
export interface GiResources {
|
|
59
|
+
readonly eye: ReadonlyVec3;
|
|
60
|
+
readonly project: (x: number, y: number, z: number, uv: Float32Array) => boolean;
|
|
61
|
+
/** How far the eye is from whatever the frame drew, and `Infinity` where it drew nothing. */
|
|
62
|
+
readonly sceneDistance: (u: number, v: number) => number;
|
|
63
|
+
/** The radiance the frame holds at a screen position. */
|
|
64
|
+
readonly screenRadiance: (u: number, v: number, out: Float32Array) => void;
|
|
65
|
+
readonly field: GlobalField;
|
|
66
|
+
/** The radiance at a world point the field trace arrived at. */
|
|
67
|
+
readonly fieldRadiance: (x: number, y: number, z: number, out: Float32Array) => void;
|
|
68
|
+
readonly grid: ProbeGrid;
|
|
69
|
+
/** Null is the off switch, and it lands on the grid's own blend. See `probeVolume.ts`. */
|
|
70
|
+
readonly visibility: ProbeVisibility | null;
|
|
71
|
+
/** `probeChannels` floats a probe, layer-major. */
|
|
72
|
+
readonly probeValues: Float32Array;
|
|
73
|
+
readonly probeChannels: number;
|
|
74
|
+
readonly screenMarch?: ScreenSpaceMarch;
|
|
75
|
+
readonly fieldMarch?: FieldMarch;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface IndirectResult {
|
|
79
|
+
/** Always true. See the module header: there is no fourth case. */
|
|
80
|
+
hit: boolean;
|
|
81
|
+
/** Which level contributed most. The radiance is the blend, not this level alone. */
|
|
82
|
+
source: GiSource;
|
|
83
|
+
/** Three channels. */
|
|
84
|
+
readonly radiance: Float32Array;
|
|
85
|
+
/** How much of the answer came from each level, summing to one. */
|
|
86
|
+
readonly blend: Float32Array;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function newIndirectResult(): IndirectResult {
|
|
90
|
+
return {
|
|
91
|
+
hit: false,
|
|
92
|
+
source: GI_SOURCE_PROBES,
|
|
93
|
+
radiance: new Float32Array(3),
|
|
94
|
+
blend: new Float32Array(3),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Scratch, because this runs once per ray per pixel and the rule about that is absolute. */
|
|
99
|
+
const SCREEN_HIT = newScreenSpaceHit();
|
|
100
|
+
const FIELD_HIT = newFieldHit();
|
|
101
|
+
const PROBE_BLEND = createProbeBlend();
|
|
102
|
+
const LEVEL = new Float32Array(3);
|
|
103
|
+
const PROBE_OUT = new Float32Array(3);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Trace one indirect ray down the chain, and always answer.
|
|
107
|
+
*
|
|
108
|
+
* The screen answers as much as its distance from the edge of the frame allows; what is left goes
|
|
109
|
+
* to the world field, as much as *its* distance from the edge of the outermost cascade allows; and
|
|
110
|
+
* whatever is left after that goes to the probes, which have nothing behind them and take it all.
|
|
111
|
+
*/
|
|
112
|
+
export function traceIndirect(
|
|
113
|
+
ray: IndirectRay,
|
|
114
|
+
resources: GiResources,
|
|
115
|
+
out: IndirectResult,
|
|
116
|
+
): IndirectResult {
|
|
117
|
+
out.radiance.fill(0);
|
|
118
|
+
out.blend.fill(0);
|
|
119
|
+
out.hit = true;
|
|
120
|
+
|
|
121
|
+
/* Level 0. Weighted by how far the hit landed from the edge of the frame, which is the one
|
|
122
|
+
boundary a viewer is guaranteed to see move. */
|
|
123
|
+
let screen = 0;
|
|
124
|
+
if (
|
|
125
|
+
traceScreen(
|
|
126
|
+
ray.point,
|
|
127
|
+
ray.normal,
|
|
128
|
+
ray.direction,
|
|
129
|
+
resources.eye,
|
|
130
|
+
resources.screenMarch ?? GI_SCREEN_MARCH,
|
|
131
|
+
resources.project,
|
|
132
|
+
resources.sceneDistance,
|
|
133
|
+
SCREEN_HIT,
|
|
134
|
+
)
|
|
135
|
+
) {
|
|
136
|
+
/*
|
|
137
|
+
* **The *path*'s margin, not the hit's.** Fading only the hit leaves the seam exactly where it
|
|
138
|
+
* was: a ray that grazes the border and lands is faded, and the ray one pixel over leaves the
|
|
139
|
+
* frame and answers nothing at all, so the weight still steps from one to zero across a line
|
|
140
|
+
* that sweeps as the camera turns. See `ScreenSpaceHit.margin`.
|
|
141
|
+
*/
|
|
142
|
+
screen = edgeFade(SCREEN_HIT.margin, REFLECTION_EDGE_FADE);
|
|
143
|
+
if (screen > 0) {
|
|
144
|
+
resources.screenRadiance(SCREEN_HIT.u, SCREEN_HIT.v, LEVEL);
|
|
145
|
+
add(out.radiance, LEVEL, screen);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Level 1, for whatever the screen did not answer. Weighted the same way against the outermost
|
|
150
|
+
cascade's face, because that face is centred on the camera and therefore moves with it too. */
|
|
151
|
+
let fieldShare = 0;
|
|
152
|
+
const remaining = 1 - screen;
|
|
153
|
+
if (remaining > 0) {
|
|
154
|
+
const march = resources.fieldMarch ?? FIELD_MARCH;
|
|
155
|
+
const coned =
|
|
156
|
+
march.coneAngle === ray.coneAngle ? march : { ...march, coneAngle: ray.coneAngle };
|
|
157
|
+
if (traceField(resources.field, ray.point, ray.normal, ray.direction, coned, FIELD_HIT)) {
|
|
158
|
+
fieldShare =
|
|
159
|
+
remaining * fieldConfidence(resources.field, FIELD_HIT.x, FIELD_HIT.y, FIELD_HIT.z);
|
|
160
|
+
if (fieldShare > 0) {
|
|
161
|
+
resources.fieldRadiance(FIELD_HIT.x, FIELD_HIT.y, FIELD_HIT.z, LEVEL);
|
|
162
|
+
add(out.radiance, LEVEL, fieldShare);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/*
|
|
168
|
+
* Level 2, for everything still unanswered. **It takes whatever is left without asking**, which
|
|
169
|
+
* is what "never wrong and only ever coarse" buys: there is no case where this declines.
|
|
170
|
+
*/
|
|
171
|
+
const probes = 1 - screen - fieldShare;
|
|
172
|
+
if (probes > 0) {
|
|
173
|
+
visibleProbes(
|
|
174
|
+
resources.grid,
|
|
175
|
+
resources.visibility,
|
|
176
|
+
ray.point[0] ?? 0,
|
|
177
|
+
ray.point[1] ?? 0,
|
|
178
|
+
ray.point[2] ?? 0,
|
|
179
|
+
PROBE_BLEND,
|
|
180
|
+
);
|
|
181
|
+
sampleProbeVolume(PROBE_BLEND, resources.probeValues, resources.probeChannels, PROBE_OUT);
|
|
182
|
+
add(out.radiance, PROBE_OUT, probes);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
out.blend[0] = screen;
|
|
186
|
+
out.blend[1] = fieldShare;
|
|
187
|
+
out.blend[2] = probes;
|
|
188
|
+
out.source =
|
|
189
|
+
screen >= fieldShare && screen >= probes
|
|
190
|
+
? GI_SOURCE_SCREEN
|
|
191
|
+
: fieldShare >= probes
|
|
192
|
+
? GI_SOURCE_FIELD
|
|
193
|
+
: GI_SOURCE_PROBES;
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* How much a world-field hit is trusted, by how far inside the outermost cascade it landed.
|
|
199
|
+
*
|
|
200
|
+
* **The same band `sampleGlobalField` fades its own cascades over**, so the two do not disagree
|
|
201
|
+
* about where the field stops being the answer. A hit right on the outer face is a hit the field
|
|
202
|
+
* barely had the information for, and the probes are what carry it the rest of the way.
|
|
203
|
+
*/
|
|
204
|
+
function fieldConfidence(field: GlobalField, x: number, y: number, z: number): number {
|
|
205
|
+
const outer = field.cascades[field.cascades.length - 1] as GlobalFieldCascade;
|
|
206
|
+
const band = GLOBAL_FIELD_BLEND * ((outer.bounds[3] as number) - (outer.bounds[0] as number));
|
|
207
|
+
if (band <= 0) return 1;
|
|
208
|
+
const p = [x, y, z];
|
|
209
|
+
let inside = Infinity;
|
|
210
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
211
|
+
inside = Math.min(
|
|
212
|
+
inside,
|
|
213
|
+
(p[axis] as number) - (outer.bounds[axis] as number),
|
|
214
|
+
(outer.bounds[axis + 3] as number) - (p[axis] as number),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return Math.min(1, Math.max(0, inside / band));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** `target += source * weight`, three channels. */
|
|
221
|
+
function add(target: Float32Array, source: Float32Array, weight: number): void {
|
|
222
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
223
|
+
target[channel] = (target[channel] as number) + (source[channel] as number) * weight;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/** Filtering indirect light without smearing it across an edge or losing any of it. */
|
|
2
|
+
|
|
3
|
+
import { clipToNeighbourhood } from '../temporalAa.ts';
|
|
4
|
+
|
|
5
|
+
import type { Rgb } from '../temporalAa.ts';
|
|
6
|
+
import type { GiSource } from './chain.ts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* **Indirect light arrives noisy, and every way of filtering it can quietly darken the scene.**
|
|
10
|
+
*
|
|
11
|
+
* A handful of rays a pixel is what makes this affordable, and a handful of rays is an estimate
|
|
12
|
+
* with variance. The filter is not a finishing touch — without it the chain is unusable — and the
|
|
13
|
+
* three ways it goes wrong all read as the *lighting* wanting a tuning pass rather than as the
|
|
14
|
+
* filter being wrong: a spatial kernel whose weights do not sum to one darkens every edge, a
|
|
15
|
+
* temporal blend that treats a disocclusion as staleness smears whatever moved, and a blend weight
|
|
16
|
+
* that never settles either oscillates or never reaches the mean it is averaging.
|
|
17
|
+
*
|
|
18
|
+
* **The plan asked this to reuse Wave 3A's reprojection, disocclusion and clamping rather than
|
|
19
|
+
* writing a second temporal filter, and one of those three exists.** `clipToNeighbourhood` is
|
|
20
|
+
* here, tested, and mirrored in `TEMPORAL_RESOLVE_FRAG` — so it is used. The other two do not
|
|
21
|
+
* exist in the tree and Wave 3A creates them, which is why **this module takes them as inputs
|
|
22
|
+
* rather than computing them**: `temporalDenoise` is handed the history a caller reprojected and a
|
|
23
|
+
* flag saying whether it may be trusted. That is the right shape regardless of which wave lands
|
|
24
|
+
* first — a denoiser that owned its own reprojection would be the second answer the plan was
|
|
25
|
+
* warning about — and it means the whole of this is testable and shippable today, with Wave 3A
|
|
26
|
+
* plugging into the two parameters rather than into a rewrite.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** One frame's worth of what the filter needs, as flat arrays a pass would upload. */
|
|
30
|
+
export interface DenoiseFrame {
|
|
31
|
+
/** Three floats a pixel. */
|
|
32
|
+
readonly radiance: Float32Array;
|
|
33
|
+
/** One a pixel, in metres from the eye. A pixel that drew nothing may be `Infinity`. */
|
|
34
|
+
readonly depth: Float32Array;
|
|
35
|
+
/** Three a pixel, unit length. */
|
|
36
|
+
readonly normal: Float32Array;
|
|
37
|
+
/** Which level of the chain answered each pixel. See `filterStepFor`. */
|
|
38
|
+
readonly source: Uint8Array;
|
|
39
|
+
readonly width: number;
|
|
40
|
+
readonly height: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SpatialOptions {
|
|
44
|
+
/**
|
|
45
|
+
* Multiplier on the kernel's spacing, in pixels. See `filterStepFor` for what chooses it.
|
|
46
|
+
*
|
|
47
|
+
* **A step rather than a radius**, which is the à-trous arrangement: the same five-tap kernel is
|
|
48
|
+
* run with its taps further apart, so a wide filter costs what a narrow one costs. A radius
|
|
49
|
+
* would cost its own square.
|
|
50
|
+
*/
|
|
51
|
+
readonly step: number;
|
|
52
|
+
/**
|
|
53
|
+
* How far apart in depth two pixels may be and still share light, as a fraction of their depth.
|
|
54
|
+
*
|
|
55
|
+
* **Relative rather than absolute**, because a centimetre is a discontinuity across a desk and
|
|
56
|
+
* nothing at all across a valley. A tenth is wide enough to keep a surface tilted away from the
|
|
57
|
+
* camera together and narrow enough to find the edge of the thing standing on it.
|
|
58
|
+
*/
|
|
59
|
+
readonly depthTolerance?: number;
|
|
60
|
+
/** The exponent on `max(dot(n, n'), 0)`. Higher keeps the filter inside one face. */
|
|
61
|
+
readonly normalSharpness?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const DEFAULT_DEPTH_TOLERANCE = 0.1;
|
|
65
|
+
const DEFAULT_NORMAL_SHARPNESS = 32;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The five-tap row, which is the kernel a separable à-trous pass runs twice.
|
|
69
|
+
*
|
|
70
|
+
* `[1, 4, 6, 4, 1] / 16`, a binomial — the discrete Gaussian, and the one every implementation of
|
|
71
|
+
* this uses. It is written here rather than computed so a reader can see that it is symmetric and
|
|
72
|
+
* that it sums to one *before* any of the edge-stopping weights touch it.
|
|
73
|
+
*/
|
|
74
|
+
const KERNEL = [1 / 16, 4 / 16, 6 / 16, 4 / 16, 1 / 16];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* How far apart the taps go, for light that came from each level of the chain.
|
|
78
|
+
*
|
|
79
|
+
* **Task 6's `source` discriminant is what this is for**, and it is the reason that field is on
|
|
80
|
+
* `IndirectResult` at all. A sample the screen answered is as sharp as the frame it came from and
|
|
81
|
+
* wants the narrowest filter there is. One the world field answered stands for a cone's worth of
|
|
82
|
+
* surface. One the probes answered is already an interpolation over metres, and filtering it
|
|
83
|
+
* narrowly buys nothing — its noise is not at pixel scale.
|
|
84
|
+
*
|
|
85
|
+
* Filtering all three at one width either smears the sharp one or leaves the coarse one noisy, and
|
|
86
|
+
* which of those a viewer notices depends on where they are standing.
|
|
87
|
+
*/
|
|
88
|
+
export function filterStepFor(source: GiSource): number {
|
|
89
|
+
return source === 0 ? 1 : source === 1 ? 2 : 4;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Scratch, because a filter pass is per pixel and the rule about allocating in one is absolute. */
|
|
93
|
+
const TOTAL = new Float32Array(3);
|
|
94
|
+
const CLIPPED: Rgb = [0, 0, 0];
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* One separable, edge-stopping pass over a frame.
|
|
98
|
+
*
|
|
99
|
+
* **The weights are renormalised by their own sum, and that is the line this module exists to get
|
|
100
|
+
* right.** A tap rejected for being on the other side of an edge has to be *removed from the
|
|
101
|
+
* average*, not set to zero and left in the denominator — the second darkens every silhouette by
|
|
102
|
+
* however much of its kernel fell off the surface, which looks like contact shadowing and is the
|
|
103
|
+
* filter eating light. `energy.test.ts` makes the same argument about the probe blend one level up.
|
|
104
|
+
*/
|
|
105
|
+
export function spatialDenoise(
|
|
106
|
+
frame: DenoiseFrame,
|
|
107
|
+
options: SpatialOptions,
|
|
108
|
+
out: Float32Array,
|
|
109
|
+
): void {
|
|
110
|
+
const depthTolerance = options.depthTolerance ?? DEFAULT_DEPTH_TOLERANCE;
|
|
111
|
+
const normalSharpness = options.normalSharpness ?? DEFAULT_NORMAL_SHARPNESS;
|
|
112
|
+
|
|
113
|
+
/* Horizontal into `out`, then vertical in place. Separable, so a five-tap kernel is ten taps
|
|
114
|
+
rather than twenty-five, and the two halves cannot disagree because they are one function. */
|
|
115
|
+
pass(frame, frame.radiance, out, options.step, depthTolerance, normalSharpness, true);
|
|
116
|
+
pass(frame, out, out, options.step, depthTolerance, normalSharpness, false);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function pass(
|
|
120
|
+
frame: DenoiseFrame,
|
|
121
|
+
input: Float32Array,
|
|
122
|
+
out: Float32Array,
|
|
123
|
+
step: number,
|
|
124
|
+
depthTolerance: number,
|
|
125
|
+
normalSharpness: number,
|
|
126
|
+
horizontal: boolean,
|
|
127
|
+
): void {
|
|
128
|
+
const { width, height, depth, normal, source } = frame;
|
|
129
|
+
/*
|
|
130
|
+
* A second pass reading its own output would filter the taps it has already written, which is a
|
|
131
|
+
* wider and lopsided kernel rather than the one declared. Copied when the two alias.
|
|
132
|
+
*/
|
|
133
|
+
const read = input === out ? new Float32Array(out) : input;
|
|
134
|
+
|
|
135
|
+
for (let y = 0; y < height; y++) {
|
|
136
|
+
for (let x = 0; x < width; x++) {
|
|
137
|
+
const at = y * width + x;
|
|
138
|
+
const centreDepth = depth[at] as number;
|
|
139
|
+
const nx = normal[at * 3] as number;
|
|
140
|
+
const ny = normal[at * 3 + 1] as number;
|
|
141
|
+
const nz = normal[at * 3 + 2] as number;
|
|
142
|
+
const spacing = Math.max(1, Math.round(step * filterStepFor(source[at] as GiSource)));
|
|
143
|
+
|
|
144
|
+
TOTAL.fill(0);
|
|
145
|
+
let weightSum = 0;
|
|
146
|
+
for (let tap = 0; tap < KERNEL.length; tap++) {
|
|
147
|
+
const offset = (tap - 2) * spacing;
|
|
148
|
+
const sx = horizontal ? x + offset : x;
|
|
149
|
+
const sy = horizontal ? y : y + offset;
|
|
150
|
+
if (sx < 0 || sx >= width || sy < 0 || sy >= height) continue;
|
|
151
|
+
const from = sy * width + sx;
|
|
152
|
+
|
|
153
|
+
/* A pixel that drew nothing has no surface to share light with. */
|
|
154
|
+
const tapDepth = depth[from] as number;
|
|
155
|
+
if (!Number.isFinite(tapDepth) || !Number.isFinite(centreDepth)) {
|
|
156
|
+
if (from !== at) continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const reference = Math.max(Math.abs(centreDepth), 1e-3);
|
|
160
|
+
const apart = Math.abs(tapDepth - centreDepth) / reference;
|
|
161
|
+
if (apart > depthTolerance) continue;
|
|
162
|
+
|
|
163
|
+
const alignment =
|
|
164
|
+
nx * (normal[from * 3] as number) +
|
|
165
|
+
ny * (normal[from * 3 + 1] as number) +
|
|
166
|
+
nz * (normal[from * 3 + 2] as number);
|
|
167
|
+
const facing = Math.max(alignment, 0) ** normalSharpness;
|
|
168
|
+
if (facing <= 1e-6) continue;
|
|
169
|
+
|
|
170
|
+
const weight = (KERNEL[tap] as number) * facing;
|
|
171
|
+
TOTAL[0] = (TOTAL[0] as number) + (read[from * 3] as number) * weight;
|
|
172
|
+
TOTAL[1] = (TOTAL[1] as number) + (read[from * 3 + 1] as number) * weight;
|
|
173
|
+
TOTAL[2] = (TOTAL[2] as number) + (read[from * 3 + 2] as number) * weight;
|
|
174
|
+
weightSum += weight;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (weightSum > 0) {
|
|
178
|
+
out[at * 3] = (TOTAL[0] as number) / weightSum;
|
|
179
|
+
out[at * 3 + 1] = (TOTAL[1] as number) / weightSum;
|
|
180
|
+
out[at * 3 + 2] = (TOTAL[2] as number) / weightSum;
|
|
181
|
+
} else {
|
|
182
|
+
/* Every neighbour was rejected, including itself: keep what was there rather than black. */
|
|
183
|
+
out[at * 3] = read[at * 3] as number;
|
|
184
|
+
out[at * 3 + 1] = read[at * 3 + 1] as number;
|
|
185
|
+
out[at * 3 + 2] = read[at * 3 + 2] as number;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The least weight this frame may be given, however long a pixel has been accumulating.
|
|
193
|
+
*
|
|
194
|
+
* **An honest running mean converges and then stops listening.** One over the count is the
|
|
195
|
+
* unbiased average of everything seen, which is exactly what is wanted while a pixel is settling
|
|
196
|
+
* and exactly wrong once the world changes: at ten thousand frames a new value moves the answer by
|
|
197
|
+
* a ten-thousandth, so a light switched on takes minutes to arrive. A twentieth is the floor, which
|
|
198
|
+
* is about a third of a second at sixty frames and is the same order as
|
|
199
|
+
* `TEMPORAL_HISTORY_BLEND`'s own one-tenth.
|
|
200
|
+
*/
|
|
201
|
+
export const DENOISE_MIN_ALPHA = 0.05;
|
|
202
|
+
|
|
203
|
+
/** What a pixel carries between frames. The caller owns one per pixel. */
|
|
204
|
+
export interface TemporalPixel {
|
|
205
|
+
/** The accumulated answer. */
|
|
206
|
+
readonly value: Float32Array;
|
|
207
|
+
/** How many frames have contributed since the last disocclusion. */
|
|
208
|
+
samples: number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function newTemporalPixel(): TemporalPixel {
|
|
212
|
+
return { value: new Float32Array(3), samples: 0 };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Blend this frame into the history a caller reprojected, and answer how much history there is.
|
|
217
|
+
*
|
|
218
|
+
* `history` is where this pixel was last frame, already reprojected — **this module does not do
|
|
219
|
+
* that and is not the place for it.** `disoccluded` says the reprojection landed on something that
|
|
220
|
+
* is no longer there; `neighbourhood`, when supplied, is the box of colours actually around this
|
|
221
|
+
* pixel this frame, and the history is clipped into it by `temporalAa.ts`'s own function.
|
|
222
|
+
*
|
|
223
|
+
* **A disocclusion resets rather than blends.** It is not a stale history, it is a wrong one: a
|
|
224
|
+
* character stepping away from a wall uncovers pixels whose history is the character, and blending
|
|
225
|
+
* nine parts of that is a smear of them that follows them about. Resetting leaves one frame of
|
|
226
|
+
* noise, which the spatial pass has already reduced and which is gone by the next frame.
|
|
227
|
+
*/
|
|
228
|
+
export function temporalDenoise(
|
|
229
|
+
current: readonly [number, number, number] | Float32Array,
|
|
230
|
+
history: readonly [number, number, number] | Float32Array,
|
|
231
|
+
neighbourhood: {
|
|
232
|
+
readonly min: readonly [number, number, number];
|
|
233
|
+
readonly max: readonly [number, number, number];
|
|
234
|
+
} | null,
|
|
235
|
+
disoccluded: boolean,
|
|
236
|
+
pixel: TemporalPixel,
|
|
237
|
+
out: Float32Array,
|
|
238
|
+
): TemporalPixel {
|
|
239
|
+
if (disoccluded) {
|
|
240
|
+
pixel.samples = 1;
|
|
241
|
+
for (let c = 0; c < 3; c++) {
|
|
242
|
+
pixel.value[c] = current[c] as number;
|
|
243
|
+
out[c] = current[c] as number;
|
|
244
|
+
}
|
|
245
|
+
return pixel;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let hx = history[0] as number;
|
|
249
|
+
let hy = history[1] as number;
|
|
250
|
+
let hz = history[2] as number;
|
|
251
|
+
if (neighbourhood !== null) {
|
|
252
|
+
clipToNeighbourhood(CLIPPED, [hx, hy, hz], neighbourhood.min, neighbourhood.max);
|
|
253
|
+
hx = CLIPPED[0];
|
|
254
|
+
hy = CLIPPED[1];
|
|
255
|
+
hz = CLIPPED[2];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/*
|
|
259
|
+
* One over the count while a pixel is settling, so what comes out is the unbiased mean of
|
|
260
|
+
* everything it has seen rather than a blend biased toward whatever it started at — which is the
|
|
261
|
+
* difference between converging *to* the noisy mean and converging near it. The floor is what
|
|
262
|
+
* keeps a settled pixel able to notice the world changing; see `DENOISE_MIN_ALPHA`.
|
|
263
|
+
*/
|
|
264
|
+
const samples = pixel.samples + 1;
|
|
265
|
+
const alpha = Math.max(1 / samples, DENOISE_MIN_ALPHA);
|
|
266
|
+
pixel.samples = samples;
|
|
267
|
+
|
|
268
|
+
const blended = [hx, hy, hz];
|
|
269
|
+
for (let c = 0; c < 3; c++) {
|
|
270
|
+
const value =
|
|
271
|
+
(blended[c] as number) + ((current[c] as number) - (blended[c] as number)) * alpha;
|
|
272
|
+
pixel.value[c] = value;
|
|
273
|
+
out[c] = value;
|
|
274
|
+
}
|
|
275
|
+
return pixel;
|
|
276
|
+
}
|