@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.
Files changed (372) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/LICENSES/FidelityFX-FSR.txt +19 -0
  3. package/LICENSES/pbrt-v4-Apache-2.0.txt +202 -0
  4. package/README.md +3 -0
  5. package/THIRD_PARTY_NOTICES.md +18 -4
  6. package/dist/assets/bvh.worker.js +1 -1
  7. package/dist/mutracer.umd.js +9328 -7614
  8. package/docs/api.md +83 -35
  9. package/docs/caustics.md +80 -0
  10. package/docs/materials.md +9 -6
  11. package/docs/performance.md +7 -0
  12. package/docs/post-processing.md +62 -9
  13. package/docs/progressive-rendering.md +53 -9
  14. package/docs/sampling.md +4 -0
  15. package/docs/technical/acceleration.md +52 -0
  16. package/docs/technical/aovs.md +79 -0
  17. package/docs/technical/caustics.md +109 -0
  18. package/docs/technical/continuous-spectrum.md +122 -0
  19. package/docs/technical/display-upscale.md +82 -0
  20. package/docs/technical/openpbr.md +99 -0
  21. package/docs/technical/pbrt-import.md +168 -0
  22. package/docs/technical/tiling.md +74 -0
  23. package/docs/technical/webgl.md +53 -0
  24. package/docs/updates.md +3 -1
  25. package/docs/viewer-checklist.md +65 -0
  26. package/docs/viewer.md +18 -6
  27. package/lib/Display.d.ts +19 -5
  28. package/lib/Display.js +10 -7
  29. package/lib/Display.js.map +1 -1
  30. package/lib/Engine.d.ts +4 -1
  31. package/lib/Engine.js +77 -14
  32. package/lib/Engine.js.map +1 -1
  33. package/lib/Render.d.ts +81 -24
  34. package/lib/Render.js +201 -26
  35. package/lib/Render.js.map +1 -1
  36. package/lib/RenderLoop.d.ts +7 -3
  37. package/lib/RenderLoop.js +68 -44
  38. package/lib/RenderLoop.js.map +1 -1
  39. package/lib/main.d.ts +4 -2
  40. package/lib/main.js +1 -0
  41. package/lib/main.js.map +1 -1
  42. package/lib/oidn.d.ts +1 -1
  43. package/lib/renderer/AovPrimitiveCache.js +39 -8
  44. package/lib/renderer/AovPrimitiveCache.js.map +1 -1
  45. package/lib/renderer/DisplayPostProcess.d.ts +8 -3
  46. package/lib/renderer/DisplayPostProcess.js +51 -18
  47. package/lib/renderer/DisplayPostProcess.js.map +1 -1
  48. package/lib/renderer/GPUTraceQueue.d.ts +2 -1
  49. package/lib/renderer/GPUTraceQueue.js +3 -1
  50. package/lib/renderer/GPUTraceQueue.js.map +1 -1
  51. package/lib/renderer/MaterialData.js +7 -2
  52. package/lib/renderer/MaterialData.js.map +1 -1
  53. package/lib/renderer/RenderingPipeline.d.ts +11 -1
  54. package/lib/renderer/RenderingPipeline.js.map +1 -1
  55. package/lib/renderer/TileRender.d.ts +2 -2
  56. package/lib/renderer/TileRender.js +128 -7
  57. package/lib/renderer/TileRender.js.map +1 -1
  58. package/lib/renderer/WGPUAov.d.ts +6 -1
  59. package/lib/renderer/WGPUAov.js +108 -14
  60. package/lib/renderer/WGPUAov.js.map +1 -1
  61. package/lib/renderer/WGPUCanvasSurface.js +12 -1
  62. package/lib/renderer/WGPUCanvasSurface.js.map +1 -1
  63. package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
  64. package/lib/renderer/WGPUCausticLightPass.js +217 -0
  65. package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
  66. package/lib/renderer/WGPUEnvMapDistribution.js +19 -19
  67. package/lib/renderer/WGPUFullQuadPass.js +3 -3
  68. package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
  69. package/lib/renderer/WGPUPhotonMap.js +191 -0
  70. package/lib/renderer/WGPUPhotonMap.js.map +1 -0
  71. package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
  72. package/lib/renderer/WGPURayTraceOutput.js +122 -28
  73. package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
  74. package/lib/renderer/WGPURayTracePass.d.ts +65 -21
  75. package/lib/renderer/WGPURayTracePass.js +730 -409
  76. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  77. package/lib/renderer/WGPURenderingPipeline.js +110 -68
  78. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  79. package/lib/renderer/WebGLAov.js +3 -3
  80. package/lib/renderer/WebGLAov.js.map +1 -1
  81. package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
  82. package/lib/renderer/WebGLFullQuadPass.js +10 -0
  83. package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
  84. package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
  85. package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -1
  86. package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
  87. package/lib/renderer/WebGLRayTracePass.d.ts +20 -11
  88. package/lib/renderer/WebGLRayTracePass.js +157 -116
  89. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  90. package/lib/renderer/WebGLRenderingPipeline.js +17 -14
  91. package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
  92. package/lib/renderer/WebGLSkyBaker.js +60 -60
  93. package/lib/renderer/accumulationTarget.d.ts +2 -1
  94. package/lib/renderer/accumulationTarget.js.map +1 -1
  95. package/lib/renderer/aov.d.ts +13 -1
  96. package/lib/renderer/aov.js +21 -2
  97. package/lib/renderer/aov.js.map +1 -1
  98. package/lib/renderer/aovDisplay.js +2 -2
  99. package/lib/renderer/aovDisplay.js.map +1 -1
  100. package/lib/renderer/binnedSahBvh.d.ts +2 -1
  101. package/lib/renderer/binnedSahBvh.js +24 -17
  102. package/lib/renderer/binnedSahBvh.js.map +1 -1
  103. package/lib/renderer/bvhAccel.d.ts +3 -1
  104. package/lib/renderer/bvhAccel.js +36 -6
  105. package/lib/renderer/bvhAccel.js.map +1 -1
  106. package/lib/renderer/cameraUniform.d.ts +1 -1
  107. package/lib/renderer/cameraUniform.js +6 -2
  108. package/lib/renderer/cameraUniform.js.map +1 -1
  109. package/lib/renderer/causticDiagnostics.d.ts +31 -0
  110. package/lib/renderer/causticDiagnostics.js +5 -0
  111. package/lib/renderer/causticDiagnostics.js.map +1 -0
  112. package/lib/renderer/causticEmission.d.ts +10 -0
  113. package/lib/renderer/causticEmission.js +40 -0
  114. package/lib/renderer/causticEmission.js.map +1 -0
  115. package/lib/renderer/causticUniform.d.ts +12 -0
  116. package/lib/renderer/causticUniform.js +22 -0
  117. package/lib/renderer/causticUniform.js.map +1 -0
  118. package/lib/renderer/caustics.d.ts +16 -0
  119. package/lib/renderer/caustics.js +93 -0
  120. package/lib/renderer/caustics.js.map +1 -0
  121. package/lib/renderer/compactVertices.d.ts +14 -0
  122. package/lib/renderer/compactVertices.js +124 -0
  123. package/lib/renderer/compactVertices.js.map +1 -0
  124. package/lib/renderer/easu.d.ts +11 -0
  125. package/lib/renderer/easu.js +156 -0
  126. package/lib/renderer/easu.js.map +1 -0
  127. package/lib/renderer/geometryUpdateState.d.ts +1 -0
  128. package/lib/renderer/geometryUpdateState.js.map +1 -1
  129. package/lib/renderer/geometryUpdates.js +3 -2
  130. package/lib/renderer/geometryUpdates.js.map +1 -1
  131. package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
  132. package/lib/renderer/glsl/aovReflectance.glsl.js +53 -53
  133. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
  134. package/lib/renderer/glsl/aovTrace.frag.js +45 -37
  135. package/lib/renderer/glsl/aovTrace.frag.js.map +1 -1
  136. package/lib/renderer/glsl/chunks/cameraProjection.glsl.d.ts +2 -0
  137. package/lib/renderer/glsl/chunks/cameraProjection.glsl.js +16 -0
  138. package/lib/renderer/glsl/chunks/cameraProjection.glsl.js.map +1 -0
  139. package/lib/renderer/glsl/chunks/cameraRay.glsl.d.ts +1 -1
  140. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -16
  141. package/lib/renderer/glsl/chunks/cameraRay.glsl.js.map +1 -1
  142. package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
  143. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
  144. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
  145. package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -73
  146. package/lib/renderer/glsl/chunks/intersect.glsl.js +478 -465
  147. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
  148. package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
  149. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +156 -153
  150. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
  151. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +771 -771
  152. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  153. package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
  154. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
  155. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +70 -63
  156. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
  157. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +89 -75
  158. package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
  159. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
  160. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +160 -118
  161. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
  162. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +14 -14
  163. package/lib/renderer/glsl/chunks/sampling.glsl.js +88 -91
  164. package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
  165. package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
  166. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.d.ts +6 -0
  167. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js +47 -0
  168. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js.map +1 -0
  169. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  170. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
  171. package/lib/renderer/glsl/depthQuery.frag.js +13 -7
  172. package/lib/renderer/glsl/depthQuery.frag.js.map +1 -1
  173. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
  174. package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
  175. package/lib/renderer/glsl/rayTrace.frag.js +338 -252
  176. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  177. package/lib/renderer/glsl/shadowCatcher.frag.js +27 -26
  178. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
  179. package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
  180. package/lib/renderer/gpuMemory.js +1 -1
  181. package/lib/renderer/gpuMemory.js.map +1 -1
  182. package/lib/renderer/lightSampling.d.ts +3 -2
  183. package/lib/renderer/lightSampling.js +14 -8
  184. package/lib/renderer/lightSampling.js.map +1 -1
  185. package/lib/renderer/openPbrEnergyLut.d.ts +2 -1
  186. package/lib/renderer/openPbrEnergyLut.js +18 -1
  187. package/lib/renderer/openPbrEnergyLut.js.map +1 -1
  188. package/lib/renderer/pathLimits.d.ts +5 -4
  189. package/lib/renderer/pathLimits.js +6 -5
  190. package/lib/renderer/pathLimits.js.map +1 -1
  191. package/lib/renderer/prepareMeshBVH.d.ts +2 -0
  192. package/lib/renderer/prepareMeshBVH.js +5 -3
  193. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  194. package/lib/renderer/progressiveScheduler.js +5 -4
  195. package/lib/renderer/progressiveScheduler.js.map +1 -1
  196. package/lib/renderer/renderPipeline.d.ts +5 -0
  197. package/lib/renderer/renderPipeline.js +1 -0
  198. package/lib/renderer/renderPipeline.js.map +1 -1
  199. package/lib/renderer/researchTransport.d.ts +31 -0
  200. package/lib/renderer/researchTransport.js +5 -0
  201. package/lib/renderer/researchTransport.js.map +1 -0
  202. package/lib/renderer/sceneAssembly.d.ts +1 -1
  203. package/lib/renderer/sceneAssembly.js +4 -3
  204. package/lib/renderer/sceneAssembly.js.map +1 -1
  205. package/lib/renderer/sceneChangeDetector.d.ts +1 -1
  206. package/lib/renderer/sceneChangeDetector.js +3 -1
  207. package/lib/renderer/sceneChangeDetector.js.map +1 -1
  208. package/lib/renderer/sceneLayoutSchema.d.ts +2 -0
  209. package/lib/renderer/sceneLayoutSchema.js +3 -3
  210. package/lib/renderer/sceneLayoutSchema.js.map +1 -1
  211. package/lib/renderer/scenePreparation.d.ts +3 -2
  212. package/lib/renderer/scenePreparation.js +18 -2
  213. package/lib/renderer/scenePreparation.js.map +1 -1
  214. package/lib/renderer/scratchSampling.js +6 -6
  215. package/lib/renderer/spectrum.d.ts +21 -0
  216. package/lib/renderer/spectrum.js +154 -0
  217. package/lib/renderer/spectrum.js.map +1 -0
  218. package/lib/renderer/staticBvhPreparation.d.ts +9 -0
  219. package/lib/renderer/staticBvhPreparation.js +8 -2
  220. package/lib/renderer/staticBvhPreparation.js.map +1 -1
  221. package/lib/renderer/surfaceHit.d.ts +10 -0
  222. package/lib/renderer/surfaceHit.js +22 -0
  223. package/lib/renderer/surfaceHit.js.map +1 -0
  224. package/lib/renderer/tangentRequirements.d.ts +2 -0
  225. package/lib/renderer/tangentRequirements.js +7 -0
  226. package/lib/renderer/tangentRequirements.js.map +1 -0
  227. package/lib/renderer/textureAtlas.d.ts +4 -0
  228. package/lib/renderer/textureAtlas.js +48 -2
  229. package/lib/renderer/textureAtlas.js.map +1 -1
  230. package/lib/renderer/webgl.js +4 -0
  231. package/lib/renderer/webgl.js.map +1 -1
  232. package/lib/renderer/webglLightSampling.d.ts +39 -0
  233. package/lib/renderer/webglLightSampling.js +15 -0
  234. package/lib/renderer/webglLightSampling.js.map +1 -0
  235. package/lib/renderer/webglVertexAttributes.d.ts +17 -0
  236. package/lib/renderer/webglVertexAttributes.js +44 -0
  237. package/lib/renderer/webglVertexAttributes.js.map +1 -0
  238. package/lib/renderer/wgpuBindlessSource.js +3 -3
  239. package/lib/renderer/wgpuMaterialConstants.js +4 -3
  240. package/lib/renderer/wgpuMaterialConstants.js.map +1 -1
  241. package/lib/renderer/wgpuSceneUniform.js +2 -2
  242. package/lib/renderer/wgpuSceneUniform.js.map +1 -1
  243. package/lib/renderer/wgpuShaderSource.d.ts +22 -0
  244. package/lib/renderer/wgpuShaderSource.js +3 -1
  245. package/lib/renderer/wgpuShaderSource.js.map +1 -1
  246. package/lib/renderer/wgsl/aovCompact.wgsl.d.ts +4 -0
  247. package/lib/renderer/wgsl/aovCompact.wgsl.js +26 -0
  248. package/lib/renderer/wgsl/aovCompact.wgsl.js.map +1 -0
  249. package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
  250. package/lib/renderer/wgsl/aovReflectance.wgsl.js +55 -55
  251. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
  252. package/lib/renderer/wgsl/aovResolve.wgsl.d.ts +4 -0
  253. package/lib/renderer/wgsl/aovResolve.wgsl.js +21 -2
  254. package/lib/renderer/wgsl/aovResolve.wgsl.js.map +1 -1
  255. package/lib/renderer/wgsl/aovSnapshot.wgsl.js +2 -2
  256. package/lib/renderer/wgsl/aovSnapshot.wgsl.js.map +1 -1
  257. package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
  258. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.d.ts +2 -0
  259. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js +56 -0
  260. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js.map +1 -0
  261. package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
  262. package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
  263. package/lib/renderer/wgsl/causticLight.compute.js +539 -0
  264. package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
  265. package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
  266. package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
  267. package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
  268. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
  269. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
  270. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
  271. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
  272. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
  273. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
  274. package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
  275. package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
  276. package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
  277. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  278. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +37 -37
  279. package/lib/renderer/wgsl/chunks/intersect.wgsl.d.ts +6 -2
  280. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +634 -620
  281. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  282. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.d.ts +1 -1
  283. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +23 -21
  284. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -1
  285. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +3 -1
  286. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +77 -74
  287. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
  288. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +795 -795
  289. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +139 -139
  290. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  291. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +5 -1
  292. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +45 -29
  293. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
  294. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
  295. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +142 -138
  296. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
  297. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
  298. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +228 -189
  299. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
  300. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
  301. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +11 -11
  302. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
  303. package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
  304. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +88 -92
  305. package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
  306. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
  307. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.d.ts +6 -0
  308. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js +66 -0
  309. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js.map +1 -0
  310. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
  311. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.d.ts +2 -0
  312. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js +61 -0
  313. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js.map +1 -0
  314. package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +2 -0
  315. package/lib/renderer/wgsl/compose.frag.wgsl.js +100 -98
  316. package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
  317. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  318. package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +1 -1
  319. package/lib/renderer/wgsl/depthQuery.wgsl.js +12 -3
  320. package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -1
  321. package/lib/renderer/wgsl/easu.wgsl.d.ts +2 -0
  322. package/lib/renderer/wgsl/easu.wgsl.js +80 -0
  323. package/lib/renderer/wgsl/easu.wgsl.js.map +1 -0
  324. package/lib/renderer/wgsl/easuDetail.wgsl.d.ts +21 -0
  325. package/lib/renderer/wgsl/easuDetail.wgsl.js +230 -0
  326. package/lib/renderer/wgsl/easuDetail.wgsl.js.map +1 -0
  327. package/lib/renderer/wgsl/jnlm.compute.js +74 -74
  328. package/lib/renderer/wgsl/jnlm.compute.js.map +1 -1
  329. package/lib/renderer/wgsl/offlineAdaptive.compute.js +26 -26
  330. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
  331. package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
  332. package/lib/renderer/wgsl/photonMap.compute.js +352 -0
  333. package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
  334. package/lib/renderer/wgsl/proceduralSky.compute.js +64 -64
  335. package/lib/renderer/wgsl/rayTrace.compute.d.ts +23 -1
  336. package/lib/renderer/wgsl/rayTrace.compute.js +531 -543
  337. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  338. package/lib/scene/MaterialState.d.ts +2 -2
  339. package/lib/scene/MaterialState.js +2 -2
  340. package/lib/scene/MaterialState.js.map +1 -1
  341. package/lib/scene/Scene.js.map +1 -1
  342. package/lib/settings/render.d.ts +22 -0
  343. package/lib/settings/render.js +2 -0
  344. package/lib/settings/render.js.map +1 -1
  345. package/lib/util/fromTHREE.js +23 -16
  346. package/lib/util/fromTHREE.js.map +1 -1
  347. package/lib/version.d.ts +1 -1
  348. package/lib/version.js +1 -1
  349. package/lib/version.js.map +1 -1
  350. package/llms.txt +2 -1
  351. package/package.json +7 -4
  352. package/lib/oidnBundledWeights.cdn.d.ts +0 -2
  353. package/lib/oidnBundledWeights.cdn.js +0 -5
  354. package/lib/oidnBundledWeights.cdn.js.map +0 -1
  355. package/lib/renderer/bvh.worker.d.ts +0 -1
  356. package/lib/renderer/bvh.worker.js +0 -16
  357. package/lib/renderer/bvh.worker.js.map +0 -1
  358. package/lib/renderer/bvhWorkerBuild.d.ts +0 -13
  359. package/lib/renderer/bvhWorkerBuild.js +0 -17
  360. package/lib/renderer/bvhWorkerBuild.js.map +0 -1
  361. package/lib/renderer/lightCore.d.ts +0 -12
  362. package/lib/renderer/lightCore.js +0 -13
  363. package/lib/renderer/lightCore.js.map +0 -1
  364. package/lib/renderer/materialCore.d.ts +0 -8
  365. package/lib/renderer/materialCore.js +0 -9
  366. package/lib/renderer/materialCore.js.map +0 -1
  367. package/lib/scene/PixelTexture.d.ts +0 -6
  368. package/lib/scene/PixelTexture.js +0 -10
  369. package/lib/scene/PixelTexture.js.map +0 -1
  370. package/lib/util/studioLighting.d.ts +0 -18
  371. package/lib/util/studioLighting.js +0 -36
  372. 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}[gv(${position})]`;
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 qE = id.x + (size.y - 1u - id.y) * size.x;
47
- let n = normalBuffer[qE].a;
48
- let moments = accumulation[qE].moments;
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 mf = 0.0;
73
- var jx = 0.0;
74
- var nW = 0.0;
75
- var jp = 0.0;
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
- nW += value.x;
81
- jp += value.x * value.x;
80
+ oL += value.x;
81
+ jB += value.x * value.x;
82
82
  if (value.y >= 0.0) {
83
- mf += value.y;
84
- jx += 1.0;
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 = mf / jx;
90
+ variance = mF / jI;
91
91
  } else {
92
- let me = nW / 9.0;
93
- variance = max(jp / 9.0 - me * me, 0.0) * 9.0 / 8.0;
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 qE = id.x + (params.size.y - 1u - id.y) * params.size.x;
118
- let n = normalAccumulation[qE];
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[qE].mask / samples;
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 gv(p: vec2i) -> u32 {
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 mi(q: vec2i, bC: vec4f, hg: vec3f) -> f32 {
163
+ fn mJ(q: vec2i, bp: vec4f, hk: vec3f) -> f32 {
164
164
  let albedo = ${loadGuide(guides, 'albedoTex', 'q')};
165
165
 
166
166
 
167
- let qd = max((albedo.rgb + bC.rgb) * 0.5, vec3f(${JNLM_ALBEDO_FLOOR}));
168
- let lN = (albedo.rgb - bC.rgb) / qd;
169
- let jk = albedo.a - bC.a;
170
- let lX = ${loadGuide(guides, 'normalTex', 'q')}.rgb - hg;
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(lN, lN) * INV_ALBEDO_BANDWIDTH2
173
- + jk * jk * INV_COVERAGE_BANDWIDTH2,
174
- dot(lX, lX) * INV_NORMAL_BANDWIDTH2
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 bC = ${loadGuide(guides, 'albedoTex', 'p')};
189
- let fR = ${loadGuide(guides, 'normalTex', 'p')};
190
- let hg = fR.rgb;
191
- let index = gv(p);
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 (fR.a <= 0.0 && ${loadGuide(guides, 'shadowCatcherTex', 'p')}.r <= 0.0) {
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 lP: array<vec2f, PATCH_SIZE>;
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
- lP[k] = guide[gv(p + vec2i(dx, dy) * step)];
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 hk = max(bC.rgb, vec3f(${JNLM_ALBEDO_FLOOR}));
212
- var ng = vec3f(0.0);
213
- var gN = vec3f(0.0);
214
- var hG = vec3f(0.0);
215
- var qp = 0.0;
216
- var gP = 0.0;
217
- var oP = 0.0;
218
- var lt = 0.0;
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 = mi(q, bC, hg);
227
+ let features = mJ(q, bp, hk);
228
228
  if (features > 16.0) { continue; }
229
229
 
230
- var gk = 0.0;
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 = lP[k];
235
- let b = guide[gv(q + vec2i(dx, dy) * step)];
236
- let rZ = a.x - b.x;
237
- gk += (rZ * rZ
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
- gk /= f32(PATCH_SIZE);
243
+ gn /= f32(PATCH_SIZE);
244
244
 
245
- let weight = exp(-max(max(gk, 0.0), features));
246
- let index = gv(q);
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 - bC.rgb) / hk;
250
+ let a = (${loadGuide(guides, 'albedoTex', 'q')}.rgb - bp.rgb) / hn;
251
251
  let value = premultiplied(c);
252
252
  sum += value * weight;
253
- ng += a * weight;
254
- gN += a * a * weight;
255
- hG += a * value.rgb * weight;
256
- qp += g.x * weight;
257
- gP += g.y * weight * weight;
258
- oP += weight;
259
- lt = max(lt, weight);
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 lO = guide[index];
267
- let dR = max(lt, CENTER_WEIGHT_MIN);
268
- let totalWeight = oP + dR;
269
- let vj = premultiplied(center);
270
- let mean = (sum + vj * dR) / totalWeight;
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 eI = ng / totalWeight;
274
- let mj = max(gN / totalWeight - eI * eI, vec3f(0.0));
275
- let rv = hG / totalWeight - eI * mean.rgb;
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 rI = bC.rgb + eI * hk;
280
- let po = mean.rgb * hk / max(rI, vec3f(${JNLM_ALBEDO_FLOOR}));
281
- let xg = clamp(rv / (mj + vec3f(${JNLM_ALBEDO_REGULARIZATION})), vec3f(0.0), po);
282
- let sD = mean.rgb - xg * eI;
283
- let filtered = vec4f(max(sD, vec3f(0.0)), max(mean.a, 0.0));
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
- (qp + lO.x * dR) / totalWeight,
292
- (gP + lO.y * dR * dR)
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":"AAAA,OAAO,6BAA6B,MAAM,4CAA4C,CAAC;AACvF,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EACL,8BAA8B,EAAE,kBAAkB,EAAE,iBAAiB,EACrE,0BAA0B,EAAE,uBAAuB,EAAE,gBAAgB,EAEtE,MAAM,YAAY,CAAC;AAIpB,SAAS,YAAY,CAAC,MAAyB,EAAE,OAAe,EAAE,IAAY;IAC5E,OAAO,MAAM,KAAK,UAAU;QAC1B,CAAC,CAAC,sBAAsB,OAAO,SAAS,IAAI,oBAAoB;QAChE,CAAC,CAAC,sBAAsB,OAAO,wBAAwB,IAAI,iBAAiB,CAAC;AACjF,CAAC;AACD,SAAS,SAAS,CAAC,MAAyB,EAAE,IAAY,EAAE,QAAgB;IAC1E,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,eAAe,IAAI,KAAK,QAAQ,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,OAAe,QAAQ,IAAI,CAAC;AAC7G,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,MAAc,EAAE,QAAgB,EAAE,IAAY;IAC9F,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;IAClC,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAChC,8BAA8B,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC;KAClF,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAErC,MAAM,UAAU,GAAG,UAAU,CAAC;;;;;;;;CAQ7B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;EACtC,UAAU;EACV,6BAA6B;;;;;2BAKJ,mBAAmB,KAAK,mBAAmB;;;;;;;;;;;;;;;CAerE,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;EACzC,UAAU;;;;2BAIe,mBAAmB,KAAK,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BrE,CAAC;AAEF,gEAAgE;AAChE,MAAM,UAAU,uBAAuB,CAAC,SAA4B,UAAU;IAC5E,OAAO,UAAU,CAAC;EAClB,UAAU;;;EAGV,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,6BAA6B;;;;;;qEAMY,CAAC,CAAC,CAAC,EAAE;;2BAE/C,mBAAmB,KAAK,mBAAmB;;;;EAIpE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC;;;;;;yDAMkC,CAAC,CAAC,CAAC,EAAE;;CAE/D,CAAC;AACF,CAAC;AACD,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,EAAE,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,EACnC,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,KACxC,kBAAkB,EAAE,SAA4B,UAAU;IAC7E,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,UAAU,CAAC;EAClB,UAAU;;;EAGV,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC;EACpC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC;;;EAGpC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,kBAAkB,CAAC;;wBAErB,YAAY;uBACb,WAAW;qBACb,SAAS;;;;gCAIE,CAAC,CAAC,GAAG,eAAe,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;kCACnC,CAAC,CAAC,GAAG,uBAAuB,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;gCAC/C,CAAC,CAAC,GAAG,eAAe,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;;;;;;;;;;;eAWpD,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;;;kDAGmB,iBAAiB;;;WAGlE,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;;;;;;;;;;;;2BAY9B,mBAAmB,KAAK,mBAAmB;;;;;;WAM/C,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;WAC9B,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;;;;;qBAKzB,SAAS,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,CAAC;;;;;;;;;;;;;;;;;6BAiB7B,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAuCnD,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yCA8BmB,iBAAiB;kCAC5B,0BAA0B;;;;;;;;;;;;;;;CAerF,CAAC;AACF,CAAC;AAED,4EAA4E;AAC5E,6DAA6D;AAC7D,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;;;;;EAKvC,kBAAkB;2BACO,mBAAmB,KAAK,mBAAmB;;;;;;;;CAQrE,CAAC","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":"AAAA,OAAO,6BAA6B,MAAM,4CAA4C,CAAC;AACvF,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EACL,8BAA8B,EAAE,kBAAkB,EAAE,iBAAiB,EACrE,0BAA0B,EAAE,uBAAuB,EAAE,gBAAgB,EAEtE,MAAM,YAAY,CAAC;AAIpB,SAAS,YAAY,CAAC,MAAyB,EAAE,OAAe,EAAE,IAAY;IAC5E,OAAO,MAAM,KAAK,UAAU;QAC1B,CAAC,CAAC,sBAAsB,OAAO,SAAS,IAAI,oBAAoB;QAChE,CAAC,CAAC,sBAAsB,OAAO,wBAAwB,IAAI,iBAAiB,CAAC;AACjF,CAAC;AACD,SAAS,SAAS,CAAC,MAAyB,EAAE,IAAY,EAAE,QAAgB;IAC1E,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,eAAe,IAAI,KAAK,QAAQ,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,OAAe,QAAQ,IAAI,CAAC;AAC7G,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,MAAc,EAAE,QAAgB,EAAE,IAAY;IAC9F,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;IAClC,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAChC,8BAA8B,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC;KAClF,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAErC,MAAM,UAAU,GAAG,UAAU,CAAC;;;;;;;;CAQ7B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;EACtC,UAAU;EACV,6BAA6B;;;;;2BAKJ,mBAAmB,KAAK,mBAAmB;;;;;;;;;;;;;;;CAerE,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;EACzC,UAAU;;;;2BAIe,mBAAmB,KAAK,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BrE,CAAC;AAEF,gEAAgE;AAChE,MAAM,UAAU,uBAAuB,CAAC,SAA4B,UAAU;IAC5E,OAAO,UAAU,CAAC;EAClB,UAAU;;;EAGV,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,6BAA6B;;;;;;qEAMY,CAAC,CAAC,CAAC,EAAE;;2BAE/C,mBAAmB,KAAK,mBAAmB;;;;EAIpE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC;;;;;;yDAMkC,CAAC,CAAC,CAAC,EAAE;;CAE/D,CAAC;AACF,CAAC;AACD,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,EAAE,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,EACnC,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,KACxC,kBAAkB,EAAE,SAA4B,UAAU;IAC7E,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,UAAU,CAAC;EAClB,UAAU;;;EAGV,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC;EACpC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC;;;EAGpC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,kBAAkB,CAAC;;wBAErB,YAAY;uBACb,WAAW;qBACb,SAAS;;;;gCAIE,CAAC,CAAC,GAAG,eAAe,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;kCACnC,CAAC,CAAC,GAAG,uBAAuB,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;gCAC/C,CAAC,CAAC,GAAG,eAAe,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;;;;;;;;;;;eAWpD,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;;;kDAGmB,iBAAiB;;;WAGlE,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;;;;;;;;;;;;2BAY9B,mBAAmB,KAAK,mBAAmB;;;;;;WAM/C,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;WAC9B,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;;;;;qBAKzB,SAAS,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,CAAC;;;;;;;;;;;;;;;;;6BAiB7B,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAuCnD,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yCA8BmB,iBAAiB;qCAC5B,0BAA0B;;;;;;;;;;;;;;;CAerF,CAAC;AACF,CAAC;AAED,4EAA4E;AAC5E,6DAA6D;AAC7D,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;;;;;EAKvC,kBAAkB;2BACO,mBAAmB,KAAK,mBAAmB;;;;;;;;CAQrE,CAAC","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"]}