@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
@@ -25,18 +25,20 @@ return clamp((value-EPS)/(1.0-2.0*EPS),0.0,0.99999994);
25
25
  `;
26
26
  // Preserve the dormant R1 uniform slot and shader spelling for source parity.
27
27
  // Production packs zero; only an explicitly registered research adapter sets it.
28
- export function causticLightCompute(constants, blasLayoutMask, hasDisplacement, mode = 'lightTracing', sceneIntersections = '', fullEmissionRandom = true) {
28
+ export function causticLightCompute(constants, blasLayoutMask, hasDisplacement, mode = 'lightTracing', sceneIntersections = '', fullEmissionRandom = true, perInstanceBounds = false, compactVertices = false, independentTextures = false) {
29
29
  const frames = !!constants.USE_ANISOTROPY_DIRECTIONS || !!constants.USE_COAT_NORMAL_FRAMES, coat = !!constants.USE_COAT_NORMAL_FRAMES;
30
- return (wgpuShaderPreamble(constants, true) +
30
+ return ((independentTextures ? 'enable wgpu_binding_array;\n' : '') + wgpuShaderPreamble(constants, true) +
31
31
  /* wgsl */ `
32
32
  ${Object.keys(WGPU_SCENE_FEATURE_BITS)
33
33
  .map((name) => `const ${name} = ${!!constants[name]};`)
34
34
  .join('\n')}
35
35
  ${common}
36
- ${core({ hasAnisotropyDirections: frames, hasCoatNormals: coat, caustics: true })}
36
+ ${core({ independentTextures, compactVertices, hasAnisotropyDirections: frames, hasCoatNormals: coat, caustics: true })}
37
37
  ${uv}
