@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,635 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A probe's own bake, on the device: trace a sphere of rays, convolve them into an octahedral map.
|
|
3
|
+
*
|
|
4
|
+
* **Two dispatches rather than one, and the split is what makes it affordable.** A probe's texel
|
|
5
|
+
* is a cosine-weighted sum over every direction, so one invocation a texel that also traced would
|
|
6
|
+
* trace the same `PROBE_DIRECTIONS` rays once per texel — 256 rays becoming 256 × edge², which for
|
|
7
|
+
* an edge of 16 is sixty-five thousand marches for a probe that needs 256. So `traceMain` casts
|
|
8
|
+
* the rays once and writes them down, and `convolveMain` reads them back.
|
|
9
|
+
*
|
|
10
|
+
* **The octahedral mapping is a third spelling and that is a hazard this file states rather than
|
|
11
|
+
* hides.** `octahedral.ts` holds it in TypeScript and in GLSL; this is WGSL, because a hand-written
|
|
12
|
+
* compute shader cannot include the generated one. `scripts/probe-bake-parity.mjs` is what holds
|
|
13
|
+
* the three together, over the texels and the directions both.
|
|
14
|
+
*
|
|
15
|
+
* **A probe's normal is its ray's own direction, which is right here and wrong on a surface.**
|
|
16
|
+
* `probeTraceField` lifts the start along the normal to keep a ray from immediately hitting the
|
|
17
|
+
* surface it left, and its header says lifting along the ray as well double-counts — for a *surface*
|
|
18
|
+
* sample, where the normal and the ray are different things. A probe stands in open space and has
|
|
19
|
+
* no surface and no normal, so the only thing to lift along is the ray, and what it buys is a probe
|
|
20
|
+
* that has come to rest inside a wall casting rays that leave it rather than all reporting a hit at
|
|
21
|
+
* zero distance.
|
|
22
|
+
*
|
|
23
|
+
* The accessors an including module must define, before including the core, are `probeIndirect`'s
|
|
24
|
+
* — see `probeTrace.wgsl.ts` — plus `bake: ProbeBake` as a uniform.
|
|
25
|
+
*/
|
|
26
|
+
import { FRAME_TURN, GOLDEN_ANGLE, PROBE_DIRECTIONS } from '../../gi/probeTrace.js';
|
|
27
|
+
import { PROBE_MARCH_WGSL, PROBE_TRACE_CORE_WGSL } from './probeTrace.wgsl.js';
|
|
28
|
+
import { PROBE_VOLUME_CORE_WGSL, PROBE_VOLUME_STRUCT_WGSL } from './probeVolume.wgsl.js';
|
|
29
|
+
import { SAMPLE_FIELD_WGSL } from './sampleField.wgsl.js';
|
|
30
|
+
/**
|
|
31
|
+
* Floats in the bake block, and where each field of it starts.
|
|
32
|
+
*
|
|
33
|
+
* **A `vec3<f32>` is sixteen-byte *aligned* and twelve bytes *long*, and an `f32` after one fills
|
|
34
|
+
* the gap rather than starting a new row.** So `counts` occupies floats 8 to 10 and `edge` is float
|
|
35
|
+
* **11**, not 12 — which cost an afternoon: written at 12, the shader read `edge` as `frame` and
|
|
36
|
+
* every direction came back rotated by the difference, at exactly the right radius. The radius
|
|
37
|
+
* matching is what said it was the angle and not the set.
|
|
38
|
+
*
|
|
39
|
+
* Exported as named offsets for that reason. A caller that counts rows gets this wrong; a caller
|
|
40
|
+
* that names the field cannot.
|
|
41
|
+
*/
|
|
42
|
+
export const BAKE_ORIGIN = 0;
|
|
43
|
+
export const BAKE_SPACING = 4;
|
|
44
|
+
export const BAKE_COUNTS = 8;
|
|
45
|
+
export const BAKE_EDGE = 11;
|
|
46
|
+
export const BAKE_FRAME = 12;
|
|
47
|
+
export const BAKE_SCHEDULED = 13;
|
|
48
|
+
/** The sun, which the trace evaluates itself so a light that moves changes the bounce. */
|
|
49
|
+
export const BAKE_SUN_DIR = 16;
|
|
50
|
+
export const BAKE_SUN_COLOUR = 20;
|
|
51
|
+
/** What a ray that left the world finds. The frame's ambient, so nothing rasterised is read. */
|
|
52
|
+
export const BAKE_SKY_COLOUR = 24;
|
|
53
|
+
/** Rounded up to the struct's own sixteen-byte alignment, with two vec3 rows for the sun. */
|
|
54
|
+
export const PROBE_BAKE_FLOATS = 28;
|
|
55
|
+
/** Invocations a workgroup, for both entry points. 64 is this backend's default everywhere. */
|
|
56
|
+
export const PROBE_BAKE_WORKGROUP = 64;
|
|
57
|
+
/** Floats a ray's radiance takes in the intermediate buffer. */
|
|
58
|
+
export const PROBE_RAY_FLOATS = 3;
|
|
59
|
+
/** Floats a baked texel takes. Three and not four: the alpha of an irradiance map means nothing. */
|
|
60
|
+
export const PROBE_TEXEL_FLOATS = 3;
|
|
61
|
+
export const PROBE_BAKE_STRUCT_WGSL = /* wgsl */ `
|
|
62
|
+
struct ProbeBake {
|
|
63
|
+
/* Where the grid's first probe stands, and how far apart they are. */
|
|
64
|
+
origin: vec3<f32>,
|
|
65
|
+
spacing: vec3<f32>,
|
|
66
|
+
/* How many probes along each axis. Whole numbers carried as floats, as every block here does. */
|
|
67
|
+
counts: vec3<f32>,
|
|
68
|
+
/* Texels across one probe's map, gutter included. */
|
|
69
|
+
edge: f32,
|
|
70
|
+
/* Which frame this is, which turns the direction set so a probe converges rather than repeats. */
|
|
71
|
+
frame: f32,
|
|
72
|
+
/* How many probes this dispatch bakes. The schedule says which. */
|
|
73
|
+
scheduled: f32,
|
|
74
|
+
pad: f32,
|
|
75
|
+
/* Towards the sun, and what colour it is. The trace shades what it hits rather than reading a
|
|
76
|
+
capture, which is what lets a bounce follow a light that moved. */
|
|
77
|
+
sunDir: vec3<f32>,
|
|
78
|
+
sunColour: vec3<f32>,
|
|
79
|
+
/* And what is outside, for a ray that left. See probeDistantRadiance. */
|
|
80
|
+
skyColour: vec3<f32>,
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
/**
|
|
84
|
+
* The octahedral mapping and the probe lattice, as text.
|
|
85
|
+
*
|
|
86
|
+
* Defines `bakeOctInsetDir(u, v, edge)` and `bakeProbePosition(layer)`.
|
|
87
|
+
*/
|
|
88
|
+
export const PROBE_BAKE_CORE_WGSL = /* wgsl */ `
|
|
89
|
+
const PROBE_BAKE_DIRECTIONS: u32 = ${String(PROBE_DIRECTIONS)}u;
|
|
90
|
+
/* Both interpolated from "gi/probeTrace.ts" rather than restated, so the two cannot drift. */
|
|
91
|
+
const PROBE_BAKE_GOLDEN_ANGLE: f32 = ${String(GOLDEN_ANGLE)};
|
|
92
|
+
const PROBE_BAKE_FRAME_TURN: f32 = ${String(FRAME_TURN)};
|
|
93
|
+
const PROBE_BAKE_TWO_PI: f32 = ${String(Math.PI * 2)};
|
|
94
|
+
|
|
95
|
+
fn bakeOctDecode(uv: vec2<f32>) -> vec3<f32> {
|
|
96
|
+
let f = uv * 2.0 - 1.0;
|
|
97
|
+
var n = vec3<f32>(f.x, f.y, 1.0 - abs(f.x) - abs(f.y));
|
|
98
|
+
let t = max(-n.z, 0.0);
|
|
99
|
+
n.x = n.x + select(t, -t, n.x >= 0.0);
|
|
100
|
+
n.y = n.y + select(t, -t, n.y >= 0.0);
|
|
101
|
+
return normalize(n);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* The fold that makes the gutter hold the direction across the seam rather than the far side. */
|
|
105
|
+
fn bakeOctWrapUv(uv: vec2<f32>) -> vec2<f32> {
|
|
106
|
+
var f = uv * 2.0 - 1.0;
|
|
107
|
+
if (f.x < -1.0 || f.x > 1.0) {
|
|
108
|
+
f.x = select(-2.0, 2.0, f.x > 0.0) - f.x;
|
|
109
|
+
f.y = -f.y;
|
|
110
|
+
}
|
|
111
|
+
if (f.y < -1.0 || f.y > 1.0) {
|
|
112
|
+
f.y = select(-2.0, 2.0, f.y > 0.0) - f.y;
|
|
113
|
+
f.x = -f.x;
|
|
114
|
+
}
|
|
115
|
+
return f * 0.5 + 0.5;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* The direction one texel of an inset map must hold. "octInsetDir", exactly. */
|
|
119
|
+
fn bakeOctInsetDir(uv: vec2<f32>, edge: f32) -> vec3<f32> {
|
|
120
|
+
return bakeOctDecode(bakeOctWrapUv((uv * edge - 1.0) / (edge - 2.0)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* Where a layer's probe stands.
|
|
125
|
+
*
|
|
126
|
+
* **x fastest, then y, then z** — "ProbeGrid.layerAt" writes that order down for this reason, and
|
|
127
|
+
* the two disagreeing would light every surface from the wrong corner of the room.
|
|
128
|
+
*
|
|
129
|
+
* **In integers, and the first version was in floats, and it was wrong on a real device.** Written
|
|
130
|
+
* as "floor(index / nx) % ny" over whole numbers carried as f32, this GPU answered 3 for 3.0 % 3.0
|
|
131
|
+
* and 0 for floor(12.0 / 12.0). The cause is the shader compiler implementing a division as a
|
|
132
|
+
* multiplication by a reciprocal: 1/3 and 1/12 are not exact in single precision, so 3 * (1/3) is
|
|
133
|
+
* 0.99999994, its truncation is 0, and the remainder is the whole numerator. An index lattice is
|
|
134
|
+
* integer arithmetic and has to be written as integer arithmetic; a float that happens to hold a
|
|
135
|
+
* whole number is not a whole number once a compiler is allowed to reassociate it.
|
|
136
|
+
*/
|
|
137
|
+
fn bakeProbePosition(layer: u32) -> vec3<f32> {
|
|
138
|
+
let nx = max(1u, u32(bake.counts.x));
|
|
139
|
+
let ny = max(1u, u32(bake.counts.y));
|
|
140
|
+
let x = layer % nx;
|
|
141
|
+
let y = (layer / nx) % ny;
|
|
142
|
+
let z = layer / (nx * ny);
|
|
143
|
+
return bake.origin + vec3<f32>(f32(x), f32(y), f32(z)) * bake.spacing;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
* The direction a ray takes, as a Fibonacci spiral turned by the frame.
|
|
148
|
+
*
|
|
149
|
+
* "probeDirection" is the reference and this is the same arithmetic. The
|
|
150
|
+
* turn is what makes a probe *converge*: an unturned set samples the same 256 directions for ever,
|
|
151
|
+
* so whatever falls between them is never seen however long the probe runs.
|
|
152
|
+
*/
|
|
153
|
+
fn bakeDirection(index: u32, frame: f32) -> vec3<f32> {
|
|
154
|
+
let i = f32(index % PROBE_BAKE_DIRECTIONS);
|
|
155
|
+
/* Offset by a half step at each end, so no direction lands exactly on a pole — where the turn
|
|
156
|
+
has no effect and two frames would trace the same ray. */
|
|
157
|
+
let z = 1.0 - (2.0 * i + 1.0) / f32(PROBE_BAKE_DIRECTIONS);
|
|
158
|
+
let radius = sqrt(max(0.0, 1.0 - z * z));
|
|
159
|
+
let angle = i * PROBE_BAKE_GOLDEN_ANGLE + frame * PROBE_BAKE_FRAME_TURN * PROBE_BAKE_TWO_PI;
|
|
160
|
+
return vec3<f32>(cos(angle) * radius, sin(angle) * radius, z);
|
|
161
|
+
}
|
|
162
|
+
`;
|
|
163
|
+
/**
|
|
164
|
+
* The two entry points, over buffers a caller binds.
|
|
165
|
+
*
|
|
166
|
+
* `schedule[i]` is the grid layer that slot `i` of this dispatch bakes, which is
|
|
167
|
+
* `probeUpdateSchedule`'s output — a plain round robin, so every probe is reached and the same
|
|
168
|
+
* frame index asks for the same probes.
|
|
169
|
+
*/
|
|
170
|
+
/*
|
|
171
|
+
* Not exported, and `scripts/wgsl-handwritten.test.mjs` is why: it validates every exported string
|
|
172
|
+
* that declares an entry point, and this one declares four over bindings a surrounding module
|
|
173
|
+
* supplies. The whole modules are the functions below, and the gate names them.
|
|
174
|
+
*/
|
|
175
|
+
const PROBE_BAKE_ENTRIES_WGSL = /* wgsl */ `
|
|
176
|
+
@compute @workgroup_size(${String(PROBE_BAKE_WORKGROUP)})
|
|
177
|
+
fn traceMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
178
|
+
let total = u32(bake.scheduled) * PROBE_BAKE_DIRECTIONS;
|
|
179
|
+
if (id.x >= total) { return; }
|
|
180
|
+
let slot = id.x / PROBE_BAKE_DIRECTIONS;
|
|
181
|
+
let ray = id.x % PROBE_BAKE_DIRECTIONS;
|
|
182
|
+
|
|
183
|
+
let layer = schedule[slot];
|
|
184
|
+
let point = bakeProbePosition(layer);
|
|
185
|
+
let direction = bakeDirection(ray, bake.frame);
|
|
186
|
+
/* Its own direction as its normal: a probe has no surface. The header says why. */
|
|
187
|
+
let radiance = probeIndirect(point, direction, direction, march.coneAngle);
|
|
188
|
+
|
|
189
|
+
let at = id.x * ${String(PROBE_RAY_FLOATS)}u;
|
|
190
|
+
rays[at] = radiance.x;
|
|
191
|
+
rays[at + 1u] = radiance.y;
|
|
192
|
+
rays[at + 2u] = radiance.z;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@compute @workgroup_size(${String(PROBE_BAKE_WORKGROUP)})
|
|
196
|
+
fn convolveMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
197
|
+
let edge = u32(bake.edge);
|
|
198
|
+
let texels = edge * edge;
|
|
199
|
+
let total = u32(bake.scheduled) * texels;
|
|
200
|
+
if (id.x >= total) { return; }
|
|
201
|
+
let slot = id.x / texels;
|
|
202
|
+
let texel = id.x % texels;
|
|
203
|
+
let u = texel % edge;
|
|
204
|
+
let v = texel / edge;
|
|
205
|
+
|
|
206
|
+
let normal = bakeOctInsetDir(
|
|
207
|
+
vec2<f32>((f32(u) + 0.5) / bake.edge, (f32(v) + 0.5) / bake.edge),
|
|
208
|
+
bake.edge,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
/*
|
|
212
|
+
* **Normalised by the weights that were actually used**, which is the whole white-furnace
|
|
213
|
+
* property and is "convolveProbe"'s own decision. Dividing by an analytic constant is right only
|
|
214
|
+
* in the limit, so a uniformly white sphere would come back slightly off white and a room would
|
|
215
|
+
* brighten or darken a little at every refresh.
|
|
216
|
+
*/
|
|
217
|
+
var weights = 0.0;
|
|
218
|
+
var sum = vec3<f32>(0.0);
|
|
219
|
+
for (var i = 0u; i < PROBE_BAKE_DIRECTIONS; i = i + 1u) {
|
|
220
|
+
let direction = bakeDirection(i, bake.frame);
|
|
221
|
+
let weight = dot(normal, direction);
|
|
222
|
+
if (weight <= 0.0) { continue; }
|
|
223
|
+
let at = (slot * PROBE_BAKE_DIRECTIONS + i) * ${String(PROBE_RAY_FLOATS)}u;
|
|
224
|
+
weights = weights + weight;
|
|
225
|
+
sum = sum + vec3<f32>(rays[at], rays[at + 1u], rays[at + 2u]) * weight;
|
|
226
|
+
}
|
|
227
|
+
/* No weight at all is a texel no sample faced, which this set does not produce — and answering
|
|
228
|
+
zero rather than dividing is what keeps it from being a hole if it ever does. */
|
|
229
|
+
let scale = select(0.0, 1.0 / weights, weights > 0.0);
|
|
230
|
+
let value = max(vec3<f32>(0.0), sum * scale);
|
|
231
|
+
|
|
232
|
+
let out = id.x * ${String(PROBE_TEXEL_FLOATS)}u;
|
|
233
|
+
irradiance[out] = value.x;
|
|
234
|
+
irradiance[out + 1u] = value.y;
|
|
235
|
+
irradiance[out + 2u] = value.z;
|
|
236
|
+
}
|
|
237
|
+
`;
|
|
238
|
+
/**
|
|
239
|
+
* The parity module: the bake over plain buffers, with a radiance that is a function of position.
|
|
240
|
+
*
|
|
241
|
+
* **The radiance is positional for the reason `probeTraceParityWgsl` gives**: a march that landed a
|
|
242
|
+
* voxel from where the reference landed would return almost the right answer from a smooth field
|
|
243
|
+
* and exactly the right one from a constant, so a constant would check the convolution and nothing
|
|
244
|
+
* about the trace.
|
|
245
|
+
*
|
|
246
|
+
* What this check is *for*, over and above `indirect-parity.mjs`, is the three pieces of arithmetic
|
|
247
|
+
* that only exist in this file: the octahedral texel-to-direction fold, the layer-to-position
|
|
248
|
+
* lattice, and the Fibonacci direction set. Each is a second spelling of something `octahedral.ts`,
|
|
249
|
+
* `probeGrid.ts` and `probeTrace.ts` already hold, and a second spelling is what this repository
|
|
250
|
+
* has learned to check rather than trust.
|
|
251
|
+
*/
|
|
252
|
+
export function probeBakeParityWgsl() {
|
|
253
|
+
return /* wgsl */ `
|
|
254
|
+
@group(0) @binding(0) var<storage, read> cascades: array<f32>;
|
|
255
|
+
@group(0) @binding(1) var<storage, read> fields: array<f32>;
|
|
256
|
+
${PROBE_MARCH_WGSL}
|
|
257
|
+
@group(0) @binding(2) var<uniform> march: ProbeMarch;
|
|
258
|
+
${PROBE_BAKE_STRUCT_WGSL}
|
|
259
|
+
@group(0) @binding(3) var<uniform> bake: ProbeBake;
|
|
260
|
+
@group(0) @binding(4) var<storage, read> schedule: array<u32>;
|
|
261
|
+
@group(0) @binding(5) var<storage, read_write> rays: array<f32>;
|
|
262
|
+
@group(0) @binding(6) var<storage, read_write> irradiance: array<f32>;
|
|
263
|
+
${PROBE_VOLUME_STRUCT_WGSL}
|
|
264
|
+
@group(0) @binding(7) var<uniform> volume: ProbeVolume;
|
|
265
|
+
/* Three floats a layer, which is what "sampleProbeVolume" blends in the reference. */
|
|
266
|
+
@group(0) @binding(8) var<storage, read> probeValues: array<f32>;
|
|
267
|
+
/* Six floats a query: a point and a direction. */
|
|
268
|
+
@group(0) @binding(9) var<storage, read> queries: array<f32>;
|
|
269
|
+
${SAMPLE_FIELD_WGSL}
|
|
270
|
+
|
|
271
|
+
/*
|
|
272
|
+
* A layer's value, ignoring the texel it was asked for.
|
|
273
|
+
*
|
|
274
|
+
* **Flat on purpose, so the blend is checked and not the sampler.** The reference is
|
|
275
|
+
* "sampleProbeVolume", which takes a fixed number of channels a layer; a parity module that
|
|
276
|
+
* interpolated a map would be comparing a bilinear filter against something that has none. What
|
|
277
|
+
* the octahedral coordinate does is checked separately, by "uvMain" against "octInsetUv".
|
|
278
|
+
*/
|
|
279
|
+
fn probeLayerSample(layer: u32, uv: vec2<f32>) -> vec3<f32> {
|
|
280
|
+
let at = layer * 3u;
|
|
281
|
+
return vec3<f32>(probeValues[at], probeValues[at + 1u], probeValues[at + 2u]);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* The same flat value: this module checks the blend, and what a production texture holds at its
|
|
285
|
+
two levels is what makes them different. */
|
|
286
|
+
fn probeLayerIrradiance(layer: u32, uv: vec2<f32>) -> vec3<f32> {
|
|
287
|
+
return probeLayerSample(layer, uv);
|
|
288
|
+
}
|
|
289
|
+
${PROBE_VOLUME_CORE_WGSL}
|
|
290
|
+
|
|
291
|
+
fn probeFieldAt(p: vec3<f32>) -> f32 {
|
|
292
|
+
return sampleField(march.levels, march.side, p);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
fn probeFieldInside(p: vec3<f32>) -> f32 {
|
|
296
|
+
return insideBy(march.levels - 1u, p);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* The same expression the harness evaluates, and the reason it is a sine of position. */
|
|
300
|
+
fn probeParityRadiance(p: vec3<f32>) -> vec3<f32> {
|
|
301
|
+
return vec3<f32>(
|
|
302
|
+
0.5 + 0.4 * sin(p.x * 1.3),
|
|
303
|
+
0.5 + 0.4 * sin(p.y * 1.7 + 1.0),
|
|
304
|
+
0.5 + 0.4 * sin(p.z * 2.3 + 2.0),
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* One expression for both, so this module still checks the march and the blend and nothing else. */
|
|
309
|
+
fn probeSurfaceRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32> {
|
|
310
|
+
return probeParityRadiance(p);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fn probeDistantRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32> {
|
|
314
|
+
return probeParityRadiance(p);
|
|
315
|
+
}
|
|
316
|
+
${PROBE_TRACE_CORE_WGSL}
|
|
317
|
+
${PROBE_BAKE_CORE_WGSL}
|
|
318
|
+
${PROBE_BAKE_ENTRIES_WGSL}
|
|
319
|
+
|
|
320
|
+
/*
|
|
321
|
+
* Three probes for the three pieces of arithmetic that exist only in this file, so each is checked
|
|
322
|
+
* on its own rather than through the answer it contributes to.
|
|
323
|
+
*
|
|
324
|
+
* **Without them the check would be end-to-end and could not say which spelling was wrong.** A
|
|
325
|
+
* direction set off by a thousandth and an octahedral fold off by a thousandth produce the same
|
|
326
|
+
* symptom — a texel slightly wrong — and a single number cannot tell a caller which to go and read.
|
|
327
|
+
* They also let the radiance comparison feed the reference the device's *own* direction, which is
|
|
328
|
+
* what keeps a float's last place in an accumulated angle from being reported as a trace failure.
|
|
329
|
+
*/
|
|
330
|
+
@compute @workgroup_size(${String(PROBE_BAKE_WORKGROUP)})
|
|
331
|
+
fn directionMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
332
|
+
if (id.x >= PROBE_BAKE_DIRECTIONS) { return; }
|
|
333
|
+
let d = bakeDirection(id.x, bake.frame);
|
|
334
|
+
let at = id.x * 3u;
|
|
335
|
+
irradiance[at] = d.x;
|
|
336
|
+
irradiance[at + 1u] = d.y;
|
|
337
|
+
irradiance[at + 2u] = d.z;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@compute @workgroup_size(${String(PROBE_BAKE_WORKGROUP)})
|
|
341
|
+
fn positionMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
342
|
+
if (id.x >= u32(bake.scheduled)) { return; }
|
|
343
|
+
let p = bakeProbePosition(schedule[id.x]);
|
|
344
|
+
let at = id.x * 3u;
|
|
345
|
+
irradiance[at] = p.x;
|
|
346
|
+
irradiance[at + 1u] = p.y;
|
|
347
|
+
irradiance[at + 2u] = p.z;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* The block as the device unpacked it, so a layout argument is settled by reading rather than
|
|
351
|
+
reasoning. "BAKE_*" names the offsets a caller writes; this is what the struct made of them. */
|
|
352
|
+
@compute @workgroup_size(${String(PROBE_BAKE_WORKGROUP)})
|
|
353
|
+
fn echoMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
354
|
+
if (id.x != 0u) { return; }
|
|
355
|
+
irradiance[0] = bake.origin.x;
|
|
356
|
+
irradiance[1] = bake.origin.y;
|
|
357
|
+
irradiance[2] = bake.origin.z;
|
|
358
|
+
irradiance[3] = bake.spacing.x;
|
|
359
|
+
irradiance[4] = bake.spacing.y;
|
|
360
|
+
irradiance[5] = bake.spacing.z;
|
|
361
|
+
irradiance[6] = bake.counts.x;
|
|
362
|
+
irradiance[7] = bake.counts.y;
|
|
363
|
+
irradiance[8] = bake.counts.z;
|
|
364
|
+
irradiance[9] = bake.edge;
|
|
365
|
+
irradiance[10] = bake.frame;
|
|
366
|
+
irradiance[11] = bake.scheduled;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* The encode, which is a different function from the fold "texelMain" checks and needs its own. */
|
|
370
|
+
@compute @workgroup_size(${String(PROBE_BAKE_WORKGROUP)})
|
|
371
|
+
fn uvMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
372
|
+
let at = id.x * 6u;
|
|
373
|
+
if (at + 5u >= arrayLength(&queries)) { return; }
|
|
374
|
+
let direction = vec3<f32>(queries[at + 3u], queries[at + 4u], queries[at + 5u]);
|
|
375
|
+
let uv = probeOctInsetUv(direction, volume.edge);
|
|
376
|
+
let out = id.x * 3u;
|
|
377
|
+
irradiance[out] = uv.x;
|
|
378
|
+
irradiance[out + 1u] = uv.y;
|
|
379
|
+
irradiance[out + 2u] = 0.0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* And the eight-probe blend, against "nearestProbes" and "sampleProbeVolume" together. */
|
|
383
|
+
@compute @workgroup_size(${String(PROBE_BAKE_WORKGROUP)})
|
|
384
|
+
fn volumeMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
385
|
+
let at = id.x * 6u;
|
|
386
|
+
if (at + 5u >= arrayLength(&queries)) { return; }
|
|
387
|
+
let point = vec3<f32>(queries[at], queries[at + 1u], queries[at + 2u]);
|
|
388
|
+
let value = probeVolumeRadianceAt(point);
|
|
389
|
+
let out = id.x * 3u;
|
|
390
|
+
irradiance[out] = value.x;
|
|
391
|
+
irradiance[out + 1u] = value.y;
|
|
392
|
+
irradiance[out + 2u] = value.z;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@compute @workgroup_size(${String(PROBE_BAKE_WORKGROUP)})
|
|
396
|
+
fn texelMain(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
397
|
+
let edge = u32(bake.edge);
|
|
398
|
+
if (id.x >= edge * edge) { return; }
|
|
399
|
+
let u = id.x % edge;
|
|
400
|
+
let v = id.x / edge;
|
|
401
|
+
let d = bakeOctInsetDir(
|
|
402
|
+
vec2<f32>((f32(u) + 0.5) / bake.edge, (f32(v) + 0.5) / bake.edge),
|
|
403
|
+
bake.edge,
|
|
404
|
+
);
|
|
405
|
+
let at = id.x * 3u;
|
|
406
|
+
irradiance[at] = d.x;
|
|
407
|
+
irradiance[at + 1u] = d.y;
|
|
408
|
+
irradiance[at + 2u] = d.z;
|
|
409
|
+
}
|
|
410
|
+
`;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* The production module: the same core, reading the renderer's composed field and probe array.
|
|
414
|
+
*
|
|
415
|
+
* **`probeRadianceAt` is the probe volume here and a sine of position in the parity module**, which
|
|
416
|
+
* is the whole reason the core takes it as an accessor. A probe traced against radiance that is
|
|
417
|
+
* itself probe radiance converges on several bounces over a few refreshes — the maintainer's answer
|
|
418
|
+
* of 2026-09-17 — and that is a property of what is bound rather than of the arithmetic.
|
|
419
|
+
*
|
|
420
|
+
* **The probe array is sampled while a later pass writes a mip of it, and that is legal.** WebGPU
|
|
421
|
+
* forbids a texture being read and written inside one pass, not across two on one encoder: the
|
|
422
|
+
* dispatches here read the array as it stood, and the blit that follows writes the levels the next
|
|
423
|
+
* frame's dispatches will read. What that costs is a frame of latency in the bounce, which is what
|
|
424
|
+
* a probe volume has anyway because it accumulates.
|
|
425
|
+
*/
|
|
426
|
+
export function probeBakeWgsl() {
|
|
427
|
+
return /* wgsl */ `
|
|
428
|
+
@group(0) @binding(0) var<storage, read> cascades: array<f32>;
|
|
429
|
+
@group(0) @binding(1) var<storage, read> fields: array<f32>;
|
|
430
|
+
${PROBE_MARCH_WGSL}
|
|
431
|
+
@group(0) @binding(2) var<uniform> march: ProbeMarch;
|
|
432
|
+
${PROBE_BAKE_STRUCT_WGSL}
|
|
433
|
+
@group(0) @binding(3) var<uniform> bake: ProbeBake;
|
|
434
|
+
@group(0) @binding(4) var<storage, read> schedule: array<u32>;
|
|
435
|
+
@group(0) @binding(5) var<storage, read_write> rays: array<f32>;
|
|
436
|
+
@group(0) @binding(6) var<storage, read_write> irradiance: array<f32>;
|
|
437
|
+
${PROBE_VOLUME_STRUCT_WGSL}
|
|
438
|
+
@group(0) @binding(7) var<uniform> volume: ProbeVolume;
|
|
439
|
+
@group(0) @binding(8) var probeArray: texture_2d_array<f32>;
|
|
440
|
+
@group(0) @binding(9) var probeSampler: sampler;
|
|
441
|
+
/* The colour of whatever won the union at each sample, three floats, same layout as the field. */
|
|
442
|
+
@group(0) @binding(10) var<storage, read> albedoField: array<f32>;
|
|
443
|
+
${SAMPLE_FIELD_WGSL}
|
|
444
|
+
|
|
445
|
+
/*
|
|
446
|
+
* The albedo at a world point, from the finest cascade that holds it, by nearest sample.
|
|
447
|
+
*
|
|
448
|
+
* **Nearest and not trilinear, which the distance beside it is.** A distance is continuous and
|
|
449
|
+
* interpolating it is the whole reason a field works; a colour is not. Blending across the seam
|
|
450
|
+
* where a red wall meets a white one produces a pink that exists nowhere in the room, and it
|
|
451
|
+
* produces it in exactly the band a bounce is most visible in. The nearest sample is a colour some
|
|
452
|
+
* surface actually has.
|
|
453
|
+
*/
|
|
454
|
+
fn probeAlbedoAt(p: vec3<f32>) -> vec3<f32> {
|
|
455
|
+
let last = march.levels - 1u;
|
|
456
|
+
var level = last;
|
|
457
|
+
for (var i = 0u; i <= last; i = i + 1u) {
|
|
458
|
+
if (insideBy(i, p) > 0.0) {
|
|
459
|
+
level = i;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
let base = level * ${String(8)}u;
|
|
464
|
+
let low = vec3<f32>(cascades[base], cascades[base + 1u], cascades[base + 2u]);
|
|
465
|
+
let step = cascades[base + 6u];
|
|
466
|
+
let side = march.side;
|
|
467
|
+
let count = f32(side);
|
|
468
|
+
var index = vec3<u32>(0u, 0u, 0u);
|
|
469
|
+
for (var axis = 0u; axis < 3u; axis = axis + 1u) {
|
|
470
|
+
let local = (p[axis] - low[axis]) / step;
|
|
471
|
+
index[axis] = u32(clamp(round(local), 0.0, count - 1.0));
|
|
472
|
+
}
|
|
473
|
+
let samples = side * side * side;
|
|
474
|
+
let at = (level * samples + index.x + side * (index.y + side * index.z)) * 3u;
|
|
475
|
+
return vec3<f32>(albedoField[at], albedoField[at + 1u], albedoField[at + 2u]);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
fn probeFieldAt(p: vec3<f32>) -> f32 {
|
|
479
|
+
return sampleField(march.levels, march.side, p);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
fn probeFieldInside(p: vec3<f32>) -> f32 {
|
|
483
|
+
return insideBy(march.levels - 1u, p);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/*
|
|
487
|
+
* One probe's map, filtered.
|
|
488
|
+
*
|
|
489
|
+
* "textureSampleLevel" rather than "textureSample" because a compute shader has no derivatives to
|
|
490
|
+
* pick a level from, and the level this wants is named rather than chosen: the irradiance level is
|
|
491
|
+
* the cosine convolution and every other level is a roughness the bounce has no use for.
|
|
492
|
+
*/
|
|
493
|
+
fn probeLayerSample(layer: u32, uv: vec2<f32>) -> vec3<f32> {
|
|
494
|
+
return textureSampleLevel(probeArray, probeSampler, uv, layer, volume.radianceLevel).rgb;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* And the level this bake writes, which is where the second bounce comes from. */
|
|
498
|
+
fn probeLayerIrradiance(layer: u32, uv: vec2<f32>) -> vec3<f32> {
|
|
499
|
+
return textureSampleLevel(probeArray, probeSampler, uv, layer, volume.level).rgb;
|
|
500
|
+
}
|
|
501
|
+
${PROBE_VOLUME_CORE_WGSL}
|
|
502
|
+
|
|
503
|
+
/*
|
|
504
|
+
* The surface normal at a point on the field, as the field's own gradient.
|
|
505
|
+
*
|
|
506
|
+
* Central differences at one voxel, which is the coarsest spacing that is not the sampler's own
|
|
507
|
+
* interpolation error: a narrower stencil on a trilinear field measures the tetrahedron the point
|
|
508
|
+
* happens to sit in rather than the surface it belongs to.
|
|
509
|
+
*/
|
|
510
|
+
fn probeFieldNormal(p: vec3<f32>) -> vec3<f32> {
|
|
511
|
+
let h = march.finestStep;
|
|
512
|
+
let gradient = vec3<f32>(
|
|
513
|
+
probeFieldAt(p + vec3<f32>(h, 0.0, 0.0)) - probeFieldAt(p - vec3<f32>(h, 0.0, 0.0)),
|
|
514
|
+
probeFieldAt(p + vec3<f32>(0.0, h, 0.0)) - probeFieldAt(p - vec3<f32>(0.0, h, 0.0)),
|
|
515
|
+
probeFieldAt(p + vec3<f32>(0.0, 0.0, h)) - probeFieldAt(p - vec3<f32>(0.0, 0.0, h)),
|
|
516
|
+
);
|
|
517
|
+
let reach = length(gradient);
|
|
518
|
+
return select(vec3<f32>(0.0, 1.0, 0.0), gradient / reach, reach > 0.0);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/*
|
|
522
|
+
* Whether the sun reaches a point, by marching the field towards it.
|
|
523
|
+
*
|
|
524
|
+
* **This is what makes the bounce follow a light that moves**, and it is the one thing a rasterised
|
|
525
|
+
* probe bake cannot do without redrawing six faces per probe. A ray that leaves along the normal
|
|
526
|
+
* and travels its whole reach without striking anything is lit.
|
|
527
|
+
*/
|
|
528
|
+
fn probeSunVisible(p: vec3<f32>, n: vec3<f32>) -> f32 {
|
|
529
|
+
let hit = probeTraceField(p, n, bake.sunDir, 0.0);
|
|
530
|
+
return select(1.0, 0.0, hit.hit);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/*
|
|
534
|
+
* What a surface the ray struck sends back towards the probe.
|
|
535
|
+
*
|
|
536
|
+
* **Its albedo times the light reaching it**, which is the whole of Task 8 and the reason the
|
|
537
|
+
* composed field carries a colour at all. A version of this that returned the probe volume's
|
|
538
|
+
* irradiance gave a red wall and a white wall the same answer, because irradiance is light
|
|
539
|
+
* arriving and what leaves a surface is that times its own colour.
|
|
540
|
+
*
|
|
541
|
+
* **The sun is evaluated here rather than read from a capture**, so a light that moves changes the
|
|
542
|
+
* bounce at the next refresh. That is the thing a rasterised bake cannot do and the whole reason
|
|
543
|
+
* this feature exists — see Task 8 of the indirect-light plan for the measurement that said so.
|
|
544
|
+
*
|
|
545
|
+
* Divided by pi because a Lambertian surface spreads what it receives over a hemisphere, and the
|
|
546
|
+
* convolution that consumes these rays is a cosine integral expecting radiance.
|
|
547
|
+
*/
|
|
548
|
+
fn probeSurfaceRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32> {
|
|
549
|
+
let n = probeFieldNormal(p);
|
|
550
|
+
let albedo = probeAlbedoAt(p);
|
|
551
|
+
let facing = max(0.0, dot(n, bake.sunDir));
|
|
552
|
+
let direct = bake.sunColour * facing * probeSunVisible(p, n);
|
|
553
|
+
/*
|
|
554
|
+
* And what has already bounced, which is the level this bake writes — so the loop closes.
|
|
555
|
+
*
|
|
556
|
+
* **Divided by pi once and not twice.** "convolveProbe" normalises by the cosine weights it
|
|
557
|
+
* actually used, so what a probe stores is the cosine-weighted *mean radiance*, which is already
|
|
558
|
+
* the irradiance over pi — the convention a Lambertian's outgoing radiance multiplies directly
|
|
559
|
+
* by its albedo. Only the sun's own irradiance needs the division.
|
|
560
|
+
*/
|
|
561
|
+
let bounced = probeVolumeIrradianceAt(p, n);
|
|
562
|
+
return albedo * (direct * ${String(1 / Math.PI)} + bounced);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/*
|
|
566
|
+
* And what a ray that left the world finds: the sky the frame is lit under.
|
|
567
|
+
*
|
|
568
|
+
* **Not the probe array's own level 0, and that was measured rather than reasoned.** Level 0 is a
|
|
569
|
+
* *rasterised* capture taken once, so a ray reading it gathers the light the room had when the
|
|
570
|
+
* capture was made — and a room whose red wall was repainted blue went on bouncing red, because
|
|
571
|
+
* most of its rays escaped through the opening and read the capture. With the sky here instead,
|
|
572
|
+
* **nothing in the trace reads anything rasterised**, which is what lets the whole solution follow
|
|
573
|
+
* a scene that changes and is the sentence Wave 4's criterion asks for.
|
|
574
|
+
*
|
|
575
|
+
* No albedo and no sun: nothing was struck, so there is no surface to shade.
|
|
576
|
+
*/
|
|
577
|
+
fn probeDistantRadiance(p: vec3<f32>, towards: vec3<f32>) -> vec3<f32> {
|
|
578
|
+
return bake.skyColour;
|
|
579
|
+
}
|
|
580
|
+
${PROBE_TRACE_CORE_WGSL}
|
|
581
|
+
${PROBE_BAKE_CORE_WGSL}
|
|
582
|
+
${PROBE_BAKE_ENTRIES_WGSL}
|
|
583
|
+
`;
|
|
584
|
+
}
|
|
585
|
+
/** Bytes between one blit's parameters and the next. A dynamic offset must be 256-aligned. */
|
|
586
|
+
export const PROBE_BLIT_STRIDE = 256;
|
|
587
|
+
/** Floats a blit's parameters hold: the slot, the map's edge, and the target level's edge. */
|
|
588
|
+
export const PROBE_BLIT_FLOATS = 4;
|
|
589
|
+
/**
|
|
590
|
+
* Carrying a baked map into the probe array the shading already samples.
|
|
591
|
+
*
|
|
592
|
+
* **A render pass rather than a storage write, because the probe array is not a storage texture.**
|
|
593
|
+
* It is created with `RENDER_ATTACHMENT | TEXTURE_BINDING` and its format is the surface's, which
|
|
594
|
+
* needs a device feature before it can be written from compute. Rendering into one mip of one layer
|
|
595
|
+
* needs neither, and the array already carries the usage for it.
|
|
596
|
+
*
|
|
597
|
+
* **The target edge is a parameter because not every level is the irradiance level.** A layer the
|
|
598
|
+
* scene has never rasterised holds undefined contents in its roughness chain, and a fragment
|
|
599
|
+
* sampling that is worse than a fragment sampling a flat colour. So a layer being filled for the
|
|
600
|
+
* first time has every level written from the same map — blocky at level 0, which is the honest
|
|
601
|
+
* thing a diffuse solution can say about a mirror — and every frame after that writes only the
|
|
602
|
+
* irradiance level.
|
|
603
|
+
*/
|
|
604
|
+
export const PROBE_BLIT_WGSL = /* wgsl */ `
|
|
605
|
+
struct Blit {
|
|
606
|
+
/* Which slot of this frame's dispatch, which is where its map starts in the buffer. */
|
|
607
|
+
slot: f32,
|
|
608
|
+
/* Texels across the baked map. */
|
|
609
|
+
edge: f32,
|
|
610
|
+
/* Texels across the level being written. Not named "target": WGSL reserves that word. */
|
|
611
|
+
levelEdge: f32,
|
|
612
|
+
pad: f32,
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
@group(0) @binding(0) var<uniform> blit: Blit;
|
|
616
|
+
@group(0) @binding(1) var<storage, read> irradiance: array<f32>;
|
|
617
|
+
|
|
618
|
+
@vertex
|
|
619
|
+
fn blitVert(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> {
|
|
620
|
+
/* One triangle that covers the target, which needs no vertex buffer and no index buffer. */
|
|
621
|
+
let x = f32((index << 1u) & 2u) * 2.0 - 1.0;
|
|
622
|
+
let y = f32(index & 2u) * 2.0 - 1.0;
|
|
623
|
+
return vec4<f32>(x, y, 0.0, 1.0);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
@fragment
|
|
627
|
+
fn blitFrag(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {
|
|
628
|
+
let edge = u32(blit.edge);
|
|
629
|
+
/* Nearest rather than filtered: the source is eight texels and the seams are the octahedron's. */
|
|
630
|
+
let u = min(u32(position.x * blit.edge / blit.levelEdge), edge - 1u);
|
|
631
|
+
let v = min(u32(position.y * blit.edge / blit.levelEdge), edge - 1u);
|
|
632
|
+
let at = (u32(blit.slot) * edge * edge + v * edge + u) * ${String(PROBE_TEXEL_FLOATS)}u;
|
|
633
|
+
return vec4<f32>(irradiance[at], irradiance[at + 1u], irradiance[at + 2u], 1.0);
|
|
634
|
+
}
|
|
635
|
+
`;
|