@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,79 @@
1
+ # Surface color AOVs
2
+
3
+ MuTracer has two pairs of surface color outputs. `diffuseAlbedo` and
4
+ `specularAlbedo` report how much light a surface reflects. `diffuseColor` and
5
+ `specularColor` report the material colors that produce it. Usage and the exact
6
+ definitions are in [surface outputs](../api.md#surface-outputs); this note explains
7
+ why both pairs exist, how they are named and how they are stored.
8
+
9
+ ## Reflectance and color
10
+
11
+ The albedo kinds are directional-hemispherical reflectance: the fraction of
12
+ uniform incident light that the prepared OpenPBR BSDF reflects toward the view
13
+ direction. They integrate the BSDF by quadrature (`aovReflectance*.wgsl.ts` and
14
+ `.glsl.ts`), so they cost more than the other outputs and support only materials
15
+ the quadrature models. Coat, fuzz, anisotropy and similar hits are marked
16
+ unsupported.
17
+
18
+ The color kinds read resolved material inputs without integration:
19
+
20
+ - diffuse: base weight × base color × (1 − metalness)
21
+ - specular: metalness × base weight × base color + (1 − metalness) ×
22
+ specular weight × specular color × F0, with F0 from the specular IOR
23
+
24
+ Both are multiplied by the coat color, weighted by the coat weight. Transmission,
25
+ partial alpha, media and nonstandard materials have zero validity.
26
+
27
+ EASU detail recovery needs the color kinds, not reflectance. It compares
28
+ display-resolution colors with their average over each render pixel and scales
29
+ the part of the upscaled radiance that follows color by that ratio. Layer factors
30
+ that are uniform across a surface, such as coat or fuzz, cancel in the ratio, so
31
+ the cheap estimate covers more surfaces than the quadrature does. Reflectance
32
+ remains the right input for compositing and other uses that need reflected energy.
33
+
34
+ ## Names
35
+
36
+ Renderers use "albedo" for both quantities:
37
+
38
+ | Renderer | Integrated reflectance | Material color |
39
+ | --- | --- | --- |
40
+ | [MoonRay](https://docs.openmoonray.org/user-reference/how-to-guides/render-outputs/material-aovs/) | `albedo`: response to unoccluded white light from all directions | `color` |
41
+ | [Mitsuba 3](https://mitsuba.readthedocs.io/en/stable/src/generated/plugins_integrators.html) | `albedo`: diffuse reflectance, approximate for some materials | none |
42
+ | [Arnold](https://help.autodesk.com/cloudhelp/ENU/AR-Core/files/ac-output-aovs/arnold_user_guide_ac_output_aovs_ac_aovs_html.html) | none | `diffuse_albedo`, `specular_albedo`: colors without lighting |
43
+ | [Cycles](https://docs.blender.org/manual/en/latest/render/layers/passes.html) | none | `Diffuse Color`, `Glossy Color`: BSDF color weights |
44
+ | V-Ray, Redshift | none | `Diffuse Filter` |
45
+
46
+ MuTracer's names match MoonRay: albedo for integrated reflectance, color for
47
+ material inputs. Albedo for reflectance also matches Mitsuba and the
48
+ "directional albedo" of energy compensation work. Arnold uses albedo for colors,
49
+ so documentation always states the definition next to the name. No surveyed
50
+ renderer names an AOV "reflectance"; the viewer uses it only in labels.
51
+
52
+ These outputs are separate from the OIDN `albedo` guide, which follows its own
53
+ path rules for denoising.
54
+
55
+ ## Storage
56
+
57
+ The WebGPU accumulation has four `vec4f` lanes per pixel (`AOV_LAYOUT`):
58
+ depth with roughness, position with hit count, diffuse and specular. A lobe's
59
+ albedo and color share its lane, selected per kind by mask bits 3–4 and 7–8, so a
60
+ render may request only one of them per lobe. Resolve, display and snapshot shaders
61
+ see a color kind as the albedo kind of its lane (`aovLaneKind`), so they need no
62
+ color-specific code. Separate lanes would add 32 bytes per pixel to every AOV
63
+ render. If both are ever needed together, adding lanes lifts the restriction
64
+ without changing the API.
65
+
66
+ Display-resolution renders requesting only `depth`, `diffuseColor` and
67
+ `specularColor` use a compact 24-byte layout: float depth and hit count, plus two
68
+ colors in half precision. It skips the center pass, so it has no ID or motion.
69
+ Half-precision sums lose precision as the sample count grows, which is acceptable
70
+ only because display resolution stops at 4 samples (`DISPLAY_AOV_SAMPLE_CAP`).
71
+ For detail recovery, 4 jittered samples matched 16 within measurement noise.
72
+ Render resolution keeps the full layout for any request.
73
+
74
+ ## Native
75
+
76
+ Native shares `aovTraceDisplay.wgsl.ts` but still requests display-resolution
77
+ colors through the albedo bits with the `AOV_DISPLAY` override. The web build never
78
+ sets that override. Native should adopt the color kinds when it is next aligned
79
+ with web AOVs.
@@ -0,0 +1,109 @@
1
+ # Caustic estimators
2
+
3
+ Path tracing finds caustics only when a BSDF sample happens to hit the light
4
+ through a smooth surface, because shadow rays treat glass as an occluder. MuTracer
5
+ adds two WebGPU estimators for those paths: light tracing and progressive photon
6
+ mapping. Usage, costs and limits are in the [caustics guide](../caustics.md); this
7
+ note explains how the paths are divided and how the estimators work.
8
+
9
+ ## Path partition
10
+
11
+ A surface is *smooth* when `isCausticSpecular` holds: no diffuse, subsurface, fuzz
12
+ or coat weight, and specular roughness at most `CAUSTIC_ROUGHNESS_MAX` (0.1). The
13
+ test depends only on the material, never on the lobe a sample chose, so every
14
+ estimator classifies a vertex the same way (`src/renderer/caustics.ts`).
15
+
16
+ Counting from the light, the *receiver* is the first non-smooth surface vertex, k is
17
+ the number of smooth vertices between the light and the receiver, and the *camera
18
+ prefix* is the path from the camera to the receiver. Surfaces skipped through alpha
19
+ are not vertices.
20
+
21
+ | Paths | Estimator |
22
+ | --- | --- |
23
+ | k = 0 | Path tracing, unchanged |
24
+ | k ≥ 1, empty camera prefix (the camera sees the receiver directly), light tracing on | Light tracing |
25
+ | Other k ≥ 1, photon mapping on | Photon mapping |
26
+ | Anything else | Path tracing's BSDF hits |
27
+
28
+ Path tracing tracks the smooth chain length and whether the receiver is the first
29
+ visible surface. When it hits a light on a path that an enabled estimator owns, the
30
+ contribution is zero. Each path is therefore counted by exactly one estimator.
31
+ When an estimator is off or unsupported (WebGL 2), its rows fall back to path
32
+ tracing, so every configuration has the same expected image and differs only in
33
+ noise. Scenes whose smooth casters use stochastic blend coverage turn both
34
+ estimators off, because their photon transport does not yet match path-tracing
35
+ energy.
36
+
37
+ ## Casters and emission
38
+
39
+ The renderer collects caster meshes and instances automatically from smooth
40
+ materials, with one conservative world-space sphere per instance. Photons are
41
+ emitted only toward caster spheres: a path whose first surface from the light is
42
+ not smooth belongs to path tracing, so restricting emission loses no estimator's
43
+ paths. Emission samples the full light surface, then accepts directions toward
44
+ the caster mixture; the direction PDF accounts for overlapping cones
45
+ (`casterDirectionPdf`). Lights are chosen by a separate power PMF, not the NEE
46
+ selection table (`causticEmission.ts`). Directional and environment light emit
47
+ from a mixture of projected caster disks.
48
+
49
+ ## Light tracing
50
+
51
+ Light tracing is BDPT's t = 1 strategy. Photons follow smooth vertices with adjoint
52
+ BSDF corrections. At the receiver, the pass samples a lens point, connects to the
53
+ camera, tests visibility and splats into the pixel with float32 compare-and-swap
54
+ atomics. It runs once per sample batch, after the last tile, in its own pass and
55
+ bind group, because the main integrator already uses the baseline eight storage
56
+ buffers. The result accumulates separately; resolve adds it to the path-traced
57
+ radiance, so readback, denoising and display see the combined image. Adaptive
58
+ sampling measures variance only on the path-traced part.
59
+
60
+ ## Photon mapping
61
+
62
+ Photon mapping uses probabilistic progressive photon mapping (Knaus and Zwicker
63
+ 2011). Each sample batch emits a new photon map on the first tile, which later tiles
64
+ reuse. The gather radius shrinks as r_i = r_1 · i^((α − 1)/2) with α = 2/3
65
+ (`photonRadius`). The first radius is `initialRadius`, or 3% of the caster sphere
66
+ radius. At the first non-smooth camera vertex the radius is further limited to
67
+ about two projected pixels (`photonVisibleRadius`); mirror prefixes use an
68
+ approximate footprint, and deeper vertices use the global radius. The pixel cap
69
+ reduces blotches after denoising, at the cost of grainier raw output.
70
+
71
+ Photons are stored at the first non-smooth surface after at least one smooth
72
+ vertex, whatever camera prefix will later find them; queries at directly visible
73
+ receivers skip them when light tracing owns those paths. Camera paths query at every non-smooth vertex within `bounces`, with a
74
+ normal-consistent surface kernel.
75
+
76
+ Each photon takes 20 bytes: float32 position and RGBE8 flux, and two octahedral
77
+ directions (incident direction and normal). Two hash-grid layers with different
78
+ cell sizes, one for first visible receivers and one for deeper queries, each sort
79
+ only a 4-byte index, for 28 bytes per photon. Grids are rebuilt every batch with a
80
+ count, a two-level parallel prefix sum and a scatter; bucket counts are powers of
81
+ two up to 262144. Batches are reduced to fit device limits and a 128 MiB budget.
82
+
83
+ Quantized flux and directions limit precision. More batches shrink the radius
84
+ but do not remove that error, so photon mapping is consistent only up to storage
85
+ precision.
86
+
87
+ ## Rejected and paused work
88
+
89
+ - **MNEE and SMS** (manifold next-event estimation and specular manifold sampling)
90
+ are paused and excluded from builds. Behind a wavy refractive surface, each
91
+ receiver can have dozens of manifold roots; Newton's method finds one, and SMS
92
+ with its 64-trial cap covered only part of their weight, so neither converged
93
+ to an unbiased result. The code remains in
94
+ `src/research/mnee/`, registered explicitly by tests and research scripts. A
95
+ restart should address multiple roots, for example with manifold path guiding
96
+ (Fan et al. 2023).
97
+ - **Separate photon denoising**, filtering photon contributions apart from path
98
+ tracing, increased error in comparisons and is not used.
99
+ - **Photon-count radius scaling**, per-caster radii and larger shrinking screen
100
+ radii remain internal diagnostics (`causticDiagnostics.ts`). None improved
101
+ equal-error time on every test scene.
102
+
103
+ ## Changing this code
104
+
105
+ - The partition runs in WGSL. WebGL traces plain paths and needs no zeroing; its
106
+ GLSL chain state exists only for the paused MNEE research path.
107
+ - Native shares the WGSL and exposes the same two estimators.
108
+ - Compare raw HDR, not denoised output, when judging energy; see the guide's
109
+ limits for denoiser artifacts.
@@ -0,0 +1,122 @@
1
+ # Continuous-wavelength dispersion
2
+
3
+ MuTracer renders dispersion with continuous wavelengths inside an RGB renderer.
4
+ Wavelength decides how light refracts and how its energy divides between RGB
5
+ channels. Lights, materials and media stay RGB, so this is not spectral rendering.
6
+ The implementation is in `src/renderer/spectrum.ts`, and it runs only when a material
7
+ has `transmissionDispersionScale > 0`.
8
+
9
+ ## Spectral response
10
+
11
+ `spectrumRgb` converts a wavelength in 400–700 nm to linear sRGB weights:
12
+
13
+ 1. Evaluate the CIE 1931 matching functions with the multi-lobe fit from Wyman, Sloan
14
+ and Shirley, "Simple Analytic Approximations to the CIE XYZ Color Matching
15
+ Functions", JCGT 2(2), 2013.
16
+ 2. Convert XYZ to linear sRGB and clamp negative values to zero.
17
+ 3. Scale each channel so its mean over a uniform wavelength is 1.
18
+
19
+ The scale keeps white light white on average. The clamp keeps out-of-gamut colors
20
+ saturated instead of colorimetrically exact, and it leaves red with a small violet
21
+ lobe, which is why the short end of a rainbow looks violet instead of blue.
22
+
23
+ ## Sampling
24
+
25
+ Both integrators sample from 5 nm inverse CDF tables. They use different estimators
26
+ because they carry different quantities.
27
+
28
+ | | Camera paths | Caustic photons |
29
+ |---|---|---|
30
+ | Distribution | The response of the path's RGB channel | The sum of the RGB responses |
31
+ | Carries | One RGB channel | RGB flux |
32
+ | Energy weight | None; the distribution is the response | `spectrumRgb(λ) / (pdf(λ) · 300)` |
33
+ | Random number | `CAMERA_WAVELENGTH`, shared by a sample's three paths | Photon emission dimension 69 |
34
+
35
+ Camera paths evaluate no response, so their effective response is a 5 nm histogram of
36
+ the fit. Photons apply the weight exactly. Sharing one random number across a camera
37
+ sample's channels keeps their error correlated with the other common random numbers.
38
+
39
+ `dispersionWavelengths: 3` traces one path per channel in each sample. With
40
+ `dispersionWavelengths: 1`, a dispersive primary hit selects one channel at random
41
+ and multiplies the result by 3; other primary hits trace full RGB. This is not hero
42
+ wavelength sampling, which carries several wavelengths along one path and combines
43
+ them with MIS.
44
+
45
+ ## Channels and gathering
46
+
47
+ A path stores its channel and its refracting wavelength separately:
48
+ `spectralChannel` selects the radiance it keeps, and `wavelength` only drives
49
+ `sampling_cauchy_ior`. A channel of 0 keeps full RGB. Red paths can sample violet, so
50
+ the channel cannot be derived from the wavelength.
51
+
52
+ Photon gathering filters photon flux by the camera path's channel. This splits RGB
53
+ across the camera paths and applies no second spectral weight, so energy is shared
54
+ between path tracing and caustics the same way as without dispersion.
55
+
56
+ ## Shared prefixes
57
+
58
+ With `dispersionWavelengths: 3`, the three channel paths of a pixel sample are
59
+ identical until the first wavelength-dependent IOR: they use the same random numbers
60
+ and carry full RGB until then. The integrators trace that prefix once, checkpoint
61
+ the path, material, auxiliary outputs and random-stream state at the first
62
+ dispersive interaction, and replay only the wavelength-specific suffix for each
63
+ channel. RGB results completed before any dispersion are reused for all three.
64
+ WGSL replays the suffixes inside the flat, alpha, volume and guide paths; GLSL
65
+ traces all three in one fragment invocation (`spectralPrefix.glsl.ts`). Images are
66
+ the same as with independent paths; only the work is shared. MNEE keeps independent
67
+ paths, because its connection solver can disperse before the camera boundary.
68
+
69
+ Tracing three suffixes in one WebGL draw makes that draw about three times longer.
70
+ See [WebGL 2 backend](./webgl.md) for the timeout risk this adds on large images.
71
+
72
+ ## Direct-light resampling
73
+
74
+ Direct lighting selects among `directLightCandidates` light samples by resampled
75
+ importance sampling. When the three channel paths share a prefix, they share one
76
+ reservoir, so the target cannot depend on the channel a path carries. The target
77
+ is the full RGB contribution with equal channel weights; only the final estimator
78
+ keeps the path's channel (`sampleMaterial.wgsl.ts`). A luminance target would
79
+ underweight saturated red and blue light and raise their noise. Equal weights reduce
80
+ that, but a strongly colored region can still converge more slowly in one channel
81
+ than with independent per-channel paths; [sampling](../sampling.md) documents this
82
+ caveat. Single-wavelength previews and non-dispersive renders keep the luminance
83
+ target. WebGL draws one light candidate and has no reservoir.
84
+
85
+ ## Short-wavelength IOR limit
86
+
87
+ Cauchy IOR rises quickly at short wavelengths. At strong dispersion scales, violet
88
+ light reaches IORs that reflect or trap much of it, and wide caustics turn yellow.
89
+ `spectrumIorWavelength` therefore eases the refracting wavelength below 500 nm toward
90
+ 475 nm, while color keeps the sampled wavelength. The mapping is monotonic and
91
+ continuous, so the gradient has no hard edge.
92
+
93
+ This limit is an artistic choice and not physically based. It narrows the violet end
94
+ of the spectrum, and strong dispersion scales still lose some blue.
95
+
96
+ ## Differences from spectral rendering
97
+
98
+ | Area | Spectral rendering | MuTracer |
99
+ |---|---|---|
100
+ | Lights | Emission varies with wavelength | RGB, with an implicit broad spectrum; a sodium lamp still disperses into a full rainbow |
101
+ | Materials and textures | Reflectance varies with wavelength, often upsampled from RGB | RGB, so metamerism cannot appear |
102
+ | Media | Absorption and scattering vary with wavelength | Three RGB coefficients |
103
+ | Thin film | Interference is integrated over wavelength | Evaluated at 620, 540 and 450 nm |
104
+ | Refractive index | Often measured curves or Sellmeier equations | Cauchy from IOR and Abbe number, with the short-wavelength limit |
105
+ | Wavelengths per path | Often several, combined with MIS | One per path or photon |
106
+ | Output color | Integrated from the spectrum into the output color space | Fixed clamped sRGB weights, normalized per channel |
107
+
108
+ These differences matter most for narrow-band lights, colored absorption over long
109
+ paths, metamerism and predictive color. Clear glass and crystal under broad lights are
110
+ closer to the spectral result, because their color comes mainly from refraction.
111
+
112
+ Full spectral rendering would change the light, material, texture and medium data
113
+ models, and requires RGB-to-spectrum upsampling in both shader backends and native.
114
+
115
+ ## Changing this code
116
+
117
+ - Update WGSL and GLSL together. Both are generated from `spectrum.ts`; caustics are
118
+ WGSL only.
119
+ - `spectralChannel` must be set wherever a path's wavelength is set.
120
+ - `tests/unit/spectrum.test.ts` covers white balance, weight bounds, channel
121
+ distributions and the IOR limit. Check the image result on a real GPU in both
122
+ backends, as described in `AGENTS.md`.
@@ -0,0 +1,82 @@
1
+ # Display upscale
2
+
3
+ `createEasuPass` enlarges a reduced-resolution render to the canvas, and with
4
+ `albedoDetail` it restores surface texture detail from full-resolution color AOVs.
5
+ Usage, costs and limits are in [post-processing](../post-processing.md); the AOV
6
+ side is in [surface color AOVs](./aovs.md). This note explains the algorithm. The
7
+ code is `src/renderer/easu.ts` with `wgsl/easu.wgsl.ts` and
8
+ `wgsl/easuDetail.wgsl.ts`.
9
+
10
+ ## Pipeline position
11
+
12
+ The pass is a display post-process: it runs after composition, tone mapping and
13
+ denoising, on display-encoded RGB. A pass can change the image size through
14
+ `outputSize`, so the upscale is ordinary chain state and needs no special case in
15
+ composition, export or denoising. Beauty, its denoising and the AOVs keep their own
16
+ resolutions; only the displayed image is enlarged.
17
+
18
+ An input at the output size is copied, and a larger input is area-averaged, so one
19
+ registration serves both a reduced preview and a full-resolution final render.
20
+
21
+ ## EASU
22
+
23
+ The enlargement is AMD FidelityFX FSR 1 EASU, without RCAS sharpening. It estimates
24
+ a local edge direction and filters along it with a 12-tap lobe. FSR expects inputs
25
+ in [0, 1], so extended-range output normalizes the neighborhood only for the
26
+ direction estimate and filters the original values. EASU cannot invent detail that
27
+ the reduced render does not contain.
28
+
29
+ ## Detail compensation
30
+
31
+ Texture detail lost at render resolution is still present in the display-resolution
32
+ `diffuseColor` and `specularColor` AOVs. Compensation transfers it:
33
+
34
+ 1. **Low guides.** The full-resolution colors are area-averaged over each render
35
+ pixel's exact footprint, including noninteger scales, so the low guides match
36
+ what beauty integrated. No second AOV allocation is needed.
37
+ 2. **Local model.** For each render pixel, a guided-filter regression over a 3×3
38
+ window fits scene-linear radiance as `slope × color + intercept` per channel.
39
+ Diffuse shading follows color (slope), while highlights and reflections do not
40
+ (intercept). Flat colors give a slope near zero, so they are left alone.
41
+ 3. **Upsampling.** The low guides are enlarged with exactly the same EASU taps and
42
+ weights as beauty, so beauty and guides share one footprint.
43
+ 4. **Ratio.** At each output pixel, the full-resolution color replaces the
44
+ upsampled one inside the model: `(slope × hi + intercept) / (slope × low +
45
+ intercept)`, clamped per channel to [0.7, 1.43]. Without a valid model it falls
46
+ back to the plain color ratio `hi / low`.
47
+ 5. **Display transform.** The ratio is scene-linear, but the image is tone-mapped.
48
+ A lookup table built from the current tone mapping and grading maps displayed
49
+ RGB back to scene-linear per channel, applies the ratio and maps it forward, so
50
+ curve shoulders and toes respond as the tone mapper would. Only the change is
51
+ added to the displayed value, which keeps it exact where the ratio is 1.
52
+
53
+ Compensation is skipped, leaving EASU alone, where it would be wrong:
54
+
55
+ - AOV validity or coverage below 0.95 (transmission, cutouts, background,
56
+ unsupported materials);
57
+ - silhouettes, detected from a change in hit coverage or a break in the planarity
58
+ of inverse depth (which is linear in screen space on any plane, so sloped
59
+ surfaces are not silhouettes);
60
+ - color below 10⁻³, treated as black.
61
+
62
+ Clipped highlights cannot be recovered: the inverse tone mapping has no
63
+ information above the display maximum.
64
+
65
+ ## Retained alternatives
66
+
67
+ `createEasuPassImpl` keeps three internal variants for comparisons:
68
+
69
+ - **Plain color ratio.** Scaling all displayed radiance by `hi / low` also scaled
70
+ highlights and other light that does not follow color, which limited the usable
71
+ ratio range. The guided model widened it to the current default.
72
+ - **Separate lobes.** Diffuse and specular ratios weighted by lobe shares estimated
73
+ from the low colors; beauty has no separate lobes to weight them exactly.
74
+ - **Silhouette reconstruction.** Front and back colors from depth-matched
75
+ low-resolution neighbors, mixed by full-resolution coverage. It sharpens
76
+ backgrounds but loses detail on thin structures, glass and reflections
77
+ compared with EASU alone.
78
+
79
+ Ratios were first applied to encoded display RGB, which over-darkened dark decals;
80
+ applying them in linear and then through the display transform let the clamp range
81
+ grow. The public tuning options were removed; `EASU_DETAIL` holds the shared
82
+ defaults, which native also uses.
@@ -0,0 +1,99 @@
1
+ # OpenPBR implementation
2
+
3
+ `OpenPBRMaterial` follows the OpenPBR Surface specification with a compact GPU
4
+ BSDF. Parameters, units and user-facing limits are in the
5
+ [materials guide](../materials.md); this note describes how the implementation
6
+ is organized and the rules for changing it.
7
+
8
+ ## Data flow
9
+
10
+ - **Layout table.** `materialLayoutSchema.ts` lists every material buffer row
11
+ (one `vec4` each) with its source field and type. The same table generates the
12
+ TypeScript packing, the WGSL struct (`materialBufferWgsl`) and the GLSL row
13
+ constants (`materialRowsGlsl`), so the three cannot drift. Reserved rows keep
14
+ their legacy contents; reusing one is an intentional migration.
15
+ - **Texture slots.** `materialTextureSlots.ts` lists every map with its target
16
+ parameters, channels, color space, shader define and feature bit. Slot order is
17
+ the atlas index contract. Public map names live in the scene layer
18
+ (`materialTextureNames.ts`).
19
+ - **Feature specialization.** Features that a scene does not use, such as
20
+ anisotropy directions or coat normals, are compiled out by generating different
21
+ WGSL source, not by patching strings after generation.
22
+
23
+ ## BSDF
24
+
25
+ The BSDF (`openPbrBsdf.wgsl.ts` and its GLSL counterpart) follows the
26
+ prepare/eval/sample/pdf organization of Adobe's
27
+ [openpbr-bsdf](https://github.com/adobe/openpbr-bsdf) and the
28
+ [OpenPBR viewer](https://github.com/portsmouth/OpenPBR-viewer):
29
+
30
+ - `openpbr_prepare` resolves the layered material once per path vertex. NEE, BSDF
31
+ sampling and MIS reuse the result, which made WebGPU samples about 11–15% faster
32
+ than preparing per evaluation.
33
+ - The lobes are fuzz, coat, metal, dielectric specular, transmission, diffuse and
34
+ subsurface. Their weights and probabilities are stored as arrays indexed by lobe,
35
+ rather than one variable per lobe.
36
+ - Metal uses the F82-tint Fresnel; specular anisotropy uses the specification's
37
+ roughness mapping. The coat roughens the base, changes the IOR ratio below it,
38
+ absorbs, and darkens through internal reflection (`coatDarkening`, default 1).
39
+ - Fuzz uses an analytic approximation. The specification's LTC fuzz (Zeltner et al.
40
+ 2022) is not implemented.
41
+
42
+ ## Energy compensation
43
+
44
+ GGX loses energy to single scattering as roughness grows. The renderer uses Adobe's
45
+ tables for opaque dielectric base coupling, ideal-metal multiple scattering and
46
+ solid ideal-dielectric reflection and transmission (`openPbrEnergyLut.ts`). Before
47
+ these tables, rough glass lost 5–36% and rough metal 1.5–20% of its energy in white
48
+ furnace tests.
49
+
50
+ The tables are stored as 8-bit samples normalized per row, with 16-bit row bounds,
51
+ differences between consecutive rows and exact 16-bit averages. This keeps the
52
+ module about 44 KB gzipped while adding the multiple-scattering tables. On the GPU
53
+ they occupy about 280 KB.
54
+
55
+ Thin-walled rough transmission has no compensation, as in the reference.
56
+
57
+ ## Import decisions
58
+
59
+ - Three.js and glTF clearcoat import with `coatDarkening = 0`, because glTF's
60
+ clearcoat does not darken the base. `userData.mutracerOpenPBR` overrides it.
61
+ - Specification names (`specularRoughnessAnisotropy`, `coatRoughnessAnisotropy`,
62
+ `subsurfaceScatterAnisotropy`, `geometryOpacity`, `geometryThinWalled`) are the
63
+ primary names; older names remain deprecated aliases.
64
+ - glTF anisotropy is converted after texture sampling, selected by
65
+ `specularAnisotropyModel: 'gltf'`.
66
+
67
+ ## Not implemented
68
+
69
+ - Statistical mixing of two materials.
70
+ - LTC fuzz.
71
+ - Resampled direct lighting on WebGL. It waits for the cause of a small diffuse
72
+ residual with 8 or 16 WebGPU light candidates; the default of 4 is unaffected.
73
+
74
+ ## Known accuracy limits
75
+
76
+ - Rough solid glass remains slightly dark in white furnace tests, under 1% without
77
+ a coat and under 1.5% with one at roughness 0.3. With compensation off it is
78
+ about 2 percentage points darker than the reference, so the remaining loss is in
79
+ the transmission integration, not the tables.
80
+ - Strongly tilted coat normals lose about 5% of their energy, because lobes are
81
+ clipped to the geometric hemisphere. The materials guide documents this.
82
+ - Strong anisotropic metal can retain a small energy error, because the tables use
83
+ isotropic roughness.
84
+ - A coat with a higher IOR than the base can produce spurious total internal
85
+ reflection in the thin film, as in the reference.
86
+
87
+ ## Acceptance rules
88
+
89
+ These maintainer rules apply to material and BSDF changes:
90
+
91
+ - A white furnace may deviate from 1 by at most 0.5%, and a coated material from
92
+ its uncoated control by at most 0.5%.
93
+ - Re-encoding the tables may change any material on either backend by at most 0.1%.
94
+ - Measure cold shader compilation with caches invalidated; increases above 10% need
95
+ a maintainer decision.
96
+ - Sparse pixel differences of at most one half-float step from compiler
97
+ reassociation are acceptable.
98
+ - Refactors keep default materials (base weight 1, no coat, isotropic, specular
99
+ color 1) pixel-identical on both backends.
@@ -0,0 +1,168 @@
1
+ # PBRT import mapping
2
+
3
+ This contributor note describes the viewer/tool importer. Usage and limits are
4
+ in [the tool guide](../../tools/pbrt/README.md).
5
+
6
+ ## Coordinates and state
7
+
8
+ The reference is `mmp/pbrt-v4@b0bdf461ba5dd6504b70ebf0932acc0fe7aa744f`.
9
+ PBRT's camera looks along local +Z. With `M = diag(1,1,-1,1)`, geometry uses
10
+ `M * objectToWorld` and the Three camera uses `M * cameraToWorld * M`.
11
+ The world reflection is baked into geometry so emissive triangles have consistent
12
+ world-space winding and normals. The winding changes when the baked transform
13
+ changes handedness, XOR `ReverseOrientation`; explicit normals only change sign
14
+ for `ReverseOrientation`. Triangle winding is then face-forwarded to shading
15
+ normals, matching PBRT meshes with authored N. Instances retain shared geometry
16
+ and use `M * T * M`.
17
+ Loop shapes refine the control mesh and push vertices to the limit surface;
18
+ area-weighted normals approximate the reference limit-surface tangents.
19
+ A reflection is absorbed into a geometry variant for negative instance transforms.
20
+
21
+ `LookAt` sets world-to-camera, so its inverse is captured at `Camera`. `WorldBegin`
22
+ resets the object transform. Include filenames and all asset filenames resolve
23
+ against the main scene directory, as `SetSearchDirectory`/`ResolveFilename` do.
24
+ `Import` inherits graphics state but restores it afterward; definitions and scene
25
+ objects remain. Deprecated transform blocks follow PBRT-v4's attribute-block
26
+ rewrite. Only the start transform is retained for animated scenes.
27
+
28
+ ## Camera and microfacets
29
+
30
+ For aspect `a = width/height`, the vertical field of view is `fov` when `a >= 1`,
31
+ otherwise `2 atan(tan(fov/2)/a)`. Lens conversion uses a 24 mm sensor height:
32
+ `focalLength = .024/(2 tan(verticalFov/2))` metres and
33
+ `fStop = focalLength/(2 lensRadius)` for metre scene units.
34
+ The focus distance stays in scene units. The linear exposure multiplier is
35
+ `ISO/100 * (shutterClose-shutterOpen)`. Film exposuretime is not a PBRT-v4
36
+ parameter and is warned and ignored, as is whitebalance. Unsupported sensors
37
+ produce a separate warning.
38
+
39
+ The pinned `TrowbridgeReitzDistribution::RoughnessToAlpha` returns `sqrt(r)`.
40
+ Since OpenPBR uses `alpha = roughness²`, the imported roughness is `r^(1/4)` with
41
+ remapping and `sqrt(r)` without it. For anisotropic alphas, the scalar roughness
42
+ is `((alphaU²+alphaV²)/2)^(1/4)` and anisotropy is
43
+ `1-min(alphaU,alphaV)/max(alphaU,alphaV)` (zero for two zero axes). This inverts
44
+ OpenPBR's RMS-preserving formula for both base and coat. The larger alpha is
45
+ assigned to the tangent axis; rotation is π/2 when v is larger, so u stays dpdu.
46
+ Anisotropic meshes also carry UV-derived tangents for the zero-rotation path.
47
+
48
+ ## Colors and light units
49
+
50
+ RGB inputs are linear sRGB. RGB illumination preserves the RGB multiplier;
51
+ PBRT normalizes the illuminant separately in `SpectrumToPhotometric`. The pinned
52
+ implementation does **not** multiply this integral by 683. The Film's CIE sensor
53
+ integrates the emitted spectrum; this cancels the illuminant normalization for
54
+ white light. Consequently a point light's RGB intensity maps directly to the
55
+ zero-radius SphereLight intensity, and an area light's RGB radiance maps to mesh
56
+ emission. Spot cone angles are half angles and use cosine smoothstep on both sides.
57
+ Distant intensity is irradiance facing the light. Sphere emitters retain meshes:
58
+ MuTracer analytic sphere lights currently do not supply visible primary-ray
59
+ emission, and adding a second emissive sampler would count the source twice.
60
+
61
+ Power multiplies point intensity by `power/(4π)`. For a spot it multiplies by
62
+ `power/[2π(1-(cos(inner)+cos(outer))/2)]`. Area-light power divides by
63
+ `π * area * (twosided ? 2 : 1)`. Image emission also requires texture-average
64
+ normalization; that case remains an approximation. Constant environment
65
+ illuminance multiplies radiance by `illuminance/π`. Image illuminance follows
66
+ the pinned renderer's `2π/N * sum(Y(pixel)*max(z,0))` normalization in light space.
67
+
68
+ Named optical data and CIE/D65 tables come from the pinned Apache-2.0 source.
69
+ Conductor normal and edge reflectances integrate Fresnel at cosines 1 and 1/7
70
+ under D65, then convert XYZ to linear sRGB. OpenPBR takes an F82 **tint**,
71
+ so the edge value is divided by `F0+(1-F0)*(6/7)^5` and limited to [0,1]. Glass uses interpolated eta at
72
+ 587.56 nm and Abbe `(etaD-1)/(etaF-etaC)`, at 486.13 and 656.27 nm.
73
+ Blackbody emission is normalized to unit luminance before light scaling.
74
+ An RGB renderer and PBRT's spectral renderer can retain material-dependent
75
+ biases; comparisons report them without changing either renderer's algorithms.
76
+ Coated conductor inputs keep `conductor.*` substrate parameters separate from
77
+ `interface.*` coat parameters. Both roughness layers use the same remap rule;
78
+ optical constants are read as curves from named data or SPD files before Fresnel
79
+ integration. Coat IOR and anisotropy use the existing OpenPBR metadata fields.
80
+
81
+ For coefficient-based subsurface materials, each RGB extinction mean free path is
82
+ `1 / (scale * (sigma_a + sigma_s))`. The largest channel becomes the OpenPBR
83
+ radius and channel ratios become radius scales. Single-scattering albedo
84
+ `sigma_s / (sigma_a + sigma_s)` is converted to the artist-facing subsurface color
85
+ by inverting the existing material packer's mapping. Authored diffuse reflectance
86
+ is already an effective scattering color. These mappings preserve the RGB medium
87
+ coefficients but do not reproduce PBRT's spectral diffusion model.
88
+ Interior homogeneous media encode total extinction, not absorption, in
89
+ transmission color: choose depth `d=1/max(1,max(sigma_t))`, set color
90
+ `exp(-sigma_t*d)` and scatter color `sigma_s/sigma_t`, with scaled coefficients.
91
+ This preserves the existing packer's coefficients without hitting its color floor.
92
+ `MediumInterface` orders inside then outside. The geometric normal points toward
93
+ outside, after ReverseOrientation and handedness. The importer rejects scenes
94
+ with a camera medium. If only an outside medium remains, conversion flips
95
+ winding and normals together and encodes it as the material interior. Two
96
+ media cannot be represented: conversion warns and uses inside only.
97
+ Material variants include both names to avoid sharing different boundaries.
98
+
99
+ The extra medium-side reversal reciprocates thick dielectric IOR: PBRT's
100
+ DielectricBxDF exchanges eta across its final normal. ReverseOrientation and
101
+ coordinate handedness already affect both normals and do not independently
102
+ reciprocate IOR. ThinDielectricBxDF uses AbsCosTheta and keeps eta unchanged.
103
+ Base specular IOR below one survives Three conversion and material packing;
104
+ both backends use the ratio for Fresnel, total internal reflection and refraction.
105
+ Coat IOR is clamped to at least one and the coat is exterior-only, so reversed
106
+ coats and coat IOR below one receive an ior-orientation warning and are excluded
107
+ from strict comparison. Reciprocal spectral dispersion retains the original
108
+ Abbe number, but a reciprocal Cauchy curve is not itself the same Cauchy fit;
109
+ this receives an ior-dispersion approximation warning and comparison exclusion.
110
+
111
+ Ordinary PBRT surfaces accept primary back-face hits. One-sided area emission
112
+ uses FrontSide because MuTracer couples primary visibility and emission sidedness;
113
+ its missing back-side surface visibility is warned about.
114
+
115
+ PBRT image textures evaluate `(s,1-t)`. Decoded images use top-down rows and
116
+ Three textures set `flipY=true`. Infinite lights use the equal-area square map,
117
+ reprojected to world-space latitude/longitude. The direct importer uses nearest
118
+ source lookup to match ImageInfiniteLight. Each target texel averages 4×4
119
+ jittered samples uniform in azimuth and cos(theta), then per-channel spherical
120
+ integrals correct residual quadrature energy error. Source texels use area 4π/N;
121
+ target bands use exact cosine differences. This preserves small bright features'
122
+ total energy while approximating their subpixel distribution. The existing glTF
123
+ metadata adapter retains its previous bilinear reprojection.
124
+
125
+ Material displacement uses forward UV finite differences with PBRT's `.0005`
126
+ fallback step, including scalar texture scale and UV repeat. The image-row
127
+ v derivative changes sign for `(s,1-t)`. The result is a tangent normal
128
+ `normalize(-dh/du,-dh/dv,1)`, never a geometric height field. This static bake
129
+ assumes unit tangents and omits footprint-dependent steps and curvature terms;
130
+ it is warned and excluded from strict comparison. Only shape displacement uses
131
+ the height-field mapping. In the pinned `interaction.cpp`, NormalMap and BumpMap
132
+ are alternatives (`if/else`): normalmap takes precedence when both exist.
133
+
134
+ ## Comparison and memory
135
+
136
+ Ordinary byte color maps retain their original data and declared color space;
137
+ unmodified linear HDR maps also share decoded pixel data. Scale graphs carry
138
+ separate multipliers. Scalar and transformed maps retain
139
+ floating-point values. Rectangular/non-power-of-two height fields are resampled
140
+ to the existing displacement layout, with a warning.
141
+
142
+ The comparison sets PBRT's pixel filter to box with radius 0.5 to match MuTracer's
143
+ uniform pixel jitter. It overrides spp and output resolution, removes Film crop
144
+ windows and pixel bounds, preserves maxdepth,
145
+ sets an opaque black background when no environment exists, removes Film sensor exposure from the reference HDR and never tone maps numerical
146
+ inputs. AOV material IDs identify approximate surface regions. This primary-hit
147
+ mask does not isolate indirect paths that touch approximate materials. PBRT uses
148
+ seeds 0/1; MuTracer uses disjoint sample blocks starting at 0/N with the same Sobol
149
+ scramble. Their difference gives a diagnostic noise estimate, not independent
150
+ Monte Carlo replicas or statistical proof of equivalence.
151
+ Whole-frame RMSE retains every comparable pixel and its noise margin. Regional
152
+ material/tile tests exclude discontinuities in object, instance or material IDs,
153
+ dilated by one pixel. Their statistic is absolute channel mean bias / standard
154
+ error, with threshold 6. Standard error is sqrt(sum of per-pixel replica-difference
155
+ variances) / pixel count; squaring a region-sum difference instead gives an
156
+ unstable single observation. Whole-frame and regional statuses are separate.
157
+ Each MuTracer run uses a fresh Chrome process to avoid retaining both scenes' caches
158
+ and allocations during memory measurement.
159
+
160
+ Text and gzip Includes are decoded by statements to avoid the single-string size
161
+ limit. The viewer parses them and PLY data in a Worker. Inline P/N/UV/index
162
+ arrays become
163
+ transferable typed views before leaving the Worker; statement locations share
164
+ the same parameter objects. Binary assets are prefetched six at a time before
165
+ conversion; progress sums completed and in-flight bytes. The download preflight
166
+ uses unique encoded input sizes and an 8x expansion estimate against an 8 GiB
167
+ working budget. This estimate is deliberately conservative and cannot predict
168
+ total process/GPU memory or prevent every browser allocation failure.