@meimu/mutracer 0.3.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 (304) 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/THIRD_PARTY_NOTICES.md +18 -4
  5. package/dist/assets/bvh.worker.js +1 -1
  6. package/dist/mutracer.umd.js +6260 -5580
  7. package/docs/api.md +45 -18
  8. package/docs/materials.md +4 -2
  9. package/docs/performance.md +7 -0
  10. package/docs/post-processing.md +62 -9
  11. package/docs/progressive-rendering.md +3 -0
  12. package/docs/sampling.md +4 -0
  13. package/docs/technical/acceleration.md +52 -0
  14. package/docs/technical/aovs.md +79 -0
  15. package/docs/technical/caustics.md +109 -0
  16. package/docs/technical/continuous-spectrum.md +29 -0
  17. package/docs/technical/display-upscale.md +82 -0
  18. package/docs/technical/openpbr.md +99 -0
  19. package/docs/technical/pbrt-import.md +168 -0
  20. package/docs/technical/tiling.md +74 -0
  21. package/docs/technical/webgl.md +53 -0
  22. package/docs/viewer.md +11 -1
  23. package/lib/Display.d.ts +19 -5
  24. package/lib/Display.js +10 -7
  25. package/lib/Display.js.map +1 -1
  26. package/lib/Engine.d.ts +2 -1
  27. package/lib/Engine.js +30 -2
  28. package/lib/Engine.js.map +1 -1
  29. package/lib/Render.d.ts +20 -11
  30. package/lib/Render.js +32 -7
  31. package/lib/Render.js.map +1 -1
  32. package/lib/main.d.ts +3 -1
  33. package/lib/main.js +1 -0
  34. package/lib/main.js.map +1 -1
  35. package/lib/oidn.d.ts +1 -1
  36. package/lib/renderer/AovPrimitiveCache.js +39 -8
  37. package/lib/renderer/AovPrimitiveCache.js.map +1 -1
  38. package/lib/renderer/DisplayPostProcess.d.ts +8 -3
  39. package/lib/renderer/DisplayPostProcess.js +51 -18
  40. package/lib/renderer/DisplayPostProcess.js.map +1 -1
  41. package/lib/renderer/MaterialData.js +7 -2
  42. package/lib/renderer/MaterialData.js.map +1 -1
  43. package/lib/renderer/RenderingPipeline.d.ts +7 -1
  44. package/lib/renderer/RenderingPipeline.js.map +1 -1
  45. package/lib/renderer/TileRender.d.ts +2 -2
  46. package/lib/renderer/TileRender.js +128 -7
  47. package/lib/renderer/TileRender.js.map +1 -1
  48. package/lib/renderer/WGPUAov.d.ts +6 -1
  49. package/lib/renderer/WGPUAov.js +108 -14
  50. package/lib/renderer/WGPUAov.js.map +1 -1
  51. package/lib/renderer/WGPUCanvasSurface.js +12 -1
  52. package/lib/renderer/WGPUCanvasSurface.js.map +1 -1
  53. package/lib/renderer/WGPUEnvMapDistribution.js +19 -19
  54. package/lib/renderer/WGPUFullQuadPass.js +3 -3
  55. package/lib/renderer/WGPURayTraceOutput.js +12 -12
  56. package/lib/renderer/WGPURayTracePass.d.ts +17 -4
  57. package/lib/renderer/WGPURayTracePass.js +194 -71
  58. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  59. package/lib/renderer/WGPURenderingPipeline.js +46 -40
  60. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  61. package/lib/renderer/WebGLAov.js +3 -3
  62. package/lib/renderer/WebGLAov.js.map +1 -1
  63. package/lib/renderer/WebGLFullQuadPass.d.ts +1 -1
  64. package/lib/renderer/WebGLFullQuadPass.js +8 -2
  65. package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
  66. package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
  67. package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -1
  68. package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
  69. package/lib/renderer/WebGLRayTracePass.d.ts +14 -10
  70. package/lib/renderer/WebGLRayTracePass.js +115 -111
  71. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  72. package/lib/renderer/WebGLRenderingPipeline.js +10 -3
  73. package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
  74. package/lib/renderer/WebGLSkyBaker.js +57 -57
  75. package/lib/renderer/aov.d.ts +13 -1
  76. package/lib/renderer/aov.js +21 -2
  77. package/lib/renderer/aov.js.map +1 -1
  78. package/lib/renderer/aovDisplay.js +2 -2
  79. package/lib/renderer/aovDisplay.js.map +1 -1
  80. package/lib/renderer/binnedSahBvh.d.ts +2 -1
  81. package/lib/renderer/binnedSahBvh.js +24 -17
  82. package/lib/renderer/binnedSahBvh.js.map +1 -1
  83. package/lib/renderer/bvhAccel.d.ts +3 -1
  84. package/lib/renderer/bvhAccel.js +36 -6
  85. package/lib/renderer/bvhAccel.js.map +1 -1
  86. package/lib/renderer/cameraUniform.d.ts +1 -1
  87. package/lib/renderer/cameraUniform.js +6 -2
  88. package/lib/renderer/cameraUniform.js.map +1 -1
  89. package/lib/renderer/compactVertices.d.ts +14 -0
  90. package/lib/renderer/compactVertices.js +124 -0
  91. package/lib/renderer/compactVertices.js.map +1 -0
  92. package/lib/renderer/easu.d.ts +11 -0
  93. package/lib/renderer/easu.js +156 -0
  94. package/lib/renderer/easu.js.map +1 -0
  95. package/lib/renderer/geometryUpdateState.d.ts +1 -0
  96. package/lib/renderer/geometryUpdateState.js.map +1 -1
  97. package/lib/renderer/geometryUpdates.js +3 -2
  98. package/lib/renderer/geometryUpdates.js.map +1 -1
  99. package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
  100. package/lib/renderer/glsl/aovReflectance.glsl.js +35 -35
  101. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
  102. package/lib/renderer/glsl/aovTrace.frag.js +44 -36
  103. package/lib/renderer/glsl/aovTrace.frag.js.map +1 -1
  104. package/lib/renderer/glsl/chunks/cameraProjection.glsl.d.ts +2 -0
  105. package/lib/renderer/glsl/chunks/cameraProjection.glsl.js +16 -0
  106. package/lib/renderer/glsl/chunks/cameraProjection.glsl.js.map +1 -0
  107. package/lib/renderer/glsl/chunks/cameraRay.glsl.d.ts +1 -1
  108. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -16
  109. package/lib/renderer/glsl/chunks/cameraRay.glsl.js.map +1 -1
  110. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +8 -8
  111. package/lib/renderer/glsl/chunks/envMap.glsl.js +67 -67
  112. package/lib/renderer/glsl/chunks/intersect.glsl.js +390 -375
  113. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
  114. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +120 -120
  115. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +562 -562
  116. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  117. package/lib/renderer/glsl/chunks/random.glsl.js +4 -4
  118. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +37 -35
  119. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
  120. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +70 -74
  121. package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
  122. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -1
  123. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +107 -88
  124. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
  125. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +11 -11
  126. package/lib/renderer/glsl/chunks/sampling.glsl.js +77 -77
  127. package/lib/renderer/glsl/chunks/sobol.glsl.js +70 -70
  128. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.d.ts +6 -0
  129. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js +47 -0
  130. package/lib/renderer/glsl/chunks/spectralPrefix.glsl.js.map +1 -0
  131. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  132. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
  133. package/lib/renderer/glsl/depthQuery.frag.js +12 -6
  134. package/lib/renderer/glsl/depthQuery.frag.js.map +1 -1
  135. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
  136. package/lib/renderer/glsl/rayTrace.frag.js +233 -170
  137. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  138. package/lib/renderer/glsl/shadowCatcher.frag.js +10 -9
  139. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
  140. package/lib/renderer/glsl/webglPipeline.frag.js +58 -58
  141. package/lib/renderer/gpuMemory.js +1 -1
  142. package/lib/renderer/gpuMemory.js.map +1 -1
  143. package/lib/renderer/openPbrEnergyLut.d.ts +2 -1
  144. package/lib/renderer/openPbrEnergyLut.js +18 -1
  145. package/lib/renderer/openPbrEnergyLut.js.map +1 -1
  146. package/lib/renderer/pathLimits.d.ts +5 -4
  147. package/lib/renderer/pathLimits.js +6 -5
  148. package/lib/renderer/pathLimits.js.map +1 -1
  149. package/lib/renderer/prepareMeshBVH.d.ts +2 -0
  150. package/lib/renderer/prepareMeshBVH.js +5 -3
  151. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  152. package/lib/renderer/progressiveScheduler.js +1 -1
  153. package/lib/renderer/progressiveScheduler.js.map +1 -1
  154. package/lib/renderer/renderPipeline.d.ts +4 -0
  155. package/lib/renderer/renderPipeline.js +1 -0
  156. package/lib/renderer/renderPipeline.js.map +1 -1
  157. package/lib/renderer/researchTransport.d.ts +1 -1
  158. package/lib/renderer/researchTransport.js.map +1 -1
  159. package/lib/renderer/sceneAssembly.d.ts +1 -1
  160. package/lib/renderer/sceneAssembly.js +4 -3
  161. package/lib/renderer/sceneAssembly.js.map +1 -1
  162. package/lib/renderer/sceneChangeDetector.d.ts +1 -1
  163. package/lib/renderer/sceneChangeDetector.js +3 -1
  164. package/lib/renderer/sceneChangeDetector.js.map +1 -1
  165. package/lib/renderer/sceneLayoutSchema.d.ts +1 -0
  166. package/lib/renderer/sceneLayoutSchema.js +2 -2
  167. package/lib/renderer/sceneLayoutSchema.js.map +1 -1
  168. package/lib/renderer/scenePreparation.d.ts +3 -2
  169. package/lib/renderer/scenePreparation.js +18 -2
  170. package/lib/renderer/scenePreparation.js.map +1 -1
  171. package/lib/renderer/scratchSampling.js +6 -6
  172. package/lib/renderer/spectrum.js +2 -2
  173. package/lib/renderer/staticBvhPreparation.d.ts +9 -0
  174. package/lib/renderer/staticBvhPreparation.js +8 -2
  175. package/lib/renderer/staticBvhPreparation.js.map +1 -1
  176. package/lib/renderer/surfaceHit.d.ts +10 -0
  177. package/lib/renderer/surfaceHit.js +22 -0
  178. package/lib/renderer/surfaceHit.js.map +1 -0
  179. package/lib/renderer/tangentRequirements.d.ts +2 -0
  180. package/lib/renderer/tangentRequirements.js +7 -0
  181. package/lib/renderer/tangentRequirements.js.map +1 -0
  182. package/lib/renderer/textureAtlas.d.ts +4 -0
  183. package/lib/renderer/textureAtlas.js +48 -2
  184. package/lib/renderer/textureAtlas.js.map +1 -1
  185. package/lib/renderer/webgl.js +4 -0
  186. package/lib/renderer/webgl.js.map +1 -1
  187. package/lib/renderer/webglVertexAttributes.d.ts +17 -0
  188. package/lib/renderer/webglVertexAttributes.js +44 -0
  189. package/lib/renderer/webglVertexAttributes.js.map +1 -0
  190. package/lib/renderer/wgpuBindlessSource.js +2 -2
  191. package/lib/renderer/wgpuMaterialConstants.js +4 -3
  192. package/lib/renderer/wgpuMaterialConstants.js.map +1 -1
  193. package/lib/renderer/wgpuSceneUniform.js +2 -2
  194. package/lib/renderer/wgpuSceneUniform.js.map +1 -1
  195. package/lib/renderer/wgpuShaderSource.d.ts +15 -0
  196. package/lib/renderer/wgpuShaderSource.js +1 -1
  197. package/lib/renderer/wgpuShaderSource.js.map +1 -1
  198. package/lib/renderer/wgsl/aovCompact.wgsl.d.ts +4 -0
  199. package/lib/renderer/wgsl/aovCompact.wgsl.js +26 -0
  200. package/lib/renderer/wgsl/aovCompact.wgsl.js.map +1 -0
  201. package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
  202. package/lib/renderer/wgsl/aovReflectance.wgsl.js +39 -39
  203. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
  204. package/lib/renderer/wgsl/aovResolve.wgsl.d.ts +4 -0
  205. package/lib/renderer/wgsl/aovResolve.wgsl.js +21 -2
  206. package/lib/renderer/wgsl/aovResolve.wgsl.js.map +1 -1
  207. package/lib/renderer/wgsl/aovSnapshot.wgsl.js +2 -2
  208. package/lib/renderer/wgsl/aovSnapshot.wgsl.js.map +1 -1
  209. package/lib/renderer/wgsl/aovTrace.wgsl.js +31 -31
  210. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.d.ts +2 -0
  211. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js +56 -0
  212. package/lib/renderer/wgsl/aovTraceDisplay.wgsl.js.map +1 -0
  213. package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
  214. package/lib/renderer/wgsl/causticLight.compute.d.ts +1 -1
  215. package/lib/renderer/wgsl/causticLight.compute.js +121 -119
  216. package/lib/renderer/wgsl/causticLight.compute.js.map +1 -1
  217. package/lib/renderer/wgsl/chunks/background.wgsl.js +46 -46
  218. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +4 -4
  219. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +3 -3
  220. package/lib/renderer/wgsl/chunks/caustics.wgsl.js +3 -3
  221. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  222. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +30 -30
  223. package/lib/renderer/wgsl/chunks/intersect.wgsl.d.ts +6 -2
  224. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +550 -542
  225. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  226. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.d.ts +1 -1
  227. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +18 -16
  228. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -1
  229. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +2 -1
  230. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +70 -69
  231. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
  232. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +593 -593
  233. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +118 -118
  234. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  235. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +4 -1
  236. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +32 -26
  237. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
  238. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +114 -114
  239. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
  240. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +159 -137
  241. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
  242. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +8 -8
  243. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +74 -74
  244. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +74 -74
  245. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.d.ts +6 -0
  246. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js +66 -0
  247. package/lib/renderer/wgsl/chunks/spectralPrefix.wgsl.js.map +1 -0
  248. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
  249. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.d.ts +2 -0
  250. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js +61 -0
  251. package/lib/renderer/wgsl/chunks/vertexAttributes.wgsl.js.map +1 -0
  252. package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +2 -0
  253. package/lib/renderer/wgsl/compose.frag.wgsl.js +99 -97
  254. package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
  255. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  256. package/lib/renderer/wgsl/depthQuery.wgsl.d.ts +1 -1
  257. package/lib/renderer/wgsl/depthQuery.wgsl.js +11 -2
  258. package/lib/renderer/wgsl/depthQuery.wgsl.js.map +1 -1
  259. package/lib/renderer/wgsl/easu.wgsl.d.ts +2 -0
  260. package/lib/renderer/wgsl/easu.wgsl.js +80 -0
  261. package/lib/renderer/wgsl/easu.wgsl.js.map +1 -0
  262. package/lib/renderer/wgsl/easuDetail.wgsl.d.ts +21 -0
  263. package/lib/renderer/wgsl/easuDetail.wgsl.js +230 -0
  264. package/lib/renderer/wgsl/easuDetail.wgsl.js.map +1 -0
  265. package/lib/renderer/wgsl/jnlm.compute.js +74 -74
  266. package/lib/renderer/wgsl/jnlm.compute.js.map +1 -1
  267. package/lib/renderer/wgsl/offlineAdaptive.compute.js +26 -26
  268. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
  269. package/lib/renderer/wgsl/photonMap.compute.js +89 -89
  270. package/lib/renderer/wgsl/proceduralSky.compute.js +61 -61
  271. package/lib/renderer/wgsl/rayTrace.compute.d.ts +14 -0
  272. package/lib/renderer/wgsl/rayTrace.compute.js +395 -333
  273. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  274. package/lib/scene/Scene.js.map +1 -1
  275. package/lib/settings/render.d.ts +11 -0
  276. package/lib/settings/render.js +1 -0
  277. package/lib/settings/render.js.map +1 -1
  278. package/lib/util/fromTHREE.js +21 -14
  279. package/lib/util/fromTHREE.js.map +1 -1
  280. package/lib/version.d.ts +1 -1
  281. package/lib/version.js +1 -1
  282. package/lib/version.js.map +1 -1
  283. package/package.json +5 -2
  284. package/lib/oidnBundledWeights.cdn.d.ts +0 -2
  285. package/lib/oidnBundledWeights.cdn.js +0 -5
  286. package/lib/oidnBundledWeights.cdn.js.map +0 -1
  287. package/lib/renderer/bvh.worker.d.ts +0 -1
  288. package/lib/renderer/bvh.worker.js +0 -16
  289. package/lib/renderer/bvh.worker.js.map +0 -1
  290. package/lib/renderer/bvhWorkerBuild.d.ts +0 -13
  291. package/lib/renderer/bvhWorkerBuild.js +0 -17
  292. package/lib/renderer/bvhWorkerBuild.js.map +0 -1
  293. package/lib/renderer/lightCore.d.ts +0 -12
  294. package/lib/renderer/lightCore.js +0 -13
  295. package/lib/renderer/lightCore.js.map +0 -1
  296. package/lib/renderer/materialCore.d.ts +0 -8
  297. package/lib/renderer/materialCore.js +0 -9
  298. package/lib/renderer/materialCore.js.map +0 -1
  299. package/lib/scene/PixelTexture.d.ts +0 -6
  300. package/lib/scene/PixelTexture.js +0 -10
  301. package/lib/scene/PixelTexture.js.map +0 -1
  302. package/lib/util/studioLighting.d.ts +0 -18
  303. package/lib/util/studioLighting.js +0 -36
  304. package/lib/util/studioLighting.js.map +0 -1
