@meimu/mutracer 0.2.0 → 0.3.1
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.md +222 -0
- package/LICENSES/FidelityFX-FSR.txt +19 -0
- package/LICENSES/pbrt-v4-Apache-2.0.txt +202 -0
- package/README.md +3 -0
- package/THIRD_PARTY_NOTICES.md +18 -4
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +9328 -7614
- package/docs/api.md +83 -35
- package/docs/caustics.md +80 -0
- package/docs/materials.md +9 -6
- package/docs/performance.md +7 -0
- package/docs/post-processing.md +62 -9
- package/docs/progressive-rendering.md +53 -9
- package/docs/sampling.md +4 -0
- package/docs/technical/acceleration.md +52 -0
- package/docs/technical/aovs.md +79 -0
- package/docs/technical/caustics.md +109 -0
- package/docs/technical/continuous-spectrum.md +122 -0
- package/docs/technical/display-upscale.md +82 -0
- package/docs/technical/openpbr.md +99 -0
- package/docs/technical/pbrt-import.md +168 -0
- package/docs/technical/tiling.md +74 -0
- package/docs/technical/webgl.md +53 -0
- package/docs/updates.md +3 -1
- package/docs/viewer-checklist.md +65 -0
- package/docs/viewer.md +18 -6
- package/lib/Display.d.ts +19 -5
- package/lib/Display.js +10 -7
- package/lib/Display.js.map +1 -1
- package/lib/Engine.d.ts +4 -1
- package/lib/Engine.js +77 -14
- package/lib/Engine.js.map +1 -1
- package/lib/Render.d.ts +81 -24
- package/lib/Render.js +201 -26
- package/lib/Render.js.map +1 -1
- package/lib/RenderLoop.d.ts +7 -3
- package/lib/RenderLoop.js +68 -44
- package/lib/RenderLoop.js.map +1 -1
- package/lib/main.d.ts +4 -2
- package/lib/main.js +1 -0
- package/lib/main.js.map +1 -1
- package/lib/oidn.d.ts +1 -1
- package/lib/renderer/AovPrimitiveCache.js +39 -8
- package/lib/renderer/AovPrimitiveCache.js.map +1 -1
- package/lib/renderer/DisplayPostProcess.d.ts +8 -3
- package/lib/renderer/DisplayPostProcess.js +51 -18
- package/lib/renderer/DisplayPostProcess.js.map +1 -1
- package/lib/renderer/GPUTraceQueue.d.ts +2 -1
- package/lib/renderer/GPUTraceQueue.js +3 -1
- package/lib/renderer/GPUTraceQueue.js.map +1 -1
- package/lib/renderer/MaterialData.js +7 -2
- package/lib/renderer/MaterialData.js.map +1 -1
- package/lib/renderer/RenderingPipeline.d.ts +11 -1
- package/lib/renderer/RenderingPipeline.js.map +1 -1
- package/lib/renderer/TileRender.d.ts +2 -2
- package/lib/renderer/TileRender.js +128 -7
- package/lib/renderer/TileRender.js.map +1 -1
- package/lib/renderer/WGPUAov.d.ts +6 -1
- package/lib/renderer/WGPUAov.js +108 -14
- package/lib/renderer/WGPUAov.js.map +1 -1
- package/lib/renderer/WGPUCanvasSurface.js +12 -1
- package/lib/renderer/WGPUCanvasSurface.js.map +1 -1
- package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
- package/lib/renderer/WGPUCausticLightPass.js +217 -0
- package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
- package/lib/renderer/WGPUEnvMapDistribution.js +19 -19
- package/lib/renderer/WGPUFullQuadPass.js +3 -3
- package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
- package/lib/renderer/WGPUPhotonMap.js +191 -0
- package/lib/renderer/WGPUPhotonMap.js.map +1 -0
- package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
- package/lib/renderer/WGPURayTraceOutput.js +122 -28
- package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
- package/lib/renderer/WGPURayTracePass.d.ts +65 -21
- package/lib/renderer/WGPURayTracePass.js +730 -409
- package/lib/renderer/WGPURayTracePass.js.map +1 -1
- package/lib/renderer/WGPURenderingPipeline.js +110 -68
- package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLAov.js +3 -3
- package/lib/renderer/WebGLAov.js.map +1 -1
- package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
- package/lib/renderer/WebGLFullQuadPass.js +10 -0
- package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
- package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
- package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -1
- package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
- package/lib/renderer/WebGLRayTracePass.d.ts +20 -11
- package/lib/renderer/WebGLRayTracePass.js +157 -116
- package/lib/renderer/WebGLRayTracePass.js.map +1 -1
- package/lib/renderer/WebGLRenderingPipeline.js +17 -14
- package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLSkyBaker.js +60 -60
- package/lib/renderer/accumulationTarget.d.ts +2 -1
- package/lib/renderer/accumulationTarget.js.map +1 -1
- package/lib/renderer/aov.d.ts +13 -1
- package/lib/renderer/aov.js +21 -2
- package/lib/renderer/aov.js.map +1 -1
- package/lib/renderer/aovDisplay.js +2 -2
- package/lib/renderer/aovDisplay.js.map +1 -1
- package/lib/renderer/binnedSahBvh.d.ts +2 -1
- package/lib/renderer/binnedSahBvh.js +24 -17
- package/lib/renderer/binnedSahBvh.js.map +1 -1
- package/lib/renderer/bvhAccel.d.ts +3 -1
- package/lib/renderer/bvhAccel.js +36 -6
- package/lib/renderer/bvhAccel.js.map +1 -1
- package/lib/renderer/cameraUniform.d.ts +1 -1
- package/lib/renderer/cameraUniform.js +6 -2
- package/lib/renderer/cameraUniform.js.map +1 -1
- package/lib/renderer/causticDiagnostics.d.ts +31 -0
- package/lib/renderer/causticDiagnostics.js +5 -0
- package/lib/renderer/causticDiagnostics.js.map +1 -0
- package/lib/renderer/causticEmission.d.ts +10 -0
- package/lib/renderer/causticEmission.js +40 -0
- package/lib/renderer/causticEmission.js.map +1 -0
- package/lib/renderer/causticUniform.d.ts +12 -0
- package/lib/renderer/causticUniform.js +22 -0
- package/lib/renderer/causticUniform.js.map +1 -0
- package/lib/renderer/caustics.d.ts +16 -0
- package/lib/renderer/caustics.js +93 -0
- package/lib/renderer/caustics.js.map +1 -0
- package/lib/renderer/compactVertices.d.ts +14 -0
- package/lib/renderer/compactVertices.js +124 -0
- package/lib/renderer/compactVertices.js.map +1 -0
- package/lib/renderer/easu.d.ts +11 -0
- package/lib/renderer/easu.js +156 -0
- package/lib/renderer/easu.js.map +1 -0
- package/lib/renderer/geometryUpdateState.d.ts +1 -0
- package/lib/renderer/geometryUpdateState.js.map +1 -1
- package/lib/renderer/geometryUpdates.js +3 -2
- package/lib/renderer/geometryUpdates.js.map +1 -1
- package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
- package/lib/renderer/glsl/aovReflectance.glsl.js +53 -53
- package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
- package/lib/renderer/glsl/aovTrace.frag.js +45 -37
- package/lib/renderer/glsl/aovTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.d.ts +2 -0
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.js +16 -0
- package/lib/renderer/glsl/chunks/cameraProjection.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/cameraRay.glsl.d.ts +1 -1
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -16
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -73
- package/lib/renderer/glsl/chunks/intersect.glsl.js +478 -465
- package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +156 -153
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +771 -771
- package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
- package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +70 -63
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js +89 -75
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +160 -118
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +14 -14
- package/lib/renderer/glsl/chunks/sampling.glsl.js +88 -91
- package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
- package/lib/renderer/glsl/chunks/spectralPrefix.glsl.d.ts +6 -0
- package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js +47 -0
- package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
- package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
- package/lib/renderer/glsl/depthQuery.frag.js +13 -7
- package/lib/renderer/glsl/depthQuery.frag.js.map +1 -1
- package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
- package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
- package/lib/renderer/glsl/rayTrace.frag.js +338 -252
- package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/shadowCatcher.frag.js +27 -26
- package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
- package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
- package/lib/renderer/gpuMemory.js +1 -1
- package/lib/renderer/gpuMemory.js.map +1 -1
- package/lib/renderer/lightSampling.d.ts +3 -2
- package/lib/renderer/lightSampling.js +14 -8
- package/lib/renderer/lightSampling.js.map +1 -1
- package/lib/renderer/openPbrEnergyLut.d.ts +2 -1
- package/lib/renderer/openPbrEnergyLut.js +18 -1
- package/lib/renderer/openPbrEnergyLut.js.map +1 -1
- package/lib/renderer/pathLimits.d.ts +5 -4
- package/lib/renderer/pathLimits.js +6 -5
- package/lib/renderer/pathLimits.js.map +1 -1
- package/lib/renderer/prepareMeshBVH.d.ts +2 -0
- package/lib/renderer/prepareMeshBVH.js +5 -3
- package/lib/renderer/prepareMeshBVH.js.map +1 -1
- package/lib/renderer/progressiveScheduler.js +5 -4
- package/lib/renderer/progressiveScheduler.js.map +1 -1
- package/lib/renderer/renderPipeline.d.ts +5 -0
- package/lib/renderer/renderPipeline.js +1 -0
- package/lib/renderer/renderPipeline.js.map +1 -1
- package/lib/renderer/researchTransport.d.ts +31 -0
- package/lib/renderer/researchTransport.js +5 -0
- package/lib/renderer/researchTransport.js.map +1 -0
- package/lib/renderer/sceneAssembly.d.ts +1 -1
- package/lib/renderer/sceneAssembly.js +4 -3
- package/lib/renderer/sceneAssembly.js.map +1 -1
- package/lib/renderer/sceneChangeDetector.d.ts +1 -1
- package/lib/renderer/sceneChangeDetector.js +3 -1
- package/lib/renderer/sceneChangeDetector.js.map +1 -1
- package/lib/renderer/sceneLayoutSchema.d.ts +2 -0
- package/lib/renderer/sceneLayoutSchema.js +3 -3
- package/lib/renderer/sceneLayoutSchema.js.map +1 -1
- package/lib/renderer/scenePreparation.d.ts +3 -2
- package/lib/renderer/scenePreparation.js +18 -2
- package/lib/renderer/scenePreparation.js.map +1 -1
- package/lib/renderer/scratchSampling.js +6 -6
- package/lib/renderer/spectrum.d.ts +21 -0
- package/lib/renderer/spectrum.js +154 -0
- package/lib/renderer/spectrum.js.map +1 -0
- package/lib/renderer/staticBvhPreparation.d.ts +9 -0
- package/lib/renderer/staticBvhPreparation.js +8 -2
- package/lib/renderer/staticBvhPreparation.js.map +1 -1
- package/lib/renderer/surfaceHit.d.ts +10 -0
- package/lib/renderer/surfaceHit.js +22 -0
- package/lib/renderer/surfaceHit.js.map +1 -0
- package/lib/renderer/tangentRequirements.d.ts +2 -0
- package/lib/renderer/tangentRequirements.js +7 -0
- package/lib/renderer/tangentRequirements.js.map +1 -0
- package/lib/renderer/textureAtlas.d.ts +4 -0
- package/lib/renderer/textureAtlas.js +48 -2
- package/lib/renderer/textureAtlas.js.map +1 -1
- package/lib/renderer/webgl.js +4 -0
- package/lib/renderer/webgl.js.map +1 -1
- package/lib/renderer/webglLightSampling.d.ts +39 -0
- package/lib/renderer/webglLightSampling.js +15 -0
- package/lib/renderer/webglLightSampling.js.map +1 -0
- package/lib/renderer/webglVertexAttributes.d.ts +17 -0
- package/lib/renderer/webglVertexAttributes.js +44 -0
- package/lib/renderer/webglVertexAttributes.js.map +1 -0
- package/lib/renderer/wgpuBindlessSource.js +3 -3
- package/lib/renderer/wgpuMaterialConstants.js +4 -3
- package/lib/renderer/wgpuMaterialConstants.js.map +1 -1
- package/lib/renderer/wgpuSceneUniform.js +2 -2
- package/lib/renderer/wgpuSceneUniform.js.map +1 -1
- package/lib/renderer/wgpuShaderSource.d.ts +22 -0
- package/lib/renderer/wgpuShaderSource.js +3 -1
- package/lib/renderer/wgpuShaderSource.js.map +1 -1
- package/lib/renderer/wgsl/aovCompact.wgsl.d.ts +4 -0
- package/lib/renderer/wgsl/aovCompact.wgsl.js +26 -0
- package/lib/renderer/wgsl/aovCompact.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
- package/lib/renderer/wgsl/aovReflectance.wgsl.js +55 -55
- package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
- package/lib/renderer/wgsl/aovResolve.wgsl.d.ts +4 -0
- package/lib/renderer/wgsl/aovResolve.wgsl.js +21 -2
- package/lib/renderer/wgsl/aovResolve.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js +2 -2
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js +56 -0
- package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
- package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
- package/lib/renderer/wgsl/causticLight.compute.js +539 -0
- package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
- package/lib/renderer/wgsl/chunks/envMap.wgsl.js +37 -37
- package/lib/renderer/wgsl/chunks/intersect.wgsl.d.ts +6 -2
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js +634 -620
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +23 -21
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +3 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +77 -74
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +795 -795
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +139 -139
- package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +5 -1
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +45 -29
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +142 -138
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +228 -189
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +11 -11
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js +88 -92
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
- package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.d.ts +6 -0
- package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js +66 -0
- package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js +61 -0
- package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/compose.frag.wgsl.js +100 -98
- package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
- package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/depthQuery.wgsl.js +12 -3
- package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/easu.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/easu.wgsl.js +80 -0
- package/lib/renderer/wgsl/easu.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.d.ts +21 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.js +230 -0
- package/lib/renderer/wgsl/easuDetail.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/jnlm.compute.js +74 -74
- package/lib/renderer/wgsl/jnlm.compute.js.map +1 -1
- package/lib/renderer/wgsl/offlineAdaptive.compute.js +26 -26
- package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
- package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
- package/lib/renderer/wgsl/photonMap.compute.js +352 -0
- package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
- package/lib/renderer/wgsl/proceduralSky.compute.js +64 -64
- package/lib/renderer/wgsl/rayTrace.compute.d.ts +23 -1
- package/lib/renderer/wgsl/rayTrace.compute.js +531 -543
- package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
- package/lib/scene/MaterialState.d.ts +2 -2
- package/lib/scene/MaterialState.js +2 -2
- package/lib/scene/MaterialState.js.map +1 -1
- package/lib/scene/Scene.js.map +1 -1
- package/lib/settings/render.d.ts +22 -0
- package/lib/settings/render.js +2 -0
- package/lib/settings/render.js.map +1 -1
- package/lib/util/fromTHREE.js +23 -16
- package/lib/util/fromTHREE.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/llms.txt +2 -1
- package/package.json +7 -4
- package/lib/oidnBundledWeights.cdn.d.ts +0 -2
- package/lib/oidnBundledWeights.cdn.js +0 -5
- package/lib/oidnBundledWeights.cdn.js.map +0 -1
- package/lib/renderer/bvh.worker.d.ts +0 -1
- package/lib/renderer/bvh.worker.js +0 -16
- package/lib/renderer/bvh.worker.js.map +0 -1
- package/lib/renderer/bvhWorkerBuild.d.ts +0 -13
- package/lib/renderer/bvhWorkerBuild.js +0 -17
- package/lib/renderer/bvhWorkerBuild.js.map +0 -1
- package/lib/renderer/lightCore.d.ts +0 -12
- package/lib/renderer/lightCore.js +0 -13
- package/lib/renderer/lightCore.js.map +0 -1
- package/lib/renderer/materialCore.d.ts +0 -8
- package/lib/renderer/materialCore.js +0 -9
- package/lib/renderer/materialCore.js.map +0 -1
- package/lib/scene/PixelTexture.d.ts +0 -6
- package/lib/scene/PixelTexture.js +0 -10
- package/lib/scene/PixelTexture.js.map +0 -1
- package/lib/util/studioLighting.d.ts +0 -18
- package/lib/util/studioLighting.js +0 -36
- package/lib/util/studioLighting.js.map +0 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { easuShader } from './easu.wgsl.js';
|
|
2
|
+
import { composeDefine } from './compose.frag.wgsl.js';
|
|
3
|
+
/** Scene-linear log2 range covered by the tone lookup table. */
|
|
4
|
+
export const TONE_LUT_LOG2 = [-24, 24];
|
|
5
|
+
export const TONE_LUT_SIZE = 1024;
|
|
6
|
+
/** Detail compensation defaults and fixed regularizers shared by the Web and native passes. */
|
|
7
|
+
export const EASU_DETAIL = {
|
|
8
|
+
ratioRange: [0.7, 1.43], depthThreshold: 0.02, coverageThreshold: 0.95,
|
|
9
|
+
// Albedo below this is treated as black; relative depth tolerance floor.
|
|
10
|
+
minimumAlbedo: 1e-3, depthTolerance: 1e-4,
|
|
11
|
+
// Regularizes the local albedo variance of the albedo-radiance fit, in squared albedo units.
|
|
12
|
+
guidedEpsilon: 1e-4,
|
|
13
|
+
};
|
|
14
|
+
// Tone table lookups and display encoding shared by the detail passes; tone.x is the maximum display value.
|
|
15
|
+
const toneCodingDefine = /* wgsl */ `
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@group(0) @binding(8) var toneLut: texture_2d<f32>;
|
|
19
|
+
@group(0) @binding(9) var<uniform> tone: vec4f;
|
|
20
|
+
fn vZ(row: i32, u: vec3f) -> vec3f {
|
|
21
|
+
let last=f32(textureDimensions(toneLut).x-1u);let p=clamp(u,vec3f(0),vec3f(1))*last;
|
|
22
|
+
let i=vec3i(floor(p));let j=min(i+1,vec3i(i32(last)));let f=p-floor(p);
|
|
23
|
+
var result=vec3f(0);
|
|
24
|
+
for(var c=0;c<3;c++){result[c]=mix(textureLoad(toneLut,vec2i(i[c],row),0)[c],textureLoad(toneLut,vec2i(j[c],row),0)[c],f[c]);}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
fn uM(AK: vec3f) -> vec3f {return vZ(0,(AK-(${TONE_LUT_LOG2[0]}.0))/${TONE_LUT_LOG2[1] - TONE_LUT_LOG2[0]}.0);}
|
|
28
|
+
fn uN(d: vec3f) -> vec3f {return vZ(1,sqrt(max(d,vec3f(0))/tone.x));}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
fn zk(d: vec3f, fw: vec3f) -> vec3f {
|
|
34
|
+
let x=uN(d);
|
|
35
|
+
let changed=max(d+uM(x+log2(max(fw,vec3f(1e-6))))-uM(x),vec3f(0));
|
|
36
|
+
return select(changed,d,abs(fw-vec3f(1))<vec3f(1e-4));
|
|
37
|
+
}
|
|
38
|
+
fn pS(c: vec3f) -> vec3f {
|
|
39
|
+
let a=max(c,vec3f(0));
|
|
40
|
+
return select(pow((a+0.055)/1.055,vec3f(2.4)),a/12.92,a<=vec3f(0.04045));
|
|
41
|
+
}
|
|
42
|
+
fn tp(c: vec3f) -> vec3f {
|
|
43
|
+
let a=max(c,vec3f(0));
|
|
44
|
+
return select(1.055*pow(a,vec3f(1.0/2.4))-0.055,a*12.92,a<=vec3f(0.0031308));
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
/**
|
|
48
|
+
* guided: scale only the albedo-correlated part of local scene-linear radiance (default);
|
|
49
|
+
* separate: lobe-share approximation; silhouettes: depth-guided edge reconstruction.
|
|
50
|
+
* The last two are retained for comparisons.
|
|
51
|
+
*/
|
|
52
|
+
export function easuDetailShader(separate = false, silhouettes = false, guided = true) {
|
|
53
|
+
return easuShader.replace(`@vertex fn vertex`, /* wgsl */ `
|
|
54
|
+
@group(0) @binding(2) var diffuseHi: texture_2d<f32>;
|
|
55
|
+
@group(0) @binding(3) var specularHi: texture_2d<f32>;
|
|
56
|
+
@group(0) @binding(4) var depthHi: texture_2d<f32>;
|
|
57
|
+
@group(0) @binding(5) var diffuseLo: texture_2d<f32>;
|
|
58
|
+
@group(0) @binding(6) var specularLo: texture_2d<f32>;
|
|
59
|
+
@group(0) @binding(7) var<uniform> detail: vec4f;
|
|
60
|
+
${silhouettes ? '@group(0) @binding(10) var depthLo: texture_2d<f32>;' : ''}
|
|
61
|
+
${guided ? '@group(0) @binding(11) var slopeLo: texture_2d<f32>;\n@group(0) @binding(12) var offsetLo: texture_2d<f32>;' : ''}
|
|
62
|
+
fn cY(image: texture_2d<f32>, p: vec2i) -> vec4f {
|
|
63
|
+
return textureLoad(image,clamp(p,vec2i(0),vec2i(textureDimensions(image))-1),0);
|
|
64
|
+
}
|
|
65
|
+
${toneCodingDefine}
|
|
66
|
+
@vertex fn vertex`).replace(`return clamp(result/total,lower,upper);`, /* wgsl */ `
|
|
67
|
+
let color=clamp(result/total,lower,upper);
|
|
68
|
+
var diffuse=vec4f(0);var specular=vec4f(0);
|
|
69
|
+
for(var i=0u;i<12u;i++){
|
|
70
|
+
|
|
71
|
+
let weight=sJ(vec2f(offsets[i])-phase,direction,length,lobe);
|
|
72
|
+
diffuse+=cY(diffuseLo,base+offsets[i])*weight;
|
|
73
|
+
specular+=cY(specularLo,base+offsets[i])*weight;
|
|
74
|
+
}
|
|
75
|
+
let dlo=min(min(cY(diffuseLo,base),cY(diffuseLo,base+vec2i(1,0))),min(cY(diffuseLo,base+vec2i(0,1)),cY(diffuseLo,base+vec2i(1,1))));
|
|
76
|
+
let dhi=max(max(cY(diffuseLo,base),cY(diffuseLo,base+vec2i(1,0))),max(cY(diffuseLo,base+vec2i(0,1)),cY(diffuseLo,base+vec2i(1,1))));
|
|
77
|
+
let slo=min(min(cY(specularLo,base),cY(specularLo,base+vec2i(1,0))),min(cY(specularLo,base+vec2i(0,1)),cY(specularLo,base+vec2i(1,1))));
|
|
78
|
+
let shi=max(max(cY(specularLo,base),cY(specularLo,base+vec2i(1,0))),max(cY(specularLo,base+vec2i(0,1)),cY(specularLo,base+vec2i(1,1))));
|
|
79
|
+
diffuse=clamp(diffuse/total,dlo,dhi);specular=clamp(specular/total,slo,shi);
|
|
80
|
+
${guided ? `var slope=vec4f(0);var pn=vec4f(0);
|
|
81
|
+
for(var i=0u;i<12u;i++){
|
|
82
|
+
let weight=sJ(vec2f(offsets[i])-phase,direction,length,lobe);
|
|
83
|
+
slope+=cY(slopeLo,base+offsets[i])*weight;pn+=cY(offsetLo,base+offsets[i])*weight;
|
|
84
|
+
}
|
|
85
|
+
var qk=cY(slopeLo,base);var te=qk;var kI=cY(offsetLo,base);var ow=kI;
|
|
86
|
+
for(var k=1;k<4;k++){
|
|
87
|
+
let q=base+vec2i(k&1,k>>1u);let a=cY(slopeLo,q);let b=cY(offsetLo,q);
|
|
88
|
+
qk=min(qk,a);te=max(te,a);kI=min(kI,b);ow=max(ow,b);
|
|
89
|
+
}
|
|
90
|
+
slope=clamp(slope/total,qk,te);pn=clamp(pn/total,kI,ow);` : ''}
|
|
91
|
+
let p=vec2i(position.xy);let size=vec2i(textureDimensions(depthHi));
|
|
92
|
+
let d=textureLoad(diffuseHi,p,0);let s=textureLoad(specularHi,p,0);let z=textureLoad(depthHi,p,0).xy;
|
|
93
|
+
let sf=max(detail.w,detail.z*abs(z.x));
|
|
94
|
+
let radius=max(vec2i(1),vec2i(ceil(outputSize.xy/vec2f(textureDimensions(colorImage)))));
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
var silhouette=false;let inverse=1.0/max(z.x,1e-6);
|
|
98
|
+
let directions=array<vec2i,4>(vec2i(1,0),vec2i(0,1),vec2i(1,1),vec2i(1,-1));
|
|
99
|
+
for(var k=0;k<4;k++){
|
|
100
|
+
let a=textureLoad(depthHi,clamp(p+directions[k]*radius,vec2i(0),size-1),0).xy;
|
|
101
|
+
let b=textureLoad(depthHi,clamp(p-directions[k]*radius,vec2i(0),size-1),0).xy;
|
|
102
|
+
silhouette=silhouette || abs(a.y-z.y)>0.5 || abs(b.y-z.y)>0.5 ||
|
|
103
|
+
(min(min(a.y,b.y),z.y)>0.0 && abs(1.0/max(a.x,1e-6)+1.0/max(b.x,1e-6)-2.0*inverse)>detail.z*inverse);
|
|
104
|
+
}
|
|
105
|
+
${silhouettes ? `
|
|
106
|
+
let guided=min(d.a,s.a)>=detail.y;
|
|
107
|
+
if(silhouette && z.y>=0.5 && !guided){return color;}
|
|
108
|
+
if(silhouette){
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
let center=vec2i(round(point));var front=vec4f(0);var back=vec4f(0);var oK=0.0;var pO=0.0;
|
|
113
|
+
for(var y=-1;y<=1;y++){for(var x=-1;x<=1;x++){
|
|
114
|
+
let q=center+vec2i(x,y);let offset=vec2f(q)-point;
|
|
115
|
+
let rB=cY(depthLo,q).xy;let yO=exp(-dot(offset,offset));
|
|
116
|
+
let Ab=exp(-pow(abs(rB.x-z.x)/sf,2.0));
|
|
117
|
+
let sample=ns(q);let value=vec4f(pS(sample.rgb),sample.a);
|
|
118
|
+
let f=yO*rB.y*rB.y*select(1.0,Ab,z.y>0.0);let b=yO*(1.0-rB.y)*(1.0-rB.y);
|
|
119
|
+
front+=value*f;oK+=f;back+=value*b;pO+=b;
|
|
120
|
+
}}
|
|
121
|
+
let ug=oK>1e-3;let xs=pO>1e-3;
|
|
122
|
+
if(!ug && !xs){return color;}
|
|
123
|
+
var zE=select(back/max(pO,1e-8),front/max(oK,1e-8),ug);
|
|
124
|
+
if(ug && xs){zE=mix(back/pO,front/oK,z.y);}
|
|
125
|
+
return vec4f(tp(zE.rgb),zE.a);
|
|
126
|
+
}
|
|
127
|
+
` : ' if(silhouette){return color;}\n'} let valid=min(min(d.a,s.a),min(diffuse.a,specular.a))>=detail.y && z.y>=detail.y;
|
|
128
|
+
if(!valid){return color;}
|
|
129
|
+
let low=diffuse.rgb+specular.rgb;
|
|
130
|
+
var fw=clamp((d.rgb+s.rgb)/max(low,vec3f(detail.x)),vec3f(outputSize.z),vec3f(outputSize.w));
|
|
131
|
+
${separate ? `
|
|
132
|
+
|
|
133
|
+
let share=diffuse.rgb/max(low,vec3f(detail.x));
|
|
134
|
+
let rd=clamp(d.rgb/max(diffuse.rgb,vec3f(detail.x)),vec3f(outputSize.z),vec3f(outputSize.w));
|
|
135
|
+
let rs=clamp(s.rgb/max(specular.rgb,vec3f(detail.x)),vec3f(outputSize.z),vec3f(outputSize.w));
|
|
136
|
+
fw=share*rd+(vec3f(1)-share)*rs;` : ''}
|
|
137
|
+
${guided ? `
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
let yJ=slope.rgb*low+pn.rgb;
|
|
141
|
+
fw=select(fw,clamp((slope.rgb*(d.rgb+s.rgb)+pn.rgb)/max(yJ,vec3f(1e-8)),vec3f(outputSize.z),vec3f(outputSize.w)),yJ>vec3f(1e-8));` : ''}
|
|
142
|
+
fw=select(vec3f(1),fw,low>vec3f(detail.x));
|
|
143
|
+
|
|
144
|
+
return vec4f(tp(zk(pS(color.rgb),fw)),color.a);`);
|
|
145
|
+
}
|
|
146
|
+
// Exact box overlap integrates noninteger footprints instead of point sampling.
|
|
147
|
+
export const albedoDownsampleShader = (depthOutput = false) => /* wgsl */ `
|
|
148
|
+
@group(0) @binding(0) var diffuse: texture_2d<f32>;
|
|
149
|
+
@group(0) @binding(1) var specular: texture_2d<f32>;
|
|
150
|
+
@group(0) @binding(2) var<uniform> outputSize: vec4f;
|
|
151
|
+
@group(0) @binding(3) var depth: texture_2d<f32>;
|
|
152
|
+
@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
|
|
153
|
+
let p=vec2f(f32((index<<1u)&2u),f32(index&2u));return vec4f(p*2.0-1.0,0,1);
|
|
154
|
+
}
|
|
155
|
+
struct uS {@location(0) diffuse: vec4f, @location(1) specular: vec4f, ${depthOutput ? '@location(2) depth: vec4f,' : ''}}
|
|
156
|
+
@fragment fn fragment(@builtin(position) p: vec4f) -> uS {
|
|
157
|
+
let scale=vec2f(textureDimensions(diffuse))/outputSize.xy;
|
|
158
|
+
let start=floor(p.xy)*scale;let end=start+scale;
|
|
159
|
+
var d=vec4f(0);var s=vec4f(0);var nearest=3.4e38;var hits=0.0;
|
|
160
|
+
for(var y=i32(floor(start.y));y<i32(ceil(end.y));y++){
|
|
161
|
+
for(var x=i32(floor(start.x));x<i32(ceil(end.x));x++){
|
|
162
|
+
let tK=max(vec2f(0),min(vec2f(vec2i(x+1,y+1)),end)-max(vec2f(vec2i(x,y)),start));let weight=tK.x*tK.y;
|
|
163
|
+
let a=textureLoad(diffuse,vec2i(x,y),0);let b=textureLoad(specular,vec2i(x,y),0);let z=textureLoad(depth,vec2i(x,y),0).xy;
|
|
164
|
+
d+=vec4f(a.rgb*a.a,a.a)*weight;s+=vec4f(b.rgb*b.a,b.a)*weight;
|
|
165
|
+
hits+=z.y*weight;if(z.y>0.0){nearest=min(nearest,z.x);}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
var result: uS;
|
|
169
|
+
${depthOutput ? 'result.depth=vec4f(select(0.0,nearest,hits>0.0),hits/(scale.x*scale.y),0,0);' : ''}
|
|
170
|
+
result.diffuse=vec4f(d.rgb/max(d.a,1e-8),d.a/(scale.x*scale.y));
|
|
171
|
+
result.specular=vec4f(s.rgb/max(s.a,1e-8),s.a/(scale.x*scale.y));return result;
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
// Builds the tone lookup rows for the current grading and tone mapping.
|
|
175
|
+
// toneMappingParams: x mode, y maximum display value, z exposure, w 1 for extended output.
|
|
176
|
+
export const toneLutShader = /* wgsl */ `
|
|
177
|
+
@group(0) @binding(0) var<uniform> toneMappingParams: vec4f;
|
|
178
|
+
@group(0) @binding(1) var<uniform> colorGradingParams: vec4f;
|
|
179
|
+
${composeDefine}
|
|
180
|
+
fn xP(x: f32) -> vec3f {
|
|
181
|
+
var v=fm(vec3f(x)*toneMappingParams.z);
|
|
182
|
+
if(toneMappingParams.w<0.5){v=bH(v);}
|
|
183
|
+
return v;
|
|
184
|
+
}
|
|
185
|
+
@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
|
|
186
|
+
let p=vec2f(f32((index<<1u)&2u),f32(index&2u));return vec4f(p*2.0-1.0,0,1);
|
|
187
|
+
}
|
|
188
|
+
@fragment fn fragment(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
189
|
+
let u=floor(p.x)/${TONE_LUT_SIZE - 1}.0;
|
|
190
|
+
if(p.y<1.0){return vec4f(xP(exp2(mix(${TONE_LUT_LOG2[0]}.0,${TONE_LUT_LOG2[1]}.0,u))),1);}
|
|
191
|
+
let d=u*u*toneMappingParams.y;var result=vec3f(0);
|
|
192
|
+
for(var c=0;c<3;c++){
|
|
193
|
+
var lo=${TONE_LUT_LOG2[0]}.0;var hi=${TONE_LUT_LOG2[1]}.0;
|
|
194
|
+
for(var i=0;i<28;i++){let mid=0.5*(lo+hi);if(xP(exp2(mid))[c]<d){lo=mid;}else{hi=mid;}}
|
|
195
|
+
result[c]=0.5*(lo+hi);
|
|
196
|
+
}
|
|
197
|
+
return vec4f(result,1);
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
// Per low-resolution pixel, fits scene-linear radiance x ~ slope * albedo + offset over a
|
|
201
|
+
// 3x3 window of guided samples, per channel (a guided-filter regression). Flat albedo
|
|
202
|
+
// gives slope ~0, so its radiance is not modulated; tone.y regularizes the variance.
|
|
203
|
+
export const guidedCoefficientShader = /* wgsl */ `
|
|
204
|
+
@group(0) @binding(0) var colorImage: texture_2d<f32>;
|
|
205
|
+
@group(0) @binding(1) var diffuseLo: texture_2d<f32>;
|
|
206
|
+
@group(0) @binding(2) var specularLo: texture_2d<f32>;
|
|
207
|
+
${toneCodingDefine}
|
|
208
|
+
fn yg(image: texture_2d<f32>, p: vec2i) -> vec4f {return textureLoad(image,clamp(p,vec2i(0),vec2i(textureDimensions(image))-1),0);}
|
|
209
|
+
@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
|
|
210
|
+
let p=vec2f(f32((index<<1u)&2u),f32(index&2u));return vec4f(p*2.0-1.0,0,1);
|
|
211
|
+
}
|
|
212
|
+
struct uS {@location(0) slope: vec4f, @location(1) offset: vec4f,}
|
|
213
|
+
@fragment fn fragment(@builtin(position) position: vec4f) -> uS {
|
|
214
|
+
let center=vec2i(position.xy);
|
|
215
|
+
var weight=0.0;var gt=vec3f(0);var nj=vec3f(0);var rC=vec3f(0);var sS=vec3f(0);
|
|
216
|
+
for(var y=-1;y<=1;y++){for(var x=-1;x<=1;x++){
|
|
217
|
+
let q=center+vec2i(x,y);let d=yg(diffuseLo,q);let s=yg(specularLo,q);
|
|
218
|
+
let w=step(0.5,min(d.a,s.a));let albedo=d.rgb+s.rgb;
|
|
219
|
+
let radiance=exp2(uN(pS(yg(colorImage,q).rgb)));
|
|
220
|
+
weight+=w;gt+=albedo*w;nj+=radiance*w;rC+=albedo*radiance*w;sS+=albedo*albedo*w;
|
|
221
|
+
}}
|
|
222
|
+
var result: uS;
|
|
223
|
+
if(weight<1.0){return result;}
|
|
224
|
+
gt/=weight;nj/=weight;rC/=weight;sS/=weight;
|
|
225
|
+
let slope=max((rC-gt*nj)/(sS-gt*gt+vec3f(tone.y)),vec3f(0));
|
|
226
|
+
result.slope=vec4f(slope,1);result.offset=vec4f(nj-slope*gt,1);
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
//# sourceMappingURL=easuDetail.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"easuDetail.wgsl.js","sourceRoot":"","sources":["../../../src/renderer/wgsl/easuDetail.wgsl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAC;AAErD,gEAAgE;AAChE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAU,CAAC;AAChD,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;AAClC,+FAA+F;AAC/F,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,UAAU,EAAE,CAAC,GAAG,EAAE,IAAI,CAAU,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI;IAC/E,yEAAyE;IACzE,aAAa,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI;IACzC,6FAA6F;IAC7F,aAAa,EAAE,IAAI;CACpB,CAAC;AAEF,4GAA4G;AAC5G,MAAM,gBAAgB,GAAG,UAAU,CAAC;;;;;;;;;;;;8CAYiC,aAAa,CAAC,CAAC,CAAC,QAAQ,aAAa,CAAC,CAAC,CAAC,GAAC,aAAa,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;CAmB7H,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAQ,GAAG,KAAK,EAAE,WAAW,GAAG,KAAK,EAAE,MAAM,GAAG,IAAI;IACnF,OAAO,UAAU,CAAC,OAAO,CAAC,mBAAmB,EAAE,UAAU,CAAC;;;;;;;EAO1D,WAAW,CAAC,CAAC,CAAC,sDAAsD,CAAC,CAAC,CAAC,EAAE;EACzE,MAAM,CAAC,CAAC,CAAC,6GAA6G,CAAC,CAAC,CAAC,EAAE;;;;EAI3H,gBAAgB;kBACA,CAAC,CAAC,OAAO,CAAC,yCAAyC,EAAE,UAAU,CAAC;;;;;;;;;;;;;;EAc9E,MAAM,CAAC,CAAC,CAAC;;;;;;;;;;yDAUoG,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;EAelH,WAAW,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;CAsBjB,CAAC,CAAC,CAAC,mCAAmC;;;;EAInC,QAAQ,CAAC,CAAC,CAAC;;;;;iCAKuB,CAAC,CAAC,CAAC,EAAE;EACvC,MAAM,CAAC,CAAC,CAAC;;;;kIAI8I,CAAC,CAAC,CAAC,EAAE;;;gDAGhF,CAAC,CAAC;AAClF,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,WAAW,GAAG,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC;;;;;;;;wEAQE,WAAW,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;EAcvH,WAAW,CAAC,CAAC,CAAC,8EAA8E,CAAC,CAAC,CAAC,EAAE;;;;CAIpG,CAAC;AAEF,wEAAwE;AACxE,2FAA2F;AAC3F,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;;;EAGtC,aAAa;;;;;;;;;;mBAUM,aAAa,GAAG,CAAC;uCACS,aAAa,CAAC,CAAC,CAAC,MAAM,aAAa,CAAC,CAAC,CAAC;;;SAGxE,aAAa,CAAC,CAAC,CAAC,aAAa,aAAa,CAAC,CAAC,CAAC;;;;;;CAMzD,CAAC;AAEF,0FAA0F;AAC1F,sFAAsF;AACtF,qFAAqF;AACrF,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;;;;EAIhD,gBAAgB;;;;;;;;;;;;;;;;;;;;;;CAsBjB,CAAC","sourcesContent":["import {easuShader} from './easu.wgsl.js';\nimport {composeDefine} from './compose.frag.wgsl.js';\n\n/** Scene-linear log2 range covered by the tone lookup table. */\nexport const TONE_LUT_LOG2 = [-24, 24] as const;\nexport const TONE_LUT_SIZE = 1024;\n/** Detail compensation defaults and fixed regularizers shared by the Web and native passes. */\nexport const EASU_DETAIL = {\n ratioRange: [0.7, 1.43] as const, depthThreshold: 0.02, coverageThreshold: 0.95,\n // Albedo below this is treated as black; relative depth tolerance floor.\n minimumAlbedo: 1e-3, depthTolerance: 1e-4,\n // Regularizes the local albedo variance of the albedo-radiance fit, in squared albedo units.\n guidedEpsilon: 1e-4,\n};\n\n// Tone table lookups and display encoding shared by the detail passes; tone.x is the maximum display value.\nconst toneCodingDefine = /* wgsl */ `\n// Row 0: display value of a gray scene-linear input at TONE_LUT_LOG2 stops, per channel.\n// Row 1: log2 scene-linear input per channel for display values (sqrt-spaced up to tone.x).\n@group(0) @binding(8) var toneLut: texture_2d<f32>;\n@group(0) @binding(9) var<uniform> tone: vec4f;\nfn toneLutRow(row: i32, u: vec3f) -> vec3f {\n let last=f32(textureDimensions(toneLut).x-1u);let p=clamp(u,vec3f(0),vec3f(1))*last;\n let i=vec3i(floor(p));let j=min(i+1,vec3i(i32(last)));let f=p-floor(p);\n var result=vec3f(0);\n for(var c=0;c<3;c++){result[c]=mix(textureLoad(toneLut,vec2i(i[c],row),0)[c],textureLoad(toneLut,vec2i(j[c],row),0)[c],f[c]);}\n return result;\n}\nfn toneForward(log2x: vec3f) -> vec3f {return toneLutRow(0,(log2x-(${TONE_LUT_LOG2[0]}.0))/${TONE_LUT_LOG2[1]-TONE_LUT_LOG2[0]}.0);}\nfn toneInverse(d: vec3f) -> vec3f {return toneLutRow(1,sqrt(max(d,vec3f(0))/tone.x));}\n// Applies a scene-linear albedo ratio before the display transform: displayed\n// RGB is mapped back to scene-linear per channel, scaled and mapped forward, so\n// curve shoulders and toes respond as the tone mapper would. Only the change is\n// added to the displayed value, which keeps it exact where the ratio is 1.\nfn toneAware(d: vec3f, ratio: vec3f) -> vec3f {\n let x=toneInverse(d);\n let changed=max(d+toneForward(x+log2(max(ratio,vec3f(1e-6))))-toneForward(x),vec3f(0));\n return select(changed,d,abs(ratio-vec3f(1))<vec3f(1e-4));\n}\nfn detailDecode(c: vec3f) -> vec3f {\n let a=max(c,vec3f(0));\n return select(pow((a+0.055)/1.055,vec3f(2.4)),a/12.92,a<=vec3f(0.04045));\n}\nfn detailEncode(c: vec3f) -> vec3f {\n let a=max(c,vec3f(0));\n return select(1.055*pow(a,vec3f(1.0/2.4))-0.055,a*12.92,a<=vec3f(0.0031308));\n}\n`;\n\n/**\n * guided: scale only the albedo-correlated part of local scene-linear radiance (default);\n * separate: lobe-share approximation; silhouettes: depth-guided edge reconstruction.\n * The last two are retained for comparisons.\n */\nexport function easuDetailShader(separate = false, silhouettes = false, guided = true) {\n return easuShader.replace(`@vertex fn vertex`, /* wgsl */ `\n@group(0) @binding(2) var diffuseHi: texture_2d<f32>;\n@group(0) @binding(3) var specularHi: texture_2d<f32>;\n@group(0) @binding(4) var depthHi: texture_2d<f32>;\n@group(0) @binding(5) var diffuseLo: texture_2d<f32>;\n@group(0) @binding(6) var specularLo: texture_2d<f32>;\n@group(0) @binding(7) var<uniform> detail: vec4f;\n${silhouettes ? '@group(0) @binding(10) var depthLo: texture_2d<f32>;' : ''}\n${guided ? '@group(0) @binding(11) var slopeLo: texture_2d<f32>;\\n@group(0) @binding(12) var offsetLo: texture_2d<f32>;' : ''}\nfn loadLow(image: texture_2d<f32>, p: vec2i) -> vec4f {\n return textureLoad(image,clamp(p,vec2i(0),vec2i(textureDimensions(image))-1),0);\n}\n${toneCodingDefine}\n@vertex fn vertex`).replace(`return clamp(result/total,lower,upper);`, /* wgsl */ `\n let color=clamp(result/total,lower,upper);\n var diffuse=vec4f(0);var specular=vec4f(0);\n for(var i=0u;i<12u;i++){\n // Identical direction, phase, support and weights as beauty.\n let weight=easuWeight(vec2f(offsets[i])-phase,direction,length,lobe);\n diffuse+=loadLow(diffuseLo,base+offsets[i])*weight;\n specular+=loadLow(specularLo,base+offsets[i])*weight;\n }\n let dlo=min(min(loadLow(diffuseLo,base),loadLow(diffuseLo,base+vec2i(1,0))),min(loadLow(diffuseLo,base+vec2i(0,1)),loadLow(diffuseLo,base+vec2i(1,1))));\n let dhi=max(max(loadLow(diffuseLo,base),loadLow(diffuseLo,base+vec2i(1,0))),max(loadLow(diffuseLo,base+vec2i(0,1)),loadLow(diffuseLo,base+vec2i(1,1))));\n let slo=min(min(loadLow(specularLo,base),loadLow(specularLo,base+vec2i(1,0))),min(loadLow(specularLo,base+vec2i(0,1)),loadLow(specularLo,base+vec2i(1,1))));\n let shi=max(max(loadLow(specularLo,base),loadLow(specularLo,base+vec2i(1,0))),max(loadLow(specularLo,base+vec2i(0,1)),loadLow(specularLo,base+vec2i(1,1))));\n diffuse=clamp(diffuse/total,dlo,dhi);specular=clamp(specular/total,slo,shi);\n ${guided ? `var slope=vec4f(0);var intercept=vec4f(0);\n for(var i=0u;i<12u;i++){\n let weight=easuWeight(vec2f(offsets[i])-phase,direction,length,lobe);\n slope+=loadLow(slopeLo,base+offsets[i])*weight;intercept+=loadLow(offsetLo,base+offsets[i])*weight;\n }\n var lowerSlope=loadLow(slopeLo,base);var upperSlope=lowerSlope;var lowerIntercept=loadLow(offsetLo,base);var upperIntercept=lowerIntercept;\n for(var k=1;k<4;k++){\n let q=base+vec2i(k&1,k>>1u);let a=loadLow(slopeLo,q);let b=loadLow(offsetLo,q);\n lowerSlope=min(lowerSlope,a);upperSlope=max(upperSlope,a);lowerIntercept=min(lowerIntercept,b);upperIntercept=max(upperIntercept,b);\n }\n slope=clamp(slope/total,lowerSlope,upperSlope);intercept=clamp(intercept/total,lowerIntercept,upperIntercept);` : ''}\n let p=vec2i(position.xy);let size=vec2i(textureDimensions(depthHi));\n let d=textureLoad(diffuseHi,p,0);let s=textureLoad(specularHi,p,0);let z=textureLoad(depthHi,p,0).xy;\n let tolerance=max(detail.w,detail.z*abs(z.x));\n let radius=max(vec2i(1),vec2i(ceil(outputSize.xy/vec2f(textureDimensions(colorImage)))));\n // A silhouette changes hit coverage or breaks the planarity of inverse depth,\n // which is linear in screen space on planes; sloped planes are not silhouettes.\n var silhouette=false;let inverse=1.0/max(z.x,1e-6);\n let directions=array<vec2i,4>(vec2i(1,0),vec2i(0,1),vec2i(1,1),vec2i(1,-1));\n for(var k=0;k<4;k++){\n let a=textureLoad(depthHi,clamp(p+directions[k]*radius,vec2i(0),size-1),0).xy;\n let b=textureLoad(depthHi,clamp(p-directions[k]*radius,vec2i(0),size-1),0).xy;\n silhouette=silhouette || abs(a.y-z.y)>0.5 || abs(b.y-z.y)>0.5 ||\n (min(min(a.y,b.y),z.y)>0.0 && abs(1.0/max(a.x,1e-6)+1.0/max(b.x,1e-6)-2.0*inverse)>detail.z*inverse);\n }\n ${silhouettes ? ` // Transmissive and other unguided hits show surfaces behind their depth; keep EASU there.\n let guided=min(d.a,s.a)>=detail.y;\n if(silhouette && z.y>=0.5 && !guided){return color;}\n if(silhouette){\n // Silhouettes: estimate this surface's color from covered low-resolution neighbors\n // at a similar nearest depth, the background from uncovered ones, and mix them by\n // this pixel's full-resolution hit coverage, which keeps edges antialiased.\n let center=vec2i(round(point));var front=vec4f(0);var back=vec4f(0);var frontWeight=0.0;var backWeight=0.0;\n for(var y=-1;y<=1;y++){for(var x=-1;x<=1;x++){\n let q=center+vec2i(x,y);let offset=vec2f(q)-point;\n let lowDepth=loadLow(depthLo,q).xy;let spatial=exp(-dot(offset,offset));\n let similar=exp(-pow(abs(lowDepth.x-z.x)/tolerance,2.0));\n let sample=loadColor(q);let value=vec4f(detailDecode(sample.rgb),sample.a);\n let f=spatial*lowDepth.y*lowDepth.y*select(1.0,similar,z.y>0.0);let b=spatial*(1.0-lowDepth.y)*(1.0-lowDepth.y);\n front+=value*f;frontWeight+=f;back+=value*b;backWeight+=b;\n }}\n let haveFront=frontWeight>1e-3;let haveBack=backWeight>1e-3;\n if(!haveFront && !haveBack){return color;}\n var mixed=select(back/max(backWeight,1e-8),front/max(frontWeight,1e-8),haveFront);\n if(haveFront && haveBack){mixed=mix(back/backWeight,front/frontWeight,z.y);}\n return vec4f(detailEncode(mixed.rgb),mixed.a);\n }\n` : ' if(silhouette){return color;}\\n'} let valid=min(min(d.a,s.a),min(diffuse.a,specular.a))>=detail.y && z.y>=detail.y;\n if(!valid){return color;}\n let low=diffuse.rgb+specular.rgb;\n var ratio=clamp((d.rgb+s.rgb)/max(low,vec3f(detail.x)),vec3f(outputSize.z),vec3f(outputSize.w));\n ${separate ? `\n // Estimate beauty lobe shares from low albedo; no separate beauty lobes are available.\n let share=diffuse.rgb/max(low,vec3f(detail.x));\n let rd=clamp(d.rgb/max(diffuse.rgb,vec3f(detail.x)),vec3f(outputSize.z),vec3f(outputSize.w));\n let rs=clamp(s.rgb/max(specular.rgb,vec3f(detail.x)),vec3f(outputSize.z),vec3f(outputSize.w));\n ratio=share*rd+(vec3f(1)-share)*rs;` : ''}\n ${guided ? `// Local radiance is modeled as slope * albedo + intercept. Only the albedo-correlated\n // part follows the full-resolution albedo; the intercept, such as specular highlights,\n // does not. Pure diffuse (zero intercept) reduces to the albedo ratio.\n let modeled=slope.rgb*low+intercept.rgb;\n ratio=select(ratio,clamp((slope.rgb*(d.rgb+s.rgb)+intercept.rgb)/max(modeled,vec3f(1e-8)),vec3f(outputSize.z),vec3f(outputSize.w)),modeled>vec3f(1e-8));` : ''}\n ratio=select(vec3f(1),ratio,low>vec3f(detail.x));\n // Albedo ratios are scene-linear; the composed image is tone mapped and sRGB encoded.\n return vec4f(detailEncode(toneAware(detailDecode(color.rgb),ratio)),color.a);`);\n}\n\n// Exact box overlap integrates noninteger footprints instead of point sampling.\nexport const albedoDownsampleShader = (depthOutput = false) => /* wgsl */ `\n@group(0) @binding(0) var diffuse: texture_2d<f32>;\n@group(0) @binding(1) var specular: texture_2d<f32>;\n@group(0) @binding(2) var<uniform> outputSize: vec4f;\n@group(0) @binding(3) var depth: texture_2d<f32>;\n@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {\n let p=vec2f(f32((index<<1u)&2u),f32(index&2u));return vec4f(p*2.0-1.0,0,1);\n}\nstruct Result {@location(0) diffuse: vec4f, @location(1) specular: vec4f, ${depthOutput ? '@location(2) depth: vec4f,' : ''}}\n@fragment fn fragment(@builtin(position) p: vec4f) -> Result {\n let scale=vec2f(textureDimensions(diffuse))/outputSize.xy;\n let start=floor(p.xy)*scale;let end=start+scale;\n var d=vec4f(0);var s=vec4f(0);var nearest=3.4e38;var hits=0.0;\n for(var y=i32(floor(start.y));y<i32(ceil(end.y));y++){\n for(var x=i32(floor(start.x));x<i32(ceil(end.x));x++){\n let overlap=max(vec2f(0),min(vec2f(vec2i(x+1,y+1)),end)-max(vec2f(vec2i(x,y)),start));let weight=overlap.x*overlap.y;\n let a=textureLoad(diffuse,vec2i(x,y),0);let b=textureLoad(specular,vec2i(x,y),0);let z=textureLoad(depth,vec2i(x,y),0).xy;\n d+=vec4f(a.rgb*a.a,a.a)*weight;s+=vec4f(b.rgb*b.a,b.a)*weight;\n hits+=z.y*weight;if(z.y>0.0){nearest=min(nearest,z.x);}\n }\n }\n var result: Result;\n ${depthOutput ? 'result.depth=vec4f(select(0.0,nearest,hits>0.0),hits/(scale.x*scale.y),0,0);' : ''}\n result.diffuse=vec4f(d.rgb/max(d.a,1e-8),d.a/(scale.x*scale.y));\n result.specular=vec4f(s.rgb/max(s.a,1e-8),s.a/(scale.x*scale.y));return result;\n}\n`;\n\n// Builds the tone lookup rows for the current grading and tone mapping.\n// toneMappingParams: x mode, y maximum display value, z exposure, w 1 for extended output.\nexport const toneLutShader = /* wgsl */ `\n@group(0) @binding(0) var<uniform> toneMappingParams: vec4f;\n@group(0) @binding(1) var<uniform> colorGradingParams: vec4f;\n${composeDefine}\nfn toneGray(x: f32) -> vec3f {\n var v=gradeColor(vec3f(x)*toneMappingParams.z);\n if(toneMappingParams.w<0.5){v=applyToneMapping(v);}\n return v;\n}\n@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {\n let p=vec2f(f32((index<<1u)&2u),f32(index&2u));return vec4f(p*2.0-1.0,0,1);\n}\n@fragment fn fragment(@builtin(position) p: vec4f) -> @location(0) vec4f {\n let u=floor(p.x)/${TONE_LUT_SIZE - 1}.0;\n if(p.y<1.0){return vec4f(toneGray(exp2(mix(${TONE_LUT_LOG2[0]}.0,${TONE_LUT_LOG2[1]}.0,u))),1);}\n let d=u*u*toneMappingParams.y;var result=vec3f(0);\n for(var c=0;c<3;c++){\n var lo=${TONE_LUT_LOG2[0]}.0;var hi=${TONE_LUT_LOG2[1]}.0;\n for(var i=0;i<28;i++){let mid=0.5*(lo+hi);if(toneGray(exp2(mid))[c]<d){lo=mid;}else{hi=mid;}}\n result[c]=0.5*(lo+hi);\n }\n return vec4f(result,1);\n}\n`;\n\n// Per low-resolution pixel, fits scene-linear radiance x ~ slope * albedo + offset over a\n// 3x3 window of guided samples, per channel (a guided-filter regression). Flat albedo\n// gives slope ~0, so its radiance is not modulated; tone.y regularizes the variance.\nexport const guidedCoefficientShader = /* wgsl */ `\n@group(0) @binding(0) var colorImage: texture_2d<f32>;\n@group(0) @binding(1) var diffuseLo: texture_2d<f32>;\n@group(0) @binding(2) var specularLo: texture_2d<f32>;\n${toneCodingDefine}\nfn loadAt(image: texture_2d<f32>, p: vec2i) -> vec4f {return textureLoad(image,clamp(p,vec2i(0),vec2i(textureDimensions(image))-1),0);}\n@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {\n let p=vec2f(f32((index<<1u)&2u),f32(index&2u));return vec4f(p*2.0-1.0,0,1);\n}\nstruct Result {@location(0) slope: vec4f, @location(1) offset: vec4f,}\n@fragment fn fragment(@builtin(position) position: vec4f) -> Result {\n let center=vec2i(position.xy);\n var weight=0.0;var meanAlbedo=vec3f(0);var meanRadiance=vec3f(0);var meanProduct=vec3f(0);var meanSquare=vec3f(0);\n for(var y=-1;y<=1;y++){for(var x=-1;x<=1;x++){\n let q=center+vec2i(x,y);let d=loadAt(diffuseLo,q);let s=loadAt(specularLo,q);\n let w=step(0.5,min(d.a,s.a));let albedo=d.rgb+s.rgb;\n let radiance=exp2(toneInverse(detailDecode(loadAt(colorImage,q).rgb)));\n weight+=w;meanAlbedo+=albedo*w;meanRadiance+=radiance*w;meanProduct+=albedo*radiance*w;meanSquare+=albedo*albedo*w;\n }}\n var result: Result;\n if(weight<1.0){return result;}\n meanAlbedo/=weight;meanRadiance/=weight;meanProduct/=weight;meanSquare/=weight;\n let slope=max((meanProduct-meanAlbedo*meanRadiance)/(meanSquare-meanAlbedo*meanAlbedo+vec3f(tone.y)),vec3f(0));\n result.slope=vec4f(slope,1);result.offset=vec4f(meanRadiance-slope*meanAlbedo,1);\n return result;\n}\n`;\n"]}
|
|
@@ -7,7 +7,7 @@ function guideBinding(guides, binding, name) {
|
|
|
7
7
|
: `@group(0) @binding(${binding}) var<storage, read> ${name}: array<vec4f>;`;
|
|
8
8
|
}
|
|
9
9
|
function loadGuide(guides, name, position) {
|
|
10
|
-
return guides === 'textures' ? `textureLoad(${name}, ${position}, 0)` : `${name}[
|
|
10
|
+
return guides === 'textures' ? `textureLoad(${name}, ${position}, 0)` : `${name}[gx(${position})]`;
|
|
11
11
|
}
|
|
12
12
|
// Shared uniform packing also fixes sensitivity and dilation for native callers.
|
|
13
13
|
export function packJNLMParameters(width, height, strength, pass) {
|
|
@@ -43,9 +43,9 @@ ${shadowCatcherAccumulationWGSL}
|
|
|
43
43
|
fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
44
44
|
let size = params.size;
|
|
45
45
|
if (id.x >= size.x || id.y >= size.y) { return; }
|
|
46
|
-
let
|
|
47
|
-
let n = normalBuffer[
|
|
48
|
-
let moments = accumulation[
|
|
46
|
+
let rJ = id.x + (size.y - 1u - id.y) * size.x;
|
|
47
|
+
let n = normalBuffer[rJ].a;
|
|
48
|
+
let moments = accumulation[rJ].moments;
|
|
49
49
|
let mean = moments.x / max(n, 1.0);
|
|
50
50
|
|
|
51
51
|
var variance = -1.0;
|
|
@@ -69,28 +69,28 @@ fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
|
69
69
|
let size = params.size;
|
|
70
70
|
if (id.x >= size.x || id.y >= size.y) { return; }
|
|
71
71
|
let center = rawGuide[id.x + id.y * size.x];
|
|
72
|
-
var
|
|
73
|
-
var
|
|
74
|
-
var
|
|
75
|
-
var
|
|
72
|
+
var mF = 0.0;
|
|
73
|
+
var jI = 0.0;
|
|
74
|
+
var oL = 0.0;
|
|
75
|
+
var jB = 0.0;
|
|
76
76
|
for (var dy = -1; dy <= 1; dy++) {
|
|
77
77
|
for (var dx = -1; dx <= 1; dx++) {
|
|
78
78
|
let q = clamp(vec2i(id.xy) + vec2i(dx, dy), vec2i(0), vec2i(size) - 1);
|
|
79
79
|
let value = rawGuide[u32(q.x) + u32(q.y) * size.x];
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
oL += value.x;
|
|
81
|
+
jB += value.x * value.x;
|
|
82
82
|
if (value.y >= 0.0) {
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
mF += value.y;
|
|
84
|
+
jI += 1.0;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
var variance: f32;
|
|
89
89
|
if (center.y >= 0.0) {
|
|
90
|
-
variance =
|
|
90
|
+
variance = mF / jI;
|
|
91
91
|
} else {
|
|
92
|
-
let
|
|
93
|
-
variance = max(
|
|
92
|
+
let mE = oL / 9.0;
|
|
93
|
+
variance = max(jB / 9.0 - mE * mE, 0.0) * 9.0 / 8.0;
|
|
94
94
|
}
|
|
95
95
|
guide[id.x + id.y * size.x] = vec2f(center.x, variance);
|
|
96
96
|
}
|
|
@@ -114,11 +114,11 @@ fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
|
114
114
|
if (id.x >= params.size.x || id.y >= params.size.y) { return; }
|
|
115
115
|
outColor[id.x + id.y * params.size.x] = textureLoad(colorTex, vec2i(id.xy), 0);
|
|
116
116
|
${guides === 'buffers' ? ` let index = id.x + id.y * params.size.x;
|
|
117
|
-
let
|
|
118
|
-
let n = normalAccumulation[
|
|
117
|
+
let rJ = id.x + (params.size.y - 1u - id.y) * params.size.x;
|
|
118
|
+
let n = normalAccumulation[rJ];
|
|
119
119
|
let samples = max(n.a, 1.0);
|
|
120
120
|
normal[index] = vec4f(n.rgb / samples * 0.5 + 0.5, select(0.0, 1.0, dot(n.rgb, n.rgb) > 0.1));
|
|
121
|
-
shadow[index] = shadowAccumulation[
|
|
121
|
+
shadow[index] = shadowAccumulation[rJ].mask / samples;
|
|
122
122
|
albedo[index] = textureLoad(albedoTex, vec2i(id.xy), 0);` : ''}
|
|
123
123
|
}
|
|
124
124
|
`;
|
|
@@ -155,23 +155,23 @@ const INV_NORMAL_BANDWIDTH2 = ${(1 / normalBandwidth ** 2).toFixed(6)};
|
|
|
155
155
|
|
|
156
156
|
const CENTER_WEIGHT_MIN = 0.01;
|
|
157
157
|
|
|
158
|
-
fn
|
|
158
|
+
fn gx(p: vec2i) -> u32 {
|
|
159
159
|
let q = clamp(p, vec2i(0), vec2i(params.size) - 1);
|
|
160
160
|
return u32(q.x) + u32(q.y) * params.size.x;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
fn
|
|
163
|
+
fn mJ(q: vec2i, bp: vec4f, hk: vec3f) -> f32 {
|
|
164
164
|
let albedo = ${loadGuide(guides, 'albedoTex', 'q')};
|
|
165
165
|
|
|
166
166
|
|
|
167
|
-
let
|
|
168
|
-
let
|
|
169
|
-
let
|
|
170
|
-
let
|
|
167
|
+
let rc = max((albedo.rgb + bp.rgb) * 0.5, vec3f(${JNLM_ALBEDO_FLOOR}));
|
|
168
|
+
let mn = (albedo.rgb - bp.rgb) / rc;
|
|
169
|
+
let jv = albedo.a - bp.a;
|
|
170
|
+
let my = ${loadGuide(guides, 'normalTex', 'q')}.rgb - hk;
|
|
171
171
|
return max(
|
|
172
|
-
dot(
|
|
173
|
-
+
|
|
174
|
-
dot(
|
|
172
|
+
dot(mn, mn) * INV_ALBEDO_BANDWIDTH2
|
|
173
|
+
+ jv * jv * INV_COVERAGE_BANDWIDTH2,
|
|
174
|
+
dot(my, my) * INV_NORMAL_BANDWIDTH2
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -185,37 +185,37 @@ let size = vec2i(params.size);
|
|
|
185
185
|
let p = vec2i(id.xy);
|
|
186
186
|
if (p.x >= size.x || p.y >= size.y) { return; }
|
|
187
187
|
let step = i32(params.dilation);
|
|
188
|
-
let
|
|
189
|
-
let
|
|
190
|
-
let
|
|
191
|
-
let index =
|
|
188
|
+
let bp = ${loadGuide(guides, 'albedoTex', 'p')};
|
|
189
|
+
let fO = ${loadGuide(guides, 'normalTex', 'p')};
|
|
190
|
+
let hk = fO.rgb;
|
|
191
|
+
let index = gx(p);
|
|
192
192
|
|
|
193
193
|
|
|
194
|
-
if (
|
|
194
|
+
if (fO.a <= 0.0 && ${loadGuide(guides, 'shadowCatcherTex', 'p')}.r <= 0.0) {
|
|
195
195
|
outColor[index] = color[index];
|
|
196
196
|
outGuide[index] = guide[index];
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
var
|
|
200
|
+
var mq: array<vec2f, PATCH_SIZE>;
|
|
201
201
|
var k = 0;
|
|
202
202
|
for (var dy = -PATCH_RADIUS; dy <= PATCH_RADIUS; dy++) {
|
|
203
203
|
for (var dx = -PATCH_RADIUS; dx <= PATCH_RADIUS; dx++) {
|
|
204
|
-
|
|
204
|
+
mq[k] = guide[gx(p + vec2i(dx, dy) * step)];
|
|
205
205
|
k++;
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
let k2 = params.colorSensitivity * params.colorSensitivity;
|
|
209
209
|
|
|
210
210
|
var sum = vec4f(0.0);
|
|
211
|
-
let
|
|
212
|
-
var
|
|
213
|
-
var
|
|
214
|
-
var
|
|
215
|
-
var
|
|
216
|
-
var
|
|
217
|
-
var
|
|
218
|
-
var
|
|
211
|
+
let hn = max(bp.rgb, vec3f(${JNLM_ALBEDO_FLOOR}));
|
|
212
|
+
var nS = vec3f(0.0);
|
|
213
|
+
var gP = vec3f(0.0);
|
|
214
|
+
var hH = vec3f(0.0);
|
|
215
|
+
var rn = 0.0;
|
|
216
|
+
var gR = 0.0;
|
|
217
|
+
var pK = 0.0;
|
|
218
|
+
var lR = 0.0;
|
|
219
219
|
for (var sy = -SEARCH_RADIUS; sy <= SEARCH_RADIUS; sy++) {
|
|
220
220
|
for (var sx = -SEARCH_RADIUS; sx <= SEARCH_RADIUS; sx++) {
|
|
221
221
|
let q = p + vec2i(sx, sy) * step;
|
|
@@ -224,63 +224,63 @@ if (
|
|
|
224
224
|
q.x < 0 || q.y < 0 || q.x >= size.x || q.y >= size.y
|
|
225
225
|
) { continue; }
|
|
226
226
|
|
|
227
|
-
let features =
|
|
227
|
+
let features = mJ(q, bp, hk);
|
|
228
228
|
if (features > 16.0) { continue; }
|
|
229
229
|
|
|
230
|
-
var
|
|
230
|
+
var gn = 0.0;
|
|
231
231
|
k = 0;
|
|
232
232
|
for (var dy = -PATCH_RADIUS; dy <= PATCH_RADIUS; dy++) {
|
|
233
233
|
for (var dx = -PATCH_RADIUS; dx <= PATCH_RADIUS; dx++) {
|
|
234
|
-
let a =
|
|
235
|
-
let b = guide[
|
|
236
|
-
let
|
|
237
|
-
|
|
234
|
+
let a = mq[k];
|
|
235
|
+
let b = guide[gx(q + vec2i(dx, dy) * step)];
|
|
236
|
+
let tn = a.x - b.x;
|
|
237
|
+
gn += (tn * tn
|
|
238
238
|
- VARIANCE_CANCELLATION * (a.y + min(a.y, b.y)))
|
|
239
239
|
/ (1e-10 + k2 * (a.y + b.y));
|
|
240
240
|
k++;
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
gn /= f32(PATCH_SIZE);
|
|
244
244
|
|
|
245
|
-
let weight = exp(-max(max(
|
|
246
|
-
let index =
|
|
245
|
+
let weight = exp(-max(max(gn, 0.0), features));
|
|
246
|
+
let index = gx(q);
|
|
247
247
|
let c = color[index];
|
|
248
248
|
let g = guide[index];
|
|
249
249
|
|
|
250
|
-
let a = (${loadGuide(guides, 'albedoTex', 'q')}.rgb -
|
|
250
|
+
let a = (${loadGuide(guides, 'albedoTex', 'q')}.rgb - bp.rgb) / hn;
|
|
251
251
|
let value = premultiplied(c);
|
|
252
252
|
sum += value * weight;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
253
|
+
nS += a * weight;
|
|
254
|
+
gP += a * a * weight;
|
|
255
|
+
hH += a * value.rgb * weight;
|
|
256
|
+
rn += g.x * weight;
|
|
257
|
+
gR += g.y * weight * weight;
|
|
258
|
+
pK += weight;
|
|
259
|
+
lR = max(lR, weight);
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
|
|
264
264
|
|
|
265
265
|
let center = color[index];
|
|
266
|
-
let
|
|
267
|
-
let
|
|
268
|
-
let totalWeight =
|
|
269
|
-
let
|
|
270
|
-
let mean = (sum +
|
|
266
|
+
let mp = guide[index];
|
|
267
|
+
let dG = max(lR, CENTER_WEIGHT_MIN);
|
|
268
|
+
let totalWeight = pK + dG;
|
|
269
|
+
let xj = premultiplied(center);
|
|
270
|
+
let mean = (sum + xj * dG) / totalWeight;
|
|
271
271
|
|
|
272
272
|
|
|
273
|
-
let
|
|
274
|
-
let
|
|
275
|
-
let
|
|
273
|
+
let eA = nS / totalWeight;
|
|
274
|
+
let mK = max(gP / totalWeight - eA * eA, vec3f(0.0));
|
|
275
|
+
let sH = hH / totalWeight - eA * mean.rgb;
|
|
276
276
|
|
|
277
277
|
|
|
278
278
|
|
|
279
|
-
let
|
|
280
|
-
let
|
|
281
|
-
let
|
|
282
|
-
let
|
|
283
|
-
let filtered = vec4f(max(
|
|
279
|
+
let gt = bp.rgb + eA * hn;
|
|
280
|
+
let qm = mean.rgb * hn / max(gt, vec3f(${JNLM_ALBEDO_FLOOR}));
|
|
281
|
+
let slope = clamp(sH / (mK + vec3f(${JNLM_ALBEDO_REGULARIZATION})), vec3f(0.0), qm);
|
|
282
|
+
let tY = mean.rgb - slope * eA;
|
|
283
|
+
let filtered = vec4f(max(tY, vec3f(0.0)), max(mean.a, 0.0));
|
|
284
284
|
let rgb = select(
|
|
285
285
|
center.rgb,
|
|
286
286
|
filtered.rgb / max(filtered.a, 1e-6),
|
|
@@ -288,8 +288,8 @@ filtered.a > 1e-6
|
|
|
288
288
|
);
|
|
289
289
|
outColor[index] = vec4f(rgb, filtered.a);
|
|
290
290
|
outGuide[index] = vec2f(
|
|
291
|
-
(
|
|
292
|
-
(
|
|
291
|
+
(rn + mp.x * dG) / totalWeight,
|
|
292
|
+
(gR + mp.y * dG * dG)
|
|
293
293
|
/ (totalWeight * totalWeight)
|
|
294
294
|
);
|
|
295
295
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jnlm.compute.js","sourceRoot":"","sources":["../../../src/renderer/wgsl/jnlm.compute.ts"],"names":[],"mappings":"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","sourcesContent":["import shadowCatcherAccumulationWGSL from './chunks/shadowCatcherAccumulation.wgsl.js';\nimport {linearToSrgbDefine} from './compose.frag.wgsl.js';\nimport {\n JNLM_DEFAULT_COLOR_SENSITIVITY, JNLM_DEFAULT_SHAPE, JNLM_ALBEDO_FLOOR,\n JNLM_ALBEDO_REGULARIZATION, JNLM_COVERAGE_BANDWIDTH, jnlmPassDilation,\n type JNLMFilterShape\n} from '../jnlm.js';\n\n// Web textures and native normalized guide buffers use the same filter body.\nexport type JNLMGuideBindings = 'textures' | 'buffers';\nfunction guideBinding(guides: JNLMGuideBindings, binding: number, name: string): string {\n return guides === 'textures'\n ? `@group(0) @binding(${binding}) var ${name}: texture_2d<f32>;`\n : `@group(0) @binding(${binding}) var<storage, read> ${name}: array<vec4f>;`;\n}\nfunction loadGuide(guides: JNLMGuideBindings, name: string, position: string): string {\n return guides === 'textures' ? `textureLoad(${name}, ${position}, 0)` : `${name}[pixelIndex(${position})]`;\n}\n\n// Shared uniform packing also fixes sensitivity and dilation for native callers.\nexport function packJNLMParameters(width: number, height: number, strength: number, pass: number): ArrayBuffer {\n const bytes = new ArrayBuffer(16);\n new Uint32Array(bytes, 0, 2).set([width, height]);\n new Float32Array(bytes, 8, 2).set([\n JNLM_DEFAULT_COLOR_SENSITIVITY * Math.max(strength, 1e-3), jnlmPassDilation(pass)\n ]);\n return bytes;\n}\n\nexport const JNLM_WORKGROUP_SIZE = 8;\n\nconst paramsWGSL = /* wgsl */ `\nstruct JNLMParams {\n size: vec2u,\n colorSensitivity: f32,\n // Pixel spacing of the search window and patches in this pass.\n dilation: f32,\n}\n@group(0) @binding(0) var<uniform> params: JNLMParams;\n`;\n\n/**\n * Pass 1: the guide mean and the variance of that mean from the accumulated\n * moments. Ray buffers are stored bottom-up; guides use texture rows.\n */\nexport const jnlmGuideWGSL = /* wgsl */ `\n${paramsWGSL}\n${shadowCatcherAccumulationWGSL}\n@group(0) @binding(1) var<storage, read> accumulation: array<ShadowCatcherAccumulation>;\n@group(0) @binding(2) var<storage, read> normalBuffer: array<vec4f>;\n@group(0) @binding(3) var<storage, read_write> rawGuide: array<vec2f>;\n\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let size = params.size;\n if (id.x >= size.x || id.y >= size.y) { return; }\n let rayIndex = id.x + (size.y - 1u - id.y) * size.x;\n let n = normalBuffer[rayIndex].a;\n let moments = accumulation[rayIndex].moments;\n let mean = moments.x / max(n, 1.0);\n // Variance of the mean, or -1 when one sample cannot estimate it.\n var variance = -1.0;\n if (n >= 2.0) {\n variance = max(moments.y - n * mean * mean, 0.0) / (n * (n - 1.0));\n }\n rawGuide[id.x + id.y * size.x] = vec2f(mean, variance);\n}\n`;\n\n/**\n * Pass 2: smooth the noisy per-pixel variance over 3 x 3 pixels. Pixels with\n * a single sample take the spatial variance of their neighborhood instead.\n */\nexport const jnlmVarianceWGSL = /* wgsl */ `\n${paramsWGSL}\n@group(0) @binding(1) var<storage, read> rawGuide: array<vec2f>;\n@group(0) @binding(2) var<storage, read_write> guide: array<vec2f>;\n\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let size = params.size;\n if (id.x >= size.x || id.y >= size.y) { return; }\n let center = rawGuide[id.x + id.y * size.x];\n var varianceSum = 0.0;\n var varianceCount = 0.0;\n var meanSum = 0.0;\n var meanSquareSum = 0.0;\n for (var dy = -1; dy <= 1; dy++) {\n for (var dx = -1; dx <= 1; dx++) {\n let q = clamp(vec2i(id.xy) + vec2i(dx, dy), vec2i(0), vec2i(size) - 1);\n let value = rawGuide[u32(q.x) + u32(q.y) * size.x];\n meanSum += value.x;\n meanSquareSum += value.x * value.x;\n if (value.y >= 0.0) {\n varianceSum += value.y;\n varianceCount += 1.0;\n }\n }\n }\n var variance: f32;\n if (center.y >= 0.0) {\n variance = varianceSum / varianceCount;\n } else {\n let spatialMean = meanSum / 9.0;\n variance = max(meanSquareSum / 9.0 - spatialMean * spatialMean, 0.0) * 9.0 / 8.0;\n }\n guide[id.x + id.y * size.x] = vec2f(center.x, variance);\n}\n`;\n\n/** Copies the composed image into the filter's color buffer. */\nexport function createJNLMLoadColorWGSL(guides: JNLMGuideBindings = 'textures'): string {\n return /* wgsl */ `\n${paramsWGSL}\n@group(0) @binding(1) var colorTex: texture_2d<f32>;\n@group(0) @binding(2) var<storage, read_write> outColor: array<vec4f>;\n${guides === 'buffers' ? `${shadowCatcherAccumulationWGSL}\n@group(0) @binding(3) var<storage, read> normalAccumulation: array<vec4f>;\n@group(0) @binding(4) var<storage, read_write> normal: array<vec4f>;\n@group(0) @binding(5) var<storage, read> shadowAccumulation: array<ShadowCatcherAccumulation>;\n@group(0) @binding(6) var<storage, read_write> shadow: array<vec4f>;\n@group(0) @binding(7) var albedoTex: texture_2d<f32>;\n@group(0) @binding(8) var<storage, read_write> albedo: array<vec4f>;` : ''}\n\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n if (id.x >= params.size.x || id.y >= params.size.y) { return; }\n outColor[id.x + id.y * params.size.x] = textureLoad(colorTex, vec2i(id.xy), 0);\n${guides === 'buffers' ? ` let index = id.x + id.y * params.size.x;\n let rayIndex = id.x + (params.size.y - 1u - id.y) * params.size.x;\n let n = normalAccumulation[rayIndex];\n let samples = max(n.a, 1.0);\n normal[index] = vec4f(n.rgb / samples * 0.5 + 0.5, select(0.0, 1.0, dot(n.rgb, n.rgb) > 0.1));\n shadow[index] = shadowAccumulation[rayIndex].mask / samples;\n albedo[index] = textureLoad(albedoTex, vec2i(id.xy), 0);` : ''}\n}\n`;\n}\nexport const jnlmLoadColorWGSL = createJNLMLoadColorWGSL();\n\n/**\n * Pass 3: the joint NL-means filter of the composed image. It also filters\n * the guide with the same weights, with the variance of the weighted mean,\n * for a following pass. The albedo regression corrects color only, keeping\n * uncertain fitted texture responses out of the matching guide.\n */\nexport function createJNLMFilterWGSL({\n searchRadius, patchRadius, albedoBandwidth, normalBandwidth\n}: JNLMFilterShape = JNLM_DEFAULT_SHAPE, guides: JNLMGuideBindings = 'textures') {\n const patchSize = (2 * patchRadius + 1) ** 2;\n return /* wgsl */ `\n${paramsWGSL}\n@group(0) @binding(1) var<storage, read> guide: array<vec2f>;\n@group(0) @binding(2) var<storage, read> color: array<vec4f>;\n${guideBinding(guides, 3, 'albedoTex')}\n${guideBinding(guides, 4, 'normalTex')}\n@group(0) @binding(5) var<storage, read_write> outColor: array<vec4f>;\n@group(0) @binding(6) var<storage, read_write> outGuide: array<vec2f>;\n${guideBinding(guides, 7, 'shadowCatcherTex')}\n\nconst SEARCH_RADIUS = ${searchRadius};\nconst PATCH_RADIUS = ${patchRadius};\nconst PATCH_SIZE = ${patchSize};\n// Variance cancellation strength alpha of the paper.\nconst VARIANCE_CANCELLATION = 1.0;\n// Inverse squared feature bandwidths. Normals are encoded as n * 0.5 + 0.5.\nconst INV_ALBEDO_BANDWIDTH2 = ${(1 / albedoBandwidth ** 2).toFixed(6)};\nconst INV_COVERAGE_BANDWIDTH2 = ${(1 / JNLM_COVERAGE_BANDWIDTH ** 2).toFixed(6)};\nconst INV_NORMAL_BANDWIDTH2 = ${(1 / normalBandwidth ** 2).toFixed(6)};\n// Lower bound of the center weight, so a pixel unlike all of its neighbors\n// (a thin highlight) is kept rather than replaced by them.\nconst CENTER_WEIGHT_MIN = 0.01;\n\nfn pixelIndex(p: vec2i) -> u32 {\n let q = clamp(p, vec2i(0), vec2i(params.size) - 1);\n return u32(q.x) + u32(q.y) * params.size.x;\n}\n\nfn featureDistance(q: vec2i, centerAlbedo: vec4f, centerNormal: vec3f) -> f32 {\n let albedo = ${loadGuide(guides, 'albedoTex', 'q')};\n // Absolute differences in linear albedo erase low-contrast dark textures.\n // Measure RGB contrast relative to both pixels; keep coverage absolute.\n let albedoScale = max((albedo.rgb + centerAlbedo.rgb) * 0.5, vec3f(${JNLM_ALBEDO_FLOOR}));\n let albedoDelta = (albedo.rgb - centerAlbedo.rgb) / albedoScale;\n let coverageDelta = albedo.a - centerAlbedo.a;\n let normalDelta = ${loadGuide(guides, 'normalTex', 'q')}.rgb - centerNormal;\n return max(\n dot(albedoDelta, albedoDelta) * INV_ALBEDO_BANDWIDTH2\n + coverageDelta * coverageDelta * INV_COVERAGE_BANDWIDTH2,\n dot(normalDelta, normalDelta) * INV_NORMAL_BANDWIDTH2\n );\n}\n\nfn premultiplied(c: vec4f) -> vec4f {\n return vec4f(c.rgb * c.a, c.a);\n}\n\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let size = vec2i(params.size);\n let p = vec2i(id.xy);\n if (p.x >= size.x || p.y >= size.y) { return; }\n let step = i32(params.dilation);\n let centerAlbedo = ${loadGuide(guides, 'albedoTex', 'p')};\n let centerNormalTexel = ${loadGuide(guides, 'normalTex', 'p')};\n let centerNormal = centerNormalTexel.rgb;\n let index = pixelIndex(p);\n // Primary misses show the background directly. It has no features that\n // protect its detail and no noise beyond antialiasing, so keep it.\n if (centerNormalTexel.a <= 0.0 && ${loadGuide(guides, 'shadowCatcherTex', 'p')}.r <= 0.0) {\n outColor[index] = color[index];\n outGuide[index] = guide[index];\n return;\n }\n\n var centerPatch: array<vec2f, PATCH_SIZE>;\n var k = 0;\n for (var dy = -PATCH_RADIUS; dy <= PATCH_RADIUS; dy++) {\n for (var dx = -PATCH_RADIUS; dx <= PATCH_RADIUS; dx++) {\n centerPatch[k] = guide[pixelIndex(p + vec2i(dx, dy) * step)];\n k++;\n }\n }\n let k2 = params.colorSensitivity * params.colorSensitivity;\n\n var sum = vec4f(0.0);\n let featureScale = max(centerAlbedo.rgb, vec3f(${JNLM_ALBEDO_FLOOR}));\n var featureSum = vec3f(0.0);\n var featureSquareSum = vec3f(0.0);\n var featureColorSum = vec3f(0.0);\n var guideSum = 0.0;\n var guideVarianceSum = 0.0;\n var weightSum = 0.0;\n var maxWeight = 0.0;\n for (var sy = -SEARCH_RADIUS; sy <= SEARCH_RADIUS; sy++) {\n for (var sx = -SEARCH_RADIUS; sx <= SEARCH_RADIUS; sx++) {\n let q = p + vec2i(sx, sy) * step;\n if (\n (sx == 0 && sy == 0) ||\n q.x < 0 || q.y < 0 || q.x >= size.x || q.y >= size.y\n ) { continue; }\n\n let features = featureDistance(q, centerAlbedo, centerNormal);\n if (features > 16.0) { continue; }\n\n var colorDistance = 0.0;\n k = 0;\n for (var dy = -PATCH_RADIUS; dy <= PATCH_RADIUS; dy++) {\n for (var dx = -PATCH_RADIUS; dx <= PATCH_RADIUS; dx++) {\n let a = centerPatch[k];\n let b = guide[pixelIndex(q + vec2i(dx, dy) * step)];\n let delta = a.x - b.x;\n colorDistance += (delta * delta\n - VARIANCE_CANCELLATION * (a.y + min(a.y, b.y)))\n / (1e-10 + k2 * (a.y + b.y));\n k++;\n }\n }\n colorDistance /= f32(PATCH_SIZE);\n\n let weight = exp(-max(max(colorDistance, 0.0), features));\n let index = pixelIndex(q);\n let c = color[index];\n let g = guide[index];\n // Filter premultiplied color so coverage and color stay consistent.\n let a = (${loadGuide(guides, 'albedoTex', 'q')}.rgb - centerAlbedo.rgb) / featureScale;\n let value = premultiplied(c);\n sum += value * weight;\n featureSum += a * weight;\n featureSquareSum += a * a * weight;\n featureColorSum += a * value.rgb * weight;\n guideSum += g.x * weight;\n guideVarianceSum += g.y * weight * weight;\n weightSum += weight;\n maxWeight = max(maxWeight, weight);\n }\n }\n\n // Weight the center like its most similar neighbor (Buades et al. 2005)\n // instead of 1, so isolated outliers are not kept unfiltered.\n let center = color[index];\n let centerGuide = guide[index];\n let centerWeight = max(maxWeight, CENTER_WEIGHT_MIN);\n let totalWeight = weightSum + centerWeight;\n let centerValue = premultiplied(center);\n let mean = (sum + centerValue * centerWeight) / totalWeight;\n // Fit each color channel against relative albedo with the same JNLM weights.\n // Centering at this pixel avoids cancellation when the feature is constant.\n let meanFeature = featureSum / totalWeight;\n let featureVariance = max(featureSquareSum / totalWeight - meanFeature * meanFeature, vec3f(0.0));\n let covariance = featureColorSum / totalWeight - meanFeature * mean.rgb;\n // A 1% contrast ridge stabilizes flat guides. Bound the response between no\n // texture dependence and multiplicative albedo modulation; specular color\n // with an unrelated albedo falls back to the weighted mean.\n let meanAlbedo = centerAlbedo.rgb + meanFeature * featureScale;\n let maximumSlope = mean.rgb * featureScale / max(meanAlbedo, vec3f(${JNLM_ALBEDO_FLOOR}));\n let slope = clamp(covariance / (featureVariance + vec3f(${JNLM_ALBEDO_REGULARIZATION})), vec3f(0.0), maximumSlope);\n let corrected = mean.rgb - slope * meanFeature;\n let filtered = vec4f(max(corrected, vec3f(0.0)), max(mean.a, 0.0));\n let rgb = select(\n center.rgb,\n filtered.rgb / max(filtered.a, 1e-6),\n filtered.a > 1e-6\n );\n outColor[index] = vec4f(rgb, filtered.a);\n outGuide[index] = vec2f(\n (guideSum + centerGuide.x * centerWeight) / totalWeight,\n (guideVarianceSum + centerGuide.y * centerWeight * centerWeight)\n / (totalWeight * totalWeight)\n );\n}\n`;\n}\n\n// Compute presentation for hosts that consume the filtered buffer directly.\n// Use the same sRGB transfer as browser display composition.\nexport const jnlmEncodeWGSL = /* wgsl */ `\nstruct OutputParams { size: vec2u, premultiply: u32, reserved: u32 }\n@group(0) @binding(0) var<uniform> params: OutputParams;\n@group(0) @binding(1) var<storage, read> color: array<vec4f>;\n@group(0) @binding(2) var output: texture_storage_2d<rgba8unorm, write>;\n${linearToSrgbDefine}\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n if (any(id.xy >= params.size)) { return; }\n let c = color[id.y * params.size.x + id.x];\n var rgb = linearToSrgb(c.rgb);\n if (params.premultiply != 0u) { rgb *= c.a; }\n textureStore(output, id.xy, vec4f(rgb, c.a));\n}\n`;\n"]}
|
|
1
|
+
{"version":3,"file":"jnlm.compute.js","sourceRoot":"","sources":["../../../src/renderer/wgsl/jnlm.compute.ts"],"names":[],"mappings":"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","sourcesContent":["import shadowCatcherAccumulationWGSL from './chunks/shadowCatcherAccumulation.wgsl.js';\nimport {linearToSrgbDefine} from './compose.frag.wgsl.js';\nimport {\n JNLM_DEFAULT_COLOR_SENSITIVITY, JNLM_DEFAULT_SHAPE, JNLM_ALBEDO_FLOOR,\n JNLM_ALBEDO_REGULARIZATION, JNLM_COVERAGE_BANDWIDTH, jnlmPassDilation,\n type JNLMFilterShape\n} from '../jnlm.js';\n\n// Web textures and native normalized guide buffers use the same filter body.\nexport type JNLMGuideBindings = 'textures' | 'buffers';\nfunction guideBinding(guides: JNLMGuideBindings, binding: number, name: string): string {\n return guides === 'textures'\n ? `@group(0) @binding(${binding}) var ${name}: texture_2d<f32>;`\n : `@group(0) @binding(${binding}) var<storage, read> ${name}: array<vec4f>;`;\n}\nfunction loadGuide(guides: JNLMGuideBindings, name: string, position: string): string {\n return guides === 'textures' ? `textureLoad(${name}, ${position}, 0)` : `${name}[pixelIndex(${position})]`;\n}\n\n// Shared uniform packing also fixes sensitivity and dilation for native callers.\nexport function packJNLMParameters(width: number, height: number, strength: number, pass: number): ArrayBuffer {\n const bytes = new ArrayBuffer(16);\n new Uint32Array(bytes, 0, 2).set([width, height]);\n new Float32Array(bytes, 8, 2).set([\n JNLM_DEFAULT_COLOR_SENSITIVITY * Math.max(strength, 1e-3), jnlmPassDilation(pass)\n ]);\n return bytes;\n}\n\nexport const JNLM_WORKGROUP_SIZE = 8;\n\nconst paramsWGSL = /* wgsl */ `\nstruct JNLMParams {\n size: vec2u,\n colorSensitivity: f32,\n // Pixel spacing of the search window and patches in this pass.\n dilation: f32,\n}\n@group(0) @binding(0) var<uniform> params: JNLMParams;\n`;\n\n/**\n * Pass 1: the guide mean and the variance of that mean from the accumulated\n * moments. Ray buffers are stored bottom-up; guides use texture rows.\n */\nexport const jnlmGuideWGSL = /* wgsl */ `\n${paramsWGSL}\n${shadowCatcherAccumulationWGSL}\n@group(0) @binding(1) var<storage, read> accumulation: array<ShadowCatcherAccumulation>;\n@group(0) @binding(2) var<storage, read> normalBuffer: array<vec4f>;\n@group(0) @binding(3) var<storage, read_write> rawGuide: array<vec2f>;\n\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let size = params.size;\n if (id.x >= size.x || id.y >= size.y) { return; }\n let rayIndex = id.x + (size.y - 1u - id.y) * size.x;\n let n = normalBuffer[rayIndex].a;\n let moments = accumulation[rayIndex].moments;\n let mean = moments.x / max(n, 1.0);\n // Variance of the mean, or -1 when one sample cannot estimate it.\n var variance = -1.0;\n if (n >= 2.0) {\n variance = max(moments.y - n * mean * mean, 0.0) / (n * (n - 1.0));\n }\n rawGuide[id.x + id.y * size.x] = vec2f(mean, variance);\n}\n`;\n\n/**\n * Pass 2: smooth the noisy per-pixel variance over 3 x 3 pixels. Pixels with\n * a single sample take the spatial variance of their neighborhood instead.\n */\nexport const jnlmVarianceWGSL = /* wgsl */ `\n${paramsWGSL}\n@group(0) @binding(1) var<storage, read> rawGuide: array<vec2f>;\n@group(0) @binding(2) var<storage, read_write> guide: array<vec2f>;\n\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let size = params.size;\n if (id.x >= size.x || id.y >= size.y) { return; }\n let center = rawGuide[id.x + id.y * size.x];\n var varianceSum = 0.0;\n var varianceCount = 0.0;\n var meanSum = 0.0;\n var meanSquareSum = 0.0;\n for (var dy = -1; dy <= 1; dy++) {\n for (var dx = -1; dx <= 1; dx++) {\n let q = clamp(vec2i(id.xy) + vec2i(dx, dy), vec2i(0), vec2i(size) - 1);\n let value = rawGuide[u32(q.x) + u32(q.y) * size.x];\n meanSum += value.x;\n meanSquareSum += value.x * value.x;\n if (value.y >= 0.0) {\n varianceSum += value.y;\n varianceCount += 1.0;\n }\n }\n }\n var variance: f32;\n if (center.y >= 0.0) {\n variance = varianceSum / varianceCount;\n } else {\n let spatialMean = meanSum / 9.0;\n variance = max(meanSquareSum / 9.0 - spatialMean * spatialMean, 0.0) * 9.0 / 8.0;\n }\n guide[id.x + id.y * size.x] = vec2f(center.x, variance);\n}\n`;\n\n/** Copies the composed image into the filter's color buffer. */\nexport function createJNLMLoadColorWGSL(guides: JNLMGuideBindings = 'textures'): string {\n return /* wgsl */ `\n${paramsWGSL}\n@group(0) @binding(1) var colorTex: texture_2d<f32>;\n@group(0) @binding(2) var<storage, read_write> outColor: array<vec4f>;\n${guides === 'buffers' ? `${shadowCatcherAccumulationWGSL}\n@group(0) @binding(3) var<storage, read> normalAccumulation: array<vec4f>;\n@group(0) @binding(4) var<storage, read_write> normal: array<vec4f>;\n@group(0) @binding(5) var<storage, read> shadowAccumulation: array<ShadowCatcherAccumulation>;\n@group(0) @binding(6) var<storage, read_write> shadow: array<vec4f>;\n@group(0) @binding(7) var albedoTex: texture_2d<f32>;\n@group(0) @binding(8) var<storage, read_write> albedo: array<vec4f>;` : ''}\n\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n if (id.x >= params.size.x || id.y >= params.size.y) { return; }\n outColor[id.x + id.y * params.size.x] = textureLoad(colorTex, vec2i(id.xy), 0);\n${guides === 'buffers' ? ` let index = id.x + id.y * params.size.x;\n let rayIndex = id.x + (params.size.y - 1u - id.y) * params.size.x;\n let n = normalAccumulation[rayIndex];\n let samples = max(n.a, 1.0);\n normal[index] = vec4f(n.rgb / samples * 0.5 + 0.5, select(0.0, 1.0, dot(n.rgb, n.rgb) > 0.1));\n shadow[index] = shadowAccumulation[rayIndex].mask / samples;\n albedo[index] = textureLoad(albedoTex, vec2i(id.xy), 0);` : ''}\n}\n`;\n}\nexport const jnlmLoadColorWGSL = createJNLMLoadColorWGSL();\n\n/**\n * Pass 3: the joint NL-means filter of the composed image. It also filters\n * the guide with the same weights, with the variance of the weighted mean,\n * for a following pass. The albedo regression corrects color only, keeping\n * uncertain fitted texture responses out of the matching guide.\n */\nexport function createJNLMFilterWGSL({\n searchRadius, patchRadius, albedoBandwidth, normalBandwidth\n}: JNLMFilterShape = JNLM_DEFAULT_SHAPE, guides: JNLMGuideBindings = 'textures') {\n const patchSize = (2 * patchRadius + 1) ** 2;\n return /* wgsl */ `\n${paramsWGSL}\n@group(0) @binding(1) var<storage, read> guide: array<vec2f>;\n@group(0) @binding(2) var<storage, read> color: array<vec4f>;\n${guideBinding(guides, 3, 'albedoTex')}\n${guideBinding(guides, 4, 'normalTex')}\n@group(0) @binding(5) var<storage, read_write> outColor: array<vec4f>;\n@group(0) @binding(6) var<storage, read_write> outGuide: array<vec2f>;\n${guideBinding(guides, 7, 'shadowCatcherTex')}\n\nconst SEARCH_RADIUS = ${searchRadius};\nconst PATCH_RADIUS = ${patchRadius};\nconst PATCH_SIZE = ${patchSize};\n// Variance cancellation strength alpha of the paper.\nconst VARIANCE_CANCELLATION = 1.0;\n// Inverse squared feature bandwidths. Normals are encoded as n * 0.5 + 0.5.\nconst INV_ALBEDO_BANDWIDTH2 = ${(1 / albedoBandwidth ** 2).toFixed(6)};\nconst INV_COVERAGE_BANDWIDTH2 = ${(1 / JNLM_COVERAGE_BANDWIDTH ** 2).toFixed(6)};\nconst INV_NORMAL_BANDWIDTH2 = ${(1 / normalBandwidth ** 2).toFixed(6)};\n// Lower bound of the center weight, so a pixel unlike all of its neighbors\n// (a thin highlight) is kept rather than replaced by them.\nconst CENTER_WEIGHT_MIN = 0.01;\n\nfn pixelIndex(p: vec2i) -> u32 {\n let q = clamp(p, vec2i(0), vec2i(params.size) - 1);\n return u32(q.x) + u32(q.y) * params.size.x;\n}\n\nfn featureDistance(q: vec2i, centerAlbedo: vec4f, centerNormal: vec3f) -> f32 {\n let albedo = ${loadGuide(guides, 'albedoTex', 'q')};\n // Absolute differences in linear albedo erase low-contrast dark textures.\n // Measure RGB contrast relative to both pixels; keep coverage absolute.\n let albedoScale = max((albedo.rgb + centerAlbedo.rgb) * 0.5, vec3f(${JNLM_ALBEDO_FLOOR}));\n let albedoDelta = (albedo.rgb - centerAlbedo.rgb) / albedoScale;\n let coverageDelta = albedo.a - centerAlbedo.a;\n let normalDelta = ${loadGuide(guides, 'normalTex', 'q')}.rgb - centerNormal;\n return max(\n dot(albedoDelta, albedoDelta) * INV_ALBEDO_BANDWIDTH2\n + coverageDelta * coverageDelta * INV_COVERAGE_BANDWIDTH2,\n dot(normalDelta, normalDelta) * INV_NORMAL_BANDWIDTH2\n );\n}\n\nfn premultiplied(c: vec4f) -> vec4f {\n return vec4f(c.rgb * c.a, c.a);\n}\n\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let size = vec2i(params.size);\n let p = vec2i(id.xy);\n if (p.x >= size.x || p.y >= size.y) { return; }\n let step = i32(params.dilation);\n let centerAlbedo = ${loadGuide(guides, 'albedoTex', 'p')};\n let centerNormalTexel = ${loadGuide(guides, 'normalTex', 'p')};\n let centerNormal = centerNormalTexel.rgb;\n let index = pixelIndex(p);\n // Primary misses show the background directly. It has no features that\n // protect its detail and no noise beyond antialiasing, so keep it.\n if (centerNormalTexel.a <= 0.0 && ${loadGuide(guides, 'shadowCatcherTex', 'p')}.r <= 0.0) {\n outColor[index] = color[index];\n outGuide[index] = guide[index];\n return;\n }\n\n var centerPatch: array<vec2f, PATCH_SIZE>;\n var k = 0;\n for (var dy = -PATCH_RADIUS; dy <= PATCH_RADIUS; dy++) {\n for (var dx = -PATCH_RADIUS; dx <= PATCH_RADIUS; dx++) {\n centerPatch[k] = guide[pixelIndex(p + vec2i(dx, dy) * step)];\n k++;\n }\n }\n let k2 = params.colorSensitivity * params.colorSensitivity;\n\n var sum = vec4f(0.0);\n let featureScale = max(centerAlbedo.rgb, vec3f(${JNLM_ALBEDO_FLOOR}));\n var featureSum = vec3f(0.0);\n var featureSquareSum = vec3f(0.0);\n var featureColorSum = vec3f(0.0);\n var guideSum = 0.0;\n var guideVarianceSum = 0.0;\n var weightSum = 0.0;\n var maxWeight = 0.0;\n for (var sy = -SEARCH_RADIUS; sy <= SEARCH_RADIUS; sy++) {\n for (var sx = -SEARCH_RADIUS; sx <= SEARCH_RADIUS; sx++) {\n let q = p + vec2i(sx, sy) * step;\n if (\n (sx == 0 && sy == 0) ||\n q.x < 0 || q.y < 0 || q.x >= size.x || q.y >= size.y\n ) { continue; }\n\n let features = featureDistance(q, centerAlbedo, centerNormal);\n if (features > 16.0) { continue; }\n\n var colorDistance = 0.0;\n k = 0;\n for (var dy = -PATCH_RADIUS; dy <= PATCH_RADIUS; dy++) {\n for (var dx = -PATCH_RADIUS; dx <= PATCH_RADIUS; dx++) {\n let a = centerPatch[k];\n let b = guide[pixelIndex(q + vec2i(dx, dy) * step)];\n let delta = a.x - b.x;\n colorDistance += (delta * delta\n - VARIANCE_CANCELLATION * (a.y + min(a.y, b.y)))\n / (1e-10 + k2 * (a.y + b.y));\n k++;\n }\n }\n colorDistance /= f32(PATCH_SIZE);\n\n let weight = exp(-max(max(colorDistance, 0.0), features));\n let index = pixelIndex(q);\n let c = color[index];\n let g = guide[index];\n // Filter premultiplied color so coverage and color stay consistent.\n let a = (${loadGuide(guides, 'albedoTex', 'q')}.rgb - centerAlbedo.rgb) / featureScale;\n let value = premultiplied(c);\n sum += value * weight;\n featureSum += a * weight;\n featureSquareSum += a * a * weight;\n featureColorSum += a * value.rgb * weight;\n guideSum += g.x * weight;\n guideVarianceSum += g.y * weight * weight;\n weightSum += weight;\n maxWeight = max(maxWeight, weight);\n }\n }\n\n // Weight the center like its most similar neighbor (Buades et al. 2005)\n // instead of 1, so isolated outliers are not kept unfiltered.\n let center = color[index];\n let centerGuide = guide[index];\n let centerWeight = max(maxWeight, CENTER_WEIGHT_MIN);\n let totalWeight = weightSum + centerWeight;\n let centerValue = premultiplied(center);\n let mean = (sum + centerValue * centerWeight) / totalWeight;\n // Fit each color channel against relative albedo with the same JNLM weights.\n // Centering at this pixel avoids cancellation when the feature is constant.\n let meanFeature = featureSum / totalWeight;\n let featureVariance = max(featureSquareSum / totalWeight - meanFeature * meanFeature, vec3f(0.0));\n let covariance = featureColorSum / totalWeight - meanFeature * mean.rgb;\n // A 1% contrast ridge stabilizes flat guides. Bound the response between no\n // texture dependence and multiplicative albedo modulation; specular color\n // with an unrelated albedo falls back to the weighted mean.\n let meanAlbedo = centerAlbedo.rgb + meanFeature * featureScale;\n let maximumSlope = mean.rgb * featureScale / max(meanAlbedo, vec3f(${JNLM_ALBEDO_FLOOR}));\n let slope = clamp(covariance / (featureVariance + vec3f(${JNLM_ALBEDO_REGULARIZATION})), vec3f(0.0), maximumSlope);\n let corrected = mean.rgb - slope * meanFeature;\n let filtered = vec4f(max(corrected, vec3f(0.0)), max(mean.a, 0.0));\n let rgb = select(\n center.rgb,\n filtered.rgb / max(filtered.a, 1e-6),\n filtered.a > 1e-6\n );\n outColor[index] = vec4f(rgb, filtered.a);\n outGuide[index] = vec2f(\n (guideSum + centerGuide.x * centerWeight) / totalWeight,\n (guideVarianceSum + centerGuide.y * centerWeight * centerWeight)\n / (totalWeight * totalWeight)\n );\n}\n`;\n}\n\n// Compute presentation for hosts that consume the filtered buffer directly.\n// Use the same sRGB transfer as browser display composition.\nexport const jnlmEncodeWGSL = /* wgsl */ `\nstruct OutputParams { size: vec2u, premultiply: u32, reserved: u32 }\n@group(0) @binding(0) var<uniform> params: OutputParams;\n@group(0) @binding(1) var<storage, read> color: array<vec4f>;\n@group(0) @binding(2) var output: texture_storage_2d<rgba8unorm, write>;\n${linearToSrgbDefine}\n@compute @workgroup_size(${JNLM_WORKGROUP_SIZE}, ${JNLM_WORKGROUP_SIZE}, 1)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n if (any(id.xy >= params.size)) { return; }\n let c = color[id.y * params.size.x + id.x];\n var rgb = linearToSrgb(c.rgb);\n if (params.premultiply != 0u) { rgb *= c.a; }\n textureStore(output, id.xy, vec4f(rgb, c.a));\n}\n`;\n"]}
|