@driftengine/core 3.63.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +111 -0
- package/NOTICE +20 -0
- package/README.md +1 -1
- package/dist/core/loop.js +18 -2
- package/dist/core/storage.js +5 -0
- package/dist/geometry/meshBuilder.d.ts +1 -1
- package/dist/geometry/meshBuilder.js +6 -2
- package/dist/index.d.ts +69 -4
- package/dist/index.js +112 -2
- package/dist/input/input.js +6 -1
- package/dist/math/exact.d.ts +13 -3
- package/dist/math/exact.js +68 -3
- package/dist/math/frustum.d.ts +17 -2
- package/dist/math/frustum.js +24 -0
- package/dist/render/backend/api.d.ts +34 -1
- package/dist/render/backend/api.js +22 -1
- package/dist/render/backend/budget.d.ts +7 -6
- package/dist/render/backend/budget.js +7 -6
- package/dist/render/backend/createRenderer.d.ts +57 -0
- package/dist/render/backend/createRenderer.js +51 -2
- package/dist/render/backend/materialChanges.d.ts +41 -0
- package/dist/render/backend/materialChanges.js +38 -0
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/renderer.d.ts +80 -1
- package/dist/render/backend/webgl2/renderer.js +208 -10
- package/dist/render/backend/webgpu/buffers.d.ts +20 -1
- package/dist/render/backend/webgpu/buffers.js +116 -58
- package/dist/render/backend/webgpu/fieldCompose.d.ts +156 -0
- package/dist/render/backend/webgpu/fieldCompose.js +396 -0
- package/dist/render/backend/webgpu/giFieldPass.d.ts +66 -0
- package/dist/render/backend/webgpu/giFieldPass.js +137 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +728 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.js +2473 -0
- package/dist/render/backend/webgpu/linePass.d.ts +4 -14
- package/dist/render/backend/webgpu/linePass.js +4 -33
- package/dist/render/backend/webgpu/probeBake.d.ts +148 -0
- package/dist/render/backend/webgpu/probeBake.js +493 -0
- package/dist/render/backend/webgpu/renderer.d.ts +379 -24
- package/dist/render/backend/webgpu/renderer.js +1369 -76
- package/dist/render/backend/webgpu/scatterPass.js +15 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +19 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.js +58 -32
- package/dist/render/causticsRenderer.d.ts +3 -1
- package/dist/render/causticsRenderer.js +4 -1
- package/dist/render/depthConvention.d.ts +12 -0
- package/dist/render/depthConvention.js +14 -0
- package/dist/render/deviceFeatures.d.ts +32 -0
- package/dist/render/deviceFeatures.js +41 -0
- package/dist/render/fog.d.ts +79 -0
- package/dist/render/fog.js +138 -0
- package/dist/render/frame/alias.d.ts +30 -0
- package/dist/render/frame/alias.js +61 -0
- package/dist/render/frame/deps.d.ts +37 -0
- package/dist/render/frame/deps.js +122 -0
- package/dist/render/frame/flushGraph.d.ts +43 -0
- package/dist/render/frame/flushGraph.js +84 -0
- package/dist/render/frame/graphSchedule.d.ts +49 -0
- package/dist/render/frame/graphSchedule.js +120 -0
- package/dist/render/frame/index.d.ts +15 -0
- package/dist/render/frame/index.js +8 -0
- package/dist/render/frame/lifetime.d.ts +24 -0
- package/dist/render/frame/lifetime.js +36 -0
- package/dist/render/frame/passTimings.d.ts +42 -0
- package/dist/render/frame/passTimings.js +38 -0
- package/dist/render/frame/validate.d.ts +15 -0
- package/dist/render/frame/validate.js +22 -0
- package/dist/render/frame/virtual.d.ts +44 -0
- package/dist/render/frame/virtual.js +61 -0
- package/dist/render/gi/chain.d.ts +82 -0
- package/dist/render/gi/chain.js +135 -0
- package/dist/render/gi/denoise.d.ts +114 -0
- package/dist/render/gi/denoise.js +169 -0
- package/dist/render/gi/fieldScene.d.ts +125 -0
- package/dist/render/gi/fieldScene.js +219 -0
- package/dist/render/gi/globalField.d.ts +157 -0
- package/dist/render/gi/globalField.js +373 -0
- package/dist/render/gi/probeGridFit.d.ts +24 -0
- package/dist/render/gi/probeGridFit.js +71 -0
- package/dist/render/gi/probeTrace.d.ts +82 -0
- package/dist/render/gi/probeTrace.js +128 -0
- package/dist/render/gi/probeVolume.d.ts +136 -0
- package/dist/render/gi/probeVolume.js +191 -0
- package/dist/render/gi/reflection.d.ts +73 -0
- package/dist/render/gi/reflection.js +109 -0
- package/dist/render/gi/traceField.d.ts +91 -0
- package/dist/render/gi/traceField.js +129 -0
- package/dist/render/gi/traceScreen.d.ts +68 -0
- package/dist/render/gi/traceScreen.js +82 -0
- package/dist/render/gpuScene.d.ts +38 -0
- package/dist/render/gpuScene.js +41 -0
- package/dist/render/gpudriven/alphaTest.d.ts +28 -0
- package/dist/render/gpudriven/alphaTest.js +32 -0
- package/dist/render/gpudriven/clusterUpload.d.ts +59 -0
- package/dist/render/gpudriven/clusterUpload.js +75 -0
- package/dist/render/gpudriven/clusterWorld.d.ts +57 -0
- package/dist/render/gpudriven/clusterWorld.js +155 -0
- package/dist/render/gpudriven/compact.d.ts +51 -0
- package/dist/render/gpudriven/compact.js +68 -0
- package/dist/render/gpudriven/cullClusters.d.ts +129 -0
- package/dist/render/gpudriven/cullClusters.js +237 -0
- package/dist/render/gpudriven/decodeTables.d.ts +64 -0
- package/dist/render/gpudriven/decodeTables.js +219 -0
- package/dist/render/gpudriven/frustum.d.ts +25 -0
- package/dist/render/gpudriven/frustum.js +59 -0
- package/dist/render/gpudriven/geometryHarness.d.ts +15 -0
- package/dist/render/gpudriven/geometryHarness.js +31 -0
- package/dist/render/gpudriven/hzb.d.ts +60 -0
- package/dist/render/gpudriven/hzb.js +97 -0
- package/dist/render/gpudriven/ibl.d.ts +85 -0
- package/dist/render/gpudriven/ibl.js +129 -0
- package/dist/render/gpudriven/indirect.d.ts +67 -0
- package/dist/render/gpudriven/indirect.js +77 -0
- package/dist/render/gpudriven/instances.d.ts +43 -0
- package/dist/render/gpudriven/instances.js +172 -0
- package/dist/render/gpudriven/lit.d.ts +165 -0
- package/dist/render/gpudriven/lit.js +165 -0
- package/dist/render/gpudriven/lodCut.d.ts +28 -0
- package/dist/render/gpudriven/lodCut.js +38 -0
- package/dist/render/gpudriven/materialBin.d.ts +60 -0
- package/dist/render/gpudriven/materialBin.js +108 -0
- package/dist/render/gpudriven/materialTable.d.ts +88 -0
- package/dist/render/gpudriven/materialTable.js +115 -0
- package/dist/render/gpudriven/pipeline.d.ts +135 -0
- package/dist/render/gpudriven/pipeline.js +237 -0
- package/dist/render/gpudriven/rangeAlloc.d.ts +50 -0
- package/dist/render/gpudriven/rangeAlloc.js +112 -0
- package/dist/render/gpudriven/sceneUpload.d.ts +96 -0
- package/dist/render/gpudriven/sceneUpload.js +115 -0
- package/dist/render/gpudriven/shadeBins.d.ts +92 -0
- package/dist/render/gpudriven/shadeBins.js +182 -0
- package/dist/render/gpudriven/shadow.d.ts +136 -0
- package/dist/render/gpudriven/shadow.js +224 -0
- package/dist/render/gpudriven/shadowCamera.d.ts +87 -0
- package/dist/render/gpudriven/shadowCamera.js +345 -0
- package/dist/render/gpudriven/streamScene.d.ts +194 -0
- package/dist/render/gpudriven/streamScene.js +459 -0
- package/dist/render/gpudriven/surfaceFrame.d.ts +25 -0
- package/dist/render/gpudriven/surfaceFrame.js +51 -0
- package/dist/render/gpudriven/twoPhase.d.ts +122 -0
- package/dist/render/gpudriven/twoPhase.js +160 -0
- package/dist/render/gpudriven/visbuffer.d.ts +25 -0
- package/dist/render/gpudriven/visbuffer.js +37 -0
- package/dist/render/halfFloat.d.ts +10 -0
- package/dist/render/halfFloat.js +23 -4
- package/dist/render/inference/attentionTiled.d.ts +27 -0
- package/dist/render/inference/attentionTiled.js +117 -0
- package/dist/render/inference/denseKernels.d.ts +29 -0
- package/dist/render/inference/denseKernels.js +312 -0
- package/dist/render/inference/device.d.ts +21 -0
- package/dist/render/inference/device.js +32 -0
- package/dist/render/inference/deviceGraph.d.ts +34 -0
- package/dist/render/inference/deviceGraph.js +17 -0
- package/dist/render/inference/fuse.d.ts +15 -0
- package/dist/render/inference/fuse.js +37 -0
- package/dist/render/inference/kernelKit.d.ts +52 -0
- package/dist/render/inference/kernelKit.js +70 -0
- package/dist/render/inference/kernels.d.ts +3 -0
- package/dist/render/inference/kernels.js +11 -0
- package/dist/render/inference/plan.d.ts +13 -0
- package/dist/render/inference/plan.js +18 -0
- package/dist/render/inference/reuse.d.ts +39 -0
- package/dist/render/inference/reuse.js +65 -0
- package/dist/render/inference/runner.d.ts +31 -0
- package/dist/render/inference/runner.js +175 -0
- package/dist/render/inference/schedule.d.ts +39 -0
- package/dist/render/inference/schedule.js +59 -0
- package/dist/render/inference/shapeKernels.d.ts +10 -0
- package/dist/render/inference/shapeKernels.js +80 -0
- package/dist/render/inference/spatialKernels.d.ts +21 -0
- package/dist/render/inference/spatialKernels.js +179 -0
- package/dist/render/inference/splatRaster.wgsl.d.ts +75 -0
- package/dist/render/inference/splatRaster.wgsl.js +337 -0
- package/dist/render/inference/splatTiles.d.ts +79 -0
- package/dist/render/inference/splatTiles.js +161 -0
- package/dist/render/lineBatch.d.ts +2 -5
- package/dist/render/lineBatch.js +4 -24
- package/dist/render/mesh.d.ts +4 -0
- package/dist/render/mesh.js +45 -1
- package/dist/render/pass.d.ts +145 -8
- package/dist/render/prefilterEnvMap.d.ts +22 -0
- package/dist/render/prefilterEnvMap.js +31 -0
- package/dist/render/readback.d.ts +30 -0
- package/dist/render/readback.js +47 -0
- package/dist/render/recon/clamp.d.ts +36 -0
- package/dist/render/recon/clamp.js +92 -0
- package/dist/render/recon/disocclusion.d.ts +60 -0
- package/dist/render/recon/disocclusion.js +109 -0
- package/dist/render/recon/frameGen.d.ts +81 -0
- package/dist/render/recon/frameGen.js +150 -0
- package/dist/render/recon/frameSizes.d.ts +34 -0
- package/dist/render/recon/frameSizes.js +58 -0
- package/dist/render/recon/jitter.d.ts +60 -0
- package/dist/render/recon/jitter.js +118 -0
- package/dist/render/recon/motionVectors.d.ts +55 -0
- package/dist/render/recon/motionVectors.js +107 -0
- package/dist/render/recon/reproject.d.ts +41 -0
- package/dist/render/recon/reproject.js +131 -0
- package/dist/render/recon/resolve.d.ts +90 -0
- package/dist/render/recon/resolve.js +313 -0
- package/dist/render/recon/tier.d.ts +24 -0
- package/dist/render/recon/tier.js +8 -0
- package/dist/render/reflectionProbe.d.ts +26 -0
- package/dist/render/reflectionProbe.js +33 -0
- package/dist/render/renderQuality.d.ts +69 -0
- package/dist/render/renderQuality.js +22 -0
- package/dist/render/screenSpaceReflection.d.ts +23 -0
- package/dist/render/screenSpaceReflection.js +22 -3
- package/dist/render/sdfFont.js +2 -0
- package/dist/render/segmentQuads.d.ts +13 -0
- package/dist/render/segmentQuads.js +32 -0
- package/dist/render/shaders/flat/lobes.d.ts +27 -0
- package/dist/render/shaders/flat/lobes.js +33 -3
- package/dist/render/shaders/flat/preamble.d.ts +7 -0
- package/dist/render/shaders/flat/preamble.js +24 -1
- package/dist/render/shaders/flat/probeGrid.d.ts +1 -1
- package/dist/render/shaders/flat/probeGrid.js +71 -2
- package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -8
- package/dist/render/shaders/generated/flat.wgsl.js +64 -24
- package/dist/render/shaders/gi/composeField.wgsl.d.ts +56 -0
- package/dist/render/shaders/gi/composeField.wgsl.js +231 -0
- package/dist/render/shaders/gi/marchField.wgsl.d.ts +30 -0
- package/dist/render/shaders/gi/marchField.wgsl.js +141 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +113 -0
- package/dist/render/shaders/gi/probeBake.wgsl.js +635 -0
- package/dist/render/shaders/gi/probeTrace.wgsl.d.ts +72 -0
- package/dist/render/shaders/gi/probeTrace.wgsl.js +261 -0
- package/dist/render/shaders/gi/probeVolume.wgsl.d.ts +49 -0
- package/dist/render/shaders/gi/probeVolume.wgsl.js +242 -0
- package/dist/render/shaders/gi/sampleField.wgsl.d.ts +32 -0
- package/dist/render/shaders/gi/sampleField.wgsl.js +134 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +26 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +339 -0
- package/dist/render/shaders/gpudriven/compact.wgsl.d.ts +29 -0
- package/dist/render/shaders/gpudriven/compact.wgsl.js +58 -0
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +90 -0
- package/dist/render/shaders/gpudriven/cull.wgsl.js +423 -0
- package/dist/render/shaders/gpudriven/decode.wgsl.d.ts +72 -0
- package/dist/render/shaders/gpudriven/decode.wgsl.js +349 -0
- package/dist/render/shaders/gpudriven/environment.wgsl.d.ts +44 -0
- package/dist/render/shaders/gpudriven/environment.wgsl.js +137 -0
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +28 -0
- package/dist/render/shaders/gpudriven/fog.wgsl.js +107 -0
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +13 -0
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +62 -0
- package/dist/render/shaders/gpudriven/materialBin.wgsl.d.ts +51 -0
- package/dist/render/shaders/gpudriven/materialBin.wgsl.js +112 -0
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +25 -0
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +43 -0
- package/dist/render/shaders/gpudriven/meshTransform.wgsl.d.ts +18 -0
- package/dist/render/shaders/gpudriven/meshTransform.wgsl.js +37 -0
- package/dist/render/shaders/gpudriven/present.wgsl.d.ts +43 -0
- package/dist/render/shaders/gpudriven/present.wgsl.js +127 -0
- package/dist/render/shaders/gpudriven/shade.wgsl.d.ts +127 -0
- package/dist/render/shaders/gpudriven/shade.wgsl.js +874 -0
- package/dist/render/shaders/gpudriven/shadow.wgsl.d.ts +44 -0
- package/dist/render/shaders/gpudriven/shadow.wgsl.js +308 -0
- package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.d.ts +28 -0
- package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.js +245 -0
- package/dist/render/shaders/network.wgsl.d.ts +101 -0
- package/dist/render/shaders/network.wgsl.js +206 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +44 -0
- package/dist/render/shaders/recon/motion.wgsl.js +96 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +51 -0
- package/dist/render/shaders/recon/resolve.wgsl.js +733 -0
- package/dist/render/temporalAa.js +8 -33
- package/dist/render/textLayout.js +12 -6
- package/dist/render/textRenderer.d.ts +3 -1
- package/dist/render/textRenderer.js +4 -1
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +11 -0
- package/dist/render/windStreakRenderer.d.ts +2 -1
- package/dist/render/windStreakRenderer.js +3 -1
- package/dist/world/cell.d.ts +63 -0
- package/dist/world/cell.js +181 -0
- package/dist/world/cellStream.d.ts +103 -0
- package/dist/world/cellStream.js +198 -0
- package/dist/world/freeze.d.ts +81 -0
- package/dist/world/freeze.js +114 -0
- package/dist/world/rebase.d.ts +29 -0
- package/dist/world/rebase.js +41 -0
- package/package.json +8 -3
- package/scripts/frames.mjs +92 -0
- package/scripts/gpuCompute.mjs +323 -0
- package/scripts/png.d.mts +16 -0
- package/scripts/png.mjs +25 -0
- package/src/core/loop.ts +19 -2
- package/src/core/storage.ts +5 -0
- package/src/geometry/meshBuilder.ts +5 -1
- package/src/index.ts +246 -4
- package/src/input/input.ts +6 -1
- package/src/math/exact.ts +69 -3
- package/src/math/frustum.ts +52 -3
- package/src/render/backend/api.ts +42 -1
- package/src/render/backend/budget.ts +7 -6
- package/src/render/backend/createRenderer.ts +92 -3
- package/src/render/backend/materialChanges.ts +54 -0
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/renderer.ts +211 -9
- package/src/render/backend/webgpu/buffers.ts +182 -69
- package/src/render/backend/webgpu/fieldCompose.ts +482 -0
- package/src/render/backend/webgpu/giFieldPass.ts +200 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +2993 -0
- package/src/render/backend/webgpu/linePass.ts +4 -39
- package/src/render/backend/webgpu/probeBake.ts +588 -0
- package/src/render/backend/webgpu/renderer.ts +1481 -76
- package/src/render/backend/webgpu/scatterPass.ts +15 -1
- package/src/render/backend/webgpu/surfaceTexturePass.ts +63 -36
- package/src/render/causticsRenderer.ts +5 -2
- package/src/render/depthConvention.ts +15 -0
- package/src/render/deviceFeatures.ts +77 -0
- package/src/render/fog.ts +185 -0
- package/src/render/frame/alias.ts +97 -0
- package/src/render/frame/deps.ts +155 -0
- package/src/render/frame/flushGraph.ts +135 -0
- package/src/render/frame/graphSchedule.ts +170 -0
- package/src/render/frame/index.ts +52 -0
- package/src/render/frame/lifetime.ts +61 -0
- package/src/render/frame/passTimings.ts +69 -0
- package/src/render/frame/validate.ts +40 -0
- package/src/render/frame/virtual.ts +91 -0
- package/src/render/gi/chain.ts +225 -0
- package/src/render/gi/denoise.ts +276 -0
- package/src/render/gi/fieldScene.ts +264 -0
- package/src/render/gi/globalField.ts +543 -0
- package/src/render/gi/probeGridFit.ts +89 -0
- package/src/render/gi/probeTrace.ts +143 -0
- package/src/render/gi/probeVolume.ts +288 -0
- package/src/render/gi/reflection.ts +163 -0
- package/src/render/gi/traceField.ts +223 -0
- package/src/render/gi/traceScreen.ts +106 -0
- package/src/render/gpuScene.ts +80 -0
- package/src/render/gpudriven/alphaTest.ts +33 -0
- package/src/render/gpudriven/clusterUpload.ts +123 -0
- package/src/render/gpudriven/clusterWorld.ts +239 -0
- package/src/render/gpudriven/compact.ts +80 -0
- package/src/render/gpudriven/cullClusters.ts +333 -0
- package/src/render/gpudriven/decodeTables.ts +332 -0
- package/src/render/gpudriven/frustum.ts +70 -0
- package/src/render/gpudriven/geometryHarness.ts +51 -0
- package/src/render/gpudriven/hzb.ts +113 -0
- package/src/render/gpudriven/ibl.ts +147 -0
- package/src/render/gpudriven/indirect.ts +89 -0
- package/src/render/gpudriven/instances.ts +234 -0
- package/src/render/gpudriven/lit.ts +329 -0
- package/src/render/gpudriven/lodCut.ts +62 -0
- package/src/render/gpudriven/materialBin.ts +131 -0
- package/src/render/gpudriven/materialTable.ts +164 -0
- package/src/render/gpudriven/pipeline.ts +336 -0
- package/src/render/gpudriven/rangeAlloc.ts +122 -0
- package/src/render/gpudriven/sceneUpload.ts +173 -0
- package/src/render/gpudriven/shadeBins.ts +222 -0
- package/src/render/gpudriven/shadow.ts +285 -0
- package/src/render/gpudriven/shadowCamera.ts +406 -0
- package/src/render/gpudriven/streamScene.ts +575 -0
- package/src/render/gpudriven/surfaceFrame.ts +68 -0
- package/src/render/gpudriven/twoPhase.ts +214 -0
- package/src/render/gpudriven/visbuffer.ts +41 -0
- package/src/render/halfFloat.ts +22 -4
- package/src/render/inference/attentionTiled.ts +132 -0
- package/src/render/inference/denseKernels.ts +369 -0
- package/src/render/inference/device.ts +44 -0
- package/src/render/inference/deviceGraph.ts +39 -0
- package/src/render/inference/fuse.ts +47 -0
- package/src/render/inference/kernelKit.ts +120 -0
- package/src/render/inference/kernels.ts +18 -0
- package/src/render/inference/plan.ts +26 -0
- package/src/render/inference/reuse.ts +79 -0
- package/src/render/inference/runner.ts +204 -0
- package/src/render/inference/schedule.ts +80 -0
- package/src/render/inference/shapeKernels.ts +112 -0
- package/src/render/inference/spatialKernels.ts +220 -0
- package/src/render/inference/splatRaster.wgsl.ts +348 -0
- package/src/render/inference/splatTiles.ts +186 -0
- package/src/render/lineBatch.ts +4 -24
- package/src/render/mesh.ts +46 -1
- package/src/render/pass.ts +155 -10
- package/src/render/prefilterEnvMap.ts +33 -0
- package/src/render/readback.ts +73 -0
- package/src/render/recon/clamp.ts +113 -0
- package/src/render/recon/disocclusion.ts +143 -0
- package/src/render/recon/frameGen.ts +191 -0
- package/src/render/recon/frameSizes.ts +64 -0
- package/src/render/recon/jitter.ts +126 -0
- package/src/render/recon/motionVectors.ts +138 -0
- package/src/render/recon/reproject.ts +179 -0
- package/src/render/recon/resolve.ts +444 -0
- package/src/render/recon/tier.ts +31 -0
- package/src/render/reflectionProbe.ts +34 -0
- package/src/render/renderQuality.ts +87 -0
- package/src/render/screenSpaceReflection.ts +37 -3
- package/src/render/sdfFont.ts +2 -0
- package/src/render/segmentQuads.ts +39 -0
- package/src/render/shaders/flat/lobes.ts +35 -3
- package/src/render/shaders/flat/preamble.ts +25 -1
- package/src/render/shaders/flat/probeGrid.ts +77 -2
- package/src/render/shaders/generated/flat.wgsl.ts +64 -24
- package/src/render/shaders/gi/composeField.wgsl.ts +239 -0
- package/src/render/shaders/gi/marchField.wgsl.ts +146 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +649 -0
- package/src/render/shaders/gi/probeTrace.wgsl.ts +269 -0
- package/src/render/shaders/gi/probeVolume.wgsl.ts +246 -0
- package/src/render/shaders/gi/sampleField.wgsl.ts +140 -0
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +343 -0
- package/src/render/shaders/gpudriven/compact.wgsl.ts +59 -0
- package/src/render/shaders/gpudriven/cull.wgsl.ts +433 -0
- package/src/render/shaders/gpudriven/decode.wgsl.ts +373 -0
- package/src/render/shaders/gpudriven/environment.wgsl.ts +141 -0
- package/src/render/shaders/gpudriven/fog.wgsl.ts +110 -0
- package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +62 -0
- package/src/render/shaders/gpudriven/materialBin.wgsl.ts +116 -0
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +46 -0
- package/src/render/shaders/gpudriven/meshTransform.wgsl.ts +37 -0
- package/src/render/shaders/gpudriven/present.wgsl.ts +129 -0
- package/src/render/shaders/gpudriven/shade.wgsl.ts +884 -0
- package/src/render/shaders/gpudriven/shadow.wgsl.ts +322 -0
- package/src/render/shaders/gpudriven/visbufferRaster.wgsl.ts +251 -0
- package/src/render/shaders/network.wgsl.ts +238 -0
- package/src/render/shaders/recon/motion.wgsl.ts +101 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +741 -0
- package/src/render/temporalAa.ts +9 -33
- package/src/render/textLayout.ts +12 -6
- package/src/render/textRenderer.ts +5 -2
- package/src/render/translucentQueue.ts +11 -0
- package/src/render/windStreakRenderer.ts +4 -2
- package/src/world/cell.ts +231 -0
- package/src/world/cellStream.ts +278 -0
- package/src/world/freeze.ts +157 -0
- package/src/world/rebase.ts +56 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A probe's own rays, on the device: `gi/chain.ts` minus its screen level.
|
|
3
|
+
*
|
|
4
|
+
* **A probe has no use for screen space, and including it would be a defect rather than a cost.**
|
|
5
|
+
* The screen level is an accelerator for rays cast from the camera's own view, where the frame
|
|
6
|
+
* already holds the answer. A probe stands in the world and its rays go everywhere, so most of them
|
|
7
|
+
* leave the frame — and the few that do not would make a *cached* probe depend on where the camera
|
|
8
|
+
* happens to be, so a probe would change its contents as the viewer turned and every surface it
|
|
9
|
+
* lights would shimmer. So the chain is entered with a projector that answers "off screen" for
|
|
10
|
+
* every point, which makes level 0 contribute exactly nothing and levels 1 and 2 take the whole
|
|
11
|
+
* ray. The reference stays one function; this is that function with one input pinned.
|
|
12
|
+
*
|
|
13
|
+
* **What a field hit is worth comes from the probes**, which is the maintainer's answer of
|
|
14
|
+
* 2026-09-17 and the reason `GiResources.fieldRadiance` is a function rather than a texture: a
|
|
15
|
+
* probe traced against radiance that is itself probe radiance converges on several bounces over a
|
|
16
|
+
* few refreshes. The direction it is read along is back down the ray — the light leaving that
|
|
17
|
+
* surface *towards* the probe is what the probe is measuring — which the chain's own signature
|
|
18
|
+
* leaves to the caller because it takes no direction.
|
|
19
|
+
*
|
|
20
|
+
* **One core, two modules**, as `shaders/recon/resolve.wgsl.ts` and `network.wgsl.ts` are. The
|
|
21
|
+
* arithmetic reads the field and the probes through functions the including module defines, so the
|
|
22
|
+
* production module can read the renderer's own buffers and the parity check can read plain ones of
|
|
23
|
+
* the same numbers.
|
|
24
|
+
*
|
|
25
|
+
* The accessors an including module must define, before including the core:
|
|
26
|
+
*
|
|
27
|
+
* ```wgsl
|
|
28
|
+
* fn probeFieldAt(p: vec3<f32>) -> f32 // the composed field's distance at a world point
|
|
29
|
+
* fn probeFieldInside(p: vec3<f32>) -> f32 // how far inside the outermost cascade, negative outside
|
|
30
|
+
* fn probeSurfaceRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32>
|
|
31
|
+
* fn probeDistantRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* **Two, and it used to be one.** A ray that struck a wall and a ray that left the world need
|
|
35
|
+
* different answers and the single accessor could not tell them apart: what leaves a wall is its
|
|
36
|
+
* albedo times the light reaching it, and what a ray finds having left is whatever the probe
|
|
37
|
+
* volume coarsely says is out there. Measured with one accessor, a red wall and a white wall
|
|
38
|
+
* returned the same colour — `demo/dev/bounce.html`, and `plans/2026-09-17-indirect-light.md`
|
|
39
|
+
* Task 8.
|
|
40
|
+
*
|
|
41
|
+
* and `march: ProbeMarch` as a uniform.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Floats in the march block, which is sixteen because of how a uniform aligns rather than how many
|
|
45
|
+
* numbers it holds: four counts, four lengths, and two `vec3`s that each take a whole four-float row.
|
|
46
|
+
*/
|
|
47
|
+
export declare const PROBE_MARCH_FLOATS = 16;
|
|
48
|
+
/** The workgroup a trace dispatches over: one invocation a direction. */
|
|
49
|
+
export declare const PROBE_TRACE_WORKGROUP = 64;
|
|
50
|
+
export declare const PROBE_MARCH_WGSL = "\nstruct ProbeMarch {\n /* Steps a ray may take, the cascades it may read, and how many samples a cascade is a side. */\n steps: u32,\n levels: u32,\n side: u32,\n rayCount: u32,\n /* How far a ray may travel, how close to a surface counts as arriving, the finest voxel. */\n reachM: f32,\n hitEpsilonM: f32,\n finestStep: f32,\n coneAngle: f32,\n /* The outermost cascade's corners, for the confidence the last level is faded by. */\n outerLow: vec3<f32>,\n outerHigh: vec3<f32>,\n}\n";
|
|
51
|
+
/**
|
|
52
|
+
* The march and the two levels, as text.
|
|
53
|
+
*
|
|
54
|
+
* Defines `probeIndirect(point, normal, direction, coneAngle) -> vec3<f32>`, which is what one of a
|
|
55
|
+
* probe's rays is worth.
|
|
56
|
+
*/
|
|
57
|
+
export declare const PROBE_TRACE_CORE_WGSL: string;
|
|
58
|
+
/** `FIELD_MARCH`'s own numbers, so a caller fills the block from one place. */
|
|
59
|
+
export declare const PROBE_MARCH_DEFAULTS: import("../../gi/traceField.ts").FieldMarch;
|
|
60
|
+
/**
|
|
61
|
+
* The parity module: the core over plain buffers, with a radiance that is a function of position.
|
|
62
|
+
*
|
|
63
|
+
* **The radiance is positional on purpose.** A march that lands a voxel away from where the
|
|
64
|
+
* reference landed would return almost the right answer from a smooth field and exactly the right
|
|
65
|
+
* one from a constant, so a constant radiance would check the *blend* and nothing about the march.
|
|
66
|
+
* A steep function of the hit's position turns a wrong hit into a wrong colour.
|
|
67
|
+
*
|
|
68
|
+
* `probeRadianceAt` is an accessor by design, so this check is of the march and the level blend
|
|
69
|
+
* alone; what a probe volume answers is the production module's business and `probeVolume.test.ts`
|
|
70
|
+
* is what holds that.
|
|
71
|
+
*/
|
|
72
|
+
export declare function probeTraceParityWgsl(): string;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A probe's own rays, on the device: `gi/chain.ts` minus its screen level.
|
|
3
|
+
*
|
|
4
|
+
* **A probe has no use for screen space, and including it would be a defect rather than a cost.**
|
|
5
|
+
* The screen level is an accelerator for rays cast from the camera's own view, where the frame
|
|
6
|
+
* already holds the answer. A probe stands in the world and its rays go everywhere, so most of them
|
|
7
|
+
* leave the frame — and the few that do not would make a *cached* probe depend on where the camera
|
|
8
|
+
* happens to be, so a probe would change its contents as the viewer turned and every surface it
|
|
9
|
+
* lights would shimmer. So the chain is entered with a projector that answers "off screen" for
|
|
10
|
+
* every point, which makes level 0 contribute exactly nothing and levels 1 and 2 take the whole
|
|
11
|
+
* ray. The reference stays one function; this is that function with one input pinned.
|
|
12
|
+
*
|
|
13
|
+
* **What a field hit is worth comes from the probes**, which is the maintainer's answer of
|
|
14
|
+
* 2026-09-17 and the reason `GiResources.fieldRadiance` is a function rather than a texture: a
|
|
15
|
+
* probe traced against radiance that is itself probe radiance converges on several bounces over a
|
|
16
|
+
* few refreshes. The direction it is read along is back down the ray — the light leaving that
|
|
17
|
+
* surface *towards* the probe is what the probe is measuring — which the chain's own signature
|
|
18
|
+
* leaves to the caller because it takes no direction.
|
|
19
|
+
*
|
|
20
|
+
* **One core, two modules**, as `shaders/recon/resolve.wgsl.ts` and `network.wgsl.ts` are. The
|
|
21
|
+
* arithmetic reads the field and the probes through functions the including module defines, so the
|
|
22
|
+
* production module can read the renderer's own buffers and the parity check can read plain ones of
|
|
23
|
+
* the same numbers.
|
|
24
|
+
*
|
|
25
|
+
* The accessors an including module must define, before including the core:
|
|
26
|
+
*
|
|
27
|
+
* ```wgsl
|
|
28
|
+
* fn probeFieldAt(p: vec3<f32>) -> f32 // the composed field's distance at a world point
|
|
29
|
+
* fn probeFieldInside(p: vec3<f32>) -> f32 // how far inside the outermost cascade, negative outside
|
|
30
|
+
* fn probeSurfaceRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32>
|
|
31
|
+
* fn probeDistantRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* **Two, and it used to be one.** A ray that struck a wall and a ray that left the world need
|
|
35
|
+
* different answers and the single accessor could not tell them apart: what leaves a wall is its
|
|
36
|
+
* albedo times the light reaching it, and what a ray finds having left is whatever the probe
|
|
37
|
+
* volume coarsely says is out there. Measured with one accessor, a red wall and a white wall
|
|
38
|
+
* returned the same colour — `demo/dev/bounce.html`, and `plans/2026-09-17-indirect-light.md`
|
|
39
|
+
* Task 8.
|
|
40
|
+
*
|
|
41
|
+
* and `march: ProbeMarch` as a uniform.
|
|
42
|
+
*/
|
|
43
|
+
import { GLOBAL_FIELD_BLEND } from '../../gi/globalField.js';
|
|
44
|
+
import { FIELD_MARCH, FIELD_START_VOXELS } from '../../gi/traceField.js';
|
|
45
|
+
import { SAMPLE_FIELD_WGSL } from './sampleField.wgsl.js';
|
|
46
|
+
/**
|
|
47
|
+
* Floats in the march block, which is sixteen because of how a uniform aligns rather than how many
|
|
48
|
+
* numbers it holds: four counts, four lengths, and two `vec3`s that each take a whole four-float row.
|
|
49
|
+
*/
|
|
50
|
+
export const PROBE_MARCH_FLOATS = 16;
|
|
51
|
+
/** The workgroup a trace dispatches over: one invocation a direction. */
|
|
52
|
+
export const PROBE_TRACE_WORKGROUP = 64;
|
|
53
|
+
/** A number as WGSL always reads it as a float, so an integer constant is not an `i32`. */
|
|
54
|
+
function f(value) {
|
|
55
|
+
return Number.isInteger(value) ? value.toFixed(1) : String(value);
|
|
56
|
+
}
|
|
57
|
+
export const PROBE_MARCH_WGSL = /* wgsl */ `
|
|
58
|
+
struct ProbeMarch {
|
|
59
|
+
/* Steps a ray may take, the cascades it may read, and how many samples a cascade is a side. */
|
|
60
|
+
steps: u32,
|
|
61
|
+
levels: u32,
|
|
62
|
+
side: u32,
|
|
63
|
+
rayCount: u32,
|
|
64
|
+
/* How far a ray may travel, how close to a surface counts as arriving, the finest voxel. */
|
|
65
|
+
reachM: f32,
|
|
66
|
+
hitEpsilonM: f32,
|
|
67
|
+
finestStep: f32,
|
|
68
|
+
coneAngle: f32,
|
|
69
|
+
/* The outermost cascade's corners, for the confidence the last level is faded by. */
|
|
70
|
+
outerLow: vec3<f32>,
|
|
71
|
+
outerHigh: vec3<f32>,
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
/**
|
|
75
|
+
* The march and the two levels, as text.
|
|
76
|
+
*
|
|
77
|
+
* Defines `probeIndirect(point, normal, direction, coneAngle) -> vec3<f32>`, which is what one of a
|
|
78
|
+
* probe's rays is worth.
|
|
79
|
+
*/
|
|
80
|
+
export const PROBE_TRACE_CORE_WGSL = /* wgsl */ `
|
|
81
|
+
const PROBE_FIELD_START_VOXELS: f32 = ${f(FIELD_START_VOXELS)};
|
|
82
|
+
const PROBE_FIELD_BLEND: f32 = ${f(GLOBAL_FIELD_BLEND)};
|
|
83
|
+
|
|
84
|
+
struct ProbeFieldHit {
|
|
85
|
+
hit: bool,
|
|
86
|
+
point: vec3<f32>,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* A cone's radius at a distance. Zero angle is a ray, which is what a probe's rays are. */
|
|
90
|
+
fn probeConeRadius(distanceM: f32, coneAngle: f32) -> f32 {
|
|
91
|
+
return select(0.0, distanceM * tan(coneAngle), coneAngle > 0.0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* March one ray through the composed world field.
|
|
96
|
+
*
|
|
97
|
+
* **Lifted along the normal and only along the normal.** Pushing the start along the ray as well
|
|
98
|
+
* double-counts: a ray leaving along its own normal would begin two offsets out and every distance
|
|
99
|
+
* it reported would carry the second one. And it does not help the case that needs help — a tangent
|
|
100
|
+
* ray pushed along itself is still on the surface, because a surface is what tangent means.
|
|
101
|
+
*/
|
|
102
|
+
fn probeTraceField(
|
|
103
|
+
point: vec3<f32>,
|
|
104
|
+
normal: vec3<f32>,
|
|
105
|
+
direction: vec3<f32>,
|
|
106
|
+
coneAngle: f32,
|
|
107
|
+
) -> ProbeFieldHit {
|
|
108
|
+
var out: ProbeFieldHit;
|
|
109
|
+
out.hit = false;
|
|
110
|
+
out.point = point;
|
|
111
|
+
|
|
112
|
+
/* Not named "length": a local of that name shadows the built-in and the next call to it fails. */
|
|
113
|
+
let reach = length(direction);
|
|
114
|
+
if (!(reach > 0.0)) { return out; }
|
|
115
|
+
let unit = direction / reach;
|
|
116
|
+
|
|
117
|
+
let start = PROBE_FIELD_START_VOXELS * march.finestStep;
|
|
118
|
+
let normalLength = length(normal);
|
|
119
|
+
let lifted = point + select(vec3<f32>(0.0), normal * (start / normalLength), normalLength > 0.0);
|
|
120
|
+
|
|
121
|
+
var travelled = 0.0;
|
|
122
|
+
for (var step = 0u; step < march.steps; step = step + 1u) {
|
|
123
|
+
let at = lifted + unit * travelled;
|
|
124
|
+
/*
|
|
125
|
+
* **Outside the outermost cascade the march stops rather than sampling.** There is no
|
|
126
|
+
* information out there: the sampler clamps to the edge, which is right for a sample and wrong
|
|
127
|
+
* for a march — stepping on clamped values converges on the cascade's boundary and reports a
|
|
128
|
+
* surface made of nothing.
|
|
129
|
+
*/
|
|
130
|
+
if (probeFieldInside(at) <= 0.0) { return out; }
|
|
131
|
+
|
|
132
|
+
let distance = probeFieldAt(at);
|
|
133
|
+
if (distance <= probeConeRadius(travelled, coneAngle) + march.hitEpsilonM) {
|
|
134
|
+
out.hit = true;
|
|
135
|
+
out.point = at;
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
/*
|
|
139
|
+
* **Step by the distance, and by at least a quarter of a voxel.** A field whose value is
|
|
140
|
+
* genuinely tiny beside a surface the cone is not wide enough to accept would otherwise take
|
|
141
|
+
* ever-smaller steps and spend the whole budget arriving nowhere, which reads as a miss and
|
|
142
|
+
* costs a fallback anyway. The floor makes that case terminate quickly instead.
|
|
143
|
+
*/
|
|
144
|
+
travelled = travelled + max(distance, march.finestStep * 0.25);
|
|
145
|
+
if (travelled > march.reachM) { return out; }
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* How much of a field hit is trusted, faded across the outer tenth of the outermost cascade.
|
|
152
|
+
*
|
|
153
|
+
* Every boundary in this chain moves with the camera — the cascades are centred on it — and a hard
|
|
154
|
+
* switch at a boundary that moves is a seam sweeping through the picture whenever the camera turns.
|
|
155
|
+
*/
|
|
156
|
+
fn probeFieldConfidence(p: vec3<f32>) -> f32 {
|
|
157
|
+
let band = PROBE_FIELD_BLEND * (march.outerHigh.x - march.outerLow.x);
|
|
158
|
+
if (band <= 0.0) { return 1.0; }
|
|
159
|
+
let gap = min(p - march.outerLow, march.outerHigh - p);
|
|
160
|
+
return clamp(min(gap.x, min(gap.y, gap.z)) / band, 0.0, 1.0);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
* One of a probe's rays, as radiance.
|
|
165
|
+
*
|
|
166
|
+
* Level 1 answers as much as its distance from the outermost cascade's face allows; level 2 takes
|
|
167
|
+
* whatever is left without asking, which is what "never wrong and only ever coarse" buys. There is
|
|
168
|
+
* no case where the probes decline, and no fourth level to fall to.
|
|
169
|
+
*/
|
|
170
|
+
fn probeIndirect(
|
|
171
|
+
point: vec3<f32>,
|
|
172
|
+
normal: vec3<f32>,
|
|
173
|
+
direction: vec3<f32>,
|
|
174
|
+
coneAngle: f32,
|
|
175
|
+
) -> vec3<f32> {
|
|
176
|
+
var radiance = vec3<f32>(0.0);
|
|
177
|
+
var fieldShare = 0.0;
|
|
178
|
+
|
|
179
|
+
let hit = probeTraceField(point, normal, direction, coneAngle);
|
|
180
|
+
if (hit.hit) {
|
|
181
|
+
fieldShare = probeFieldConfidence(hit.point);
|
|
182
|
+
if (fieldShare > 0.0) {
|
|
183
|
+
/* Back down the ray: what this surface sends *towards* the probe is what the probe measures. */
|
|
184
|
+
radiance = radiance + probeSurfaceRadiance(hit.point, -direction) * fieldShare;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let probes = 1.0 - fieldShare;
|
|
189
|
+
if (probes > 0.0) {
|
|
190
|
+
radiance = radiance + probeDistantRadiance(point + direction * march.reachM, -direction) * probes;
|
|
191
|
+
}
|
|
192
|
+
return radiance;
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
/** `FIELD_MARCH`'s own numbers, so a caller fills the block from one place. */
|
|
196
|
+
export const PROBE_MARCH_DEFAULTS = FIELD_MARCH;
|
|
197
|
+
/**
|
|
198
|
+
* The parity module: the core over plain buffers, with a radiance that is a function of position.
|
|
199
|
+
*
|
|
200
|
+
* **The radiance is positional on purpose.** A march that lands a voxel away from where the
|
|
201
|
+
* reference landed would return almost the right answer from a smooth field and exactly the right
|
|
202
|
+
* one from a constant, so a constant radiance would check the *blend* and nothing about the march.
|
|
203
|
+
* A steep function of the hit's position turns a wrong hit into a wrong colour.
|
|
204
|
+
*
|
|
205
|
+
* `probeRadianceAt` is an accessor by design, so this check is of the march and the level blend
|
|
206
|
+
* alone; what a probe volume answers is the production module's business and `probeVolume.test.ts`
|
|
207
|
+
* is what holds that.
|
|
208
|
+
*/
|
|
209
|
+
export function probeTraceParityWgsl() {
|
|
210
|
+
return /* wgsl */ `
|
|
211
|
+
@group(0) @binding(0) var<storage, read> cascades: array<f32>;
|
|
212
|
+
@group(0) @binding(1) var<storage, read> fields: array<f32>;
|
|
213
|
+
${PROBE_MARCH_WGSL}
|
|
214
|
+
@group(0) @binding(2) var<uniform> march: ProbeMarch;
|
|
215
|
+
/* Seven floats a ray: the point, the normal, the direction. The cone angle is the march's. */
|
|
216
|
+
@group(0) @binding(3) var<storage, read> rays: array<f32>;
|
|
217
|
+
@group(0) @binding(4) var<storage, read_write> radiance: array<f32>;
|
|
218
|
+
${SAMPLE_FIELD_WGSL}
|
|
219
|
+
|
|
220
|
+
fn probeFieldAt(p: vec3<f32>) -> f32 {
|
|
221
|
+
return sampleField(march.levels, march.side, p);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fn probeFieldInside(p: vec3<f32>) -> f32 {
|
|
225
|
+
return insideBy(march.levels - 1u, p);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* The same expression the harness evaluates, and the reason it is a sine of position. */
|
|
229
|
+
fn probeParityRadiance(p: vec3<f32>) -> vec3<f32> {
|
|
230
|
+
return vec3<f32>(
|
|
231
|
+
0.5 + 0.4 * sin(p.x * 1.3),
|
|
232
|
+
0.5 + 0.4 * sin(p.y * 1.7 + 1.0),
|
|
233
|
+
0.5 + 0.4 * sin(p.z * 2.3 + 2.0),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* One expression for both, so this module keeps checking the march and the blend and nothing else. */
|
|
238
|
+
fn probeSurfaceRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32> {
|
|
239
|
+
return probeParityRadiance(p);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn probeDistantRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32> {
|
|
243
|
+
return probeParityRadiance(p);
|
|
244
|
+
}
|
|
245
|
+
${PROBE_TRACE_CORE_WGSL}
|
|
246
|
+
|
|
247
|
+
@compute @workgroup_size(${PROBE_TRACE_WORKGROUP})
|
|
248
|
+
fn traceMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
249
|
+
if (id.x >= march.rayCount) { return; }
|
|
250
|
+
let at = id.x * 9u;
|
|
251
|
+
let point = vec3<f32>(rays[at], rays[at + 1u], rays[at + 2u]);
|
|
252
|
+
let normal = vec3<f32>(rays[at + 3u], rays[at + 4u], rays[at + 5u]);
|
|
253
|
+
let direction = vec3<f32>(rays[at + 6u], rays[at + 7u], rays[at + 8u]);
|
|
254
|
+
let value = probeIndirect(point, normal, direction, march.coneAngle);
|
|
255
|
+
let out = id.x * 3u;
|
|
256
|
+
radiance[out] = value.x;
|
|
257
|
+
radiance[out + 1u] = value.y;
|
|
258
|
+
radiance[out + 2u] = value.z;
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the probe grid answers at a world point, on the device.
|
|
3
|
+
*
|
|
4
|
+
* **This is the fourth spelling of one idea and the file says so rather than hoping.**
|
|
5
|
+
* `probeGrid.ts` holds the eight-probe trilinear blend in TypeScript, `octahedral.ts` holds the
|
|
6
|
+
* mapping in TypeScript and again in GLSL, and `shaders/flat/probeGrid.ts` holds both together in
|
|
7
|
+
* the GLSL the lit pass uses. This is WGSL, because a hand-written compute shader cannot include a
|
|
8
|
+
* generated one — and `scripts/probe-bake-parity.mjs` is what holds it to the others rather than
|
|
9
|
+
* a reader's confidence that four copies of an expression say the same thing.
|
|
10
|
+
*
|
|
11
|
+
* **Why a probe reads the grid at all**: the maintainer's answer of 2026-09-17 is that what a field
|
|
12
|
+
* hit is worth comes from the probes, so a probe traced against radiance that is itself probe
|
|
13
|
+
* radiance converges on several bounces over a few refreshes. That is what makes this the multi-
|
|
14
|
+
* bounce half of Wave 4A rather than a single-bounce approximation of it.
|
|
15
|
+
*
|
|
16
|
+
* **No visibility term here, deliberately.** `visibleProbes` exists to stop light reaching a
|
|
17
|
+
* *surface* through a wall it is behind, and it is what `gridIrradiance` applies when shading. A
|
|
18
|
+
* probe asking "what is the radiance out there" is a different question with a different failure:
|
|
19
|
+
* crushing a blind probe's weight would make the bounce darker than the room, and the reference
|
|
20
|
+
* this is checked against — `indirect-parity.mjs` — enters the chain with `visibility: null` for
|
|
21
|
+
* exactly this reason.
|
|
22
|
+
*
|
|
23
|
+
* The accessor an including module must define, before including the core:
|
|
24
|
+
*
|
|
25
|
+
* ```wgsl
|
|
26
|
+
* fn probeLayerSample(layer: u32, uv: vec2<f32>) -> vec3<f32> // at the radiance level
|
|
27
|
+
* fn probeLayerIrradiance(layer: u32, uv: vec2<f32>) -> vec3<f32> // at the irradiance level
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* and `volume: ProbeVolume` as a uniform. Production samples a texture; the parity module reads a
|
|
31
|
+
* plain buffer of the same numbers.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Floats in the volume block, and where each field starts.
|
|
35
|
+
*
|
|
36
|
+
* Named offsets for the reason `probeBake.wgsl.ts` gives and paid for: an `f32` after a `vec3<f32>`
|
|
37
|
+
* fills the vec3's padding rather than starting a new row, so `edge` is float **11**.
|
|
38
|
+
*/
|
|
39
|
+
export declare const VOLUME_ORIGIN = 0;
|
|
40
|
+
export declare const VOLUME_INV_SPACING = 4;
|
|
41
|
+
export declare const VOLUME_COUNTS = 8;
|
|
42
|
+
export declare const VOLUME_EDGE = 11;
|
|
43
|
+
export declare const VOLUME_LEVEL = 12;
|
|
44
|
+
export declare const VOLUME_RADIANCE_EDGE = 13;
|
|
45
|
+
export declare const VOLUME_RADIANCE_LEVEL = 14;
|
|
46
|
+
/** Fifty-two bytes of fields rounded up to the struct's own sixteen-byte alignment. */
|
|
47
|
+
export declare const PROBE_VOLUME_FLOATS = 16;
|
|
48
|
+
export declare const PROBE_VOLUME_STRUCT_WGSL = "\nstruct ProbeVolume {\n /* Where the grid's first probe stands, and one over the step between them. */\n origin: vec3<f32>,\n invSpacing: vec3<f32>,\n /* How many probes along each axis. Whole numbers carried as floats. */\n counts: vec3<f32>,\n /* Texels across the level being sampled, gutter included. */\n edge: f32,\n /* Which mip of the probe array that level is. */\n level: f32,\n /* And the level that holds *radiance* rather than irradiance, with its own edge. */\n radianceEdge: f32,\n radianceLevel: f32,\n padA: f32,\n}\n";
|
|
49
|
+
export declare const PROBE_VOLUME_CORE_WGSL = "\n/* The octahedral encode, matching \"octEncode\" in \"shaders/octahedral.ts\". */\nfn probeOctEncode(d: vec3<f32>) -> vec2<f32> {\n let n = d / (abs(d.x) + abs(d.y) + abs(d.z));\n var p = n.xy;\n if (n.z < 0.0) {\n p = (1.0 - abs(vec2<f32>(n.y, n.x))) *\n vec2<f32>(select(-1.0, 1.0, n.x >= 0.0), select(-1.0, 1.0, n.y >= 0.0));\n }\n return p * 0.5 + 0.5;\n}\n\n/*\n * Where to sample a direction in a map whose outer ring is a gutter.\n *\n * The inner region is \"edge - 2\" texels offset by one, so a direction at the very border of the\n * octahedral square lands on the last real texel and the tap that interpolates past it reads the\n * gutter \u2014 which the bake filled with the folded direction.\n */\nfn probeOctInsetUv(d: vec3<f32>, edge: f32) -> vec2<f32> {\n return (probeOctEncode(d) * (edge - 2.0) + 1.0) / edge;\n}\n\n/*\n * One axis of the lookup: which probe is below the point, and how far past it the point is.\n *\n * **The index is integer arithmetic and the fraction is not**, which is the distinction row 139 of\n * the resume file is about. A world position divided by a spacing is a genuine float division and\n * its floor is a genuine floor; what must never be written in floats is a *lattice* \u2014 an index\n * decomposed out of a layer number \u2014 because a device may implement that division as a\n * multiplication by a reciprocal and answer 3 for 3.0 % 3.0.\n *\n * The lower index stops one short of the last probe so the pair \"index\" and \"index + 1\" is always\n * inside the grid, and a single-probe axis has no pair at all \u2014 both corners are probe zero and\n * the fraction is zero, so that axis multiplies every weight by one.\n */\nstruct ProbeCell {\n index: u32,\n fraction: f32,\n}\n\nfn probeCell(value: f32, origin: f32, invSpacing: f32, count: u32) -> ProbeCell {\n var out: ProbeCell;\n out.index = 0u;\n out.fraction = 0.0;\n if (count <= 1u) { return out; }\n let local = (value - origin) * invSpacing;\n let span = f32(count - 1u);\n let clamped = clamp(local, 0.0, span);\n let index = min(u32(floor(clamped)), count - 2u);\n out.index = index;\n out.fraction = clamped - f32(index);\n return out;\n}\n\n/*\n * The layer a probe at whole lattice coordinates occupies. x fastest, then y, then z.\n *\n * **Not clamped, and \"ProbeGrid.layerAt\" is** \u2014 the difference is who may call them. \"layerAt\" is a\n * public method whose contract is that it accepts anything, so it clamps; this is reached from one\n * place, under two guarantees that together make the clamp unreachable. \"probeCell\" returns an\n * index no greater than \"count - 2\", so \"index + 1\" is inside; and on an axis only one probe wide\n * it returns index 0 with fraction 0, so the far corner's weight is zero and the caller skips it\n * before asking. A clamp here would be a line no perturbation can make fail, which this session\n * has already had to remove twice.\n */\nfn probeLayerAt(ix: u32, iy: u32, iz: u32, counts: vec3<u32>) -> u32 {\n return ix + counts.x * (iy + counts.y * iz);\n}\n\n/*\n * What has already bounced onto a surface facing the normal at a point, from the level this writes.\n *\n * **One shared direction, and that is right here where a per-corner one is right for radiance.**\n * An irradiance map is indexed by the *normal of the surface being lit*, not by where the light\n * came from, so every probe is asked the same question: what reaches a surface facing this way.\n * Reading it at the level the bake writes is what closes the loop \u2014 the second bounce is the first\n * one read back.\n */\nfn probeVolumeIrradianceAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {\n let counts = vec3<u32>(\n max(1u, u32(volume.counts.x)),\n max(1u, u32(volume.counts.y)),\n max(1u, u32(volume.counts.z)),\n );\n let cx = probeCell(p.x, volume.origin.x, volume.invSpacing.x, counts.x);\n let cy = probeCell(p.y, volume.origin.y, volume.invSpacing.y, counts.y);\n let cz = probeCell(p.z, volume.origin.z, volume.invSpacing.z, counts.z);\n let uv = probeOctInsetUv(n, volume.edge);\n\n var sum = vec3<f32>(0.0);\n for (var oz = 0u; oz < 2u; oz = oz + 1u) {\n let wz = select(1.0 - cz.fraction, cz.fraction, oz == 1u);\n for (var oy = 0u; oy < 2u; oy = oy + 1u) {\n let wy = select(1.0 - cy.fraction, cy.fraction, oy == 1u);\n for (var ox = 0u; ox < 2u; ox = ox + 1u) {\n let wx = select(1.0 - cx.fraction, cx.fraction, ox == 1u);\n let weight = wx * wy * wz;\n if (weight <= 0.0) { continue; }\n let layer = probeLayerAt(cx.index + ox, cy.index + oy, cz.index + oz, counts);\n sum = sum + probeLayerIrradiance(layer, uv) * weight;\n }\n }\n }\n return sum;\n}\n\n/* Where a layer's probe stands, which a parallax read needs and the lattice above decides. */\nfn probeVolumeProbeAt(layer: u32, counts: vec3<u32>) -> vec3<f32> {\n let x = layer % counts.x;\n let y = (layer / counts.x) % counts.y;\n let z = layer / (counts.x * counts.y);\n let spacing = vec3<f32>(\n select(0.0, 1.0 / volume.invSpacing.x, volume.invSpacing.x != 0.0),\n select(0.0, 1.0 / volume.invSpacing.y, volume.invSpacing.y != 0.0),\n select(0.0, 1.0 / volume.invSpacing.z, volume.invSpacing.z != 0.0),\n );\n return volume.origin + vec3<f32>(f32(x), f32(y), f32(z)) * spacing;\n}\n\n/*\n * The radiance leaving a world point, as the grid saw it.\n *\n * **A ray needs radiance and the first version read irradiance**, which is what\n * \"demo/dev/bounce.html\" measured as a bounce dimmer and greyer than the rasterised grid it was\n * meant to replace. Irradiance is light *arriving* at a point, already cosine-convolved over a\n * hemisphere; what a ray that struck a wall needs is the light *leaving* that wall towards it. The\n * two differ by the surface's albedo \u2014 so a red wall and a white wall at the same irradiance\n * returned the same colour, and the red never travelled.\n *\n * **Level 0 of the probe array is that radiance**, and it needs no albedo because it is a\n * rasterised capture of the shaded room: the red wall is red in it. \u00A76.4 of the design names\n * \"envProbeArray.ts\" as this track's starting point and leaves how much of it to reuse to\n * measurement, which is this.\n *\n * **Each corner is read along its own direction to the point**, which is what makes this a\n * position lookup rather than a direction one. Eight probes sampled along one shared direction\n * answer \"what is over there from roughly here\"; eight probes each sampled towards \"p\" answer\n * \"what is at \"p\"\", and the difference is the parallax that a marched hit exists to provide. It is\n * also why no direction is passed in: deriving it removes the reversed-direction mistake that\n * \"probe-bake-parity.mjs\" records being unable to see.\n */\nfn probeVolumeRadianceAt(p: vec3<f32>) -> vec3<f32> {\n let counts = vec3<u32>(\n max(1u, u32(volume.counts.x)),\n max(1u, u32(volume.counts.y)),\n max(1u, u32(volume.counts.z)),\n );\n let cx = probeCell(p.x, volume.origin.x, volume.invSpacing.x, counts.x);\n let cy = probeCell(p.y, volume.origin.y, volume.invSpacing.y, counts.y);\n let cz = probeCell(p.z, volume.origin.z, volume.invSpacing.z, counts.z);\n\n var sum = vec3<f32>(0.0);\n for (var oz = 0u; oz < 2u; oz = oz + 1u) {\n let wz = select(1.0 - cz.fraction, cz.fraction, oz == 1u);\n for (var oy = 0u; oy < 2u; oy = oy + 1u) {\n let wy = select(1.0 - cy.fraction, cy.fraction, oy == 1u);\n for (var ox = 0u; ox < 2u; ox = ox + 1u) {\n let wx = select(1.0 - cx.fraction, cx.fraction, ox == 1u);\n let weight = wx * wy * wz;\n if (weight <= 0.0) { continue; }\n let layer = probeLayerAt(cx.index + ox, cy.index + oy, cz.index + oz, counts);\n /*\n * From that probe towards the point, which is the parallax. A probe standing exactly on\n * the point has no direction to offer and takes its own zeroth texel, which is as good an\n * answer as exists for a point inside a probe.\n */\n let offset = p - probeVolumeProbeAt(layer, counts);\n let reach = length(offset);\n let towards = select(vec3<f32>(0.0, 0.0, 1.0), offset / reach, reach > 0.0);\n let uv = probeOctInsetUv(towards, volume.radianceEdge);\n sum = sum + probeLayerSample(layer, uv) * weight;\n }\n }\n }\n return sum;\n}\n";
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the probe grid answers at a world point, on the device.
|
|
3
|
+
*
|
|
4
|
+
* **This is the fourth spelling of one idea and the file says so rather than hoping.**
|
|
5
|
+
* `probeGrid.ts` holds the eight-probe trilinear blend in TypeScript, `octahedral.ts` holds the
|
|
6
|
+
* mapping in TypeScript and again in GLSL, and `shaders/flat/probeGrid.ts` holds both together in
|
|
7
|
+
* the GLSL the lit pass uses. This is WGSL, because a hand-written compute shader cannot include a
|
|
8
|
+
* generated one — and `scripts/probe-bake-parity.mjs` is what holds it to the others rather than
|
|
9
|
+
* a reader's confidence that four copies of an expression say the same thing.
|
|
10
|
+
*
|
|
11
|
+
* **Why a probe reads the grid at all**: the maintainer's answer of 2026-09-17 is that what a field
|
|
12
|
+
* hit is worth comes from the probes, so a probe traced against radiance that is itself probe
|
|
13
|
+
* radiance converges on several bounces over a few refreshes. That is what makes this the multi-
|
|
14
|
+
* bounce half of Wave 4A rather than a single-bounce approximation of it.
|
|
15
|
+
*
|
|
16
|
+
* **No visibility term here, deliberately.** `visibleProbes` exists to stop light reaching a
|
|
17
|
+
* *surface* through a wall it is behind, and it is what `gridIrradiance` applies when shading. A
|
|
18
|
+
* probe asking "what is the radiance out there" is a different question with a different failure:
|
|
19
|
+
* crushing a blind probe's weight would make the bounce darker than the room, and the reference
|
|
20
|
+
* this is checked against — `indirect-parity.mjs` — enters the chain with `visibility: null` for
|
|
21
|
+
* exactly this reason.
|
|
22
|
+
*
|
|
23
|
+
* The accessor an including module must define, before including the core:
|
|
24
|
+
*
|
|
25
|
+
* ```wgsl
|
|
26
|
+
* fn probeLayerSample(layer: u32, uv: vec2<f32>) -> vec3<f32> // at the radiance level
|
|
27
|
+
* fn probeLayerIrradiance(layer: u32, uv: vec2<f32>) -> vec3<f32> // at the irradiance level
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* and `volume: ProbeVolume` as a uniform. Production samples a texture; the parity module reads a
|
|
31
|
+
* plain buffer of the same numbers.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Floats in the volume block, and where each field starts.
|
|
35
|
+
*
|
|
36
|
+
* Named offsets for the reason `probeBake.wgsl.ts` gives and paid for: an `f32` after a `vec3<f32>`
|
|
37
|
+
* fills the vec3's padding rather than starting a new row, so `edge` is float **11**.
|
|
38
|
+
*/
|
|
39
|
+
export const VOLUME_ORIGIN = 0;
|
|
40
|
+
export const VOLUME_INV_SPACING = 4;
|
|
41
|
+
export const VOLUME_COUNTS = 8;
|
|
42
|
+
export const VOLUME_EDGE = 11;
|
|
43
|
+
export const VOLUME_LEVEL = 12;
|
|
44
|
+
export const VOLUME_RADIANCE_EDGE = 13;
|
|
45
|
+
export const VOLUME_RADIANCE_LEVEL = 14;
|
|
46
|
+
/** Fifty-two bytes of fields rounded up to the struct's own sixteen-byte alignment. */
|
|
47
|
+
export const PROBE_VOLUME_FLOATS = 16;
|
|
48
|
+
export const PROBE_VOLUME_STRUCT_WGSL = /* wgsl */ `
|
|
49
|
+
struct ProbeVolume {
|
|
50
|
+
/* Where the grid's first probe stands, and one over the step between them. */
|
|
51
|
+
origin: vec3<f32>,
|
|
52
|
+
invSpacing: vec3<f32>,
|
|
53
|
+
/* How many probes along each axis. Whole numbers carried as floats. */
|
|
54
|
+
counts: vec3<f32>,
|
|
55
|
+
/* Texels across the level being sampled, gutter included. */
|
|
56
|
+
edge: f32,
|
|
57
|
+
/* Which mip of the probe array that level is. */
|
|
58
|
+
level: f32,
|
|
59
|
+
/* And the level that holds *radiance* rather than irradiance, with its own edge. */
|
|
60
|
+
radianceEdge: f32,
|
|
61
|
+
radianceLevel: f32,
|
|
62
|
+
padA: f32,
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
export const PROBE_VOLUME_CORE_WGSL = /* wgsl */ `
|
|
66
|
+
/* The octahedral encode, matching "octEncode" in "shaders/octahedral.ts". */
|
|
67
|
+
fn probeOctEncode(d: vec3<f32>) -> vec2<f32> {
|
|
68
|
+
let n = d / (abs(d.x) + abs(d.y) + abs(d.z));
|
|
69
|
+
var p = n.xy;
|
|
70
|
+
if (n.z < 0.0) {
|
|
71
|
+
p = (1.0 - abs(vec2<f32>(n.y, n.x))) *
|
|
72
|
+
vec2<f32>(select(-1.0, 1.0, n.x >= 0.0), select(-1.0, 1.0, n.y >= 0.0));
|
|
73
|
+
}
|
|
74
|
+
return p * 0.5 + 0.5;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* Where to sample a direction in a map whose outer ring is a gutter.
|
|
79
|
+
*
|
|
80
|
+
* The inner region is "edge - 2" texels offset by one, so a direction at the very border of the
|
|
81
|
+
* octahedral square lands on the last real texel and the tap that interpolates past it reads the
|
|
82
|
+
* gutter — which the bake filled with the folded direction.
|
|
83
|
+
*/
|
|
84
|
+
fn probeOctInsetUv(d: vec3<f32>, edge: f32) -> vec2<f32> {
|
|
85
|
+
return (probeOctEncode(d) * (edge - 2.0) + 1.0) / edge;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* One axis of the lookup: which probe is below the point, and how far past it the point is.
|
|
90
|
+
*
|
|
91
|
+
* **The index is integer arithmetic and the fraction is not**, which is the distinction row 139 of
|
|
92
|
+
* the resume file is about. A world position divided by a spacing is a genuine float division and
|
|
93
|
+
* its floor is a genuine floor; what must never be written in floats is a *lattice* — an index
|
|
94
|
+
* decomposed out of a layer number — because a device may implement that division as a
|
|
95
|
+
* multiplication by a reciprocal and answer 3 for 3.0 % 3.0.
|
|
96
|
+
*
|
|
97
|
+
* The lower index stops one short of the last probe so the pair "index" and "index + 1" is always
|
|
98
|
+
* inside the grid, and a single-probe axis has no pair at all — both corners are probe zero and
|
|
99
|
+
* the fraction is zero, so that axis multiplies every weight by one.
|
|
100
|
+
*/
|
|
101
|
+
struct ProbeCell {
|
|
102
|
+
index: u32,
|
|
103
|
+
fraction: f32,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn probeCell(value: f32, origin: f32, invSpacing: f32, count: u32) -> ProbeCell {
|
|
107
|
+
var out: ProbeCell;
|
|
108
|
+
out.index = 0u;
|
|
109
|
+
out.fraction = 0.0;
|
|
110
|
+
if (count <= 1u) { return out; }
|
|
111
|
+
let local = (value - origin) * invSpacing;
|
|
112
|
+
let span = f32(count - 1u);
|
|
113
|
+
let clamped = clamp(local, 0.0, span);
|
|
114
|
+
let index = min(u32(floor(clamped)), count - 2u);
|
|
115
|
+
out.index = index;
|
|
116
|
+
out.fraction = clamped - f32(index);
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
* The layer a probe at whole lattice coordinates occupies. x fastest, then y, then z.
|
|
122
|
+
*
|
|
123
|
+
* **Not clamped, and "ProbeGrid.layerAt" is** — the difference is who may call them. "layerAt" is a
|
|
124
|
+
* public method whose contract is that it accepts anything, so it clamps; this is reached from one
|
|
125
|
+
* place, under two guarantees that together make the clamp unreachable. "probeCell" returns an
|
|
126
|
+
* index no greater than "count - 2", so "index + 1" is inside; and on an axis only one probe wide
|
|
127
|
+
* it returns index 0 with fraction 0, so the far corner's weight is zero and the caller skips it
|
|
128
|
+
* before asking. A clamp here would be a line no perturbation can make fail, which this session
|
|
129
|
+
* has already had to remove twice.
|
|
130
|
+
*/
|
|
131
|
+
fn probeLayerAt(ix: u32, iy: u32, iz: u32, counts: vec3<u32>) -> u32 {
|
|
132
|
+
return ix + counts.x * (iy + counts.y * iz);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/*
|
|
136
|
+
* What has already bounced onto a surface facing the normal at a point, from the level this writes.
|
|
137
|
+
*
|
|
138
|
+
* **One shared direction, and that is right here where a per-corner one is right for radiance.**
|
|
139
|
+
* An irradiance map is indexed by the *normal of the surface being lit*, not by where the light
|
|
140
|
+
* came from, so every probe is asked the same question: what reaches a surface facing this way.
|
|
141
|
+
* Reading it at the level the bake writes is what closes the loop — the second bounce is the first
|
|
142
|
+
* one read back.
|
|
143
|
+
*/
|
|
144
|
+
fn probeVolumeIrradianceAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {
|
|
145
|
+
let counts = vec3<u32>(
|
|
146
|
+
max(1u, u32(volume.counts.x)),
|
|
147
|
+
max(1u, u32(volume.counts.y)),
|
|
148
|
+
max(1u, u32(volume.counts.z)),
|
|
149
|
+
);
|
|
150
|
+
let cx = probeCell(p.x, volume.origin.x, volume.invSpacing.x, counts.x);
|
|
151
|
+
let cy = probeCell(p.y, volume.origin.y, volume.invSpacing.y, counts.y);
|
|
152
|
+
let cz = probeCell(p.z, volume.origin.z, volume.invSpacing.z, counts.z);
|
|
153
|
+
let uv = probeOctInsetUv(n, volume.edge);
|
|
154
|
+
|
|
155
|
+
var sum = vec3<f32>(0.0);
|
|
156
|
+
for (var oz = 0u; oz < 2u; oz = oz + 1u) {
|
|
157
|
+
let wz = select(1.0 - cz.fraction, cz.fraction, oz == 1u);
|
|
158
|
+
for (var oy = 0u; oy < 2u; oy = oy + 1u) {
|
|
159
|
+
let wy = select(1.0 - cy.fraction, cy.fraction, oy == 1u);
|
|
160
|
+
for (var ox = 0u; ox < 2u; ox = ox + 1u) {
|
|
161
|
+
let wx = select(1.0 - cx.fraction, cx.fraction, ox == 1u);
|
|
162
|
+
let weight = wx * wy * wz;
|
|
163
|
+
if (weight <= 0.0) { continue; }
|
|
164
|
+
let layer = probeLayerAt(cx.index + ox, cy.index + oy, cz.index + oz, counts);
|
|
165
|
+
sum = sum + probeLayerIrradiance(layer, uv) * weight;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return sum;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Where a layer's probe stands, which a parallax read needs and the lattice above decides. */
|
|
173
|
+
fn probeVolumeProbeAt(layer: u32, counts: vec3<u32>) -> vec3<f32> {
|
|
174
|
+
let x = layer % counts.x;
|
|
175
|
+
let y = (layer / counts.x) % counts.y;
|
|
176
|
+
let z = layer / (counts.x * counts.y);
|
|
177
|
+
let spacing = vec3<f32>(
|
|
178
|
+
select(0.0, 1.0 / volume.invSpacing.x, volume.invSpacing.x != 0.0),
|
|
179
|
+
select(0.0, 1.0 / volume.invSpacing.y, volume.invSpacing.y != 0.0),
|
|
180
|
+
select(0.0, 1.0 / volume.invSpacing.z, volume.invSpacing.z != 0.0),
|
|
181
|
+
);
|
|
182
|
+
return volume.origin + vec3<f32>(f32(x), f32(y), f32(z)) * spacing;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/*
|
|
186
|
+
* The radiance leaving a world point, as the grid saw it.
|
|
187
|
+
*
|
|
188
|
+
* **A ray needs radiance and the first version read irradiance**, which is what
|
|
189
|
+
* "demo/dev/bounce.html" measured as a bounce dimmer and greyer than the rasterised grid it was
|
|
190
|
+
* meant to replace. Irradiance is light *arriving* at a point, already cosine-convolved over a
|
|
191
|
+
* hemisphere; what a ray that struck a wall needs is the light *leaving* that wall towards it. The
|
|
192
|
+
* two differ by the surface's albedo — so a red wall and a white wall at the same irradiance
|
|
193
|
+
* returned the same colour, and the red never travelled.
|
|
194
|
+
*
|
|
195
|
+
* **Level 0 of the probe array is that radiance**, and it needs no albedo because it is a
|
|
196
|
+
* rasterised capture of the shaded room: the red wall is red in it. §6.4 of the design names
|
|
197
|
+
* "envProbeArray.ts" as this track's starting point and leaves how much of it to reuse to
|
|
198
|
+
* measurement, which is this.
|
|
199
|
+
*
|
|
200
|
+
* **Each corner is read along its own direction to the point**, which is what makes this a
|
|
201
|
+
* position lookup rather than a direction one. Eight probes sampled along one shared direction
|
|
202
|
+
* answer "what is over there from roughly here"; eight probes each sampled towards "p" answer
|
|
203
|
+
* "what is at "p"", and the difference is the parallax that a marched hit exists to provide. It is
|
|
204
|
+
* also why no direction is passed in: deriving it removes the reversed-direction mistake that
|
|
205
|
+
* "probe-bake-parity.mjs" records being unable to see.
|
|
206
|
+
*/
|
|
207
|
+
fn probeVolumeRadianceAt(p: vec3<f32>) -> vec3<f32> {
|
|
208
|
+
let counts = vec3<u32>(
|
|
209
|
+
max(1u, u32(volume.counts.x)),
|
|
210
|
+
max(1u, u32(volume.counts.y)),
|
|
211
|
+
max(1u, u32(volume.counts.z)),
|
|
212
|
+
);
|
|
213
|
+
let cx = probeCell(p.x, volume.origin.x, volume.invSpacing.x, counts.x);
|
|
214
|
+
let cy = probeCell(p.y, volume.origin.y, volume.invSpacing.y, counts.y);
|
|
215
|
+
let cz = probeCell(p.z, volume.origin.z, volume.invSpacing.z, counts.z);
|
|
216
|
+
|
|
217
|
+
var sum = vec3<f32>(0.0);
|
|
218
|
+
for (var oz = 0u; oz < 2u; oz = oz + 1u) {
|
|
219
|
+
let wz = select(1.0 - cz.fraction, cz.fraction, oz == 1u);
|
|
220
|
+
for (var oy = 0u; oy < 2u; oy = oy + 1u) {
|
|
221
|
+
let wy = select(1.0 - cy.fraction, cy.fraction, oy == 1u);
|
|
222
|
+
for (var ox = 0u; ox < 2u; ox = ox + 1u) {
|
|
223
|
+
let wx = select(1.0 - cx.fraction, cx.fraction, ox == 1u);
|
|
224
|
+
let weight = wx * wy * wz;
|
|
225
|
+
if (weight <= 0.0) { continue; }
|
|
226
|
+
let layer = probeLayerAt(cx.index + ox, cy.index + oy, cz.index + oz, counts);
|
|
227
|
+
/*
|
|
228
|
+
* From that probe towards the point, which is the parallax. A probe standing exactly on
|
|
229
|
+
* the point has no direction to offer and takes its own zeroth texel, which is as good an
|
|
230
|
+
* answer as exists for a point inside a probe.
|
|
231
|
+
*/
|
|
232
|
+
let offset = p - probeVolumeProbeAt(layer, counts);
|
|
233
|
+
let reach = length(offset);
|
|
234
|
+
let towards = select(vec3<f32>(0.0, 0.0, 1.0), offset / reach, reach > 0.0);
|
|
235
|
+
let uv = probeOctInsetUv(towards, volume.radianceEdge);
|
|
236
|
+
sum = sum + probeLayerSample(layer, uv) * weight;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return sum;
|
|
241
|
+
}
|
|
242
|
+
`;
|