38
- ${createMaterialBufferWgsl({ hasAnisotropyDirections: frames, hasCoatNormals: coat, caustics: true })}
38
+ ${createMaterialBufferWgsl({ independentTextures, hasAnisotropyDirections: frames, hasCoatNormals: coat, caustics: true })}
39
39
  ${intersect({
40
+ perInstanceBounds,
41
+ compactVertices,
40
42
  hasDisplacement,
41
43
  blasLayoutMask,
42
44
  hasAnisotropyDirections: frames,
@@ -75,7 +77,7 @@ fn photonStore(i:u32,value:vec4u) {
75
77
  let width=textureDimensions(photons).x;
76
78
  textureStore(photons,vec2i(vec2u(i%width,i/width)),value);
77
79
  }
78
- fn pU(i:u32,value:u32) {
80
+ fn qX(i:u32,value:u32) {
79
81
  let buckets=min(${PHOTON_GRID_SIZE}u,1u<<(32u-countLeadingZeros(max(256u,caustic.photonCount*2u)-1u)));
80
82
  let index=8u+buckets*4u+caustic.photonCount*2u+i;
81
83
  let width=textureDimensions(photonDirections).x;
@@ -86,29 +88,29 @@ textureStore(photonDirections,vec2i(vec2u(index%width,index/width)),vec4u(value)
86
88
 
87
89
 
88
90
  ${causticUnitRandomWgsl}
89
- fn fB(dim:u32)->f32{return ${fullEmissionRandom ? 'causticUnitRandom(sample1D(dim))' : 'sample1D(dim)'}
91
+ fn fN(dim:u32)->f32{return ${fullEmissionRandom ? 'causticUnitRandom(sample1D(dim))' : 'sample1D(dim)'}
90
92
  ;
91
93
  }
92
- fn hE(dim:u32)->vec2f {
94
+ fn ie(dim:u32)->vec2f {
93
95
  let value=sample2D(dim);
94
96
  return
95
97
  ${fullEmissionRandom ? 'vec2f(causticUnitRandom(value.x),causticUnitRandom(value.y))' : 'value'}
96
98
  ;
97
99
  }
98
- fn nH(i:u32)->vec4f {
100
+ fn oF(i:u32)->vec4f {
99
101
  let width=textureDimensions(causticDistribution).x;
100
102
  return textureLoad(causticDistribution,vec2i(vec2u(i%width,i/width)),0);
101
103
  }
102
- fn ds(i:u32)->vec4f {
103
- return nH(i);
104
+ fn dz(i:u32)->vec4f {
105
+ return oF(i);
104
106
  }
105
107
 
106
108
 
107
109
 
108
- var<private> kH:vec3f;
110
+ var<private> lu:vec3f;
109
111
  var<private> causticEmissionAreaPdf:f32;
110
- fn kL(origin:vec3f,position:vec3f,normal:vec3f,emission:vec3f,area:f32,pmf:f32,result:ptr<function,LightSampleRec>) {
111
- kH=position;
112
+ fn lE(origin:vec3f,position:vec3f,normal:vec3f,emission:vec3f,area:f32,pmf:f32,result:ptr<function,LightSampleRec>) {
113
+ lu=position;
112
114
  causticEmissionAreaPdf=pmf/max(area,1e-20);
113
115
  let offset=position-origin;
114
116
  (*result).dist=length(offset);
@@ -117,7 +119,7 @@ let offset=position-origin;
117
119
  (*result).emission=emission;
118
120
  (*result).pdf=pmf;
119
121
  }
120
- fn rn(origin:vec3f,rnd:vec3f,result:ptr<function,LightSampleRec>) {
122
+ fn sE(origin:vec3f,rnd:vec3f,result:ptr<function,LightSampleRec>) {
121
123
  causticEmissionAreaPdf=0.0;
122
124
  (*result).pdf=0.0;
123
125
  (*result).sourceType=0u;
@@ -127,14 +129,14 @@ return;
127
129
  }
128
130
  let scaled=rnd.z*f32(caustic.choiceCount);
129
131
  let column=min(u32(scaled),caustic.choiceCount-1u);
130
- let entry=nH(caustic.casterCount+column*2u);
132
+ let entry=oF(caustic.casterCount+column*2u);
131
133
  let selected=select(bitcast<u32>(entry.y),column,fract(scaled)<entry.x);
132
- let choice=bitcast<vec4u>(nH(caustic.casterCount+selected*2u));
133
- let pmf=nH(caustic.casterCount+selected*2u+1u).x;
134
+ let choice=bitcast<vec4u>(oF(caustic.casterCount+selected*2u));
135
+ let pmf=oF(caustic.casterCount+selected*2u+1u).x;
134
136
  (*result).sourceIndex=choice.w;
135
137
  if(choice.z==1u) {
136
138
  (*result).sourceType=0u;
137
- ig(rnd.xy,result);
139
+ iM(rnd.xy,result);
138
140
  (*result).pdf*=pmf;
139
141
  }
140
142
  else if(choice.z==2u) {
@@ -143,15 +145,15 @@ var light=analyticLights[choice.w];
143
145
  let kind=i32(light.litType);
144
146
  if(kind==RECT_LIGHT) {
145
147
  let position=light.position+light.u*(rnd.x-.5)+light.v*(rnd.y-.5);
146
- kL(origin,position,normalize(cross(light.u,light.v)),light.emission,light.area,pmf,result);
148
+ lE(origin,position,normalize(cross(light.u,light.v)),light.emission,light.area,pmf,result);
147
149
  (*result).isDelta=light.radiusOrSpread<=.001;
148
- bC(light,position,light.position-.5*(light.u+light.v),result);
150
+ bD(light,position,light.position-.5*(light.u+light.v),result);
149
151
  }
150
152
  else if((kind==SPHERE_LIGHT||kind==SPOT_LIGHT)&&light.radiusOrSpread>0.0) {
151
153
  let z=1.0-2.0*rnd.x;
152
- let dr=sqrt(max(0.0,1.0-z*z));
153
- let normal=vec3f(dr*cos(TWOPI*rnd.y),dr*sin(TWOPI*rnd.y),z);
154
- kL(origin,light.position+light.radiusOrSpread*normal,normal,light.emission,light.area,pmf,result);
154
+ let dw=sqrt(max(0.0,1.0-z*z));
155
+ let normal=vec3f(dw*cos(TWOPI*rnd.y),dw*sin(TWOPI*rnd.y),z);
156
+ lE(origin,light.position+light.radiusOrSpread*normal,normal,light.emission,light.area,pmf,result);
155
157
  }
156
158
  else {
157
159
  light.selectionPmf=1.0;
@@ -168,55 +170,55 @@ let position=bary.x*triangle.p0+bary.y*triangle.p1+bary.z*triangle.p2;
168
170
  let normal=normalize(cross(triangle.p1-triangle.p0,triangle.p2-triangle.p0));
169
171
  let uv=bary.x*triangle.uv0+bary.y*triangle.uv1+bary.z*triangle.uv2;
170
172
  let uv1=bary.x*triangle.secondaryUv0+bary.y*triangle.secondaryUv1+bary.z*triangle.secondaryUv2;
171
- kL(origin,position,normal,getMaterialEmission(i32(triangle.materialIndex),uv,uv1),triangle.area,pmf,result);
173
+ lE(origin,position,normal,getMaterialEmission(i32(triangle.materialIndex),uv,uv1),triangle.area,pmf,result);
172
174
  }
173
175
  (*result).selectionPmf=pmf;
174
176
  }
175
- fn lj(s:vec4f,origin:vec3f)->f32 {
177
+ fn mf(s:vec4f,origin:vec3f)->f32 {
176
178
  let bG=dot(s.xyz-origin,s.xyz-origin);
177
179
  return select(-1.0,sqrt(max(0.0,1.0-s.w*s.w/bG)),bG>s.w*s.w);
178
180
  }
179
181
 
180
182
 
181
- fn dN(s:vec4f)->f32 {
183
+ fn dS(s:vec4f)->f32 {
182
184
  if((caustic.experiments&2u)==0u) { return 1.0; }
183
- let tB=caustic.viewProjection*vec4f(s.xyz,1.0);
185
+ let vb=caustic.viewProjection*vec4f(s.xyz,1.0);
184
186
  let m=caustic.viewProjection;
185
187
  let extent=s.w*max(length(vec3f(m[0].x,m[1].x,m[2].x)),length(vec3f(m[0].y,m[1].y,m[2].y)));
186
- if(tB.w<=0.0||abs(tB.x)>tB.w+extent||abs(tB.y)>tB.w+extent) { return .05; }
187
- return .05+min(1.0,extent*extent/max(tB.w*tB.w,1e-20));
188
+ if(vb.w<=0.0||abs(vb.x)>vb.w+extent||abs(vb.y)>vb.w+extent) { return .05; }
189
+ return .05+min(1.0,extent*extent/max(vb.w*vb.w,1e-20));
188
190
  }
189
- fn sv(origin:vec3f,rnd:vec3f,pdf:ptr<function,f32>,ky:ptr<function,f32>)->vec3f {
191
+ fn tV(origin:vec3f,rnd:vec3f,pdf:ptr<function,f32>,lh:ptr<function,f32>)->vec3f {
190
192
  var total=0.0;
191
193
  for(var i=0u;i<caustic.casterCount;i++) {
192
- total+=(1.0-lj(ds(i),origin))*dN(ds(i));
194
+ total+=(1.0-mf(dz(i),origin))*dS(dz(i));
193
195
  }
194
196
  var choice=rnd.z*total;
195
- var selected=ds(0u);
197
+ var selected=dz(0u);
196
198
  for(var i=0u;i<caustic.casterCount;i++) {
197
- let sphere=ds(i);
199
+ let sphere=dz(i);
198
200
  selected=sphere;
199
- choice-=(1.0-lj(sphere,origin))*dN(sphere);
201
+ choice-=(1.0-mf(sphere,origin))*dS(sphere);
200
202
  if(choice<=0.0) {
201
203
  break;
202
204
  }
203
205
  }
204
- *ky=selected.w;
206
+ *lh=selected.w;
205
207
  let offset=selected.xyz-origin;
206
208
  let axis=normalize(select(vec3f(0.0,0.0,1.0),offset,dot(offset,offset)>1e-20));
207
- let cosine=mix(1.0,lj(selected,origin),rnd.x);
208
- let uC=sqrt(max(0.0,1.0-cosine*cosine));
209
+ let cosine=mix(1.0,mf(selected,origin),rnd.x);
210
+ let wk=sqrt(max(0.0,1.0-cosine*cosine));
209
211
  var t:vec3f;
210
212
  var b:vec3f;
211
213
  sampling_orthonormal_basis(axis,&t,&b);
212
- let direction=axis*cosine+(t*cos(TWOPI*rnd.y)+b*sin(TWOPI*rnd.y))*uC;
214
+ let direction=axis*cosine+(t*cos(TWOPI*rnd.y)+b*sin(TWOPI*rnd.y))*wk;
213
215
 
214
216
  var overlaps=0.0;
215
217
  for(var i=0u;i<caustic.casterCount;i++) {
216
- let sphere=ds(i);
217
- let c=lj(sphere,origin);
218
+ let sphere=dz(i);
219
+ let c=mf(sphere,origin);
218
220
  if(c<0.0||dot(normalize(sphere.xyz-origin),direction)>=c) {
219
- overlaps+=dN(sphere);
221
+ overlaps+=dS(sphere);
220
222
  }
221
223
  }
222
224
  *pdf=overlaps/max(TWOPI*total,1e-20);
@@ -224,26 +226,26 @@ return direction;
224
226
  }
225
227
  ${mode === 'lightTracing'
226
228
  ? /* wgsl */ `${atomic}
227
- fn causticCameraConnect(si: SurfaceInteraction, material: OpenPbrResolvedMaterial, hQ: Ray, flux: vec3f, extinction: vec3f) {
229
+ fn causticCameraConnect(si: SurfaceInteraction, material: OpenPbrResolvedMaterial, iA: Ray, flux: vec3f, extinction: vec3f) {
228
230
  let eye=cameraUniform.viewInv[3].xyz;
229
231
  let forward=-normalize(cameraUniform.viewInv[2].xyz);
230
- let rB=hE(causticDim(68u));
231
- let radius=cameraUniform.apertureRadius*sqrt(rB.x);
232
- var uV=eye+radius*(cos(TWOPI*rB.y)*cameraUniform.viewInv[0].xyz+sin(TWOPI*rB.y)*cameraUniform.viewInv[1].xyz);
232
+ let sQ=ie(causticDim(68u));
233
+ let radius=cameraUniform.apertureRadius*sqrt(sQ.x);
234
+ var wJ=eye+radius*(cos(TWOPI*sQ.y)*cameraUniform.viewInv[0].xyz+sin(TWOPI*sQ.y)*cameraUniform.viewInv[1].xyz);
233
235
  if(caustic.orthographic!=0u) {
234
- uV=si.position-forward*dot(si.position-eye,forward);
235
- }
236
- let xM=uV-si.position;
237
- let bG=dot(xM,xM);
238
- let direction=normalize(xM);
239
- let wf=uV+(si.position-uV)*cameraUniform.focusDistance/max(dot(si.position-uV,forward),1e-12);
240
- let qO=select(wf,si.position,caustic.orthographic!=0u);
241
- let tB=caustic.viewProjection*vec4f(qO,1.0);
242
- if(tB.w<=0.0||dot(si.position-eye,forward)<=0.0) {
236
+ wJ=si.position-forward*dot(si.position-eye,forward);
237
+ }
238
+ let zJ=wJ-si.position;
239
+ let bG=dot(zJ,zJ);
240
+ let direction=normalize(zJ);
241
+ let ye=wJ+(si.position-wJ)*cameraUniform.focusDistance/max(dot(si.position-wJ,forward),1e-12);
242
+ let sc=select(ye,si.position,caustic.orthographic!=0u);
243
+ let vb=caustic.viewProjection*vec4f(sc,1.0);
244
+ if(vb.w<=0.0||dot(si.position-eye,forward)<=0.0) {
243
245
  return;
244
246
  }
245
- let wY=tB.xy/tB.w*0.5+0.5;
246
- if(any(wY<vec2f(0.0))||any(wY>=vec2f(1.0))) {
247
+ let yY=vb.xy/vb.w*0.5+0.5;
248
+ if(any(yY<vec2f(0.0))||any(yY>=vec2f(1.0))) {
247
249
  return;
248
250
  }
249
251
  var shadow:Ray;
@@ -252,31 +254,31 @@ if(intersectSceneShadow(shadow,sqrt(bG)-2.0*EPS,si.triangleIndex,si.instanceInde
252
254
  return;
253
255
  }
254
256
  var m=material;
255
- let normal=select(-si.normal,si.normal,dot(si.normal,-hQ.d)>=0.0);
256
- m.inside=dot(si.faceNormal,-hQ.d)<0.0;
257
+ let normal=select(-si.normal,si.normal,dot(si.normal,-iA.d)>=0.0);
258
+ m.inside=dot(si.faceNormal,-iA.d)<0.0;
257
259
  m.relativeIor=select(1.0/m.specularIor,m.specularIor,m.inside);
258
260
  ${frames
259
261
  ? /* wgsl */ ` m.directionTangent=si.directionTangent;m.directionBitangent=si.directionBitangent;m.geometryTangent=si.geometryTangent;m.geometryBitangent=si.geometryBitangent;`
260
262
  : ''}
261
263
  ${coat
262
- ? /* wgsl */ ` m.coatNormal=normal;m.geometricNormal=select(-si.faceNormal,si.faceNormal,dot(si.faceNormal,-hQ.d)>=0.0);`
264
+ ? /* wgsl */ ` m.coatNormal=normal;m.geometricNormal=select(-si.faceNormal,si.faceNormal,dot(si.faceNormal,-iA.d)>=0.0);`
263
265
  : ''}
264
- let prepared=bV(si.tangent,m,-hQ.d,normal);
266
+ let prepared=bX(si.tangent,m,-iA.d,normal);
265
267
  var pdf:f32;
266
268
  let response=openpbr_eval_prepared(prepared,direction,&pdf);
267
- let mj=dot(-direction,-normalize(cameraUniform.viewInv[2].xyz));
268
- if(mj<=0.0) {
269
+ let ne=dot(-direction,-normalize(cameraUniform.viewInv[2].xyz));
270
+ if(ne<=0.0) {
269
271
  return;
270
272
  }
271
- let wg=caustic.cameraScale*rtUniform.outputSize.x*rtUniform.outputSize.y /
272
- select(max(bG*mj*mj*mj,1e-12),1.0,caustic.orthographic!=0u);
273
+ let yf=caustic.cameraScale*rtUniform.outputSize.x*rtUniform.outputSize.y /
274
+ select(max(bG*ne*ne*ne,1e-12),1.0,caustic.orthographic!=0u);
273
275
  let transmittance=exp(-extinction*sqrt(bG));
274
- let value=flux*transmittance*response*causticAdjoint(-hQ.d,direction,normal,si.faceNormal)*wg/f32(caustic.photonCount);
275
- let pixel=vec2u(wY*rtUniform.outputSize);
276
+ let value=flux*transmittance*response*causticAdjoint(-iA.d,direction,normal,si.faceNormal)*yf/f32(caustic.photonCount);
277
+ let pixel=vec2u(yY*rtUniform.outputSize);
276
278
  let index=(pixel.x+pixel.y*u32(rtUniform.outputSize.x))*3u;
277
- kM(index,value.x);
278
- kM(index+1u,value.y);
279
- kM(index+2u,value.z);
279
+ lF(index,value.x);
280
+ lF(index+1u,value.y);
281
+ lF(index+2u,value.z);
280
282
  }
281
283
  `
282
284
  : ''}
@@ -287,18 +289,18 @@ if(photonIndex>=caustic.photonCount) {
287
289
  return;
288
290
  }
289
291
  ${mode === 'photonMapping' ? 'photonStore(photonIndex,vec4u(0u));' : ''}
290
- aI=vec2u(photonIndex%u32(rtUniform.outputSize.x),photonIndex/u32(rtUniform.outputSize.x));
292
+ aJ=vec2u(photonIndex%u32(rtUniform.outputSize.x),photonIndex/u32(rtUniform.outputSize.x));
291
293
  aC=caustic.batch;
292
294
  samplingBounce=0u;
293
- ca=0u;
294
- fi(aI,i32(ak(caustic.batch,rtUniform.sequenceSeed)));
295
+ hashedCounter=0u;
296
+ fA(aJ,i32(ak(caustic.batch,rtUniform.sequenceSeed)));
295
297
  var wavelength=0.0;
296
298
  var wavelengthPdf=1.0;
297
299
  if(SPECTRAL) {
298
- wavelength=spectrumSample(${SPECTRUM_PHOTON_DISTRIBUTION}u,fB(causticDim(69u)),&wavelengthPdf);
300
+ wavelength=spectrumSample(${SPECTRUM_PHOTON_DISTRIBUTION}u,fN(causticDim(69u)),&wavelengthPdf);
299
301
  }
300
302
  var ls:LightSampleRec;
301
- rn(caustic.casterSphere.xyz,vec3f(hE(causticDim(60u)),fB(causticDim(61u))),&ls);
303
+ sE(caustic.casterSphere.xyz,vec3f(ie(causticDim(60u)),fN(causticDim(61u))),&ls);
302
304
  if(ls.pdf<=0.0) {
303
305
  return;
304
306
  }
@@ -307,41 +309,41 @@ if(ls.sourceType==2u) {
307
309
  infinite=i32(analyticLights[ls.sourceIndex].litType)==DIRECTIONAL_LIGHT;
308
310
  }
309
311
  var origin=caustic.casterSphere.xyz+ls.direction*ls.dist;
310
- if(causticEmissionAreaPdf>0.0) { origin=kH; }
312
+ if(causticEmissionAreaPdf>0.0) { origin=lu; }
311
313
  var direction:vec3f;
312
314
  var flux:vec3f;
313
- var ky=caustic.casterSphere.w;
315
+ var lh=caustic.casterSphere.w;
314
316
  if(infinite) {
315
317
  direction=-ls.direction;
316
318
 
317
319
  var area=0.0;
318
320
  for(var i=0u;i<caustic.casterCount;i++) {
319
- let s=ds(i);
320
- area+=s.w*s.w*dN(s);
321
+ let s=dz(i);
322
+ area+=s.w*s.w*dS(s);
321
323
  }
322
- var choice=fB(causticDim(63u))*area;
323
- var sphere=ds(0u);
324
+ var choice=fN(causticDim(63u))*area;
325
+ var sphere=dz(0u);
324
326
  for(var i=0u;i<caustic.casterCount;i++) {
325
- sphere=ds(i);
326
- choice-=sphere.w*sphere.w*dN(sphere);
327
+ sphere=dz(i);
328
+ choice-=sphere.w*sphere.w*dS(sphere);
327
329
  if(choice<=0.0) {
328
330
  break;
329
331
  }
330
332
  }
331
- ky=sphere.w;
333
+ lh=sphere.w;
332
334
  var t:vec3f;
333
335
  var b:vec3f;
334
336
  sampling_orthonormal_basis(direction,&t,&b);
335
- let rnd=hE(causticDim(62u));
337
+ let rnd=ie(causticDim(62u));
336
338
  origin=sphere.xyz-direction*dot(sphere.xyz-caustic.casterSphere.xyz,direction)-direction*caustic.casterSphere.w;
337
339
  origin+=sphere.w*sqrt(rnd.x)*(t*cos(TWOPI*rnd.y)+b*sin(TWOPI*rnd.y));
338
340
  var overlaps=0.0;
339
341
  for(var i=0u;i<caustic.casterCount;i++) {
340
- let s=ds(i);
342
+ let s=dz(i);
341
343
  let offset=origin-s.xyz;
342
- let qO=offset-direction*dot(offset,direction);
343
- if(dot(qO,qO)<=s.w*s.w) {
344
- overlaps+=dN(s);
344
+ let sc=offset-direction*dot(offset,direction);
345
+ if(dot(sc,sc)<=s.w*s.w) {
346
+ overlaps+=dS(s);
345
347
  }
346
348
  }
347
349
  let diskPdf=overlaps/max(PI*area,1e-20);
@@ -362,9 +364,9 @@ if(areaPdf<=0.0) {
362
364
  return;
363
365
  }
364
366
  var directionPdf:f32;
365
- direction=sv(origin,vec3f(hE(causticDim(62u)),fB(causticDim(63u))),&directionPdf,&ky);
367
+ direction=tV(origin,vec3f(ie(causticDim(62u)),fN(causticDim(63u))),&directionPdf,&lh);
366
368
  var emission=ls.emission;
367
- var mL=max(0.0,dot(ls.normal,direction));
369
+ var nN=max(0.0,dot(ls.normal,direction));
368
370
  if(ls.sourceType==2u) {
369
371
  let source=analyticLights[ls.sourceIndex];
370
372
  emission=source.emission;
@@ -372,27 +374,27 @@ if(i32(source.litType)==SPOT_LIGHT) {
372
374
  emission*=evaluateSpotEmissionMultiplier(source,direction);
373
375
  }
374
376
  if(ls.isDelta) {
375
- mL=1.0;
377
+ nN=1.0;
376
378
  }
377
- if(i32(source.litType)==RECT_LIGHT&&mL<cos(0.5*PI*source.radiusOrSpread)) {
379
+ if(i32(source.litType)==RECT_LIGHT&&nN<cos(0.5*PI*source.radiusOrSpread)) {
378
380
  return;
379
381
  }
380
382
  if(i32(source.litType)==RECT_LIGHT) {
381
- var un:LightSampleRec;
382
- un.emission=emission;
383
- bC(source,origin,source.position-0.5*(source.u+source.v),&un);
384
- emission=un.emission;
383
+ var vX:LightSampleRec;
384
+ vX.emission=emission;
385
+ bD(source,origin,source.position-0.5*(source.u+source.v),&vX);
386
+ emission=vX.emission;
385
387
  }
386
388
  }
387
- flux=emission*mL/(areaPdf*directionPdf);
389
+ flux=emission*nN/(areaPdf*directionPdf);
388
390
  }
389
391
  if(SPECTRAL) {
390
392
  flux*=spectrumRgb(wavelength)/(wavelengthPdf*${SPECTRUM_WAVELENGTH_RANGE}.0);
391
393
  }
392
394
  var ray:Ray;
393
395
  initRay(&ray,origin+EPS*direction,direction);
394
- var vt=0u;
395
- var wN=false;
396
+ var xk=0u;
397
+ var yN=false;
396
398
  var extinction=vec3f(0.0);
397
399
  var scattering=vec3f(0.0);
398
400
  var si:SurfaceInteraction;
@@ -408,7 +410,7 @@ return;
408
410
  }
409
411
  var m:OpenPbrResolvedMaterial;
410
412
  getMaterial(&m,si.materialIndex,si.uv,si.uv1,si.instanceIndex,si.vertexColor);
411
- if(m.alpha<1.0&&fB(causticDim(79u))>=m.alpha) {
413
+ if(m.alpha<1.0&&fN(causticDim(79u))>=m.alpha) {
412
414
  initRay(&ray,si.position+EPS*ray.d,ray.d);
413
415
  continue;
414
416
  }
@@ -416,9 +418,9 @@ if(m.materialType==SHADOW_CATCHER) {
416
418
  return;
417
419
  }
418
420
  let segment=distance(ray.o,si.position);
419
- let channel=min(u32(sample1D(bE(MEDIUM_CHANNEL))*3.0),2u);
421
+ let channel=min(u32(sample1D(aX(MEDIUM_CHANNEL))*3.0),2u);
420
422
  if(extinction[channel]>0.0&&any(scattering>vec3f(0.0))) {
421
- let sampled=-log(max(sample1D(bE(MEDIUM_DISTANCE)),1e-7))/extinction[channel];
423
+ let sampled=-log(max(sample1D(aX(MEDIUM_DISTANCE)),1e-7))/extinction[channel];
422
424
 
423
425
 
424
426
  if(sampled<segment) {
@@ -426,31 +428,31 @@ return;
426
428
  }
427
429
  }
428
430
  let transmittance=exp(-extinction*segment);
429
- let xK=(transmittance.x+transmittance.y+transmittance.z)/3.0;
430
- flux*=transmittance/select(1.0,max(xK,1e-20),any(scattering>vec3f(0.0)));
431
+ let oq=(transmittance.x+transmittance.y+transmittance.z)/3.0;
432
+ flux*=transmittance/select(1.0,max(oq,1e-20),any(scattering>vec3f(0.0)));
431
433
  if(SPECTRAL) {
432
434
  m.specularIor=sampling_cauchy_ior(spectrumIorWavelength(wavelength),m.specularIor,m.transmissionDispersionScale,m.dispersionAbbeNumber);
433
435
  }
434
436
  if(!isCausticSpecular(m)) {
435
- let wj=ls.sourceType!=0u;
436
- let r1=wj&&vt==1u&&wN&&m.baseWeight*(1.0-m.baseMetalness)*(1.0-m.transmissionWeight)>0.0&&m.subsurfaceWeight==0.0;
437
- if(vt>0u
437
+ let yi=ls.sourceType!=0u;
438
+ let r1=yi&&xk==1u&&yN&&m.baseWeight*(1.0-m.baseMetalness)*(1.0-m.transmissionWeight)>0.0&&m.subsurfaceWeight==0.0;
439
+ if(xk>0u
438
440
  ${mode === 'lightTracing' ? '&&!(caustic.mnee!=0u&&r1)' : ''}){${mode === 'lightTracing'
439
441
  ? 'causticCameraConnect(si,m,ray,flux,extinction);'
440
442
  : /* wgsl */ `
441
443
  let position=bitcast<vec3u>(si.position);
442
- photonStore(photonIndex,vec4u(position,sS(flux)));
444
+ photonStore(photonIndex,vec4u(position,us(flux)));
443
445
  if((caustic.experiments&1u)!=0u) {
444
446
  var maximum=1e-8;
445
- for(var i=0u;i<caustic.casterCount;i++) { maximum=max(maximum,ds(i).w); }
447
+ for(var i=0u;i<caustic.casterCount;i++) { maximum=max(maximum,dz(i).w); }
446
448
  let buckets=min(${PHOTON_GRID_SIZE}u,1u<<(32u-countLeadingZeros(max(256u,caustic.photonCount*2u)-1u)));
447
449
  let index=8u+buckets*4u+caustic.photonCount*3u+photonIndex;
448
450
  let width=textureDimensions(photonDirections).x;
449
- textureStore(photonDirections,vec2i(vec2u(index%width,index/width)),vec4u(bitcast<u32>(ky/maximum)));
451
+ textureStore(photonDirections,vec2i(vec2u(index%width,index/width)),vec4u(bitcast<u32>(lh/maximum)));
450
452
  }
451
- let hQ=vec2u(round(pN(-ray.d)*255.0));
452
- let normal=vec2u(round(pN(si.faceNormal)*vec2f(255.0,127.0)));
453
- pU(photonIndex,normal.x|(normal.y<<8u)|(select(0u,1u,r1)<<15u)|(hQ.x<<16u)|(hQ.y<<24u));
453
+ let iA=vec2u(round(qR(-ray.d)*255.0));
454
+ let normal=vec2u(round(qR(si.faceNormal)*vec2f(255.0,127.0)));
455
+ qX(photonIndex,normal.x|(normal.y<<8u)|(select(0u,1u,r1)<<15u)|(iA.x<<16u)|(iA.y<<24u));
454
456
  `}
455
457
  }
456
458
  return;
@@ -465,7 +467,7 @@ ${frames
465
467
  ${coat
466
468
  ? /* wgsl */ ` m.coatNormal=normal;m.geometricNormal=select(-si.faceNormal,si.faceNormal,dot(si.faceNormal,view)>=0.0);`
467
469
  : ''}
468
- let prepared=bV(si.tangent,m,view,normal);
470
+ let prepared=bX(si.tangent,m,view,normal);
469
471
  var next:vec3f;
470
472
  var pdf:f32;
471
473
  var transmission:bool;
@@ -475,14 +477,14 @@ var diffuse:bool;
475
477
  var lobe:u32;
476
478
  var marginal:f32;
477
479
  var full:vec3f;
478
- let response=dl(prepared,vec3f(sample2D(bE(BSDF_DIRECTION)),sample1D(bE(BSDF_LOBE))),&next,&pdf,&transmission,&roughness,&specular,&diffuse,&lobe,&marginal,&full);
480
+ let response=dr(prepared,vec3f(sample2D(aX(BSDF_DIRECTION)),sample1D(aX(BSDF_LOBE))),&next,&pdf,&transmission,&roughness,&specular,&diffuse,&lobe,&marginal,&full);
479
481
  if(!(pdf>0.0)) {
480
482
  return;
481
483
  }
482
- if(vt==0u&&ls.sourceType==0u&&transmission) {
484
+ if(xk==0u&&ls.sourceType==0u&&transmission) {
483
485
  var reverse:Ray;
484
486
  initRay(&reverse,si.position,-ray.d);
485
- let bg=ft(reverse);
487
+ let bg=fI(reverse);
486
488
  flux*=bg.rgb*bg.a/max(ls.emission,vec3f(1e-20));
487
489
  }
488
490
  flux*=response/pdf*causticAdjoint(view,next,normal,si.faceNormal);
@@ -496,8 +498,8 @@ if(transmission&&!m.thinWalled&&m.mediumType>0) {
496
498
  extinction=select(m.mediumExtinction,vec3f(0.0),m.inside);
497
499
  scattering=select(m.mediumScattering,vec3f(0.0),m.inside);
498
500
  }
499
- vt++;
500
- wN=vt==1u&&((isCausticT(m)&&transmission)||(isCausticR(m)&&!transmission));
501
+ xk++;
502
+ yN=xk==1u&&((isCausticT(m)&&transmission)||(isCausticR(m)&&!transmission));
501
503
  initRay(&ray,si.position+EPS*next,next);
502
504
  }
503
505
  }
@@ -1 +1 @@
1
- {"version":3,"file":"causticLight.compute.js","sourceRoot":"","sources":["../../../src/renderer/wgsl/causticLight.compute.ts"],"names":[],"mappings":"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","sourcesContent":["import {PHOTON_GRID_SIZE, photonPackingWgsl} from './photonMap.compute.js';\nimport {SPECTRUM_PHOTON_DISTRIBUTION, SPECTRUM_WAVELENGTH_RANGE, spectrumWgsl} from '../spectrum.js';\nimport background from './chunks/background.wgsl.js';\nimport atomic from './chunks/causticAtomic.wgsl.js';\nimport adjoint from './chunks/causticAdjoint.wgsl.js';\nimport common from './chunks/common.wgsl.js';\nimport core from './chunks/rayTraceCore.wgsl.js';\nimport {createMaterialBufferWgsl} from './chunks/materialBuffer.wgsl.js';\nimport intersect from './chunks/intersect.wgsl.js';\nimport rand from './chunks/rand.wgsl.js';\nimport random from './chunks/random.wgsl.js';\nimport env from './chunks/envMap.wgsl.js';\nimport sampling from './chunks/sampling.wgsl.js';\nimport {createOpenPbrBsdfWgsl} from './chunks/openPbrBsdf.wgsl.js';\nimport light from './chunks/sampleLight.wgsl.js';\nimport uv from './chunks/uvProjection.wgsl.js';\nimport {causticPartitionWGSL} from './chunks/caustics.wgsl.js';\nimport {wgpuShaderPreamble} from '../wgpuShaderSource.js';\nimport {WGPU_SCENE_FEATURE_BITS} from '../WGPUSceneFeatures.js';\n\nexport const CAUSTIC_UNIFORM_BYTES = 112;\nexport const causticUnitRandomWgsl = /* wgsl */ `\n fn causticUnitRandom(value:f32)->f32 {\n return clamp((value-EPS)/(1.0-2.0*EPS),0.0,0.99999994);\n }\n`;\n\n// Preserve the dormant R1 uniform slot and shader spelling for source parity.\n// Production packs zero; only an explicitly registered research adapter sets it.\nexport function causticLightCompute(\n constants: Record<string, boolean | number>,\n blasLayoutMask: number,\n hasDisplacement: boolean,\n mode: 'lightTracing' | 'photonMapping' = 'lightTracing',\n sceneIntersections = '',\n fullEmissionRandom = true\n) {\n const frames = !!constants.USE_ANISOTROPY_DIRECTIONS || !!constants.USE_COAT_NORMAL_FRAMES,\n coat = !!constants.USE_COAT_NORMAL_FRAMES;\n return (\n wgpuShaderPreamble(constants, true) +\n /* wgsl */ `\n${Object.keys(WGPU_SCENE_FEATURE_BITS)\n .map((name) => `const ${name} = ${!!constants[name]};`)\n .join('\\n')}\n${common}\n${core({hasAnisotropyDirections: frames, hasCoatNormals: coat, caustics: true})}\n${uv}\n${createMaterialBufferWgsl({hasAnisotropyDirections: frames, hasCoatNormals: coat, caustics: true})}\n${intersect({\n hasDisplacement,\n blasLayoutMask,\n hasAnisotropyDirections: frames,\n hasCoatNormals: coat,\n sceneIntersections\n})}\n${rand}\n${random}\n${env}\n${sampling}\n${spectrumWgsl}\n${createOpenPbrBsdfWgsl(frames, coat)}\n${light}\n${background.replace(\n /\\b(?:USE_BACKGROUND|USE_BACKGROUND_MAP|USE_GROUNDED_BACKGROUND)\\b/g,\n (name) =>\n `((rtUniform.backgroundFlags & ${\n {USE_BACKGROUND: 1, USE_BACKGROUND_MAP: 2, USE_GROUNDED_BACKGROUND: 4}[name]\n }) != 0)`\n)}\n${causticPartitionWGSL(false, false)}\n${adjoint}\n struct CausticLightUniform {\n casterSphere: vec4f,\n viewProjection: mat4x4f,\n photonCount: u32,\n batch: u32,\n mnee: u32,\n cameraScale: f32,\n casterCount: u32,\n choiceCount: u32,\n orthographic: u32,\n experiments: u32,\n }\n @group(3) @binding(0) var<uniform> caustic: CausticLightUniform;\n${\n mode === 'lightTracing'\n ? '@group(3) @binding(1) var<storage,read_write> splats: array<atomic<u32>>;'\n : `${photonPackingWgsl}\n @group(3)@binding(1)var photons:texture_storage_2d<rgba32uint,write>;\n @group(3)@binding(3)var photonDirections:texture_storage_2d<r32uint,write>;\n fn photonStore(i:u32,value:vec4u) {\n let width=textureDimensions(photons).x;\n textureStore(photons,vec2i(vec2u(i%width,i/width)),value);\n }\n fn photonStoreDirections(i:u32,value:u32) {\n let buckets=min(${PHOTON_GRID_SIZE}u,1u<<(32u-countLeadingZeros(max(256u,caustic.photonCount*2u)-1u)));\n let index=8u+buckets*4u+caustic.photonCount*2u+i;\n let width=textureDimensions(photonDirections).x;\n textureStore(photonDirections,vec2i(vec2u(index%width,index/width)),vec4u(value));\n }\n`\n}\n @group(3) @binding(2) var causticDistribution:texture_2d<f32>;\n // Geometric emission PDFs assume the full disk/cone/area support. The generic\n // BSDF sampler contracts randoms by EPS; undo that contraction for these warps.\n${causticUnitRandomWgsl}\nfn causticEmission1D(dim:u32)->f32{return ${\n fullEmissionRandom ? 'causticUnitRandom(sample1D(dim))' : 'sample1D(dim)'\n }\n ;\n}\nfn causticEmission2D(dim:u32)->vec2f {\n let value=sample2D(dim);\n return\n${fullEmissionRandom ? 'vec2f(causticUnitRandom(value.x),causticUnitRandom(value.y))' : 'value'}\n ;\n}\nfn causticLoad(i:u32)->vec4f {\n let width=textureDimensions(causticDistribution).x;\n return textureLoad(causticDistribution,vec2i(vec2u(i%width,i/width)),0);\n}\nfn causticSphere(i:u32)->vec4f {\n return causticLoad(i);\n}\n// Photon emission samples a source surface, independently of any receiver.\n// A NEE sample viewed from the aggregate caster center can reject an emitter\n// which still illuminates another caster, or sample only part of a sphere.\nvar<private> causticEmissionOrigin:vec3f;\nvar<private> causticEmissionAreaPdf:f32;\nfn causticAreaLight(origin:vec3f,position:vec3f,normal:vec3f,emission:vec3f,area:f32,pmf:f32,result:ptr<function,LightSampleRec>) {\n causticEmissionOrigin=position;\n causticEmissionAreaPdf=pmf/max(area,1e-20);\n let offset=position-origin;\n (*result).dist=length(offset);\n (*result).direction=offset/max((*result).dist,1e-20);\n (*result).normal=normal;\n (*result).emission=emission;\n (*result).pdf=pmf;\n}\nfn causticSampleLight(origin:vec3f,rnd:vec3f,result:ptr<function,LightSampleRec>) {\n causticEmissionAreaPdf=0.0;\n (*result).pdf=0.0;\n (*result).sourceType=0u;\n (*result).isDelta=false;\n if(caustic.choiceCount==0u) {\n return;\n }\n let scaled=rnd.z*f32(caustic.choiceCount);\n let column=min(u32(scaled),caustic.choiceCount-1u);\n let entry=causticLoad(caustic.casterCount+column*2u);\n let selected=select(bitcast<u32>(entry.y),column,fract(scaled)<entry.x);\n let choice=bitcast<vec4u>(causticLoad(caustic.casterCount+selected*2u));\n let pmf=causticLoad(caustic.casterCount+selected*2u+1u).x;\n (*result).sourceIndex=choice.w;\n if(choice.z==1u) {\n (*result).sourceType=0u;\n sampleEnvLight(rnd.xy,result);\n (*result).pdf*=pmf;\n }\n else if(choice.z==2u) {\n (*result).sourceType=2u;\n var light=analyticLights[choice.w];\n let kind=i32(light.litType);\n if(kind==RECT_LIGHT) {\n let position=light.position+light.u*(rnd.x-.5)+light.v*(rnd.y-.5);\n causticAreaLight(origin,position,normalize(cross(light.u,light.v)),light.emission,light.area,pmf,result);\n (*result).isDelta=light.radiusOrSpread<=.001;\n light_apply_rect_texture(light,position,light.position-.5*(light.u+light.v),result);\n }\n else if((kind==SPHERE_LIGHT||kind==SPOT_LIGHT)&&light.radiusOrSpread>0.0) {\n let z=1.0-2.0*rnd.x;\n let radial=sqrt(max(0.0,1.0-z*z));\n let normal=vec3f(radial*cos(TWOPI*rnd.y),radial*sin(TWOPI*rnd.y),z);\n causticAreaLight(origin,light.position+light.radiusOrSpread*normal,normal,light.emission,light.area,pmf,result);\n }\n else {\n light.selectionPmf=1.0;\n sampleAnalyticLight(light,origin,rnd.xy,result);\n (*result).pdf*=pmf;\n }\n }\n else {\n (*result).sourceType=1u;\n let triangle=getEmissiveTriangle(i32(choice.w));\n let u=sqrt(rnd.x);\n let bary=vec3f(1.0-u,u*(1.0-rnd.y),u*rnd.y);\n let position=bary.x*triangle.p0+bary.y*triangle.p1+bary.z*triangle.p2;\n let normal=normalize(cross(triangle.p1-triangle.p0,triangle.p2-triangle.p0));\n let uv=bary.x*triangle.uv0+bary.y*triangle.uv1+bary.z*triangle.uv2;\n let uv1=bary.x*triangle.secondaryUv0+bary.y*triangle.secondaryUv1+bary.z*triangle.secondaryUv2;\n causticAreaLight(origin,position,normal,getMaterialEmission(i32(triangle.materialIndex),uv,uv1),triangle.area,pmf,result);\n }\n (*result).selectionPmf=pmf;\n}\nfn causticCosMax(s:vec4f,origin:vec3f)->f32 {\n let distanceSquared=dot(s.xyz-origin,s.xyz-origin);\n return select(-1.0,sqrt(max(0.0,1.0-s.w*s.w/distanceSquared)),distanceSquared>s.w*s.w);\n}\n// A positive floor retains paths from offscreen or occluded casters. The\n// complete weighted mixture PDF below, including overlaps, compensates selection.\nfn causticTargetWeight(s:vec4f)->f32 {\n if((caustic.experiments&2u)==0u) { return 1.0; }\n let clip=caustic.viewProjection*vec4f(s.xyz,1.0);\n let m=caustic.viewProjection;\n let extent=s.w*max(length(vec3f(m[0].x,m[1].x,m[2].x)),length(vec3f(m[0].y,m[1].y,m[2].y)));\n if(clip.w<=0.0||abs(clip.x)>clip.w+extent||abs(clip.y)>clip.w+extent) { return .05; }\n return .05+min(1.0,extent*extent/max(clip.w*clip.w,1e-20));\n}\nfn causticDirection(origin:vec3f,rnd:vec3f,pdf:ptr<function,f32>,targetRadius:ptr<function,f32>)->vec3f {\n var total=0.0;\n for(var i=0u;i<caustic.casterCount;i++) {\n total+=(1.0-causticCosMax(causticSphere(i),origin))*causticTargetWeight(causticSphere(i));\n }\n var choice=rnd.z*total;\n var selected=causticSphere(0u);\n for(var i=0u;i<caustic.casterCount;i++) {\n let sphere=causticSphere(i);\n selected=sphere;\n choice-=(1.0-causticCosMax(sphere,origin))*causticTargetWeight(sphere);\n if(choice<=0.0) {\n break;\n }\n }\n *targetRadius=selected.w;\n let offset=selected.xyz-origin;\n let axis=normalize(select(vec3f(0.0,0.0,1.0),offset,dot(offset,offset)>1e-20));\n let cosine=mix(1.0,causticCosMax(selected,origin),rnd.x);\n let sine=sqrt(max(0.0,1.0-cosine*cosine));\n var t:vec3f;\n var b:vec3f;\n sampling_orthonormal_basis(axis,&t,&b);\n let direction=axis*cosine+(t*cos(TWOPI*rnd.y)+b*sin(TWOPI*rnd.y))*sine;\n // Evaluate the entire mixture, since intersecting bounding cones overlap.\n var overlaps=0.0;\n for(var i=0u;i<caustic.casterCount;i++) {\n let sphere=causticSphere(i);\n let c=causticCosMax(sphere,origin);\n if(c<0.0||dot(normalize(sphere.xyz-origin),direction)>=c) {\n overlaps+=causticTargetWeight(sphere);\n }\n }\n *pdf=overlaps/max(TWOPI*total,1e-20);\n return direction;\n}\n${\n mode === 'lightTracing'\n ? /* wgsl */ `${atomic}\n fn causticCameraConnect(si: SurfaceInteraction, material: OpenPbrResolvedMaterial, incoming: Ray, flux: vec3f, extinction: vec3f) {\n let eye=cameraUniform.viewInv[3].xyz;\n let forward=-normalize(cameraUniform.viewInv[2].xyz);\n let lensSample=causticEmission2D(causticDim(68u));\n let radius=cameraUniform.apertureRadius*sqrt(lensSample.x);\n var lens=eye+radius*(cos(TWOPI*lensSample.y)*cameraUniform.viewInv[0].xyz+sin(TWOPI*lensSample.y)*cameraUniform.viewInv[1].xyz);\n if(caustic.orthographic!=0u) {\n lens=si.position-forward*dot(si.position-eye,forward);\n }\n let toEye=lens-si.position;\n let distanceSquared=dot(toEye,toEye);\n let direction=normalize(toEye);\n let focalPoint=lens+(si.position-lens)*cameraUniform.focusDistance/max(dot(si.position-lens,forward),1e-12);\n let projected=select(focalPoint,si.position,caustic.orthographic!=0u);\n let clip=caustic.viewProjection*vec4f(projected,1.0);\n if(clip.w<=0.0||dot(si.position-eye,forward)<=0.0) {\n return;\n }\n let film=clip.xy/clip.w*0.5+0.5;\n if(any(film<vec2f(0.0))||any(film>=vec2f(1.0))) {\n return;\n }\n var shadow:Ray;\n initRay(&shadow,si.position+EPS*direction,direction);\n if(intersectSceneShadow(shadow,sqrt(distanceSquared)-2.0*EPS,si.triangleIndex,si.instanceIndex)) {\n return;\n }\n var m=material;\n let normal=select(-si.normal,si.normal,dot(si.normal,-incoming.d)>=0.0);\n m.inside=dot(si.faceNormal,-incoming.d)<0.0;\n m.relativeIor=select(1.0/m.specularIor,m.specularIor,m.inside);\n${\n frames\n ? /* wgsl */ ` m.directionTangent=si.directionTangent;m.directionBitangent=si.directionBitangent;m.geometryTangent=si.geometryTangent;m.geometryBitangent=si.geometryBitangent;`\n : ''\n}\n${\n coat\n ? /* wgsl */ ` m.coatNormal=normal;m.geometricNormal=select(-si.faceNormal,si.faceNormal,dot(si.faceNormal,-incoming.d)>=0.0);`\n : ''\n}\n let prepared=openpbr_prepare(si.tangent,m,-incoming.d,normal);\n var pdf:f32;\n let response=openpbr_eval_prepared(prepared,direction,&pdf);\n let cameraCosine=dot(-direction,-normalize(cameraUniform.viewInv[2].xyz));\n if(cameraCosine<=0.0) {\n return;\n }\n let importance=caustic.cameraScale*rtUniform.outputSize.x*rtUniform.outputSize.y /\n select(max(distanceSquared*cameraCosine*cameraCosine*cameraCosine,1e-12),1.0,caustic.orthographic!=0u);\n let transmittance=exp(-extinction*sqrt(distanceSquared));\n let value=flux*transmittance*response*causticAdjoint(-incoming.d,direction,normal,si.faceNormal)*importance/f32(caustic.photonCount);\n let pixel=vec2u(film*rtUniform.outputSize);\n let index=(pixel.x+pixel.y*u32(rtUniform.outputSize.x))*3u;\n causticAtomicAdd(index,value.x);\n causticAtomicAdd(index+1u,value.y);\n causticAtomicAdd(index+2u,value.z);\n}\n`\n : ''\n}\n @compute @workgroup_size(64)\n fn main(@builtin(global_invocation_id) id:vec3u) {\n let photonIndex=id.x+id.y*4194240u;\n if(photonIndex>=caustic.photonCount) {\n return;\n }\n${mode === 'photonMapping' ? 'photonStore(photonIndex,vec4u(0u));' : ''}\n samplingPixel=vec2u(photonIndex%u32(rtUniform.outputSize.x),photonIndex/u32(rtUniform.outputSize.x));\n pixelSampleIndex=caustic.batch;\n samplingBounce=0u;\n hashedCounter=0u;\n rng_initialize(samplingPixel,i32(sampleSeed(caustic.batch,rtUniform.sequenceSeed)));\n var wavelength=0.0;\n var wavelengthPdf=1.0;\n if(SPECTRAL) {\n wavelength=spectrumSample(${SPECTRUM_PHOTON_DISTRIBUTION}u,causticEmission1D(causticDim(69u)),&wavelengthPdf);\n }\n var ls:LightSampleRec;\n causticSampleLight(caustic.casterSphere.xyz,vec3f(causticEmission2D(causticDim(60u)),causticEmission1D(causticDim(61u))),&ls);\n if(ls.pdf<=0.0) {\n return;\n }\n var infinite=ls.sourceType==0u;\n if(ls.sourceType==2u) {\n infinite=i32(analyticLights[ls.sourceIndex].litType)==DIRECTIONAL_LIGHT;\n }\n var origin=caustic.casterSphere.xyz+ls.direction*ls.dist;\n if(causticEmissionAreaPdf>0.0) { origin=causticEmissionOrigin; }\n var direction:vec3f;\n var flux:vec3f;\n var targetRadius=caustic.casterSphere.w;\n if(infinite) {\n direction=-ls.direction;\n // Mixture of projected caster disks. Every overlap contributes to the PDF.\n var area=0.0;\n for(var i=0u;i<caustic.casterCount;i++) {\n let s=causticSphere(i);\n area+=s.w*s.w*causticTargetWeight(s);\n }\n var choice=causticEmission1D(causticDim(63u))*area;\n var sphere=causticSphere(0u);\n for(var i=0u;i<caustic.casterCount;i++) {\n sphere=causticSphere(i);\n choice-=sphere.w*sphere.w*causticTargetWeight(sphere);\n if(choice<=0.0) {\n break;\n }\n }\n targetRadius=sphere.w;\n var t:vec3f;\n var b:vec3f;\n sampling_orthonormal_basis(direction,&t,&b);\n let rnd=causticEmission2D(causticDim(62u));\n origin=sphere.xyz-direction*dot(sphere.xyz-caustic.casterSphere.xyz,direction)-direction*caustic.casterSphere.w;\n origin+=sphere.w*sqrt(rnd.x)*(t*cos(TWOPI*rnd.y)+b*sin(TWOPI*rnd.y));\n var overlaps=0.0;\n for(var i=0u;i<caustic.casterCount;i++) {\n let s=causticSphere(i);\n let offset=origin-s.xyz;\n let projected=offset-direction*dot(offset,direction);\n if(dot(projected,projected)<=s.w*s.w) {\n overlaps+=causticTargetWeight(s);\n }\n }\n let diskPdf=overlaps/max(PI*area,1e-20);\n flux=ls.emission/(ls.pdf*diskPdf);\n }\n else if(ls.sourceType==2u&&i32(analyticLights[ls.sourceIndex].litType)==RECT_LIGHT&&ls.isDelta) {\n // A zero-spread rectangle is a collimated beam, matching PT's delta source.\n direction=ls.normal;\n flux=ls.emission/causticEmissionAreaPdf;\n }\n else {\n var areaPdf=ls.pdf*abs(dot(ls.normal,-ls.direction))/max(ls.dist*ls.dist,1e-20);\n if(causticEmissionAreaPdf>0.0) { areaPdf=causticEmissionAreaPdf; }\n if(ls.isDelta) {\n areaPdf=ls.selectionPmf;\n }\n if(areaPdf<=0.0) {\n return;\n }\n var directionPdf:f32;\n direction=causticDirection(origin,vec3f(causticEmission2D(causticDim(62u)),causticEmission1D(causticDim(63u))),&directionPdf,&targetRadius);\n var emission=ls.emission;\n var emissionCosine=max(0.0,dot(ls.normal,direction));\n if(ls.sourceType==2u) {\n let source=analyticLights[ls.sourceIndex];\n emission=source.emission;\n if(i32(source.litType)==SPOT_LIGHT) {\n emission*=evaluateSpotEmissionMultiplier(source,direction);\n }\n if(ls.isDelta) {\n emissionCosine=1.0;\n }\n if(i32(source.litType)==RECT_LIGHT&&emissionCosine<cos(0.5*PI*source.radiusOrSpread)) {\n return;\n }\n if(i32(source.litType)==RECT_LIGHT) {\n var textured:LightSampleRec;\n textured.emission=emission;\n light_apply_rect_texture(source,origin,source.position-0.5*(source.u+source.v),&textured);\n emission=textured.emission;\n }\n }\n flux=emission*emissionCosine/(areaPdf*directionPdf);\n }\n if(SPECTRAL) {\n flux*=spectrumRgb(wavelength)/(wavelengthPdf*${SPECTRUM_WAVELENGTH_RANGE}.0);\n }\n var ray:Ray;\n initRay(&ray,origin+EPS*direction,direction);\n var chain=0u;\n var singleT=false;\n var extinction=vec3f(0.0);\n var scattering=vec3f(0.0);\n var si:SurfaceInteraction;\n for(var depth=0;\n${mode === 'photonMapping' ? 'depth<=max(rtUniform.bounces,0)' : 'depth<20'}\n ;\n depth++) {\n samplingBounce=u32(depth);\n si.triangleIndex=-1;\n intersectScene(ray,&si,0,true,depth>0);\n if(!si.hit) {\n return;\n }\n var m:OpenPbrResolvedMaterial;\n getMaterial(&m,si.materialIndex,si.uv,si.uv1,si.instanceIndex,si.vertexColor);\n if(m.alpha<1.0&&causticEmission1D(causticDim(79u))>=m.alpha) {\n initRay(&ray,si.position+EPS*ray.d,ray.d);\n continue;\n }\n if(m.materialType==SHADOW_CATCHER) {\n return;\n }\n let segment=distance(ray.o,si.position);\n let channel=min(u32(sample1D(bounceDim(MEDIUM_CHANNEL))*3.0),2u);\n if(extinction[channel]>0.0&&any(scattering>vec3f(0.0))) {\n let sampled=-log(max(sample1D(bounceDim(MEDIUM_DISTANCE)),1e-7))/extinction[channel];\n // A volume event belongs to PT's R3. Surface-reaching photons use the\n // same RGB survival mixture as PT, retaining absorption and scattering.\n if(sampled<segment) {\n return;\n }\n }\n let transmittance=exp(-extinction*segment);\n let survival=(transmittance.x+transmittance.y+transmittance.z)/3.0;\n flux*=transmittance/select(1.0,max(survival,1e-20),any(scattering>vec3f(0.0)));\n if(SPECTRAL) {\n m.specularIor=sampling_cauchy_ior(spectrumIorWavelength(wavelength),m.specularIor,m.transmissionDispersionScale,m.dispersionAbbeNumber);\n }\n if(!isCausticSpecular(m)) {\n let mneeSource=ls.sourceType!=0u;\n let r1=mneeSource&&chain==1u&&singleT&&m.baseWeight*(1.0-m.baseMetalness)*(1.0-m.transmissionWeight)>0.0&&m.subsurfaceWeight==0.0;\n if(chain>0u\n${mode === 'lightTracing' ? '&&!(caustic.mnee!=0u&&r1)' : ''}){${\n mode === 'lightTracing'\n ? 'causticCameraConnect(si,m,ray,flux,extinction);'\n : /* wgsl */ `\n let position=bitcast<vec3u>(si.position);\n photonStore(photonIndex,vec4u(position,photonEncodeFlux(flux)));\n if((caustic.experiments&1u)!=0u) {\n var maximum=1e-8;\n for(var i=0u;i<caustic.casterCount;i++) { maximum=max(maximum,causticSphere(i).w); }\n let buckets=min(${PHOTON_GRID_SIZE}u,1u<<(32u-countLeadingZeros(max(256u,caustic.photonCount*2u)-1u)));\n let index=8u+buckets*4u+caustic.photonCount*3u+photonIndex;\n let width=textureDimensions(photonDirections).x;\n textureStore(photonDirections,vec2i(vec2u(index%width,index/width)),vec4u(bitcast<u32>(targetRadius/maximum)));\n }\n let incoming=vec2u(round(photonEncodeOct(-ray.d)*255.0));\n let normal=vec2u(round(photonEncodeOct(si.faceNormal)*vec2f(255.0,127.0)));\n photonStoreDirections(photonIndex,normal.x|(normal.y<<8u)|(select(0u,1u,r1)<<15u)|(incoming.x<<16u)|(incoming.y<<24u));\n`\n }\n}\nreturn;\n}\nlet view=-ray.d;\nlet normal=select(-si.normal,si.normal,dot(si.normal,view)>=0.0);\nm.inside=dot(si.faceNormal,view)<0.0;\nm.relativeIor=select(1.0/m.specularIor,m.specularIor,m.inside);\n${\n frames\n ? /* wgsl */ ` m.directionTangent=si.directionTangent;m.directionBitangent=si.directionBitangent;m.geometryTangent=si.geometryTangent;m.geometryBitangent=si.geometryBitangent;`\n : ''\n}\n${\n coat\n ? /* wgsl */ ` m.coatNormal=normal;m.geometricNormal=select(-si.faceNormal,si.faceNormal,dot(si.faceNormal,view)>=0.0);`\n : ''\n}\n let prepared=openpbr_prepare(si.tangent,m,view,normal);\n var next:vec3f;\n var pdf:f32;\n var transmission:bool;\n var roughness:f32;\n var specular:bool;\n var diffuse:bool;\n var lobe:u32;\n var marginal:f32;\n var full:vec3f;\n let response=openpbr_sample_prepared(prepared,vec3f(sample2D(bounceDim(BSDF_DIRECTION)),sample1D(bounceDim(BSDF_LOBE))),&next,&pdf,&transmission,&roughness,&specular,&diffuse,&lobe,&marginal,&full);\n if(!(pdf>0.0)) {\n return;\n }\n if(chain==0u&&ls.sourceType==0u&&transmission) {\n var reverse:Ray;\n initRay(&reverse,si.position,-ray.d);\n let bg=sampleSceneBackground(reverse);\n flux*=bg.rgb*bg.a/max(ls.emission,vec3f(1e-20));\n }\n flux*=response/pdf*causticAdjoint(view,next,normal,si.faceNormal);\n // The shared BSDF is a radiance sampler. Cancel its eta_i/eta_t squared\n // factor for adjoint (light-to-camera) propagation.\n if(transmission&&!m.thinWalled) {\n flux/=m.relativeIor*m.relativeIor;\n }\n // Match PT: a clear solid interface does not replace the active medium.\n if(transmission&&!m.thinWalled&&m.mediumType>0) {\n extinction=select(m.mediumExtinction,vec3f(0.0),m.inside);\n scattering=select(m.mediumScattering,vec3f(0.0),m.inside);\n }\n chain++;\n singleT=chain==1u&&((isCausticT(m)&&transmission)||(isCausticR(m)&&!transmission));\n initRay(&ray,si.position+EPS*next,next);\n}\n}\n`\n );\n}\n\nexport const causticAccumulateCompute = /* wgsl */ `\n @group(0) @binding(0) var<storage,read_write> splats:array<atomic<u32>>;\n @group(0) @binding(1) var<storage,read_write> accumulation:array<vec4f>;\n @compute @workgroup_size(64)\n fn main(@builtin(global_invocation_id) id:vec3u, @builtin(num_workgroups) groups:vec3u) {\n // Large images exceed one dispatch row; preserve the linear pixel order.\n let pixel=id.x+id.y*groups.x*64u;\n if(pixel>=arrayLength(&accumulation)) {\n return;\n }\n let i=pixel*3u;\n accumulation[pixel]+=vec4f(vec3f(bitcast<f32>(atomicLoad(&splats[i])),bitcast<f32>(atomicLoad(&splats[i+1u])),bitcast<f32>(atomicLoad(&splats[i+2u]))),1.0);\n }\n`;\n\n/** Native buffer resolve has the same independent batch normalization as web. */\nexport const causticBufferCompositeCompute = /* wgsl */ `\n @group(0)@binding(0)var<storage,read>pt:array<vec4f>;\n @group(0)@binding(1)var<storage,read>caustics:array<vec4f>;\n @group(0)@binding(2)var<storage,read>normal:array<vec4f>;\n @group(0)@binding(3)var<storage,read_write>output:array<vec4f>;\n @compute @workgroup_size(64)fn main(@builtin(global_invocation_id)id:vec3u,@builtin(num_workgroups)groups:vec3u) {\n // Large images exceed one dispatch row; preserve the linear pixel order.\n let pixel=id.x+id.y*groups.x*64u;\n if(pixel>=arrayLength(&output)) {\n return;\n }\n let c=caustics[pixel];\n let p=pt[pixel];\n output[pixel]=vec4f(p.rgb+c.rgb/max(c.a,1.0)*normal[pixel].a,p.a);\n }\n`;\n"]}
1
+ {"version":3,"file":"causticLight.compute.js","sourceRoot":"","sources":["../../../src/renderer/wgsl/causticLight.compute.ts"],"names":[],"mappings":"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","sourcesContent":["import {PHOTON_GRID_SIZE, photonPackingWgsl} from './photonMap.compute.js';\nimport {SPECTRUM_PHOTON_DISTRIBUTION, SPECTRUM_WAVELENGTH_RANGE, spectrumWgsl} from '../spectrum.js';\nimport background from './chunks/background.wgsl.js';\nimport atomic from './chunks/causticAtomic.wgsl.js';\nimport adjoint from './chunks/causticAdjoint.wgsl.js';\nimport common from './chunks/common.wgsl.js';\nimport core from './chunks/rayTraceCore.wgsl.js';\nimport {createMaterialBufferWgsl} from './chunks/materialBuffer.wgsl.js';\nimport intersect from './chunks/intersect.wgsl.js';\nimport rand from './chunks/rand.wgsl.js';\nimport random from './chunks/random.wgsl.js';\nimport env from './chunks/envMap.wgsl.js';\nimport sampling from './chunks/sampling.wgsl.js';\nimport {createOpenPbrBsdfWgsl} from './chunks/openPbrBsdf.wgsl.js';\nimport light from './chunks/sampleLight.wgsl.js';\nimport uv from './chunks/uvProjection.wgsl.js';\nimport {causticPartitionWGSL} from './chunks/caustics.wgsl.js';\nimport {wgpuShaderPreamble} from '../wgpuShaderSource.js';\nimport {WGPU_SCENE_FEATURE_BITS} from '../WGPUSceneFeatures.js';\n\nexport const CAUSTIC_UNIFORM_BYTES = 112;\nexport const causticUnitRandomWgsl = /* wgsl */ `\n fn causticUnitRandom(value:f32)->f32 {\n return clamp((value-EPS)/(1.0-2.0*EPS),0.0,0.99999994);\n }\n`;\n\n// Preserve the dormant R1 uniform slot and shader spelling for source parity.\n// Production packs zero; only an explicitly registered research adapter sets it.\nexport function causticLightCompute(\n constants: Record<string, boolean | number>,\n blasLayoutMask: number,\n hasDisplacement: boolean,\n mode: 'lightTracing' | 'photonMapping' = 'lightTracing',\n sceneIntersections = '',\n fullEmissionRandom = true,\n perInstanceBounds = false,\n compactVertices = false,\n independentTextures = false\n) {\n const frames = !!constants.USE_ANISOTROPY_DIRECTIONS || !!constants.USE_COAT_NORMAL_FRAMES,\n coat = !!constants.USE_COAT_NORMAL_FRAMES;\n return (\n (independentTextures ? 'enable wgpu_binding_array;\\n' : '') + wgpuShaderPreamble(constants, true) +\n /* wgsl */ `\n${Object.keys(WGPU_SCENE_FEATURE_BITS)\n .map((name) => `const ${name} = ${!!constants[name]};`)\n .join('\\n')}\n${common}\n${core({independentTextures, compactVertices, hasAnisotropyDirections: frames, hasCoatNormals: coat, caustics: true})}\n${uv}\n${createMaterialBufferWgsl({independentTextures, hasAnisotropyDirections: frames, hasCoatNormals: coat, caustics: true})}\n${intersect({\n perInstanceBounds,\n compactVertices,\n hasDisplacement,\n blasLayoutMask,\n hasAnisotropyDirections: frames,\n hasCoatNormals: coat,\n sceneIntersections\n})}\n${rand}\n${random}\n${env}\n${sampling}\n${spectrumWgsl}\n${createOpenPbrBsdfWgsl(frames, coat)}\n${light}\n${background.replace(\n /\\b(?:USE_BACKGROUND|USE_BACKGROUND_MAP|USE_GROUNDED_BACKGROUND)\\b/g,\n (name) =>\n `((rtUniform.backgroundFlags & ${\n {USE_BACKGROUND: 1, USE_BACKGROUND_MAP: 2, USE_GROUNDED_BACKGROUND: 4}[name]\n }) != 0)`\n)}\n${causticPartitionWGSL(false, false)}\n${adjoint}\n struct CausticLightUniform {\n casterSphere: vec4f,\n viewProjection: mat4x4f,\n photonCount: u32,\n batch: u32,\n mnee: u32,\n cameraScale: f32,\n casterCount: u32,\n choiceCount: u32,\n orthographic: u32,\n experiments: u32,\n }\n @group(3) @binding(0) var<uniform> caustic: CausticLightUniform;\n${\n mode === 'lightTracing'\n ? '@group(3) @binding(1) var<storage,read_write> splats: array<atomic<u32>>;'\n : `${photonPackingWgsl}\n @group(3)@binding(1)var photons:texture_storage_2d<rgba32uint,write>;\n @group(3)@binding(3)var photonDirections:texture_storage_2d<r32uint,write>;\n fn photonStore(i:u32,value:vec4u) {\n let width=textureDimensions(photons).x;\n textureStore(photons,vec2i(vec2u(i%width,i/width)),value);\n }\n fn photonStoreDirections(i:u32,value:u32) {\n let buckets=min(${PHOTON_GRID_SIZE}u,1u<<(32u-countLeadingZeros(max(256u,caustic.photonCount*2u)-1u)));\n let index=8u+buckets*4u+caustic.photonCount*2u+i;\n let width=textureDimensions(photonDirections).x;\n textureStore(photonDirections,vec2i(vec2u(index%width,index/width)),vec4u(value));\n }\n`\n}\n @group(3) @binding(2) var causticDistribution:texture_2d<f32>;\n // Geometric emission PDFs assume the full disk/cone/area support. The generic\n // BSDF sampler contracts randoms by EPS; undo that contraction for these warps.\n${causticUnitRandomWgsl}\nfn causticEmission1D(dim:u32)->f32{return ${\n fullEmissionRandom ? 'causticUnitRandom(sample1D(dim))' : 'sample1D(dim)'\n }\n ;\n}\nfn causticEmission2D(dim:u32)->vec2f {\n let value=sample2D(dim);\n return\n${fullEmissionRandom ? 'vec2f(causticUnitRandom(value.x),causticUnitRandom(value.y))' : 'value'}\n ;\n}\nfn causticLoad(i:u32)->vec4f {\n let width=textureDimensions(causticDistribution).x;\n return textureLoad(causticDistribution,vec2i(vec2u(i%width,i/width)),0);\n}\nfn causticSphere(i:u32)->vec4f {\n return causticLoad(i);\n}\n// Photon emission samples a source surface, independently of any receiver.\n// A NEE sample viewed from the aggregate caster center can reject an emitter\n// which still illuminates another caster, or sample only part of a sphere.\nvar<private> causticEmissionOrigin:vec3f;\nvar<private> causticEmissionAreaPdf:f32;\nfn causticAreaLight(origin:vec3f,position:vec3f,normal:vec3f,emission:vec3f,area:f32,pmf:f32,result:ptr<function,LightSampleRec>) {\n causticEmissionOrigin=position;\n causticEmissionAreaPdf=pmf/max(area,1e-20);\n let offset=position-origin;\n (*result).dist=length(offset);\n (*result).direction=offset/max((*result).dist,1e-20);\n (*result).normal=normal;\n (*result).emission=emission;\n (*result).pdf=pmf;\n}\nfn causticSampleLight(origin:vec3f,rnd:vec3f,result:ptr<function,LightSampleRec>) {\n causticEmissionAreaPdf=0.0;\n (*result).pdf=0.0;\n (*result).sourceType=0u;\n (*result).isDelta=false;\n if(caustic.choiceCount==0u) {\n return;\n }\n let scaled=rnd.z*f32(caustic.choiceCount);\n let column=min(u32(scaled),caustic.choiceCount-1u);\n let entry=causticLoad(caustic.casterCount+column*2u);\n let selected=select(bitcast<u32>(entry.y),column,fract(scaled)<entry.x);\n let choice=bitcast<vec4u>(causticLoad(caustic.casterCount+selected*2u));\n let pmf=causticLoad(caustic.casterCount+selected*2u+1u).x;\n (*result).sourceIndex=choice.w;\n if(choice.z==1u) {\n (*result).sourceType=0u;\n sampleEnvLight(rnd.xy,result);\n (*result).pdf*=pmf;\n }\n else if(choice.z==2u) {\n (*result).sourceType=2u;\n var light=analyticLights[choice.w];\n let kind=i32(light.litType);\n if(kind==RECT_LIGHT) {\n let position=light.position+light.u*(rnd.x-.5)+light.v*(rnd.y-.5);\n causticAreaLight(origin,position,normalize(cross(light.u,light.v)),light.emission,light.area,pmf,result);\n (*result).isDelta=light.radiusOrSpread<=.001;\n light_apply_rect_texture(light,position,light.position-.5*(light.u+light.v),result);\n }\n else if((kind==SPHERE_LIGHT||kind==SPOT_LIGHT)&&light.radiusOrSpread>0.0) {\n let z=1.0-2.0*rnd.x;\n let radial=sqrt(max(0.0,1.0-z*z));\n let normal=vec3f(radial*cos(TWOPI*rnd.y),radial*sin(TWOPI*rnd.y),z);\n causticAreaLight(origin,light.position+light.radiusOrSpread*normal,normal,light.emission,light.area,pmf,result);\n }\n else {\n light.selectionPmf=1.0;\n sampleAnalyticLight(light,origin,rnd.xy,result);\n (*result).pdf*=pmf;\n }\n }\n else {\n (*result).sourceType=1u;\n let triangle=getEmissiveTriangle(i32(choice.w));\n let u=sqrt(rnd.x);\n let bary=vec3f(1.0-u,u*(1.0-rnd.y),u*rnd.y);\n let position=bary.x*triangle.p0+bary.y*triangle.p1+bary.z*triangle.p2;\n let normal=normalize(cross(triangle.p1-triangle.p0,triangle.p2-triangle.p0));\n let uv=bary.x*triangle.uv0+bary.y*triangle.uv1+bary.z*triangle.uv2;\n let uv1=bary.x*triangle.secondaryUv0+bary.y*triangle.secondaryUv1+bary.z*triangle.secondaryUv2;\n causticAreaLight(origin,position,normal,getMaterialEmission(i32(triangle.materialIndex),uv,uv1),triangle.area,pmf,result);\n }\n (*result).selectionPmf=pmf;\n}\nfn causticCosMax(s:vec4f,origin:vec3f)->f32 {\n let distanceSquared=dot(s.xyz-origin,s.xyz-origin);\n return select(-1.0,sqrt(max(0.0,1.0-s.w*s.w/distanceSquared)),distanceSquared>s.w*s.w);\n}\n// A positive floor retains paths from offscreen or occluded casters. The\n// complete weighted mixture PDF below, including overlaps, compensates selection.\nfn causticTargetWeight(s:vec4f)->f32 {\n if((caustic.experiments&2u)==0u) { return 1.0; }\n let clip=caustic.viewProjection*vec4f(s.xyz,1.0);\n let m=caustic.viewProjection;\n let extent=s.w*max(length(vec3f(m[0].x,m[1].x,m[2].x)),length(vec3f(m[0].y,m[1].y,m[2].y)));\n if(clip.w<=0.0||abs(clip.x)>clip.w+extent||abs(clip.y)>clip.w+extent) { return .05; }\n return .05+min(1.0,extent*extent/max(clip.w*clip.w,1e-20));\n}\nfn causticDirection(origin:vec3f,rnd:vec3f,pdf:ptr<function,f32>,targetRadius:ptr<function,f32>)->vec3f {\n var total=0.0;\n for(var i=0u;i<caustic.casterCount;i++) {\n total+=(1.0-causticCosMax(causticSphere(i),origin))*causticTargetWeight(causticSphere(i));\n }\n var choice=rnd.z*total;\n var selected=causticSphere(0u);\n for(var i=0u;i<caustic.casterCount;i++) {\n let sphere=causticSphere(i);\n selected=sphere;\n choice-=(1.0-causticCosMax(sphere,origin))*causticTargetWeight(sphere);\n if(choice<=0.0) {\n break;\n }\n }\n *targetRadius=selected.w;\n let offset=selected.xyz-origin;\n let axis=normalize(select(vec3f(0.0,0.0,1.0),offset,dot(offset,offset)>1e-20));\n let cosine=mix(1.0,causticCosMax(selected,origin),rnd.x);\n let sine=sqrt(max(0.0,1.0-cosine*cosine));\n var t:vec3f;\n var b:vec3f;\n sampling_orthonormal_basis(axis,&t,&b);\n let direction=axis*cosine+(t*cos(TWOPI*rnd.y)+b*sin(TWOPI*rnd.y))*sine;\n // Evaluate the entire mixture, since intersecting bounding cones overlap.\n var overlaps=0.0;\n for(var i=0u;i<caustic.casterCount;i++) {\n let sphere=causticSphere(i);\n let c=causticCosMax(sphere,origin);\n if(c<0.0||dot(normalize(sphere.xyz-origin),direction)>=c) {\n overlaps+=causticTargetWeight(sphere);\n }\n }\n *pdf=overlaps/max(TWOPI*total,1e-20);\n return direction;\n}\n${\n mode === 'lightTracing'\n ? /* wgsl */ `${atomic}\n fn causticCameraConnect(si: SurfaceInteraction, material: OpenPbrResolvedMaterial, incoming: Ray, flux: vec3f, extinction: vec3f) {\n let eye=cameraUniform.viewInv[3].xyz;\n let forward=-normalize(cameraUniform.viewInv[2].xyz);\n let lensSample=causticEmission2D(causticDim(68u));\n let radius=cameraUniform.apertureRadius*sqrt(lensSample.x);\n var lens=eye+radius*(cos(TWOPI*lensSample.y)*cameraUniform.viewInv[0].xyz+sin(TWOPI*lensSample.y)*cameraUniform.viewInv[1].xyz);\n if(caustic.orthographic!=0u) {\n lens=si.position-forward*dot(si.position-eye,forward);\n }\n let toEye=lens-si.position;\n let distanceSquared=dot(toEye,toEye);\n let direction=normalize(toEye);\n let focalPoint=lens+(si.position-lens)*cameraUniform.focusDistance/max(dot(si.position-lens,forward),1e-12);\n let projected=select(focalPoint,si.position,caustic.orthographic!=0u);\n let clip=caustic.viewProjection*vec4f(projected,1.0);\n if(clip.w<=0.0||dot(si.position-eye,forward)<=0.0) {\n return;\n }\n let film=clip.xy/clip.w*0.5+0.5;\n if(any(film<vec2f(0.0))||any(film>=vec2f(1.0))) {\n return;\n }\n var shadow:Ray;\n initRay(&shadow,si.position+EPS*direction,direction);\n if(intersectSceneShadow(shadow,sqrt(distanceSquared)-2.0*EPS,si.triangleIndex,si.instanceIndex)) {\n return;\n }\n var m=material;\n let normal=select(-si.normal,si.normal,dot(si.normal,-incoming.d)>=0.0);\n m.inside=dot(si.faceNormal,-incoming.d)<0.0;\n m.relativeIor=select(1.0/m.specularIor,m.specularIor,m.inside);\n${\n frames\n ? /* wgsl */ ` m.directionTangent=si.directionTangent;m.directionBitangent=si.directionBitangent;m.geometryTangent=si.geometryTangent;m.geometryBitangent=si.geometryBitangent;`\n : ''\n}\n${\n coat\n ? /* wgsl */ ` m.coatNormal=normal;m.geometricNormal=select(-si.faceNormal,si.faceNormal,dot(si.faceNormal,-incoming.d)>=0.0);`\n : ''\n}\n let prepared=openpbr_prepare(si.tangent,m,-incoming.d,normal);\n var pdf:f32;\n let response=openpbr_eval_prepared(prepared,direction,&pdf);\n let cameraCosine=dot(-direction,-normalize(cameraUniform.viewInv[2].xyz));\n if(cameraCosine<=0.0) {\n return;\n }\n let importance=caustic.cameraScale*rtUniform.outputSize.x*rtUniform.outputSize.y /\n select(max(distanceSquared*cameraCosine*cameraCosine*cameraCosine,1e-12),1.0,caustic.orthographic!=0u);\n let transmittance=exp(-extinction*sqrt(distanceSquared));\n let value=flux*transmittance*response*causticAdjoint(-incoming.d,direction,normal,si.faceNormal)*importance/f32(caustic.photonCount);\n let pixel=vec2u(film*rtUniform.outputSize);\n let index=(pixel.x+pixel.y*u32(rtUniform.outputSize.x))*3u;\n causticAtomicAdd(index,value.x);\n causticAtomicAdd(index+1u,value.y);\n causticAtomicAdd(index+2u,value.z);\n}\n`\n : ''\n}\n @compute @workgroup_size(64)\n fn main(@builtin(global_invocation_id) id:vec3u) {\n let photonIndex=id.x+id.y*4194240u;\n if(photonIndex>=caustic.photonCount) {\n return;\n }\n${mode === 'photonMapping' ? 'photonStore(photonIndex,vec4u(0u));' : ''}\n samplingPixel=vec2u(photonIndex%u32(rtUniform.outputSize.x),photonIndex/u32(rtUniform.outputSize.x));\n pixelSampleIndex=caustic.batch;\n samplingBounce=0u;\n hashedCounter=0u;\n rng_initialize(samplingPixel,i32(sampleSeed(caustic.batch,rtUniform.sequenceSeed)));\n var wavelength=0.0;\n var wavelengthPdf=1.0;\n if(SPECTRAL) {\n wavelength=spectrumSample(${SPECTRUM_PHOTON_DISTRIBUTION}u,causticEmission1D(causticDim(69u)),&wavelengthPdf);\n }\n var ls:LightSampleRec;\n causticSampleLight(caustic.casterSphere.xyz,vec3f(causticEmission2D(causticDim(60u)),causticEmission1D(causticDim(61u))),&ls);\n if(ls.pdf<=0.0) {\n return;\n }\n var infinite=ls.sourceType==0u;\n if(ls.sourceType==2u) {\n infinite=i32(analyticLights[ls.sourceIndex].litType)==DIRECTIONAL_LIGHT;\n }\n var origin=caustic.casterSphere.xyz+ls.direction*ls.dist;\n if(causticEmissionAreaPdf>0.0) { origin=causticEmissionOrigin; }\n var direction:vec3f;\n var flux:vec3f;\n var targetRadius=caustic.casterSphere.w;\n if(infinite) {\n direction=-ls.direction;\n // Mixture of projected caster disks. Every overlap contributes to the PDF.\n var area=0.0;\n for(var i=0u;i<caustic.casterCount;i++) {\n let s=causticSphere(i);\n area+=s.w*s.w*causticTargetWeight(s);\n }\n var choice=causticEmission1D(causticDim(63u))*area;\n var sphere=causticSphere(0u);\n for(var i=0u;i<caustic.casterCount;i++) {\n sphere=causticSphere(i);\n choice-=sphere.w*sphere.w*causticTargetWeight(sphere);\n if(choice<=0.0) {\n break;\n }\n }\n targetRadius=sphere.w;\n var t:vec3f;\n var b:vec3f;\n sampling_orthonormal_basis(direction,&t,&b);\n let rnd=causticEmission2D(causticDim(62u));\n origin=sphere.xyz-direction*dot(sphere.xyz-caustic.casterSphere.xyz,direction)-direction*caustic.casterSphere.w;\n origin+=sphere.w*sqrt(rnd.x)*(t*cos(TWOPI*rnd.y)+b*sin(TWOPI*rnd.y));\n var overlaps=0.0;\n for(var i=0u;i<caustic.casterCount;i++) {\n let s=causticSphere(i);\n let offset=origin-s.xyz;\n let projected=offset-direction*dot(offset,direction);\n if(dot(projected,projected)<=s.w*s.w) {\n overlaps+=causticTargetWeight(s);\n }\n }\n let diskPdf=overlaps/max(PI*area,1e-20);\n flux=ls.emission/(ls.pdf*diskPdf);\n }\n else if(ls.sourceType==2u&&i32(analyticLights[ls.sourceIndex].litType)==RECT_LIGHT&&ls.isDelta) {\n // A zero-spread rectangle is a collimated beam, matching PT's delta source.\n direction=ls.normal;\n flux=ls.emission/causticEmissionAreaPdf;\n }\n else {\n var areaPdf=ls.pdf*abs(dot(ls.normal,-ls.direction))/max(ls.dist*ls.dist,1e-20);\n if(causticEmissionAreaPdf>0.0) { areaPdf=causticEmissionAreaPdf; }\n if(ls.isDelta) {\n areaPdf=ls.selectionPmf;\n }\n if(areaPdf<=0.0) {\n return;\n }\n var directionPdf:f32;\n direction=causticDirection(origin,vec3f(causticEmission2D(causticDim(62u)),causticEmission1D(causticDim(63u))),&directionPdf,&targetRadius);\n var emission=ls.emission;\n var emissionCosine=max(0.0,dot(ls.normal,direction));\n if(ls.sourceType==2u) {\n let source=analyticLights[ls.sourceIndex];\n emission=source.emission;\n if(i32(source.litType)==SPOT_LIGHT) {\n emission*=evaluateSpotEmissionMultiplier(source,direction);\n }\n if(ls.isDelta) {\n emissionCosine=1.0;\n }\n if(i32(source.litType)==RECT_LIGHT&&emissionCosine<cos(0.5*PI*source.radiusOrSpread)) {\n return;\n }\n if(i32(source.litType)==RECT_LIGHT) {\n var textured:LightSampleRec;\n textured.emission=emission;\n light_apply_rect_texture(source,origin,source.position-0.5*(source.u+source.v),&textured);\n emission=textured.emission;\n }\n }\n flux=emission*emissionCosine/(areaPdf*directionPdf);\n }\n if(SPECTRAL) {\n flux*=spectrumRgb(wavelength)/(wavelengthPdf*${SPECTRUM_WAVELENGTH_RANGE}.0);\n }\n var ray:Ray;\n initRay(&ray,origin+EPS*direction,direction);\n var chain=0u;\n var singleT=false;\n var extinction=vec3f(0.0);\n var scattering=vec3f(0.0);\n var si:SurfaceInteraction;\n for(var depth=0;\n${mode === 'photonMapping' ? 'depth<=max(rtUniform.bounces,0)' : 'depth<20'}\n ;\n depth++) {\n samplingBounce=u32(depth);\n si.triangleIndex=-1;\n intersectScene(ray,&si,0,true,depth>0);\n if(!si.hit) {\n return;\n }\n var m:OpenPbrResolvedMaterial;\n getMaterial(&m,si.materialIndex,si.uv,si.uv1,si.instanceIndex,si.vertexColor);\n if(m.alpha<1.0&&causticEmission1D(causticDim(79u))>=m.alpha) {\n initRay(&ray,si.position+EPS*ray.d,ray.d);\n continue;\n }\n if(m.materialType==SHADOW_CATCHER) {\n return;\n }\n let segment=distance(ray.o,si.position);\n let channel=min(u32(sample1D(bounceDim(MEDIUM_CHANNEL))*3.0),2u);\n if(extinction[channel]>0.0&&any(scattering>vec3f(0.0))) {\n let sampled=-log(max(sample1D(bounceDim(MEDIUM_DISTANCE)),1e-7))/extinction[channel];\n // A volume event belongs to PT's R3. Surface-reaching photons use the\n // same RGB survival mixture as PT, retaining absorption and scattering.\n if(sampled<segment) {\n return;\n }\n }\n let transmittance=exp(-extinction*segment);\n let survival=(transmittance.x+transmittance.y+transmittance.z)/3.0;\n flux*=transmittance/select(1.0,max(survival,1e-20),any(scattering>vec3f(0.0)));\n if(SPECTRAL) {\n m.specularIor=sampling_cauchy_ior(spectrumIorWavelength(wavelength),m.specularIor,m.transmissionDispersionScale,m.dispersionAbbeNumber);\n }\n if(!isCausticSpecular(m)) {\n let mneeSource=ls.sourceType!=0u;\n let r1=mneeSource&&chain==1u&&singleT&&m.baseWeight*(1.0-m.baseMetalness)*(1.0-m.transmissionWeight)>0.0&&m.subsurfaceWeight==0.0;\n if(chain>0u\n${mode === 'lightTracing' ? '&&!(caustic.mnee!=0u&&r1)' : ''}){${\n mode === 'lightTracing'\n ? 'causticCameraConnect(si,m,ray,flux,extinction);'\n : /* wgsl */ `\n let position=bitcast<vec3u>(si.position);\n photonStore(photonIndex,vec4u(position,photonEncodeFlux(flux)));\n if((caustic.experiments&1u)!=0u) {\n var maximum=1e-8;\n for(var i=0u;i<caustic.casterCount;i++) { maximum=max(maximum,causticSphere(i).w); }\n let buckets=min(${PHOTON_GRID_SIZE}u,1u<<(32u-countLeadingZeros(max(256u,caustic.photonCount*2u)-1u)));\n let index=8u+buckets*4u+caustic.photonCount*3u+photonIndex;\n let width=textureDimensions(photonDirections).x;\n textureStore(photonDirections,vec2i(vec2u(index%width,index/width)),vec4u(bitcast<u32>(targetRadius/maximum)));\n }\n let incoming=vec2u(round(photonEncodeOct(-ray.d)*255.0));\n let normal=vec2u(round(photonEncodeOct(si.faceNormal)*vec2f(255.0,127.0)));\n photonStoreDirections(photonIndex,normal.x|(normal.y<<8u)|(select(0u,1u,r1)<<15u)|(incoming.x<<16u)|(incoming.y<<24u));\n`\n }\n}\nreturn;\n}\nlet view=-ray.d;\nlet normal=select(-si.normal,si.normal,dot(si.normal,view)>=0.0);\nm.inside=dot(si.faceNormal,view)<0.0;\nm.relativeIor=select(1.0/m.specularIor,m.specularIor,m.inside);\n${\n frames\n ? /* wgsl */ ` m.directionTangent=si.directionTangent;m.directionBitangent=si.directionBitangent;m.geometryTangent=si.geometryTangent;m.geometryBitangent=si.geometryBitangent;`\n : ''\n}\n${\n coat\n ? /* wgsl */ ` m.coatNormal=normal;m.geometricNormal=select(-si.faceNormal,si.faceNormal,dot(si.faceNormal,view)>=0.0);`\n : ''\n}\n let prepared=openpbr_prepare(si.tangent,m,view,normal);\n var next:vec3f;\n var pdf:f32;\n var transmission:bool;\n var roughness:f32;\n var specular:bool;\n var diffuse:bool;\n var lobe:u32;\n var marginal:f32;\n var full:vec3f;\n let response=openpbr_sample_prepared(prepared,vec3f(sample2D(bounceDim(BSDF_DIRECTION)),sample1D(bounceDim(BSDF_LOBE))),&next,&pdf,&transmission,&roughness,&specular,&diffuse,&lobe,&marginal,&full);\n if(!(pdf>0.0)) {\n return;\n }\n if(chain==0u&&ls.sourceType==0u&&transmission) {\n var reverse:Ray;\n initRay(&reverse,si.position,-ray.d);\n let bg=sampleSceneBackground(reverse);\n flux*=bg.rgb*bg.a/max(ls.emission,vec3f(1e-20));\n }\n flux*=response/pdf*causticAdjoint(view,next,normal,si.faceNormal);\n // The shared BSDF is a radiance sampler. Cancel its eta_i/eta_t squared\n // factor for adjoint (light-to-camera) propagation.\n if(transmission&&!m.thinWalled) {\n flux/=m.relativeIor*m.relativeIor;\n }\n // Match PT: a clear solid interface does not replace the active medium.\n if(transmission&&!m.thinWalled&&m.mediumType>0) {\n extinction=select(m.mediumExtinction,vec3f(0.0),m.inside);\n scattering=select(m.mediumScattering,vec3f(0.0),m.inside);\n }\n chain++;\n singleT=chain==1u&&((isCausticT(m)&&transmission)||(isCausticR(m)&&!transmission));\n initRay(&ray,si.position+EPS*next,next);\n}\n}\n`\n );\n}\n\nexport const causticAccumulateCompute = /* wgsl */ `\n @group(0) @binding(0) var<storage,read_write> splats:array<atomic<u32>>;\n @group(0) @binding(1) var<storage,read_write> accumulation:array<vec4f>;\n @compute @workgroup_size(64)\n fn main(@builtin(global_invocation_id) id:vec3u, @builtin(num_workgroups) groups:vec3u) {\n // Large images exceed one dispatch row; preserve the linear pixel order.\n let pixel=id.x+id.y*groups.x*64u;\n if(pixel>=arrayLength(&accumulation)) {\n return;\n }\n let i=pixel*3u;\n accumulation[pixel]+=vec4f(vec3f(bitcast<f32>(atomicLoad(&splats[i])),bitcast<f32>(atomicLoad(&splats[i+1u])),bitcast<f32>(atomicLoad(&splats[i+2u]))),1.0);\n }\n`;\n\n/** Native buffer resolve has the same independent batch normalization as web. */\nexport const causticBufferCompositeCompute = /* wgsl */ `\n @group(0)@binding(0)var<storage,read>pt:array<vec4f>;\n @group(0)@binding(1)var<storage,read>caustics:array<vec4f>;\n @group(0)@binding(2)var<storage,read>normal:array<vec4f>;\n @group(0)@binding(3)var<storage,read_write>output:array<vec4f>;\n @compute @workgroup_size(64)fn main(@builtin(global_invocation_id)id:vec3u,@builtin(num_workgroups)groups:vec3u) {\n // Large images exceed one dispatch row; preserve the linear pixel order.\n let pixel=id.x+id.y*groups.x*64u;\n if(pixel>=arrayLength(&output)) {\n return;\n }\n let c=caustics[pixel];\n let p=pt[pixel];\n output[pixel]=vec4f(p.rgb+c.rgb/max(c.a,1.0)*normal[pixel].a,p.a);\n }\n`;\n"]}