package/docs/api.md CHANGED
@@ -127,6 +127,7 @@ other sequences retain their completed samples.
127
127
  | Option | Default | Meaning |
128
128
  | --- | --- | --- |
129
129
  | `maxSamples` | `2048` | Complete sample budget |
130
+ | `aovResolution` | `'render'` | `'display'` traces AOVs at canvas size on WebGPU, capped at 4 samples regardless of `aovSamples`; requires a scale-based render. WebGL falls back with one warning |
130
131
  | `aovs` / `aovSamples` | `[]` / `16` | Optional [surface outputs](#surface-outputs) / independent positive integer sample cap |
131
132
  | `bounces` / `clamping` | `3` / `0` | Scattering depth / per-sample radiance clamp; 0 disables clamping |
132
133
  | `samplingPattern` | `'sobol'` | Sobol, ZSobol or random; see [sampling](./sampling.md) |
@@ -144,8 +145,9 @@ other sequences retain their completed samples.
144
145
  | `fallback` | `null` | Single-level Render on the same engine |
145
146
  | `denoiser` | `null` | Per-render configuration; read back through the frozen `denoiser` getter and change with `setDenoiser()`; see [denoising](./denoising.md) |
146
147
 
147
- `bounces` is rounded and clamped to [1, 20] in both backends; NaN becomes 1.
148
- Paths allow at most `min(bounces * 3, 20)` events, including transmission,
148
+ `bounces` is rounded and clamped to [1, 50] on WebGPU or [1, 20] on WebGL;
149
+ NaN becomes 1. Paths allow at most `min(bounces * 3, 50)` events on WebGPU
150
+ or `min(bounces * 3, 20)` on WebGL, including transmission,
149
151
  alpha skips and escape checks. Opaque paths also terminate at their scattering
150
152
  limit. The event budget can truncate deep transparent paths before the requested
151
153
  scattering depth. On Intel Mac WebGL, the event limit is `min(bounces, 3)`, doubled
@@ -350,9 +352,20 @@ const depth = await render.readPixels('depth'); // Float32Array, RGBA
350
352
  const ids = await render.readPixels('id'); // Uint32Array, RGBA
351
353
  ```
352
354
 
353
- You can change `render.aovs` or `render.aovSamples` after sampling. Continue
355
+ You can change `render.aovs`, `render.aovSamples` or `render.aovResolution` after sampling. Continue
354
356
  calling `refine()` to regenerate AOVs for the current view; beauty samples and
355
- completed denoising are preserved. AOV accumulation restarts when either changes.
357
+ completed denoising are preserved. AOV accumulation restarts when any of them changes.
358
+
359
+ To keep beauty at reduced resolution while reading or displaying full-resolution
360
+ surface outputs, set `aovResolution: 'display'` on a scale-based render. The
361
+ default is `'render'`. AOV readbacks and visualization use their own dimensions;
362
+ beauty and denoiser guides keep the render dimensions. Both modes stop at
363
+ `min(samples, aovSamples)`; display mode also caps at 4, follows canvas resizes,
364
+ and rejects fixed-size renders. During tracing, display mode starts after the first
365
+ beauty sample and adds one AOV sample per frame; the frame that reaches
366
+ `maxSamples` completes the rest. WebGL falls back to render dimensions and warns
367
+ once per render. Each kind has the same meaning in both modes.
368
+ Custom denoiser AOV inputs require `'render'` resolution.
356
369
 
357
370
  | Kind | RGBA contents |
358
371
  | --- | --- |
@@ -361,13 +374,14 @@ completed denoising are preserved. AOV accumulation restarts when either changes
361
374
  | `roughness` | Specular roughness, metalness, unsupported-reflectance fraction, coverage |
362
375
  | `diffuseAlbedo` | Directional-hemispherical diffuse reflectance RGB, valid coverage |
363
376
  | `specularAlbedo` | Directional-hemispherical specular reflectance RGB, valid coverage |
377
+ | `diffuseColor` | Diffuse texture color: base weight × base color × (1 − metalness), tinted by coat color; valid coverage |
378
+ | `specularColor` | Specular texture color: metal base color plus dielectric specular color × F0, tinted by coat color; valid coverage |
364
379
  | `id` | Object, local instance (0 for ordinary meshes), material, source triangle index; background is `0xFFFFFFFF` in all channels |
365
380
  | `motion` | Previous position minus current position in pixels, x right and y down; z = 0, alpha = 3 for valid history or 0 otherwise |
366
381
 
367
- Depth, position, roughness and reflectance average independent jittered primary
368
- rays, including depth of field, up to `min(samples, aovSamples)`. RGB values use
369
- hit-count normalization; alpha is hit coverage over all AOV samples. Reflectance
370
- uses only supported hits for both normalization and valid coverage. A blank pixel
382
+ Depth, position, roughness, reflectance and color average independent jittered primary
383
+ rays, including depth of field. RGB values use hit-count normalization; alpha is hit coverage over all AOV samples. Reflectance
384
+ and color use only supported hits for both normalization and valid coverage. A blank pixel
371
385
  is zero. These passes preserve beauty samples but do not sample the same paths.
372
386
  Alpha holes use the first surface with alpha ≥ 0.5, skipping shadow catchers,
373
387
  with at most 32 layers.
@@ -392,14 +406,23 @@ Reflectance outputs are separate from the existing OIDN `albedo` guide.
392
406
  Coat, fuzz, anisotropy, subsurface/volume scattering, bevel, thin film,
393
407
  dispersion, partial alpha and nonstandard materials are unsupported by this
394
408
  reflectance estimate. Such hits contribute to `roughness.b` and have zero
395
- reflectance validity. Reflectance uses expensive BSDF quadrature; request it
396
- only when needed.
409
+ reflectance validity. Reflectance tracing can be expensive; request it only when needed.
410
+
411
+ `diffuseColor` and `specularColor` read resolved material inputs without
412
+ integrating the BSDF, so they are cheap. They support coat, fuzz and anisotropic
413
+ surfaces; transmission, partial alpha, media and nonstandard materials have zero
414
+ color validity. A render can request `diffuseAlbedo` or `diffuseColor`, not both;
415
+ the same applies to `specularAlbedo` and `specularColor`.
397
416
 
398
417
  Reading a kind absent from `render.aovs` rejects. A custom denoiser can request
399
418
  its own [AOV inputs](./denoising.md#custom-denoisers). With neither request there
400
- are no AOV allocations or passes. Enabling any kind costs about 128 GPU bytes
401
- per pixel on WebGPU or 192 on WebGL, plus scene identity/history data; each
402
- custom denoiser AOV snapshot adds 16 bytes per pixel. A render may retain one
419
+ are no AOV allocations or passes. Enabling any kind normally costs about 128 GPU bytes
420
+ per AOV pixel on WebGPU or 192 on WebGL, plus scene identity/history data.
421
+ WebGPU display mode requesting only `depth`, `diffuseColor` and/or `specularColor` costs
422
+ 24 bytes per pixel plus small scene metadata; borrowing all three GPU textures
423
+ adds another 24 bytes per pixel (about 288 MB total at 6 MP). Its color
424
+ accumulation has half precision. Other AOV selections use the full allocation;
425
+ each custom denoiser AOV snapshot adds 16 bytes per pixel. A render may retain one
403
426
  reusable snapshot per kind between denoising calls. Readback adds a CPU copy.
404
427
  WebGPU dimensions remain subject to device buffer limits. A full-frame AOV
405
428
  sample runs independently of tile/adaptive sampling and may exceed the render's
@@ -421,7 +444,7 @@ const png = await engine.display.toBlob(render);
421
444
  | `depth` | Black at the near depth, white at the far depth, clamped outside the range |
422
445
  | `position` | World xyz mapped to RGB; origin is 0.5 and ±position scale maps to 0/1 |
423
446
  | `roughness` | RGB = roughness, metalness, unsupported-reflectance fraction |
424
- | `diffuseAlbedo`, `specularAlbedo` | Linear reflectance RGB clamped to [0, 1], then sRGB encoded |
447
+ | `diffuseAlbedo`, `specularAlbedo`, `diffuseColor`, `specularColor` | Linear RGB clamped to [0, 1], then sRGB encoded |
425
448
  | `id` | Stable pseudocolor for the selected uint32 ID component; colors can collide |
426
449
  | `motion` | Direction as hue (+x is red, +y is yellow-green), displacement/scale as brightness |
427
450
 
@@ -598,10 +621,14 @@ see [denoising](./denoising.md#custom-oidn-networks). The main entry exports
598
621
  ## GPU output and display passes
599
622
 
600
623
  `render.getOutputTexture(kind)` borrows native HDR or the latest display image;
601
- `display.addPostProcess(pass)` returns unregister and runs on every present/export.
602
- Both are WebGPU-only. Textures are rounded up to multiples of 16 pixels, so a
603
- fixed render's texture can include extra pixels around the requested image. See
604
- [formats, lifecycle and overlay example](./post-processing.md).
624
+ `render.getAovTexture('diffuseColor' | 'specularColor' | 'depth')` borrows raw
625
+ linear AOV textures at their own resolution after sampling; WebGL returns undefined.
626
+ `display.addPostProcess(pass, options?)` returns unregister and runs on every present/export.
627
+ Both are WebGPU-only. `options.outputSize` defaults to preserving input dimensions;
628
+ the final display texture and export can differ from the render size. Fixed renders
629
+ are cropped to their requested rectangle before export passes. HDR textures can
630
+ include extra pixels around a fixed render's requested image. See
631
+ [formats, lifecycle, EASU upscale and overlay examples](./post-processing.md).
605
632
 
606
633
  Anisotropy direction options: `anisotropyMap` (default `undefined`) reads linear RG as a tangent direction and B as a multiplier for base specular strength. `specularAnisotropyRotation` (default `0`) rotates that direction counter-clockwise in radians; `coatAnisotropyRotation` (default `0`) rotates the coat relative to the geometry tangent. See [materials](./materials.md#anisotropy-directions) for frame and fallback conventions.
607
634
 
package/docs/materials.md CHANGED
@@ -51,12 +51,14 @@ the textures they reference still share atlas space.
51
51
 
52
52
  Units: `transmissionDepth` and `subsurfaceRadius` are in scene units;
53
53
  `thinFilmThickness` is in micrometres (`0.45` = 450 nm).
54
+ Subsurface materials with short scattering radii may appear darker than in earlier releases.
54
55
 
55
56
  `baseWeight` scales diffuse reflection and the metallic normal-incidence color
56
57
  (`baseWeight * baseColor`). Dielectric specular, transmission and subsurface
57
58
  retain their own strengths. For metals, `specularColor` tints the F82 Fresnel
58
59
  curve near grazing angles and `specularWeight` scales the entire reflection,
59
60
  including thin-film reflection. At their default values, F82 reduces to Schlick.
61
+ Low-roughness metals may appear slightly darker under broad lighting than in earlier releases.
60
62
 
61
63
  Native `specularRoughnessAnisotropy` and `coatRoughnessAnisotropy` use the OpenPBR GGX mapping:
62
64
  `alphaT = roughness² * sqrt(2 / (1 + (1 - anisotropy)²))` and
@@ -129,7 +131,7 @@ an alternate serialization format.
129
131
  | --- | --- | --- |
130
132
  | `map` | RGB (sRGB), A | Multiplies `baseColor`; alpha use set by `mapAlphaUsage` |
131
133
  | `alphaMap` | G | Opacity, as in Three.js |
132
- | `normalMap`, `normalScale` | RGB | Tangent space; tangents are generated when missing |
134
+ | `normalMap`, `normalScale` | RGB | Tangent space; uses triangle UV derivatives |
133
135
  | `metallicRoughnessMap` | G = roughness, B = metalness | glTF convention |
134
136
  | `roughnessMap` | G | |
135
137
  | `metalnessMap` | B | |
@@ -288,6 +290,6 @@ Set `specularAnisotropyRotation` or provide `anisotropyMap` to orient the base s
288
290
 
289
291
  `coatAnisotropyRotation` defaults to zero and uses the geometry tangent independently of the base map. Base and coat use their respective directions and [normals](#coat-normals). Zero anisotropy strength ignores the direction.
290
292
 
291
- Provided tangent XYZ and optional W handedness are preserved. For XYZ tangents the sign comes from triangle UV derivatives. Missing or degenerate tangents use UV derivatives and then a stable orthonormal fallback; missing tangents produce one diagnostic per geometry. The direction texture's selected UV set is used for derivative fallback, including UV1. A provided XYZW tangent remains authoritative. Directions are reorthogonalized against the final shading normal, including normal maps and back faces, and instance transforms preserve mirrored handedness. UV scale, offset and flipY affect texel lookup; they do not rotate the encoded direction.
293
+ Provided tangent XYZ and optional W handedness are preserved. Missing tangents are generated from primary UVs only when a material using the geometry needs anisotropy or an active coat normal map; adding such a material prepares them automatically. For XYZ tangents the sign comes from triangle UV derivatives. Missing UVs or degenerate tangents use UV derivatives where available and then a stable orthonormal fallback. The direction texture's selected UV set is used for derivative fallback, including UV1. A provided XYZW tangent remains authoritative. Directions are reorthogonalized against the final shading normal, including normal maps and back faces, and instance transforms preserve mirrored handedness. UV scale, offset and flipY affect texel lookup; they do not rotate the encoded direction.
292
294
 
293
295
  The direction and strength inputs use native OpenPBR semantics by default. For imported glTF/Three.js strength, select `specularAnisotropyModel: 'gltf'`; the same roughness and strength properties then retain source values. Source scalar inputs are clamped to [0, 1] before texture factors; non-finite source roughness/strength fall back to 0.3/0. Native scalar handling is unchanged. Zero source strength preserves the original roughness path. The conversion matches the GGX alpha axes before existing minimum-alpha clamping; Fresnel, masking, multiple scattering and OpenPBR coating remain the library's model. See [the conversion rules](./threejs.md#anisotropy-source-conversion).
@@ -38,6 +38,13 @@ A tuning checklist. Each item links to the page with the details.
38
38
  - Textures share an atlas bounded by `textureAtlasBudgetBytes`; larger textures
39
39
  are downscaled. Check `engine.getDiagnostics()?.textureAtlas`.
40
40
 
41
+ WebGPU quantizes vertex normal and tangent directions; scenes with displacement
42
+ retain full normal precision. Vertex RGB values in
43
+ `[0, 1]` have at most `1/510` absolute error per channel; scenes containing
44
+ negative or HDR vertex colors retain float32 RGB instead. Positions and UV
45
+ coordinates retain float32 precision. Large scenes still depend on the device's
46
+ buffer limits and the browser's available CPU memory.
47
+
41
48
  ## Animation and editing
42
49
 
43
50
  See [scene updates](./updates.md) for the cost of each kind of edit.
@@ -18,8 +18,9 @@ remove(); // Safe to call again.
18
18
 
19
19
  Passes run in registration order on every `present()` and `toBlob()`, after
20
20
  composition and denoising. They receive the selected complete render, including
21
- fallbacks, at its actual dimensions. Export uses the same chain without resizing
22
- or changing the display canvas. `readPixels()` never runs these passes.
21
+ fallbacks, at its actual dimensions. Export uses the same chain without changing
22
+ the display canvas; fixed-size renders are cropped to their requested size before
23
+ entering the chain. `readPixels()` never runs these passes.
23
24
 
24
25
  For a complete overlay implementation, see
25
26
  [`createCrosshairPass`](../examples/shared/crosshairPass.ts). It loads the input
@@ -35,12 +36,55 @@ A changing overlay can read application state in its callback. `RenderLoop`
35
36
  stops presenting after convergence, so explicitly call `display.present(final)`
36
37
  when that state changes. This refreshes the overlay without tracing more samples.
37
38
 
39
+ For edge-adaptive enlargement without sharpening:
40
+
41
+ ```ts
42
+ import {createEasuPass} from '@meimu/mutracer';
43
+ const pass = createEasuPass();
44
+ const removeUpscale = engine.display.addPostProcess(pass, {
45
+ outputSize: (_, display) => display
46
+ });
47
+ ```
48
+
49
+ Register the upscale before overlays that should stay at canvas resolution. An
50
+ input that already has the output size is copied unchanged and a larger one is
51
+ area-averaged, so one registration can serve a full-resolution or supersampled
52
+ final render and a reduced-resolution preview.
53
+
54
+ To recover texture detail, enable `diffuseColor`, `specularColor` and `depth`
55
+ on a scale-based render with `aovResolution: 'display'`, and create the pass with
56
+ `createEasuPass({albedoDetail: true})`. Keep beauty at the desired reduced
57
+ `resolutionScale`. Unregister the pass, then call `pass.dispose()` when finished
58
+ to release its scratch images.
59
+
60
+ EASU works on display-encoded RGB after tone mapping, including extended-range
61
+ output, and preserves alpha. EASU alone cannot recover missing texture detail.
62
+ Albedo compensation recovers supported surface texture detail but cannot recover
63
+ reflection, refraction or geometric detail. It maps the displayed image back
64
+ through the display transform, estimates locally how much radiance follows albedo,
65
+ and changes only that part, so specular highlights mostly keep their brightness.
66
+ Clipped highlights cannot be recovered. Transmission, cutouts, background, low
67
+ coverage and depth boundaries receive EASU alone. With detail, a present costs about
68
+ 2.6 ms at 6 MP on an Apple M4 Max, compared with about 0.5 ms for EASU alone.
69
+
38
70
  ## Options and image semantics
39
71
 
40
72
  `getOutputTexture(kind)` requires `'hdr'` or `'display'`; both return undefined
41
73
  on WebGL, before a complete sample or after disposal. There are no passes by
42
74
  default. `addPostProcess(pass)` returns an idempotent unregister function.
43
- `PostProcess` and `PostProcessContext` are exported types.
75
+ `PostProcess`, `PostProcessContext` and `PostProcessOptions` are exported types.
76
+
77
+ Use `addPostProcess(pass, {outputSize: (input, display) => display})` to output
78
+ at canvas resolution. The default preserves the input dimensions. Each callback
79
+ receives the previous pass's dimensions and the current canvas dimensions, also
80
+ for `toBlob()`. A chain can change size more than once. Return positive integer
81
+ width and height within `device.limits.maxTextureDimension2D`; invalid dimensions
82
+ discard the whole chain and emit an engine error, as callback exceptions do.
83
+
84
+ `ctx.width` and `ctx.height` remain the actual render dimensions. Read
85
+ `ctx.input.width/height` and `ctx.output.width/height` for the pass dimensions.
86
+ `toBlob()` exports the final chain dimensions; `getOutputTexture('display')`
87
+ borrows that actual-sized image, which may differ from the render size.
44
88
 
45
89
  | Image | Meaning |
46
90
  | --- | --- |
@@ -59,6 +103,21 @@ by primary-hit coverage for partially hit pixels, restoring the per-sample
59
103
  average. Thus that CPU readback can differ from the borrowed native HDR texture.
60
104
  See [custom denoiser image semantics](./denoising.md#custom-denoisers).
61
105
 
106
+ `createEasuPass()` defaults to EASU alone. `albedoDetail` defaults to `false`.
107
+ When the displayed render lacks those AOVs or display resolution, the pass warns
108
+ once and upscales without detail; frames whose AOVs are not ready yet or differ
109
+ from the output size also use EASU alone. The optional AOVs add tracing and
110
+ memory cost; see [surface outputs](./api.md#surface-outputs).
111
+
112
+ `render.getAovTexture(kind)` borrows enabled `diffuseColor`, `specularColor` or
113
+ `depth` outputs after an AOV sample completes, only on WebGPU. Colors use
114
+ `rgba8unorm-srgb`, which loads as linear values in [0, 1], with valid coverage in
115
+ alpha; depth uses `rg32float`, with
116
+ camera-forward depth in red and hit coverage in green. These raw textures follow
117
+ the same borrowing lifetime as `getOutputTexture()` and have the AOV dimensions.
118
+ Each is resolved from the accumulation when its samples change, and the first
119
+ call for a kind also creates its pipeline.
120
+
62
121
  ## Caveats
63
122
 
64
123
  Textures are borrowed and read-only. Do not retain, write or destroy returned
@@ -79,9 +138,3 @@ discards the chain's recorded commands, displays the original composed image
79
138
  and emits the engine `error` event. GPU validation errors remain WebGPU errors;
80
139
  the callback must record valid commands.
81
140
 
82
- ## Implementation notes
83
-
84
- The composed image is kept separately, so a thrown callback cannot leave a
85
- partly processed result.
86
- Display passes do not enter denoiser inputs, change samples or invalidate cached
87
- filter results. HDR-stage callbacks are outside this API.
@@ -146,6 +146,9 @@ camera edits discard an unfinished sample and restart against the new inputs.
146
146
  `maxTiles` caps the sample's tile grid (rounded up to a rectangular grid), so a
147
147
  smaller value makes larger tiles. It does not cap work per frame or per call;
148
148
  use `timeBudgetMs` and the between-frame deadline to control queued work.
149
+ With GPU timing available, WebGPU can keep tiles above `timeBudgetMs` when the
150
+ budget cannot be reached or further subdivision brings little speedup. This limits
151
+ extra offline rendering cost while allowing longer pauses between updates.
149
152
 
150
153
  ## Reading and exporting
151
154
 
package/docs/sampling.md CHANGED
@@ -23,6 +23,10 @@ render.samplingPattern = 'zsobol';
23
23
  `sampleSequenceOffset`. Use disjoint offset ranges for animation frames.
24
24
  - **Backends**: WebGPU and WebGL 2 can differ in floating-point rounding even
25
25
  with identical sequence points.
26
+ - **Dispersion**: on WebGPU, `dispersionWavelengths: 3` with
27
+ `directLightCandidates > 1` can need more samples in individual RGB channels
28
+ under strongly colored lighting and materials. Lower overall noise does not
29
+ guarantee lower noise in every channel.
26
30
  - **Fireflies**: very narrow specular lobes under directional lights can
27
31
  produce rare, large contributions with any pattern. Sampling does not clamp
28
32
  them; use `render.clamping` if needed.
@@ -0,0 +1,52 @@
1
+ # Acceleration structures
2
+
3
+ The renderer traces a two-level hierarchy: one bottom-level BVH (BLAS) per geometry
4
+ and a top-level BVH (TLAS) over mesh instances. Builders, layouts and preparation
5
+ are documented in the [BVH guide](../bvh.md); this note records the choices behind
6
+ the defaults and the alternatives that were measured. The code is
7
+ `src/renderer/bvhAccel.ts` and `binnedSahBvh.ts`.
8
+
9
+ ## TLAS
10
+
11
+ - **Builder.** The TLAS always uses the built-in binned SAH builder, a JavaScript
12
+ port of TinyBVH's `BVH::Build`, built and refitted on the calling thread.
13
+ - **Bins.** The web TLAS uses 64 SAH bins (`WEB_TLAS_BINS`); native keeps 8. More
14
+ bins separate overlapping static instances in scenes with many instances.
15
+ Higher instance costs, exact SAH and forced fallbacks were compared and not
16
+ adopted. The rebuilt tree can pick a different object at coincident faces, which
17
+ changes `id` AOVs there; other AOVs matched, and HDR differed only by rounding.
18
+ - **Instance bounds.** Each instance's world bounds, padded by displacement, are
19
+ stored after the TLAS. Leaf traversal tests them before transforming the ray
20
+ into the BLAS's space, so a ray that misses an instance skips the transform and
21
+ the BLAS root test. Both web backends and the caustic kernel use them.
22
+
23
+ ## BLAS
24
+
25
+ - **Defaults.** The BVH2 layout with spatial splits off stays the default after a
26
+ matrix of builder, layout and split settings. CWBVH was slower on the measured
27
+ scenes despite its smaller nodes. Spatial splits help some scenes at three to
28
+ five times the build time, so they stay opt-in.
29
+ - **Traversal tweaks.** Changes to box tests and stack handling showed no reliable
30
+ gain and were not adopted.
31
+
32
+ ## Static merge prototype
33
+
34
+ A prototype merged static instances into one world-space BLAS, with world-space
35
+ triangle bounds and vertex tests in local space. It was faster on a heavy
36
+ many-instance scene, but:
37
+
38
+ - coincident geometry needs the old tree as a fallback to keep object identity,
39
+ which costs memory and cold build time;
40
+ - HDR and legacy guides differ by rare rounding, and subsurface scattering, alpha
41
+ random streams, instance colors and picking were not verified;
42
+ - incremental updates and the choice of which instances count as static are
43
+ undesigned.
44
+
45
+ It is not in production. Pursuing it needs both web layouts, an update strategy and
46
+ those verifications, then a native port and comparison.
47
+
48
+ ## Changing this code
49
+
50
+ - TLAS and BLAS changes affect `id` AOVs and picking at ties; compare those as well
51
+ as HDR.
52
+ - Keep native's TLAS parameters unless native is aligned in a separate change.
@@ -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.
@@ -53,6 +53,35 @@ Photon gathering filters photon flux by the camera path's channel. This splits R
53
53
  across the camera paths and applies no second spectral weight, so energy is shared
54
54
  between path tracing and caustics the same way as without dispersion.
55
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
+
56
85
  ## Short-wavelength IOR limit
57
86
 
58
87
  Cauchy IOR rises quickly at short wavelengths. At strong dispersion scales, violet