@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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a probe asks the indirect chain, and what it does with the answers.
|
|
3
|
+
*
|
|
4
|
+
* **A probe's measurement is an octahedral irradiance map**, because that is what the shading
|
|
5
|
+
* already samples: `gridIrradiance` in `shaders/flat/probeGrid.ts` blends eight probes and reads
|
|
6
|
+
* each through `probeIrradiance(dir, layer)`. So nothing downstream of a probe changes — only where
|
|
7
|
+
* the numbers in that map came from. Today they come from six rasterised faces of the world;
|
|
8
|
+
* `chain.ts` is what they come from once this is wired up, and the difference is a second bounce
|
|
9
|
+
* and radiance from surfaces that were never in frame.
|
|
10
|
+
*
|
|
11
|
+
* **Why a spherical Fibonacci spiral and not random directions.** At a few hundred samples a random
|
|
12
|
+
* set leaves holes and pairs, and a probe with a hole in its sphere has looked at part of its world
|
|
13
|
+
* and reports all of it — as a dark patch in a direction nothing happened to be traced along. The
|
|
14
|
+
* spiral's points are as far from each other as the count allows, and its cost is one sine and one
|
|
15
|
+
* cosine a direction.
|
|
16
|
+
*
|
|
17
|
+
* **And the set turns every frame, which is what makes a probe converge.** A fixed set measures the
|
|
18
|
+
* same few hundred directions for ever, so whatever it misses it misses permanently. Rotating it by
|
|
19
|
+
* an irrational fraction of a turn each refresh means successive refreshes sample different
|
|
20
|
+
* directions, and the accumulation over a few refreshes is a denser set than any one of them.
|
|
21
|
+
*/
|
|
22
|
+
import { octInsetDir } from '../shaders/octahedral.js';
|
|
23
|
+
/**
|
|
24
|
+
* Why a scene is not lit by what bounces on WebGL2, in words, so both backends read one sentence.
|
|
25
|
+
*
|
|
26
|
+
* **A probe is a few hundred rays and that is a compute dispatch.** This backend has no compute
|
|
27
|
+
* stage, and the alternatives are both worse than saying so: tracing on the processor is a
|
|
28
|
+
* main-thread cost proportional to the grid, and approximating with more frequent cube bakes means
|
|
29
|
+
* the two backends light one scene differently — which is the disagreement every published scene's
|
|
30
|
+
* pixel gate exists to prevent.
|
|
31
|
+
*
|
|
32
|
+
* Written here rather than in either renderer for the reason `OIT_MULTISAMPLE_REFUSAL` is written
|
|
33
|
+
* outside both: it is a decision the two share, so it belongs where neither owns it.
|
|
34
|
+
*/
|
|
35
|
+
export const INDIRECT_LIGHT_WEBGL2_REFUSAL = 'driftengine: indirect light traces a few hundred rays a probe, which is a compute dispatch, ' +
|
|
36
|
+
'and WebGL2 has no compute stage — so `quality.indirectLight` lights nothing on this backend ' +
|
|
37
|
+
'and the probe grid keeps the bake it has always had. It is WebGPU only, as reconstruction is.';
|
|
38
|
+
/**
|
|
39
|
+
* Directions a probe traces per refresh.
|
|
40
|
+
*
|
|
41
|
+
* **A few hundred, and the number is a cost rather than a quality.** A refresh is this many rays
|
|
42
|
+
* down a three-level chain, and `probeUpdateSchedule` decides how many probes pay it a frame. The
|
|
43
|
+
* convergence comes from the rotation between refreshes, not from this count, so it is set where a
|
|
44
|
+
* single refresh is already recognisable rather than where one refresh is final.
|
|
45
|
+
*/
|
|
46
|
+
export const PROBE_DIRECTIONS = 256;
|
|
47
|
+
/**
|
|
48
|
+
* The golden angle, which is what spaces a spiral's points as far apart as a count allows.
|
|
49
|
+
*
|
|
50
|
+
* Exported because `shaders/gi/probeBake.wgsl.ts` interpolates it: a shader cannot import a
|
|
51
|
+
* constant, so the choice is one value written here and pasted in, or two literals kept in step by
|
|
52
|
+
* hand. `probe-bake-parity.mjs` checks the result either way, but only one of them can be right by
|
|
53
|
+
* construction.
|
|
54
|
+
*/
|
|
55
|
+
export const GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
|
|
56
|
+
/**
|
|
57
|
+
* How far the set turns between one refresh and the next, in turns.
|
|
58
|
+
*
|
|
59
|
+
* The golden ratio's fractional part: successive frames land their offsets as far from each other
|
|
60
|
+
* as the sequence allows, for the same reason the spiral's own points do, and it never repeats.
|
|
61
|
+
*/
|
|
62
|
+
export const FRAME_TURN = 0.618033988749895;
|
|
63
|
+
/**
|
|
64
|
+
* Direction `index` of the set this `frame` traces, into `out`.
|
|
65
|
+
*
|
|
66
|
+
* The spiral runs `z` from near `+1` to near `-1` in equal steps and turns by the golden angle at
|
|
67
|
+
* each, which is the construction whose points are evenly spread on a sphere; the frame's own
|
|
68
|
+
* rotation is added to that turn.
|
|
69
|
+
*/
|
|
70
|
+
export function probeDirection(index, frame, out) {
|
|
71
|
+
const i = ((index % PROBE_DIRECTIONS) + PROBE_DIRECTIONS) % PROBE_DIRECTIONS;
|
|
72
|
+
/* Offset by a half step at each end, so no direction lands exactly on a pole — where the turn
|
|
73
|
+
has no effect and two frames would trace the same ray. */
|
|
74
|
+
const z = 1 - (2 * i + 1) / PROBE_DIRECTIONS;
|
|
75
|
+
const radius = Math.sqrt(Math.max(0, 1 - z * z));
|
|
76
|
+
const angle = i * GOLDEN_ANGLE + frame * FRAME_TURN * Math.PI * 2;
|
|
77
|
+
out[0] = Math.cos(angle) * radius;
|
|
78
|
+
out[1] = Math.sin(angle) * radius;
|
|
79
|
+
out[2] = z;
|
|
80
|
+
}
|
|
81
|
+
const TEXEL_DIR = new Float32Array(3);
|
|
82
|
+
/**
|
|
83
|
+
* The cosine convolution of `count` radiance samples into an octahedral map of `edge` texels.
|
|
84
|
+
*
|
|
85
|
+
* Each texel's direction is the normal of a surface facing that way, and its value is the
|
|
86
|
+
* irradiance such a surface receives: every sample weighted by how much of its hemisphere it
|
|
87
|
+
* occupies, which for a cosine lobe is `max(0, dot(normal, direction))`.
|
|
88
|
+
*
|
|
89
|
+
* **Normalised by the weights that were actually used, and that is the whole of the white-furnace
|
|
90
|
+
* property.** Dividing by an analytic constant — pi, or the sample count — is right only in the
|
|
91
|
+
* limit and wrong for any finite set, so a uniformly white sphere would convolve to something
|
|
92
|
+
* slightly other than white and a room would brighten or darken a little at every refresh. Dividing
|
|
93
|
+
* by the sum of the weights is exact for any set, and it is what makes a constant come back
|
|
94
|
+
* constant to the last place a float holds.
|
|
95
|
+
*/
|
|
96
|
+
export function convolveProbe(samples, directions, count, edge, out) {
|
|
97
|
+
const texels = Math.max(1, Math.floor(edge));
|
|
98
|
+
for (let v = 0; v < texels; v += 1) {
|
|
99
|
+
for (let u = 0; u < texels; u += 1) {
|
|
100
|
+
octInsetDir((u + 0.5) / texels, (v + 0.5) / texels, texels, TEXEL_DIR);
|
|
101
|
+
const nx = TEXEL_DIR[0];
|
|
102
|
+
const ny = TEXEL_DIR[1];
|
|
103
|
+
const nz = TEXEL_DIR[2];
|
|
104
|
+
let weights = 0;
|
|
105
|
+
let r = 0;
|
|
106
|
+
let g = 0;
|
|
107
|
+
let b = 0;
|
|
108
|
+
for (let i = 0; i < count; i += 1) {
|
|
109
|
+
const weight = nx * directions[i * 3] +
|
|
110
|
+
ny * directions[i * 3 + 1] +
|
|
111
|
+
nz * directions[i * 3 + 2];
|
|
112
|
+
if (weight <= 0)
|
|
113
|
+
continue;
|
|
114
|
+
weights += weight;
|
|
115
|
+
r += samples[i * 3] * weight;
|
|
116
|
+
g += samples[i * 3 + 1] * weight;
|
|
117
|
+
b += samples[i * 3 + 2] * weight;
|
|
118
|
+
}
|
|
119
|
+
const at = (v * texels + u) * 3;
|
|
120
|
+
/* No weight at all is a texel no sample faced, which a set this dense does not produce —
|
|
121
|
+
and answering zero rather than dividing is what keeps it from being a hole if it ever does. */
|
|
122
|
+
const scale = weights > 0 ? 1 / weights : 0;
|
|
123
|
+
out[at] = Math.max(0, r * scale);
|
|
124
|
+
out[at + 1] = Math.max(0, g * scale);
|
|
125
|
+
out[at + 2] = Math.max(0, b * scale);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/** The last level of the chain: a probe volume that knows where the walls are. */
|
|
2
|
+
import type { ProbeBlend, ProbeGrid } from '../probeGrid.ts';
|
|
3
|
+
/**
|
|
4
|
+
* **A probe volume is never wrong and only ever coarse, and that is what it is for.**
|
|
5
|
+
*
|
|
6
|
+
* It is the level the chain falls back to when the screen could not answer and the world field
|
|
7
|
+
* could not either, so it has nothing behind it and must always answer. What makes that safe is
|
|
8
|
+
* that it is an interpolation between measurements rather than a trace: no ray to escape, no frame
|
|
9
|
+
* to leave, no resolution to run out of. The cost is that it is smooth where the world is not.
|
|
10
|
+
*
|
|
11
|
+
* **The one way it *is* wrong is the wall, and every implementation of this has shipped it.**
|
|
12
|
+
* `probeGrid.ts`'s blend is eight corners and three lerps — it reads the lattice and the point and
|
|
13
|
+
* nothing else — so a point in a dark room takes a trilinear share of a probe standing in the lit
|
|
14
|
+
* room next door. Light comes out of solid walls, and no amount of probe density fixes it: halving
|
|
15
|
+
* the spacing halves the width of the glow and leaves it exactly as bright.
|
|
16
|
+
*
|
|
17
|
+
* That is not a defect in `probeGrid.ts`. Its header argues for a bare lattice at length and the
|
|
18
|
+
* argument is about *uniform pressure* — free placement wants a position and an extent per probe
|
|
19
|
+
* against a flat shader already 1.7x over WebGL2's guaranteed `MAX_FRAGMENT_UNIFORM_VECTORS`. This
|
|
20
|
+
* adds the term that was missing without touching any of that: a second array beside the radiance
|
|
21
|
+
* one, holding what each probe could see, and a weight computed from it.
|
|
22
|
+
*
|
|
23
|
+
* **Passing `null` for the visibility is the whole of the off switch**, and it lands on
|
|
24
|
+
* `nearestProbes` itself rather than on a path that reproduces it — so a scene with no visibility
|
|
25
|
+
* volume renders exactly the frame it rendered before this file existed.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* What each probe could see, as one octahedral map of depth moments per probe.
|
|
29
|
+
*
|
|
30
|
+
* **Two moments and not one distance.** A single distance gives a binary answer — in front of the
|
|
31
|
+
* wall or behind it — and a binary answer aliases along every edge in the scene, because the probe
|
|
32
|
+
* that can see a point and the probe that cannot are adjacent. The mean and the mean of squares
|
|
33
|
+
* give a *variance*, and a Chebyshev bound on that variance is a soft weight: confident where the
|
|
34
|
+
* geometry is flat, uncertain where it is not, which is exactly where a hard answer would be wrong.
|
|
35
|
+
* It is the same arithmetic a variance shadow map uses, for the same reason.
|
|
36
|
+
*
|
|
37
|
+
* Octahedral rather than a cube, matching `EnvProbeArray`: one layer a probe, no faces to seam, and
|
|
38
|
+
* no sampler per probe. `octInsetUv` is the mapping, so a direction lands in the same texel here as
|
|
39
|
+
* it does in the radiance map.
|
|
40
|
+
*/
|
|
41
|
+
export interface ProbeVisibility {
|
|
42
|
+
/** Two floats a texel: mean distance, then mean of squares. One map a probe, layer-major. */
|
|
43
|
+
readonly moments: Float32Array;
|
|
44
|
+
/** Texels a side, the outer ring being the gutter `octInsetUv` insets past. */
|
|
45
|
+
readonly edge: number;
|
|
46
|
+
readonly layers: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The least variance a texel is treated as having, in square metres.
|
|
50
|
+
*
|
|
51
|
+
* **Without it the falloff is a step**, because a probe looking at a flat wall records the same
|
|
52
|
+
* distance in every direction of the cone and the variance is exactly zero — so the Chebyshev
|
|
53
|
+
* bound is zero everywhere past the wall and one everywhere in front of it, and the seam between
|
|
54
|
+
* them is a hard line that crawls as the probes are rebaked. Two hundredths of a square metre is
|
|
55
|
+
* about fourteen centimetres of slop, which is below a probe spacing anybody would choose and
|
|
56
|
+
* above the depth error of a bake.
|
|
57
|
+
*/
|
|
58
|
+
export declare const PROBE_VISIBILITY_VARIANCE_FLOOR = 0.02;
|
|
59
|
+
/**
|
|
60
|
+
* How sharply the Chebyshev bound is turned into a weight.
|
|
61
|
+
*
|
|
62
|
+
* The bound is generous by construction — it is an inequality, not a measurement — so a probe that
|
|
63
|
+
* is mostly occluded still scores a third of a fully visible one. Cubing it turns that third into
|
|
64
|
+
* a twenty-seventh, which is the difference between a wall that reads as a wall and one that reads
|
|
65
|
+
* as a curtain. Three is what the technique this follows uses, and it is here as a constant so the
|
|
66
|
+
* next person can see it is a choice.
|
|
67
|
+
*/
|
|
68
|
+
export declare const PROBE_VISIBILITY_SHARPNESS = 3;
|
|
69
|
+
/**
|
|
70
|
+
* Below this share, a probe is treated as contributing nothing at all.
|
|
71
|
+
*
|
|
72
|
+
* **A weight that is merely very small is not the same as zero, and the difference is the last
|
|
73
|
+
* percent of the leak.** A probe on the far side of a wall scores about four ten-thousandths here;
|
|
74
|
+
* kept, it puts that fraction of the lit room into the dark one, which is faint and is visible
|
|
75
|
+
* because the eye reads a gradient in a dark room extremely well. Crushed, the wall is a wall.
|
|
76
|
+
*
|
|
77
|
+
* It is applied to the *product* of the trilinear weight and the visibility, because that is what a
|
|
78
|
+
* probe actually contributes, and it is applied in `visibleProbes` rather than in
|
|
79
|
+
* `probeVisibilityWeight` — the raw bound stays continuous, which is what lets it be tested as a
|
|
80
|
+
* falloff rather than as a switch.
|
|
81
|
+
*/
|
|
82
|
+
export declare const PROBE_VISIBILITY_CRUSH = 0.01;
|
|
83
|
+
export declare function createProbeVisibility(edge: number, layers: number): ProbeVisibility;
|
|
84
|
+
/**
|
|
85
|
+
* Fill one probe's map from what it can see.
|
|
86
|
+
*
|
|
87
|
+
* `distanceInDirection` is the bake: how far the geometry is from this probe along a direction.
|
|
88
|
+
* Given as a callback because how that is measured is the caller's business — a `traceField` march
|
|
89
|
+
* offline, a depth cube read back, or a closed form in a test.
|
|
90
|
+
*/
|
|
91
|
+
export declare function bakeProbeVisibility(visibility: ProbeVisibility, layer: number, distanceInDirection: (x: number, y: number, z: number) => number): void;
|
|
92
|
+
/**
|
|
93
|
+
* How much a probe's answer is trusted at a point this far away in this direction.
|
|
94
|
+
*
|
|
95
|
+
* One in front of what the probe saw; a Chebyshev bound on the depth variance behind it, cubed.
|
|
96
|
+
* Continuous everywhere, which is what `visibleProbes` then crushes rather than this.
|
|
97
|
+
*/
|
|
98
|
+
export declare function probeVisibilityWeight(visibility: ProbeVisibility, layer: number, x: number, y: number, z: number, distanceM: number): number;
|
|
99
|
+
/**
|
|
100
|
+
* The eight probes around a point, weighted by what each of them can actually see.
|
|
101
|
+
*
|
|
102
|
+
* With `visibility` null this is `nearestProbes` and nothing else — see the module header for why
|
|
103
|
+
* that matters. With one, each corner's trilinear weight is multiplied by its visibility, the
|
|
104
|
+
* result is crushed and the eight are renormalised so they still sum to one. **Renormalising is
|
|
105
|
+
* what keeps the blend from losing energy**, which is the property the white furnace in Task 10
|
|
106
|
+
* rests on: a solution that quietly drops a share of its probes darkens every bounce.
|
|
107
|
+
*
|
|
108
|
+
* **A point every probe is blind to still gets an answer**, because this is the last level of the
|
|
109
|
+
* chain and there is nothing behind it. When every weight crushes to zero — a point inside a solid,
|
|
110
|
+
* or a sealed void with a probe on each side of a wall — the nearest probe by distance takes the
|
|
111
|
+
* whole weight. That is the coarse answer this level exists to always have.
|
|
112
|
+
*/
|
|
113
|
+
export declare function visibleProbes(grid: ProbeGrid, visibility: ProbeVisibility | null, x: number, y: number, z: number, out: ProbeBlend): ProbeBlend;
|
|
114
|
+
/**
|
|
115
|
+
* Blend what the probes carry, `channels` floats each, into a target the caller owns.
|
|
116
|
+
*
|
|
117
|
+
* Channel count rather than a fixed three, because what a probe carries is the caller's decision:
|
|
118
|
+
* three for an ambient colour, nine for a spherical-harmonic band, a whole octahedral map for a
|
|
119
|
+
* directional one. The blend is the same arithmetic whatever it is, and writing it once is what
|
|
120
|
+
* stops two of them disagreeing about what the weights mean.
|
|
121
|
+
*/
|
|
122
|
+
export declare function sampleProbeVolume(blend: ProbeBlend, values: Float32Array, channels: number, out: Float32Array): Float32Array;
|
|
123
|
+
/**
|
|
124
|
+
* Which probes to rebake on this frame.
|
|
125
|
+
*
|
|
126
|
+
* **A grid rebaked in one frame is six draws of the world per probe.** Sixty-four probes is 384
|
|
127
|
+
* submissions in one frame and a hitch a viewer sees; `EnvProbeArray` already bakes a probe at a
|
|
128
|
+
* time for that reason and gates the whole grid until every layer is filled. What is here is the
|
|
129
|
+
* schedule, and the part of it that is easy to get wrong is not the bound — it is that **every
|
|
130
|
+
* probe is reached**. A subset chosen by distance, or by what moved, leaves some probe stale for
|
|
131
|
+
* ever, and a stale probe in a room whose light changed is a room lit by the previous scene.
|
|
132
|
+
*
|
|
133
|
+
* A plain round robin, so it is also reproducible: the same frame index asks for the same probes,
|
|
134
|
+
* which is what the replay fingerprint needs and what makes a bug in it repeatable.
|
|
135
|
+
*/
|
|
136
|
+
export declare function probeUpdateSchedule(layers: number, perFrame: number, frameIndex: number, out: Int32Array): number;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/** The last level of the chain: a probe volume that knows where the walls are. */
|
|
2
|
+
import { nearestProbes } from '../probeGrid.js';
|
|
3
|
+
import { octInsetDir, octInsetUv } from '../shaders/octahedral.js';
|
|
4
|
+
/**
|
|
5
|
+
* The least variance a texel is treated as having, in square metres.
|
|
6
|
+
*
|
|
7
|
+
* **Without it the falloff is a step**, because a probe looking at a flat wall records the same
|
|
8
|
+
* distance in every direction of the cone and the variance is exactly zero — so the Chebyshev
|
|
9
|
+
* bound is zero everywhere past the wall and one everywhere in front of it, and the seam between
|
|
10
|
+
* them is a hard line that crawls as the probes are rebaked. Two hundredths of a square metre is
|
|
11
|
+
* about fourteen centimetres of slop, which is below a probe spacing anybody would choose and
|
|
12
|
+
* above the depth error of a bake.
|
|
13
|
+
*/
|
|
14
|
+
export const PROBE_VISIBILITY_VARIANCE_FLOOR = 0.02;
|
|
15
|
+
/**
|
|
16
|
+
* How sharply the Chebyshev bound is turned into a weight.
|
|
17
|
+
*
|
|
18
|
+
* The bound is generous by construction — it is an inequality, not a measurement — so a probe that
|
|
19
|
+
* is mostly occluded still scores a third of a fully visible one. Cubing it turns that third into
|
|
20
|
+
* a twenty-seventh, which is the difference between a wall that reads as a wall and one that reads
|
|
21
|
+
* as a curtain. Three is what the technique this follows uses, and it is here as a constant so the
|
|
22
|
+
* next person can see it is a choice.
|
|
23
|
+
*/
|
|
24
|
+
export const PROBE_VISIBILITY_SHARPNESS = 3;
|
|
25
|
+
/**
|
|
26
|
+
* Below this share, a probe is treated as contributing nothing at all.
|
|
27
|
+
*
|
|
28
|
+
* **A weight that is merely very small is not the same as zero, and the difference is the last
|
|
29
|
+
* percent of the leak.** A probe on the far side of a wall scores about four ten-thousandths here;
|
|
30
|
+
* kept, it puts that fraction of the lit room into the dark one, which is faint and is visible
|
|
31
|
+
* because the eye reads a gradient in a dark room extremely well. Crushed, the wall is a wall.
|
|
32
|
+
*
|
|
33
|
+
* It is applied to the *product* of the trilinear weight and the visibility, because that is what a
|
|
34
|
+
* probe actually contributes, and it is applied in `visibleProbes` rather than in
|
|
35
|
+
* `probeVisibilityWeight` — the raw bound stays continuous, which is what lets it be tested as a
|
|
36
|
+
* falloff rather than as a switch.
|
|
37
|
+
*/
|
|
38
|
+
export const PROBE_VISIBILITY_CRUSH = 1e-2;
|
|
39
|
+
export function createProbeVisibility(edge, layers) {
|
|
40
|
+
const side = Math.trunc(edge);
|
|
41
|
+
if (!(side >= 4)) {
|
|
42
|
+
throw new Error(`createProbeVisibility: edge is ${String(edge)} and the minimum is 4. The outer ring is a ` +
|
|
43
|
+
'gutter, so anything smaller is two real texels a side.');
|
|
44
|
+
}
|
|
45
|
+
const count = Math.max(1, Math.trunc(layers));
|
|
46
|
+
return { moments: new Float32Array(side * side * 2 * count), edge: side, layers: count };
|
|
47
|
+
}
|
|
48
|
+
/** Scratch, so neither baking nor sampling allocates. */
|
|
49
|
+
const UV = [0, 0];
|
|
50
|
+
const DIR = [0, 0, 0];
|
|
51
|
+
const PROBE_AT = [0, 0, 0];
|
|
52
|
+
/**
|
|
53
|
+
* Fill one probe's map from what it can see.
|
|
54
|
+
*
|
|
55
|
+
* `distanceInDirection` is the bake: how far the geometry is from this probe along a direction.
|
|
56
|
+
* Given as a callback because how that is measured is the caller's business — a `traceField` march
|
|
57
|
+
* offline, a depth cube read back, or a closed form in a test.
|
|
58
|
+
*/
|
|
59
|
+
export function bakeProbeVisibility(visibility, layer, distanceInDirection) {
|
|
60
|
+
const { edge, moments } = visibility;
|
|
61
|
+
const base = layer * edge * edge * 2;
|
|
62
|
+
for (let ty = 0; ty < edge; ty++) {
|
|
63
|
+
for (let tx = 0; tx < edge; tx++) {
|
|
64
|
+
octInsetDir((tx + 0.5) / edge, (ty + 0.5) / edge, edge, DIR);
|
|
65
|
+
const distance = distanceInDirection(DIR[0], DIR[1], DIR[2]);
|
|
66
|
+
const at = base + (tx + ty * edge) * 2;
|
|
67
|
+
moments[at] = distance;
|
|
68
|
+
moments[at + 1] = distance * distance;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** The moments one probe recorded in one direction, as a two-element read. */
|
|
73
|
+
function momentsAt(visibility, layer, x, y, z) {
|
|
74
|
+
const { edge, moments } = visibility;
|
|
75
|
+
octInsetUv(x, y, z, edge, UV);
|
|
76
|
+
const tx = clampTexel(Math.floor(UV[0] * edge), edge);
|
|
77
|
+
const ty = clampTexel(Math.floor(UV[1] * edge), edge);
|
|
78
|
+
const at = layer * edge * edge * 2 + (tx + ty * edge) * 2;
|
|
79
|
+
return { mean: moments[at], meanSquare: moments[at + 1] };
|
|
80
|
+
}
|
|
81
|
+
function clampTexel(value, edge) {
|
|
82
|
+
return value < 0 ? 0 : value > edge - 1 ? edge - 1 : value;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* How much a probe's answer is trusted at a point this far away in this direction.
|
|
86
|
+
*
|
|
87
|
+
* One in front of what the probe saw; a Chebyshev bound on the depth variance behind it, cubed.
|
|
88
|
+
* Continuous everywhere, which is what `visibleProbes` then crushes rather than this.
|
|
89
|
+
*/
|
|
90
|
+
export function probeVisibilityWeight(visibility, layer, x, y, z, distanceM) {
|
|
91
|
+
const { mean, meanSquare } = momentsAt(visibility, layer, x, y, z);
|
|
92
|
+
if (distanceM <= mean)
|
|
93
|
+
return 1;
|
|
94
|
+
const variance = Math.max(Math.abs(mean * mean - meanSquare), PROBE_VISIBILITY_VARIANCE_FLOOR);
|
|
95
|
+
const beyond = distanceM - mean;
|
|
96
|
+
const chebyshev = variance / (variance + beyond * beyond);
|
|
97
|
+
return chebyshev ** PROBE_VISIBILITY_SHARPNESS;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The eight probes around a point, weighted by what each of them can actually see.
|
|
101
|
+
*
|
|
102
|
+
* With `visibility` null this is `nearestProbes` and nothing else — see the module header for why
|
|
103
|
+
* that matters. With one, each corner's trilinear weight is multiplied by its visibility, the
|
|
104
|
+
* result is crushed and the eight are renormalised so they still sum to one. **Renormalising is
|
|
105
|
+
* what keeps the blend from losing energy**, which is the property the white furnace in Task 10
|
|
106
|
+
* rests on: a solution that quietly drops a share of its probes darkens every bounce.
|
|
107
|
+
*
|
|
108
|
+
* **A point every probe is blind to still gets an answer**, because this is the last level of the
|
|
109
|
+
* chain and there is nothing behind it. When every weight crushes to zero — a point inside a solid,
|
|
110
|
+
* or a sealed void with a probe on each side of a wall — the nearest probe by distance takes the
|
|
111
|
+
* whole weight. That is the coarse answer this level exists to always have.
|
|
112
|
+
*/
|
|
113
|
+
export function visibleProbes(grid, visibility, x, y, z, out) {
|
|
114
|
+
nearestProbes(grid, x, y, z, out);
|
|
115
|
+
if (visibility === null)
|
|
116
|
+
return out;
|
|
117
|
+
let total = 0;
|
|
118
|
+
let nearest = 0;
|
|
119
|
+
let nearestDistance = Infinity;
|
|
120
|
+
for (let slot = 0; slot < out.layers.length; slot++) {
|
|
121
|
+
const layer = out.layers[slot];
|
|
122
|
+
grid.positionOf(layer, PROBE_AT);
|
|
123
|
+
const dx = x - PROBE_AT[0];
|
|
124
|
+
const dy = y - PROBE_AT[1];
|
|
125
|
+
const dz = z - PROBE_AT[2];
|
|
126
|
+
const distance = Math.hypot(dx, dy, dz);
|
|
127
|
+
if (distance < nearestDistance) {
|
|
128
|
+
nearestDistance = distance;
|
|
129
|
+
nearest = slot;
|
|
130
|
+
}
|
|
131
|
+
/* A point standing exactly on a probe is seen by it, and has no direction to ask about. */
|
|
132
|
+
const seen = distance > 0 ? probeVisibilityWeight(visibility, layer, dx, dy, dz, distance) : 1;
|
|
133
|
+
const weight = out.weights[slot] * seen;
|
|
134
|
+
const kept = weight < PROBE_VISIBILITY_CRUSH ? 0 : weight;
|
|
135
|
+
out.weights[slot] = kept;
|
|
136
|
+
total += kept;
|
|
137
|
+
}
|
|
138
|
+
if (total > 0) {
|
|
139
|
+
for (let slot = 0; slot < out.weights.length; slot++) {
|
|
140
|
+
out.weights[slot] = out.weights[slot] / total;
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
out.weights.fill(0);
|
|
145
|
+
out.weights[nearest] = 1;
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Blend what the probes carry, `channels` floats each, into a target the caller owns.
|
|
150
|
+
*
|
|
151
|
+
* Channel count rather than a fixed three, because what a probe carries is the caller's decision:
|
|
152
|
+
* three for an ambient colour, nine for a spherical-harmonic band, a whole octahedral map for a
|
|
153
|
+
* directional one. The blend is the same arithmetic whatever it is, and writing it once is what
|
|
154
|
+
* stops two of them disagreeing about what the weights mean.
|
|
155
|
+
*/
|
|
156
|
+
export function sampleProbeVolume(blend, values, channels, out) {
|
|
157
|
+
for (let channel = 0; channel < channels; channel++)
|
|
158
|
+
out[channel] = 0;
|
|
159
|
+
for (let slot = 0; slot < blend.layers.length; slot++) {
|
|
160
|
+
const weight = blend.weights[slot];
|
|
161
|
+
if (weight === 0)
|
|
162
|
+
continue;
|
|
163
|
+
const base = blend.layers[slot] * channels;
|
|
164
|
+
for (let channel = 0; channel < channels; channel++) {
|
|
165
|
+
out[channel] = out[channel] + values[base + channel] * weight;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Which probes to rebake on this frame.
|
|
172
|
+
*
|
|
173
|
+
* **A grid rebaked in one frame is six draws of the world per probe.** Sixty-four probes is 384
|
|
174
|
+
* submissions in one frame and a hitch a viewer sees; `EnvProbeArray` already bakes a probe at a
|
|
175
|
+
* time for that reason and gates the whole grid until every layer is filled. What is here is the
|
|
176
|
+
* schedule, and the part of it that is easy to get wrong is not the bound — it is that **every
|
|
177
|
+
* probe is reached**. A subset chosen by distance, or by what moved, leaves some probe stale for
|
|
178
|
+
* ever, and a stale probe in a room whose light changed is a room lit by the previous scene.
|
|
179
|
+
*
|
|
180
|
+
* A plain round robin, so it is also reproducible: the same frame index asks for the same probes,
|
|
181
|
+
* which is what the replay fingerprint needs and what makes a bug in it repeatable.
|
|
182
|
+
*/
|
|
183
|
+
export function probeUpdateSchedule(layers, perFrame, frameIndex, out) {
|
|
184
|
+
const count = Math.min(Math.max(0, Math.trunc(perFrame)), layers, out.length);
|
|
185
|
+
if (count < 1 || layers < 1)
|
|
186
|
+
return 0;
|
|
187
|
+
const start = (((Math.trunc(frameIndex) * count) % layers) + layers) % layers;
|
|
188
|
+
for (let i = 0; i < count; i++)
|
|
189
|
+
out[i] = (start + i) % layers;
|
|
190
|
+
return count;
|
|
191
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** A reflection is an indirect ray with a narrow cone, so it goes down the same chain. */
|
|
2
|
+
import type { ReadonlyVec3 } from 'gl-matrix';
|
|
3
|
+
import type { GiResources, IndirectResult } from './chain.ts';
|
|
4
|
+
/**
|
|
5
|
+
* **The plan asked for a fallback bolted onto `screenSpaceReflection.ts`, and this is beside it
|
|
6
|
+
* instead.** That module is in `render/` and the chain is in `render/gi/`; importing upward would
|
|
7
|
+
* put the whole global-illumination tree behind every scene that draws a mirror, which is the
|
|
8
|
+
* opposite of the "off by default" this wave is built around. The dependency runs the other way
|
|
9
|
+
* and `screenSpaceReflection.ts` is untouched — a scene that never asks for the fallback imports
|
|
10
|
+
* none of this and renders exactly the frame it rendered before.
|
|
11
|
+
*
|
|
12
|
+
* What is left once the chain exists is small, and that is the point: **a reflection is an
|
|
13
|
+
* indirect ray whose direction is the mirror of the view and whose cone is the roughness.** Writing
|
|
14
|
+
* a second fallback for it would be a second thing to disagree with the first.
|
|
15
|
+
*/
|
|
16
|
+
/** A reflective surface, as a shading path holds one. */
|
|
17
|
+
export interface ReflectiveSample {
|
|
18
|
+
readonly point: ReadonlyVec3;
|
|
19
|
+
readonly normal: ReadonlyVec3;
|
|
20
|
+
/** Which way the eye is looking, toward the surface. Need not be unit length. */
|
|
21
|
+
readonly view: ReadonlyVec3;
|
|
22
|
+
/** 0 is a mirror, 1 is fully diffuse. See `reflectionCone`. */
|
|
23
|
+
readonly roughness: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ReflectionOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Whether a ray the frame cannot answer continues into the world.
|
|
28
|
+
*
|
|
29
|
+
* **Off is the shipped behaviour and off is the default a caller should assume.** With it off
|
|
30
|
+
* this is `traceScreenSpaceRay` and nothing else: a miss is a miss, the surface keeps whatever
|
|
31
|
+
* the existing fade leaves it, and every scene renders the frame it rendered before this file
|
|
32
|
+
* existed. That is this wave's first global constraint, and it is asserted in the test rather
|
|
33
|
+
* than trusted.
|
|
34
|
+
*/
|
|
35
|
+
readonly fallback: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Mirror a direction about a normal.
|
|
39
|
+
*
|
|
40
|
+
* `d - 2 (d . n) n`, with `n` normalised first because an interpolated normal is rarely unit — and
|
|
41
|
+
* a normal of length 1.01 mirrors to a direction that is not unit either, which a march then takes
|
|
42
|
+
* as a step length.
|
|
43
|
+
*
|
|
44
|
+
* **A direction already leaving the surface is mirrored back into it**, and that is not
|
|
45
|
+
* special-cased into something else. A caller that hands over a view vector pointing away from the
|
|
46
|
+
* surface has a bug one level up, and answering plausibly would hide it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function reflectDirection(direction: ReadonlyVec3, normal: ReadonlyVec3, out: Float32Array): Float32Array;
|
|
49
|
+
/**
|
|
50
|
+
* The cone half-angle a roughness reflects through.
|
|
51
|
+
*
|
|
52
|
+
* **`atan(roughness^2)`, which is the GGX lobe's own width rather than a curve chosen to look
|
|
53
|
+
* right.** The distribution's half-angle at half its peak is close to `atan(alpha)` and `alpha` is
|
|
54
|
+
* `roughness^2` in the parameterisation this engine uses everywhere else — `roughnessForLevel` in
|
|
55
|
+
* `prefilterEnvMap.ts` maps a mip chain by the same square, so a surface that samples level three
|
|
56
|
+
* of the prefiltered cube and a surface that cone-traces the world field agree about how blurred
|
|
57
|
+
* they are. Two curves here would be two blurs that diverge with distance.
|
|
58
|
+
*
|
|
59
|
+
* Zero is exactly zero, so a mirror traces a pencil ray and no epsilon creeps into the one case a
|
|
60
|
+
* viewer inspects most closely. One is `atan(1)`, a quarter turn's half — wide, and still short of
|
|
61
|
+
* the hemisphere a fully diffuse surface gathers over, which is `traceIndirect`'s job and not this.
|
|
62
|
+
*/
|
|
63
|
+
export declare function reflectionCone(roughness: number): number;
|
|
64
|
+
/**
|
|
65
|
+
* Trace a reflection down the chain, or down its first level alone.
|
|
66
|
+
*
|
|
67
|
+
* With `fallback` off, only the screen answers and a miss stays a miss — which is what
|
|
68
|
+
* `screenSpaceReflection.ts` has always done and what keeps every published scene where it is.
|
|
69
|
+
* With it on, a ray leaving the frame continues into the world's distance field and, behind that,
|
|
70
|
+
* the probes: a mirror at the edge of the picture reflects the world rather than fading to a matte
|
|
71
|
+
* surface as the camera turns.
|
|
72
|
+
*/
|
|
73
|
+
export declare function traceReflection(surface: ReflectiveSample, resources: GiResources, options: ReflectionOptions, out: IndirectResult): IndirectResult;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** A reflection is an indirect ray with a narrow cone, so it goes down the same chain. */
|
|
2
|
+
import { GI_SOURCE_SCREEN, traceIndirect } from './chain.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mirror a direction about a normal.
|
|
5
|
+
*
|
|
6
|
+
* `d - 2 (d . n) n`, with `n` normalised first because an interpolated normal is rarely unit — and
|
|
7
|
+
* a normal of length 1.01 mirrors to a direction that is not unit either, which a march then takes
|
|
8
|
+
* as a step length.
|
|
9
|
+
*
|
|
10
|
+
* **A direction already leaving the surface is mirrored back into it**, and that is not
|
|
11
|
+
* special-cased into something else. A caller that hands over a view vector pointing away from the
|
|
12
|
+
* surface has a bug one level up, and answering plausibly would hide it.
|
|
13
|
+
*/
|
|
14
|
+
export function reflectDirection(direction, normal, out) {
|
|
15
|
+
const dx = direction[0] ?? 0;
|
|
16
|
+
const dy = direction[1] ?? 0;
|
|
17
|
+
const dz = direction[2] ?? 0;
|
|
18
|
+
let nx = normal[0] ?? 0;
|
|
19
|
+
let ny = normal[1] ?? 0;
|
|
20
|
+
let nz = normal[2] ?? 0;
|
|
21
|
+
const length = Math.hypot(nx, ny, nz);
|
|
22
|
+
if (length > 0) {
|
|
23
|
+
nx /= length;
|
|
24
|
+
ny /= length;
|
|
25
|
+
nz /= length;
|
|
26
|
+
}
|
|
27
|
+
const along = 2 * (dx * nx + dy * ny + dz * nz);
|
|
28
|
+
out[0] = dx - along * nx;
|
|
29
|
+
out[1] = dy - along * ny;
|
|
30
|
+
out[2] = dz - along * nz;
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The cone half-angle a roughness reflects through.
|
|
35
|
+
*
|
|
36
|
+
* **`atan(roughness^2)`, which is the GGX lobe's own width rather than a curve chosen to look
|
|
37
|
+
* right.** The distribution's half-angle at half its peak is close to `atan(alpha)` and `alpha` is
|
|
38
|
+
* `roughness^2` in the parameterisation this engine uses everywhere else — `roughnessForLevel` in
|
|
39
|
+
* `prefilterEnvMap.ts` maps a mip chain by the same square, so a surface that samples level three
|
|
40
|
+
* of the prefiltered cube and a surface that cone-traces the world field agree about how blurred
|
|
41
|
+
* they are. Two curves here would be two blurs that diverge with distance.
|
|
42
|
+
*
|
|
43
|
+
* Zero is exactly zero, so a mirror traces a pencil ray and no epsilon creeps into the one case a
|
|
44
|
+
* viewer inspects most closely. One is `atan(1)`, a quarter turn's half — wide, and still short of
|
|
45
|
+
* the hemisphere a fully diffuse surface gathers over, which is `traceIndirect`'s job and not this.
|
|
46
|
+
*/
|
|
47
|
+
export function reflectionCone(roughness) {
|
|
48
|
+
const clamped = roughness <= 0 ? 0 : roughness >= 1 ? 1 : roughness;
|
|
49
|
+
return Math.atan(clamped * clamped);
|
|
50
|
+
}
|
|
51
|
+
/** Scratch, because this runs per reflective pixel. */
|
|
52
|
+
const MIRRORED = new Float32Array(3);
|
|
53
|
+
/**
|
|
54
|
+
* Trace a reflection down the chain, or down its first level alone.
|
|
55
|
+
*
|
|
56
|
+
* With `fallback` off, only the screen answers and a miss stays a miss — which is what
|
|
57
|
+
* `screenSpaceReflection.ts` has always done and what keeps every published scene where it is.
|
|
58
|
+
* With it on, a ray leaving the frame continues into the world's distance field and, behind that,
|
|
59
|
+
* the probes: a mirror at the edge of the picture reflects the world rather than fading to a matte
|
|
60
|
+
* surface as the camera turns.
|
|
61
|
+
*/
|
|
62
|
+
export function traceReflection(surface, resources, options, out) {
|
|
63
|
+
reflectDirection(surface.view, surface.normal, MIRRORED);
|
|
64
|
+
const ray = {
|
|
65
|
+
point: surface.point,
|
|
66
|
+
normal: surface.normal,
|
|
67
|
+
direction: MIRRORED,
|
|
68
|
+
coneAngle: reflectionCone(surface.roughness),
|
|
69
|
+
};
|
|
70
|
+
if (options.fallback)
|
|
71
|
+
return traceIndirect(ray, resources, out);
|
|
72
|
+
/*
|
|
73
|
+
* The screen alone. Reached by handing the chain a resource set whose other two levels cannot
|
|
74
|
+
* contribute — rather than by a second copy of the march — so the frame's own answer is
|
|
75
|
+
* byte-identical either way and cannot drift from the fallback path's.
|
|
76
|
+
*/
|
|
77
|
+
traceIndirect(ray, { ...resources, ...SCREEN_ONLY }, out);
|
|
78
|
+
if (out.blend[0] === 0) {
|
|
79
|
+
out.hit = false;
|
|
80
|
+
out.radiance.fill(0);
|
|
81
|
+
out.blend.fill(0);
|
|
82
|
+
out.source = GI_SOURCE_SCREEN;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
/* What the frame could not answer is left unanswered rather than handed downward. */
|
|
86
|
+
out.radiance[0] = out.radiance[0] / out.blend[0];
|
|
87
|
+
out.radiance[1] = out.radiance[1] / out.blend[0];
|
|
88
|
+
out.radiance[2] = out.radiance[2] / out.blend[0];
|
|
89
|
+
out.blend[0] = 1;
|
|
90
|
+
out.blend[1] = 0;
|
|
91
|
+
out.blend[2] = 0;
|
|
92
|
+
out.source = GI_SOURCE_SCREEN;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The two levels below the screen, silenced.
|
|
98
|
+
*
|
|
99
|
+
* A field whose march can never hit and a probe set that carries nothing, so `traceIndirect` runs
|
|
100
|
+
* its ordinary path and the screen's share is all there is to normalise. Frozen at module scope
|
|
101
|
+
* because this is a per-pixel call and the rule about allocating in one is absolute.
|
|
102
|
+
*/
|
|
103
|
+
const SCREEN_ONLY = {
|
|
104
|
+
fieldRadiance: (_x, _y, _z, out) => {
|
|
105
|
+
out.fill(0);
|
|
106
|
+
},
|
|
107
|
+
probeValues: new Float32Array(0),
|
|
108
|
+
probeChannels: 0,
|
|
109
|
+
};
|