@meimu/mutracer 0.1.5 → 0.3.0

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 (590) hide show
  1. package/LICENSES/Classroom-CC0.txt +121 -0
  2. package/README.md +10 -7
  3. package/THIRD_PARTY_NOTICES.md +13 -8
  4. package/dist/assets/bvh.worker.js +1 -1
  5. package/dist/mutracer.umd.js +12316 -9228
  6. package/docs/api.md +232 -21
  7. package/docs/caustics.md +80 -0
  8. package/docs/denoising.md +30 -15
  9. package/docs/materials.md +6 -5
  10. package/docs/performance.md +5 -2
  11. package/docs/progressive-rendering.md +73 -1
  12. package/docs/technical/continuous-spectrum.md +93 -0
  13. package/docs/threejs.md +14 -5
  14. package/docs/updates.md +46 -2
  15. package/docs/viewer-checklist.md +65 -0
  16. package/docs/viewer.md +15 -8
  17. package/lib/Display.d.ts +13 -2
  18. package/lib/Display.js +28 -7
  19. package/lib/Display.js.map +1 -1
  20. package/lib/Engine.d.ts +4 -0
  21. package/lib/Engine.js +99 -37
  22. package/lib/Engine.js.map +1 -1
  23. package/lib/Render.d.ts +98 -15
  24. package/lib/Render.js +275 -27
  25. package/lib/Render.js.map +1 -1
  26. package/lib/RenderLoop.d.ts +11 -1
  27. package/lib/RenderLoop.js +120 -15
  28. package/lib/RenderLoop.js.map +1 -1
  29. package/lib/main.d.ts +6 -3
  30. package/lib/main.js +1 -0
  31. package/lib/main.js.map +1 -1
  32. package/lib/math/access.d.ts +4 -0
  33. package/lib/math/access.js +10 -0
  34. package/lib/math/access.js.map +1 -0
  35. package/lib/math/mat3.d.ts +4 -0
  36. package/lib/math/mat3.js +54 -0
  37. package/lib/math/mat3.js.map +1 -0
  38. package/lib/math/mat4.d.ts +19 -0
  39. package/lib/math/mat4.js +413 -0
  40. package/lib/math/mat4.js.map +1 -0
  41. package/lib/math/storage.d.ts +6 -0
  42. package/lib/math/storage.js +22 -0
  43. package/lib/math/storage.js.map +1 -0
  44. package/lib/math/types.d.ts +3 -0
  45. package/lib/math/types.js +2 -0
  46. package/lib/math/types.js.map +1 -0
  47. package/lib/math/vec2.d.ts +13 -0
  48. package/lib/math/vec2.js +52 -0
  49. package/lib/math/vec2.js.map +1 -0
  50. package/lib/math/vec3.d.ts +23 -0
  51. package/lib/math/vec3.js +139 -0
  52. package/lib/math/vec3.js.map +1 -0
  53. package/lib/math.d.ts +5 -17
  54. package/lib/math.js +5 -55
  55. package/lib/math.js.map +1 -1
  56. package/lib/oidn.d.ts +1 -1
  57. package/lib/oidn.js +4 -2
  58. package/lib/oidn.js.map +1 -1
  59. package/lib/oidnBundledWeights.cdn.d.ts +2 -2
  60. package/lib/renderer/AovPrimitiveCache.d.ts +10 -0
  61. package/lib/renderer/AovPrimitiveCache.js +48 -0
  62. package/lib/renderer/AovPrimitiveCache.js.map +1 -0
  63. package/lib/renderer/Box3.d.ts +5 -0
  64. package/lib/renderer/Box3.js +9 -0
  65. package/lib/renderer/Box3.js.map +1 -0
  66. package/lib/renderer/GPUTraceQueue.d.ts +11 -0
  67. package/lib/renderer/GPUTraceQueue.js +33 -0
  68. package/lib/renderer/GPUTraceQueue.js.map +1 -0
  69. package/lib/renderer/MaterialData.d.ts +15 -0
  70. package/lib/renderer/MaterialData.js +61 -0
  71. package/lib/renderer/MaterialData.js.map +1 -0
  72. package/lib/renderer/MaterialTable.d.ts +11 -98
  73. package/lib/renderer/MaterialTable.js +34 -412
  74. package/lib/renderer/MaterialTable.js.map +1 -1
  75. package/lib/renderer/RenderingPipeline.d.ts +36 -13
  76. package/lib/renderer/RenderingPipeline.js +1 -17
  77. package/lib/renderer/RenderingPipeline.js.map +1 -1
  78. package/lib/renderer/TileRender.d.ts +0 -1
  79. package/lib/renderer/TileRender.js +14 -1
  80. package/lib/renderer/TileRender.js.map +1 -1
  81. package/lib/renderer/WGPUAov.d.ts +39 -0
  82. package/lib/renderer/WGPUAov.js +239 -0
  83. package/lib/renderer/WGPUAov.js.map +1 -0
  84. package/lib/renderer/WGPUBindlessMaterialData.d.ts +17 -0
  85. package/lib/renderer/WGPUBindlessMaterialData.js +48 -0
  86. package/lib/renderer/WGPUBindlessMaterialData.js.map +1 -0
  87. package/lib/renderer/WGPUBindlessTextures.d.ts +47 -0
  88. package/lib/renderer/WGPUBindlessTextures.js +180 -0
  89. package/lib/renderer/WGPUBindlessTextures.js.map +1 -0
  90. package/lib/renderer/WGPUBloomPass.js +17 -97
  91. package/lib/renderer/WGPUBloomPass.js.map +1 -1
  92. package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
  93. package/lib/renderer/WGPUCausticLightPass.js +217 -0
  94. package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
  95. package/lib/renderer/WGPUComputePass.d.ts +1 -1
  96. package/lib/renderer/WGPUComputePass.js.map +1 -1
  97. package/lib/renderer/WGPUEnvMapDistribution.d.ts +12 -0
  98. package/lib/renderer/WGPUEnvMapDistribution.js +150 -0
  99. package/lib/renderer/WGPUEnvMapDistribution.js.map +1 -0
  100. package/lib/renderer/WGPUFullQuadPass.js +10 -9
  101. package/lib/renderer/WGPUFullQuadPass.js.map +1 -1
  102. package/lib/renderer/WGPUJNLMDenoiser.d.ts +1 -20
  103. package/lib/renderer/WGPUJNLMDenoiser.js +4 -267
  104. package/lib/renderer/WGPUJNLMDenoiser.js.map +1 -1
  105. package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
  106. package/lib/renderer/WGPUPhotonMap.js +191 -0
  107. package/lib/renderer/WGPUPhotonMap.js.map +1 -0
  108. package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
  109. package/lib/renderer/WGPURayTraceOutput.js +132 -29
  110. package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
  111. package/lib/renderer/WGPURayTracePass.d.ts +83 -56
  112. package/lib/renderer/WGPURayTracePass.js +884 -837
  113. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  114. package/lib/renderer/WGPURenderingPipeline.d.ts +5 -3
  115. package/lib/renderer/WGPURenderingPipeline.js +297 -82
  116. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  117. package/lib/renderer/WGPUShaderCache.d.ts +2 -2
  118. package/lib/renderer/WGPUShaderCache.js +10 -5
  119. package/lib/renderer/WGPUShaderCache.js.map +1 -1
  120. package/lib/renderer/WGPUShaderCapabilities.d.ts +10 -0
  121. package/lib/renderer/WGPUShaderCapabilities.js +54 -0
  122. package/lib/renderer/WGPUShaderCapabilities.js.map +1 -0
  123. package/lib/renderer/WGPUSkyBaker.d.ts +13 -0
  124. package/lib/renderer/WGPUSkyBaker.js +85 -0
  125. package/lib/renderer/WGPUSkyBaker.js.map +1 -0
  126. package/lib/renderer/WebGLAov.d.ts +41 -0
  127. package/lib/renderer/WebGLAov.js +106 -0
  128. package/lib/renderer/WebGLAov.js.map +1 -0
  129. package/lib/renderer/WebGLFullQuadPass.d.ts +7 -4
  130. package/lib/renderer/WebGLFullQuadPass.js +22 -2
  131. package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
  132. package/lib/renderer/WebGLJNLMDenoiser.d.ts +1 -1
  133. package/lib/renderer/WebGLJNLMDenoiser.js +83 -74
  134. package/lib/renderer/WebGLJNLMDenoiser.js.map +1 -1
  135. package/lib/renderer/WebGLRayTraceOutputPass.js +7 -4
  136. package/lib/renderer/WebGLRayTraceOutputPass.js.map +1 -1
  137. package/lib/renderer/WebGLRayTracePass.d.ts +18 -3
  138. package/lib/renderer/WebGLRayTracePass.js +121 -9
  139. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  140. package/lib/renderer/WebGLRenderingPipeline.js +126 -22
  141. package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
  142. package/lib/renderer/WebGLSkyBaker.d.ts +39 -0
  143. package/lib/renderer/WebGLSkyBaker.js +422 -0
  144. package/lib/renderer/WebGLSkyBaker.js.map +1 -0
  145. package/lib/renderer/WebGLSkySource.d.ts +9 -0
  146. package/lib/renderer/WebGLSkySource.js +28 -0
  147. package/lib/renderer/WebGLSkySource.js.map +1 -0
  148. package/lib/renderer/accumulationTarget.d.ts +6 -0
  149. package/lib/renderer/accumulationTarget.js.map +1 -1
  150. package/lib/renderer/aov.d.ts +42 -0
  151. package/lib/renderer/aov.js +50 -0
  152. package/lib/renderer/aov.js.map +1 -0
  153. package/lib/renderer/aovDisplay.d.ts +14 -0
  154. package/lib/renderer/aovDisplay.js +32 -0
  155. package/lib/renderer/aovDisplay.js.map +1 -0
  156. package/lib/renderer/aovScene.d.ts +21 -0
  157. package/lib/renderer/aovScene.js +139 -0
  158. package/lib/renderer/aovScene.js.map +1 -0
  159. package/lib/renderer/binnedSahBvh.js +2 -1
  160. package/lib/renderer/binnedSahBvh.js.map +1 -1
  161. package/lib/renderer/bvhAccel.d.ts +20 -10
  162. package/lib/renderer/bvhAccel.js +168 -58
  163. package/lib/renderer/bvhAccel.js.map +1 -1
  164. package/lib/renderer/bvhWorkerBuild.d.ts +1 -0
  165. package/lib/renderer/cameraSnapshot.js +2 -1
  166. package/lib/renderer/cameraSnapshot.js.map +1 -1
  167. package/lib/renderer/cameraUniform.d.ts +2 -0
  168. package/lib/renderer/cameraUniform.js +12 -0
  169. package/lib/renderer/cameraUniform.js.map +1 -0
  170. package/lib/renderer/causticDiagnostics.d.ts +31 -0
  171. package/lib/renderer/causticDiagnostics.js +5 -0
  172. package/lib/renderer/causticDiagnostics.js.map +1 -0
  173. package/lib/renderer/causticEmission.d.ts +10 -0
  174. package/lib/renderer/causticEmission.js +40 -0
  175. package/lib/renderer/causticEmission.js.map +1 -0
  176. package/lib/renderer/causticUniform.d.ts +12 -0
  177. package/lib/renderer/causticUniform.js +22 -0
  178. package/lib/renderer/causticUniform.js.map +1 -0
  179. package/lib/renderer/caustics.d.ts +16 -0
  180. package/lib/renderer/caustics.js +93 -0
  181. package/lib/renderer/caustics.js.map +1 -0
  182. package/lib/renderer/committedScene.js +4 -0
  183. package/lib/renderer/committedScene.js.map +1 -1
  184. package/lib/renderer/denoiser.d.ts +3 -11
  185. package/lib/renderer/denoiser.js.map +1 -1
  186. package/lib/renderer/depthQuery.js +4 -1
  187. package/lib/renderer/depthQuery.js.map +1 -1
  188. package/lib/renderer/displacementPreparation.d.ts +11 -0
  189. package/lib/renderer/displacementPreparation.js +351 -0
  190. package/lib/renderer/displacementPreparation.js.map +1 -0
  191. package/lib/renderer/displacementRange.d.ts +1 -0
  192. package/lib/renderer/displacementRange.js +9 -0
  193. package/lib/renderer/displacementRange.js.map +1 -0
  194. package/lib/renderer/displayParityDiagnostics.d.ts +11 -0
  195. package/lib/renderer/displayParityDiagnostics.js +2 -0
  196. package/lib/renderer/displayParityDiagnostics.js.map +1 -0
  197. package/lib/renderer/envMapCreation.d.ts +9 -8
  198. package/lib/renderer/envMapCreation.js +36 -12
  199. package/lib/renderer/envMapCreation.js.map +1 -1
  200. package/lib/renderer/envMapDistribution.d.ts +12 -2
  201. package/lib/renderer/envMapDistribution.js +43 -34
  202. package/lib/renderer/envMapDistribution.js.map +1 -1
  203. package/lib/renderer/geometryBvhBounds.d.ts +4 -0
  204. package/lib/renderer/geometryBvhBounds.js +82 -0
  205. package/lib/renderer/geometryBvhBounds.js.map +1 -0
  206. package/lib/renderer/geometryUpdateState.d.ts +32 -0
  207. package/lib/renderer/geometryUpdateState.js +2 -0
  208. package/lib/renderer/geometryUpdateState.js.map +1 -0
  209. package/lib/renderer/geometryUpdates.d.ts +17 -9
  210. package/lib/renderer/geometryUpdates.js +92 -33
  211. package/lib/renderer/geometryUpdates.js.map +1 -1
  212. package/lib/renderer/glsl/aovDisplay.frag.d.ts +3 -0
  213. package/lib/renderer/glsl/aovDisplay.frag.js +54 -0
  214. package/lib/renderer/glsl/aovDisplay.frag.js.map +1 -0
  215. package/lib/renderer/glsl/aovReflectance.glsl.d.ts +2 -0
  216. package/lib/renderer/glsl/aovReflectance.glsl.js +87 -0
  217. package/lib/renderer/glsl/aovReflectance.glsl.js.map +1 -0
  218. package/lib/renderer/glsl/aovReflectanceSamples.glsl.d.ts +2 -0
  219. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +135 -0
  220. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js.map +1 -0
  221. package/lib/renderer/glsl/aovTrace.frag.d.ts +2 -0
  222. package/lib/renderer/glsl/aovTrace.frag.js +121 -0
  223. package/lib/renderer/glsl/aovTrace.frag.js.map +1 -0
  224. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +12 -12
  225. package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
  226. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
  227. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
  228. package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -67
  229. package/lib/renderer/glsl/chunks/envMap.glsl.js.map +1 -1
  230. package/lib/renderer/glsl/chunks/intersect.glsl.js +432 -434
  231. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
  232. package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
  233. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +160 -157
  234. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
  235. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +802 -802
  236. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  237. package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
  238. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
  239. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +72 -67
  240. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
  241. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +94 -76
  242. package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
  243. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
  244. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +143 -120
  245. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
  246. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +21 -18
  247. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js.map +1 -1
  248. package/lib/renderer/glsl/chunks/sampling.glsl.js +91 -94
  249. package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
  250. package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
  251. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  252. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +12 -12
  253. package/lib/renderer/glsl/chunks/uvProjection.glsl.js.map +1 -1
  254. package/lib/renderer/glsl/depthQuery.frag.js +7 -7
  255. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +17 -9
  256. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js.map +1 -1
  257. package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
  258. package/lib/renderer/glsl/rayTrace.frag.js +271 -248
  259. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  260. package/lib/renderer/glsl/shadowCatcher.frag.js +37 -32
  261. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
  262. package/lib/renderer/glsl/webglPipeline.frag.js +65 -60
  263. package/lib/renderer/glsl/webglPipeline.frag.js.map +1 -1
  264. package/lib/renderer/lightConstants.d.ts +10 -0
  265. package/lib/renderer/lightConstants.js +11 -0
  266. package/lib/renderer/lightConstants.js.map +1 -0
  267. package/lib/renderer/lightCore.d.ts +12 -0
  268. package/lib/renderer/lightCore.js +13 -0
  269. package/lib/renderer/lightCore.js.map +1 -0
  270. package/lib/renderer/lightSampling.d.ts +22 -18
  271. package/lib/renderer/lightSampling.js +135 -62
  272. package/lib/renderer/lightSampling.js.map +1 -1
  273. package/lib/renderer/lightsData.d.ts +21 -0
  274. package/lib/renderer/lightsData.js +192 -0
  275. package/lib/renderer/lightsData.js.map +1 -0
  276. package/lib/renderer/lightsUtil.d.ts +3 -26
  277. package/lib/renderer/lightsUtil.js +3 -153
  278. package/lib/renderer/lightsUtil.js.map +1 -1
  279. package/lib/renderer/materialBufferData.d.ts +70 -0
  280. package/lib/renderer/materialBufferData.js +2 -0
  281. package/lib/renderer/materialBufferData.js.map +1 -0
  282. package/lib/renderer/materialConstants.d.ts +11 -0
  283. package/lib/renderer/materialConstants.js +11 -0
  284. package/lib/renderer/materialConstants.js.map +1 -1
  285. package/lib/renderer/materialCore.d.ts +8 -0
  286. package/lib/renderer/materialCore.js +9 -0
  287. package/lib/renderer/materialCore.js.map +1 -0
  288. package/lib/renderer/materialEmission.d.ts +2 -2
  289. package/lib/renderer/materialEmission.js +3 -3
  290. package/lib/renderer/materialEmission.js.map +1 -1
  291. package/lib/renderer/materialLayout.d.ts +2 -101
  292. package/lib/renderer/materialLayout.js +110 -172
  293. package/lib/renderer/materialLayout.js.map +1 -1
  294. package/lib/renderer/materialLayoutSchema.d.ts +26 -0
  295. package/lib/renderer/materialLayoutSchema.js +172 -0
  296. package/lib/renderer/materialLayoutSchema.js.map +1 -0
  297. package/lib/renderer/materialParams.d.ts +69 -0
  298. package/lib/renderer/materialParams.js +272 -0
  299. package/lib/renderer/materialParams.js.map +1 -0
  300. package/lib/renderer/materialTextureSlots.d.ts +14 -324
  301. package/lib/renderer/materialTextureSlots.js +21 -8
  302. package/lib/renderer/materialTextureSlots.js.map +1 -1
  303. package/lib/renderer/materialTextures.d.ts +6 -0
  304. package/lib/renderer/{texturesFromMaterials.js → materialTextures.js} +1 -2
  305. package/lib/renderer/materialTextures.js.map +1 -0
  306. package/lib/renderer/mergeMeshesToGeometry.js +6 -147
  307. package/lib/renderer/mergeMeshesToGeometry.js.map +1 -1
  308. package/lib/renderer/meshInstances.d.ts +4 -21
  309. package/lib/renderer/meshInstances.js +10 -56
  310. package/lib/renderer/meshInstances.js.map +1 -1
  311. package/lib/renderer/oidnNetwork.d.ts +2 -2
  312. package/lib/renderer/oidnNetwork.js +1 -1
  313. package/lib/renderer/oidnNetwork.js.map +1 -1
  314. package/lib/renderer/openPbrEnergyLut.js +850 -846
  315. package/lib/renderer/openPbrEnergyLut.js.map +1 -1
  316. package/lib/renderer/ownedDenoiser.d.ts +9 -0
  317. package/lib/renderer/ownedDenoiser.js +2 -0
  318. package/lib/renderer/ownedDenoiser.js.map +1 -0
  319. package/lib/renderer/packedMeshInstances.d.ts +23 -0
  320. package/lib/renderer/packedMeshInstances.js +80 -0
  321. package/lib/renderer/packedMeshInstances.js.map +1 -0
  322. package/lib/renderer/prepareMeshBVH.d.ts +12 -47
  323. package/lib/renderer/prepareMeshBVH.js +21 -642
  324. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  325. package/lib/renderer/proceduralEnvironmentPower.d.ts +3 -0
  326. package/lib/renderer/proceduralEnvironmentPower.js +8 -0
  327. package/lib/renderer/proceduralEnvironmentPower.js.map +1 -0
  328. package/lib/renderer/progressiveScheduler.d.ts +1 -0
  329. package/lib/renderer/progressiveScheduler.js +7 -3
  330. package/lib/renderer/progressiveScheduler.js.map +1 -1
  331. package/lib/renderer/renderPipeline.d.ts +4 -1
  332. package/lib/renderer/renderPipeline.js.map +1 -1
  333. package/lib/renderer/renderingLimits.d.ts +9 -0
  334. package/lib/renderer/renderingLimits.js +18 -0
  335. package/lib/renderer/renderingLimits.js.map +1 -0
  336. package/lib/renderer/researchTransport.d.ts +31 -0
  337. package/lib/renderer/researchTransport.js +5 -0
  338. package/lib/renderer/researchTransport.js.map +1 -0
  339. package/lib/renderer/sceneAssembly.d.ts +73 -0
  340. package/lib/renderer/sceneAssembly.js +109 -0
  341. package/lib/renderer/sceneAssembly.js.map +1 -0
  342. package/lib/renderer/sceneChangeCore.d.ts +39 -0
  343. package/lib/renderer/sceneChangeCore.js +307 -0
  344. package/lib/renderer/sceneChangeCore.js.map +1 -0
  345. package/lib/renderer/sceneChangeDetector.d.ts +3 -6
  346. package/lib/renderer/sceneChangeDetector.js +24 -150
  347. package/lib/renderer/sceneChangeDetector.js.map +1 -1
  348. package/lib/renderer/sceneLayoutSchema.d.ts +119 -0
  349. package/lib/renderer/sceneLayoutSchema.js +111 -0
  350. package/lib/renderer/sceneLayoutSchema.js.map +1 -0
  351. package/lib/renderer/sceneLightChanges.d.ts +3 -3
  352. package/lib/renderer/sceneLightChanges.js.map +1 -1
  353. package/lib/renderer/scenePreparation.d.ts +55 -0
  354. package/lib/renderer/scenePreparation.js +360 -0
  355. package/lib/renderer/scenePreparation.js.map +1 -0
  356. package/lib/renderer/scratchSampling.js +18 -18
  357. package/lib/renderer/scratchSampling.js.map +1 -1
  358. package/lib/renderer/shader.d.ts +2 -0
  359. package/lib/renderer/shader.js +2 -0
  360. package/lib/renderer/shader.js.map +1 -1
  361. package/lib/renderer/shaderCapabilities.d.ts +23 -0
  362. package/lib/renderer/shaderCapabilities.js +55 -0
  363. package/lib/renderer/shaderCapabilities.js.map +1 -0
  364. package/lib/renderer/shaderCapacity.d.ts +2 -0
  365. package/lib/renderer/shaderCapacity.js +5 -0
  366. package/lib/renderer/shaderCapacity.js.map +1 -0
  367. package/lib/renderer/skyModel.d.ts +9 -0
  368. package/lib/renderer/skyModel.js +10 -0
  369. package/lib/renderer/skyModel.js.map +1 -0
  370. package/lib/renderer/spectrum.d.ts +21 -0
  371. package/lib/renderer/spectrum.js +154 -0
  372. package/lib/renderer/spectrum.js.map +1 -0
  373. package/lib/renderer/textureAtlas.d.ts +60 -0
  374. package/lib/renderer/textureAtlas.js +145 -0
  375. package/lib/renderer/textureAtlas.js.map +1 -0
  376. package/lib/renderer/textureAtlasPixels.d.ts +7 -0
  377. package/lib/renderer/textureAtlasPixels.js +68 -0
  378. package/lib/renderer/textureAtlasPixels.js.map +1 -0
  379. package/lib/renderer/util.d.ts +1 -1
  380. package/lib/renderer/util.js.map +1 -1
  381. package/lib/renderer/webgl.d.ts +9 -2
  382. package/lib/renderer/webgl.js +54 -9
  383. package/lib/renderer/webgl.js.map +1 -1
  384. package/lib/renderer/webglLightSampling.d.ts +39 -0
  385. package/lib/renderer/webglLightSampling.js +15 -0
  386. package/lib/renderer/webglLightSampling.js.map +1 -0
  387. package/lib/renderer/wgpuBindless.d.ts +26 -0
  388. package/lib/renderer/wgpuBindless.js +95 -0
  389. package/lib/renderer/wgpuBindless.js.map +1 -0
  390. package/lib/renderer/wgpuBindlessSource.d.ts +2 -0
  391. package/lib/renderer/wgpuBindlessSource.js +51 -0
  392. package/lib/renderer/wgpuBindlessSource.js.map +1 -0
  393. package/lib/renderer/wgpuMaterialConstants.d.ts +4 -0
  394. package/lib/renderer/wgpuMaterialConstants.js +52 -0
  395. package/lib/renderer/wgpuMaterialConstants.js.map +1 -0
  396. package/lib/renderer/wgpuSceneUniform.d.ts +42 -0
  397. package/lib/renderer/wgpuSceneUniform.js +125 -0
  398. package/lib/renderer/wgpuSceneUniform.js.map +1 -0
  399. package/lib/renderer/wgpuShaderSource.d.ts +22 -0
  400. package/lib/renderer/wgpuShaderSource.js +33 -0
  401. package/lib/renderer/wgpuShaderSource.js.map +1 -0
  402. package/lib/renderer/wgsl/aovDisplay.wgsl.d.ts +1 -0
  403. package/lib/renderer/wgsl/aovDisplay.wgsl.js +70 -0
  404. package/lib/renderer/wgsl/aovDisplay.wgsl.js.map +1 -0
  405. package/lib/renderer/wgsl/aovReflectance.wgsl.d.ts +2 -0
  406. package/lib/renderer/wgsl/aovReflectance.wgsl.js +98 -0
  407. package/lib/renderer/wgsl/aovReflectance.wgsl.js.map +1 -0
  408. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.d.ts +2 -0
  409. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +135 -0
  410. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js.map +1 -0
  411. package/lib/renderer/wgsl/aovResolve.wgsl.d.ts +5 -0
  412. package/lib/renderer/wgsl/aovResolve.wgsl.js +38 -0
  413. package/lib/renderer/wgsl/aovResolve.wgsl.js.map +1 -0
  414. package/lib/renderer/wgsl/aovSnapshot.wgsl.d.ts +4 -0
  415. package/lib/renderer/wgsl/aovSnapshot.wgsl.js +21 -0
  416. package/lib/renderer/wgsl/aovSnapshot.wgsl.js.map +1 -0
  417. package/lib/renderer/wgsl/aovTrace.wgsl.d.ts +2 -0
  418. package/lib/renderer/wgsl/aovTrace.wgsl.js +115 -0
  419. package/lib/renderer/wgsl/aovTrace.wgsl.js.map +1 -0
  420. package/lib/renderer/wgsl/bloom.wgsl.d.ts +13 -0
  421. package/lib/renderer/wgsl/bloom.wgsl.js +92 -0
  422. package/lib/renderer/wgsl/bloom.wgsl.js.map +1 -0
  423. package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
  424. package/lib/renderer/wgsl/causticLight.compute.js +537 -0
  425. package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
  426. package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
  427. package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
  428. package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
  429. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
  430. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
  431. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
  432. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
  433. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
  434. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
  435. package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
  436. package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
  437. package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
  438. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  439. package/lib/renderer/wgsl/chunks/envMap.wgsl.d.ts +1 -1
  440. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +41 -35
  441. package/lib/renderer/wgsl/chunks/envMap.wgsl.js.map +1 -1
  442. package/lib/renderer/wgsl/chunks/intersect.wgsl.d.ts +13 -1
  443. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +747 -704
  444. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  445. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +16 -16
  446. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js.map +1 -1
  447. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +4 -1
  448. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +83 -79
  449. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
  450. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +855 -857
  451. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js.map +1 -1
  452. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +137 -137
  453. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  454. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +3 -1
  455. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +52 -32
  456. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
  457. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
  458. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +152 -144
  459. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
  460. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
  461. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +269 -245
  462. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
  463. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
  464. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +27 -14
  465. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
  466. package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
  467. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +91 -95
  468. package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
  469. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.d.ts +1 -1
  470. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js +1 -0
  471. package/lib/renderer/wgsl/chunks/shadowCatcherAccumulation.wgsl.js.map +1 -1
  472. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
  473. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +12 -12
  474. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js.map +1 -1
  475. package/lib/renderer/wgsl/compose.frag.wgsl.d.ts +8 -3
  476. package/lib/renderer/wgsl/compose.frag.wgsl.js +143 -118
  477. package/lib/renderer/wgsl/compose.frag.wgsl.js.map +1 -1
  478. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  479. package/lib/renderer/wgsl/depthQuery.wgsl.js +7 -7
  480. package/lib/renderer/wgsl/jnlm.compute.d.ts +25 -0
  481. package/lib/renderer/wgsl/jnlm.compute.js +315 -0
  482. package/lib/renderer/wgsl/jnlm.compute.js.map +1 -0
  483. package/lib/renderer/wgsl/offlineAdaptive.compute.js +28 -28
  484. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.d.ts +1 -0
  485. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +17 -0
  486. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js.map +1 -0
  487. package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
  488. package/lib/renderer/wgsl/photonMap.compute.js +352 -0
  489. package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
  490. package/lib/renderer/wgsl/proceduralSky.compute.d.ts +1 -0
  491. package/lib/renderer/wgsl/proceduralSky.compute.js +198 -0
  492. package/lib/renderer/wgsl/proceduralSky.compute.js.map +1 -0
  493. package/lib/renderer/wgsl/rayTrace.compute.d.ts +25 -1
  494. package/lib/renderer/wgsl/rayTrace.compute.js +645 -500
  495. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  496. package/lib/scene/Camera.js +7 -3
  497. package/lib/scene/Camera.js.map +1 -1
  498. package/lib/scene/EnvironmentLight.d.ts +5 -9
  499. package/lib/scene/EnvironmentLight.js +4 -14
  500. package/lib/scene/EnvironmentLight.js.map +1 -1
  501. package/lib/scene/EnvironmentLightState.d.ts +18 -0
  502. package/lib/scene/EnvironmentLightState.js +19 -0
  503. package/lib/scene/EnvironmentLightState.js.map +1 -0
  504. package/lib/scene/Geometry.d.ts +12 -6
  505. package/lib/scene/Geometry.js +37 -13
  506. package/lib/scene/Geometry.js.map +1 -1
  507. package/lib/scene/InstanceState.d.ts +39 -0
  508. package/lib/scene/InstanceState.js +77 -0
  509. package/lib/scene/InstanceState.js.map +1 -0
  510. package/lib/scene/InstancedMesh.d.ts +1 -1
  511. package/lib/scene/InstancedMesh.js +3 -0
  512. package/lib/scene/InstancedMesh.js.map +1 -1
  513. package/lib/scene/MaterialState.d.ts +209 -0
  514. package/lib/scene/MaterialState.js +231 -0
  515. package/lib/scene/MaterialState.js.map +1 -0
  516. package/lib/scene/Mesh.d.ts +4 -6
  517. package/lib/scene/Mesh.js +3 -11
  518. package/lib/scene/Mesh.js.map +1 -1
  519. package/lib/scene/MeshState.d.ts +20 -0
  520. package/lib/scene/MeshState.js +35 -0
  521. package/lib/scene/MeshState.js.map +1 -0
  522. package/lib/scene/OpenPBRMaterial.d.ts +6 -168
  523. package/lib/scene/OpenPBRMaterial.js +5 -223
  524. package/lib/scene/OpenPBRMaterial.js.map +1 -1
  525. package/lib/scene/PixelTexture.d.ts +6 -0
  526. package/lib/scene/PixelTexture.js +10 -0
  527. package/lib/scene/PixelTexture.js.map +1 -0
  528. package/lib/scene/ProceduralSky.d.ts +24 -0
  529. package/lib/scene/ProceduralSky.js +40 -0
  530. package/lib/scene/ProceduralSky.js.map +1 -0
  531. package/lib/scene/RectLight.d.ts +2 -13
  532. package/lib/scene/RectLight.js +3 -21
  533. package/lib/scene/RectLight.js.map +1 -1
  534. package/lib/scene/RectLightState.d.ts +31 -0
  535. package/lib/scene/RectLightState.js +26 -0
  536. package/lib/scene/RectLightState.js.map +1 -0
  537. package/lib/scene/Scene.d.ts +15 -4
  538. package/lib/scene/Scene.js.map +1 -1
  539. package/lib/scene/SceneNode.js +7 -6
  540. package/lib/scene/SceneNode.js.map +1 -1
  541. package/lib/scene/Texture.d.ts +10 -41
  542. package/lib/scene/Texture.js +4 -33
  543. package/lib/scene/Texture.js.map +1 -1
  544. package/lib/scene/TextureState.d.ts +35 -0
  545. package/lib/scene/TextureState.js +35 -0
  546. package/lib/scene/TextureState.js.map +1 -0
  547. package/lib/scene/attributeData.d.ts +4 -0
  548. package/lib/scene/attributeData.js +10 -0
  549. package/lib/scene/attributeData.js.map +1 -0
  550. package/lib/scene/lightState.d.ts +8 -0
  551. package/lib/scene/lightState.js +2 -0
  552. package/lib/scene/lightState.js.map +1 -0
  553. package/lib/scene/openPbrSchema.d.ts +7 -12
  554. package/lib/scene/openPbrSchema.js +273 -28
  555. package/lib/scene/openPbrSchema.js.map +1 -1
  556. package/lib/scene/skyParameters.d.ts +20 -0
  557. package/lib/scene/skyParameters.js +28 -0
  558. package/lib/scene/skyParameters.js.map +1 -0
  559. package/lib/scene/textureData.d.ts +9 -0
  560. package/lib/scene/textureData.js +2 -0
  561. package/lib/scene/textureData.js.map +1 -0
  562. package/lib/scene/texturePixels.d.ts +6 -0
  563. package/lib/scene/texturePixels.js +10 -0
  564. package/lib/scene/texturePixels.js.map +1 -0
  565. package/lib/settings/display.d.ts +33 -0
  566. package/lib/settings/display.js +7 -0
  567. package/lib/settings/display.js.map +1 -1
  568. package/lib/settings/render.d.ts +36 -3
  569. package/lib/settings/render.js +4 -1
  570. package/lib/settings/render.js.map +1 -1
  571. package/lib/tinybvh/tinybvh.wasm +0 -0
  572. package/lib/util/commonGeometry.js +7 -7
  573. package/lib/util/commonGeometry.js.map +1 -1
  574. package/lib/util/fromTHREE.d.ts +4 -1
  575. package/lib/util/fromTHREE.js +20 -7
  576. package/lib/util/fromTHREE.js.map +1 -1
  577. package/lib/util/geometry.js +42 -29
  578. package/lib/util/geometry.js.map +1 -1
  579. package/lib/util/loadRGBE.js +3 -4
  580. package/lib/util/loadRGBE.js.map +1 -1
  581. package/lib/util/studioLighting.d.ts +18 -0
  582. package/lib/util/studioLighting.js +36 -0
  583. package/lib/util/studioLighting.js.map +1 -0
  584. package/lib/version.d.ts +1 -1
  585. package/lib/version.js +1 -1
  586. package/lib/version.js.map +1 -1
  587. package/llms.txt +5 -0
  588. package/package.json +33 -7
  589. package/lib/renderer/texturesFromMaterials.d.ts +0 -6
  590. package/lib/renderer/texturesFromMaterials.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"openPbrBsdf.wgsl.js","sourceRoot":"","sources":["../../../../src/renderer/wgsl/chunks/openPbrBsdf.wgsl.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * OpenPBR energy-compensation, EON and thin-film portions Copyright 2026 Adobe.\n * Licensed under the Apache License, Version 2.0.\n * https://www.apache.org/licenses/LICENSE-2.0\n */\n\n/*\n * Renderer-specific OpenPBR layered BSDF.\n *\n * The prepare/eval/sample/pdf organization and fixed-lobe composition follow:\n * - https://github.com/adobe/openpbr-bsdf\n * - https://github.com/portsmouth/OpenPBR-viewer\n *\n * This compact GPU implementation reuses the renderer's GGX VNDF and volume\n * integrator. Adobe's opaque-dielectric energy tables are used for the base\n * rough-diffuse/specular coupling; fuzz still uses an analytic approximation.\n */\n\nimport { OPENPBR_METAL_ENERGY_LUT_ROW, OPENPBR_METAL_ENERGY_LUT_AVERAGE_ROW, OPENPBR_DIELECTRIC_ENERGY_LUT_ROW, OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW, OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW } from '../../openPbrEnergyLut.js';\n\nexport function createOpenPbrBsdfWgsl(hasAnisotropyDirections = true, hasCoatNormals = false) {\n return /* wgsl */ `\nconst OPENPBR_LOBE_FUZZ: i32 = 0;\nconst OPENPBR_LOBE_COAT: i32 = 1;\nconst OPENPBR_LOBE_METAL: i32 = 2;\nconst OPENPBR_LOBE_SPECULAR: i32 = 3;\nconst OPENPBR_LOBE_TRANSMISSION: i32 = 4;\nconst OPENPBR_LOBE_DIFFUSE: i32 = 5;\nconst OPENPBR_LOBE_SUBSURFACE: i32 = 6;\nconst OPENPBR_LOBE_COUNT: i32 = 7;\n\nstruct OpenPbrBasis {\n tangent: vec3f,\n bitangent: vec3f,\n normal: vec3f,\n};\n\nstruct OpenPbrPreparedBsdf {\n material: OpenPbrResolvedMaterial,\n basis: OpenPbrBasis,\n${hasAnisotropyDirections ? /* wgsl */ `\n coatBasis: OpenPbrBasis,\n directionFrames: bool,\n` : /* wgsl */ ``}\n${hasCoatNormals ? /* wgsl */ ` coatViewLocal: vec3f,` : ''}\n viewLocal: vec3f,\n diffuseColor: vec3f,\n specularEtaTOverEtaI: f32,\n refractionEtaTOverEtaI: f32,\n transmissionReflectionIor: f32,\n thinFilmExteriorIor: f32,\n thinFilmBaseIor: f32,\n specularAlpha: f32,\n diffuseSpecularEnergyCompensation: f32,\n coatBaseScale: vec3f,\n metalMsScale: vec3f,\n metalMsFactor: f32,\n metalMsProbability: f32,\n\n dielectricMsReflectionScale: vec3f,\n dielectricMsTransmissionScale: vec3f,\n dielectricMsViewLoss: f32,\n dielectricMsReflectionRatio: f32,\n dielectricMsProbability: f32,\n\n weights: array<f32, OPENPBR_LOBE_COUNT>,\n probabilities: array<f32, OPENPBR_LOBE_COUNT>,\n};\n\nfn openpbr_luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.212671, 0.715160, 0.072169));\n}\n\nfn openpbr_f0_from_ior(etaTOverEtaI: f32) -> f32 {\n let value = (etaTOverEtaI - 1.0) / (etaTOverEtaI + 1.0);\n return value * value;\n}\n\nfn openpbr_apply_specular_weight_to_ior(\n etaTOverEtaI: f32,\n specularWeight: f32\n) -> f32 {\n let scaledF0 = min(\n openpbr_f0_from_ior(etaTOverEtaI) * specularWeight,\n 0.9999\n );\n let sqrtF0 = sqrt(scaledF0);\n let externalIor = (1.0 + sqrtF0) / (1.0 - sqrtF0);\n return select(externalIor, 1.0 / externalIor, etaTOverEtaI < 1.0);\n}\n\nfn openpbr_dielectric_fresnel(cosTheta: f32, relativeIor: f32) -> f32 {\n if (abs(relativeIor - 1.0) < 1e-6) {\n return 0.0;\n }\n return sampling_dielectric_fresnel(cosTheta, relativeIor);\n}\n\nfn openpbr_energy_ior_index(ior: f32) -> f32 {\n if (ior < 1.0) {\n return 15.0 - ((1.0 / ior - 1.0) / 1.5) * 15.0;\n }\n return 16.0 + ((ior - 1.0) / 1.5) * 15.0;\n}\n\nfn openpbr_energy_fetch(cosIndex: i32, row: i32) -> f32 {\n return textureLoad(openPbrEnergyLut, vec2i(cosIndex, row), 0).r;\n}\n\nfn openpbr_energy_trilinear(\n exactIor: f32,\n exactAlpha: f32,\n exactCosine: f32\n) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let cosineIndex = clamp(exactCosine, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let cosine0 = i32(floor(cosineIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let cosine1 = min(cosine0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let cosineMix = cosineIndex - f32(cosine0);\n\n let v000 = openpbr_energy_fetch(cosine0, ior0 * 32 + alpha0);\n let v001 = openpbr_energy_fetch(cosine1, ior0 * 32 + alpha0);\n let v010 = openpbr_energy_fetch(cosine0, ior0 * 32 + alpha1);\n let v011 = openpbr_energy_fetch(cosine1, ior0 * 32 + alpha1);\n let v100 = openpbr_energy_fetch(cosine0, ior1 * 32 + alpha0);\n let v101 = openpbr_energy_fetch(cosine1, ior1 * 32 + alpha0);\n let v110 = openpbr_energy_fetch(cosine0, ior1 * 32 + alpha1);\n let v111 = openpbr_energy_fetch(cosine1, ior1 * 32 + alpha1);\n let v00 = mix(v000, v100, iorMix);\n let v01 = mix(v001, v101, iorMix);\n let v10 = mix(v010, v110, iorMix);\n let v11 = mix(v011, v111, iorMix);\n return mix(\n mix(v00, v10, alphaMix),\n mix(v01, v11, alphaMix),\n cosineMix\n );\n}\n\nfn openpbr_energy_bilinear(exactIor: f32, exactAlpha: f32) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let v00 = openpbr_energy_fetch(alpha0, 1024 + ior0);\n let v01 = openpbr_energy_fetch(alpha1, 1024 + ior0);\n let v10 = openpbr_energy_fetch(alpha0, 1024 + ior1);\n let v11 = openpbr_energy_fetch(alpha1, 1024 + ior1);\n return mix(\n mix(v00, v10, iorMix),\n mix(v01, v11, iorMix),\n alphaMix\n );\n}\n\n// Adobe c91aad1 ideal dielectric tables clamp IOR indices; unlike opaque\n// dielectric data they do not extrapolate outside [0.4, 2.5].\nfn openpbr_ideal_energy_trilinear(\n exactIor: f32,\n exactAlpha: f32,\n exactCosine: f32\n) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let cosineIndex = clamp(exactCosine, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let cosine0 = i32(floor(cosineIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let cosine1 = min(cosine0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let cosineMix = cosineIndex - f32(cosine0);\n\n let v000 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha0);\n let v001 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha0);\n let v010 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha1);\n let v011 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha1);\n let v100 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha0);\n let v101 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha0);\n let v110 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha1);\n let v111 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha1);\n let v00 = mix(v000, v100, iorMix);\n let v01 = mix(v001, v101, iorMix);\n let v10 = mix(v010, v110, iorMix);\n let v11 = mix(v011, v111, iorMix);\n return mix(\n mix(v00, v10, alphaMix),\n mix(v01, v11, alphaMix),\n cosineMix\n );\n}\n\nfn openpbr_ideal_average_bilinear(exactIor: f32, exactAlpha: f32) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let v00 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior0);\n let v01 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior0);\n let v10 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior1);\n let v11 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior1);\n return mix(\n mix(v00, v10, iorMix),\n mix(v01, v11, iorMix),\n alphaMix\n );\n}\n\nfn openpbr_ideal_ratio_bilinear(exactIor: f32, exactAlpha: f32) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let v00 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior0);\n let v01 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior0);\n let v10 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior1);\n let v11 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior1);\n return mix(\n mix(v00, v10, iorMix),\n mix(v01, v11, iorMix),\n alphaMix\n );\n}\n\nfn openpbr_ideal_dielectric_loss(ior: f32, alpha: f32, cosine: f32) -> f32 {\n return openpbr_ideal_energy_trilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0, cosine * 31.0);\n}\nfn openpbr_ideal_dielectric_average_loss(ior: f32, alpha: f32) -> f32 {\n return openpbr_ideal_average_bilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0);\n}\nfn openpbr_ideal_dielectric_reflection_ratio(ior: f32, alpha: f32) -> f32 {\n return openpbr_ideal_ratio_bilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0);\n}\n\n// Adobe openpbr_microfacet_multiple_scattering_{data,lobes}.h (c91aad1):\n// ideal-metal loss E=1-singleScatterEnergy, indexed by sqrt(alpha) and cosine.\nfn openpbr_metal_energy_complement(alpha: f32, cosine: f32) -> f32 {\n let roughnessIndex = clamp(sqrt(alpha) * 31.0, 0.0, 31.0);\n let cosineIndex = clamp(cosine * 31.0, 0.0, 31.0);\n let r0 = i32(floor(roughnessIndex));\n let c0 = i32(floor(cosineIndex));\n let r1 = min(r0 + 1, 31);\n let c1 = min(c0 + 1, 31);\n return mix(\n mix(openpbr_energy_fetch(c0, ${OPENPBR_METAL_ENERGY_LUT_ROW} + r0), openpbr_energy_fetch(c1, ${OPENPBR_METAL_ENERGY_LUT_ROW} + r0), fract(cosineIndex)),\n mix(openpbr_energy_fetch(c0, ${OPENPBR_METAL_ENERGY_LUT_ROW} + r1), openpbr_energy_fetch(c1, ${OPENPBR_METAL_ENERGY_LUT_ROW} + r1), fract(cosineIndex)),\n fract(roughnessIndex));\n}\n\nfn openpbr_metal_average_energy_complement(alpha: f32) -> f32 {\n let index = clamp(sqrt(alpha) * 31.0, 0.0, 31.0);\n let i0 = i32(floor(index));\n return mix(openpbr_energy_fetch(i0, ${OPENPBR_METAL_ENERGY_LUT_AVERAGE_ROW}),\n openpbr_energy_fetch(min(i0 + 1, 31), ${OPENPBR_METAL_ENERGY_LUT_AVERAGE_ROW}), fract(index));\n}\n\nfn openpbr_extrapolate_energy_complement(value: f32, ior: f32) -> f32 {\n if (ior > 2.5 || ior < 0.4) {\n let maxF0 = openpbr_f0_from_ior(2.5);\n let progress = (openpbr_f0_from_ior(ior) - maxF0) / (1.0 - maxF0);\n return (1.0 - progress) * value;\n }\n return value;\n}\n\nfn openpbr_opaque_energy_complement(\n ior: f32,\n alpha: f32,\n cosine: f32\n) -> f32 {\n let value = openpbr_energy_trilinear(\n openpbr_energy_ior_index(ior),\n sqrt(alpha) * 31.0,\n cosine * 31.0\n );\n return openpbr_extrapolate_energy_complement(value, ior);\n}\n\nfn openpbr_opaque_average_energy_complement(ior: f32, alpha: f32) -> f32 {\n let value = openpbr_energy_bilinear(\n openpbr_energy_ior_index(ior),\n sqrt(alpha) * 31.0\n );\n return openpbr_extrapolate_energy_complement(value, ior);\n}\n\nfn openpbr_to_world(basis: OpenPbrBasis, direction: vec3f) -> vec3f {\n return direction.x * basis.tangent\n + direction.y * basis.bitangent\n + direction.z * basis.normal;\n}\n\nfn openpbr_to_local(basis: OpenPbrBasis, direction: vec3f) -> vec3f {\n return vec3f(\n dot(direction, basis.tangent),\n dot(direction, basis.bitangent),\n dot(direction, basis.normal)\n );\n}\n\nfn openpbr_make_basis(\n inputTangent: vec3f,\n normal: vec3f,\n anisotropy: f32\n) -> OpenPbrBasis {\n var basis: OpenPbrBasis;\n basis.normal = normal;\n var tangent = inputTangent;\n var bitangent = vec3f(0.0);\n if (dot(tangent, tangent) < 0.001 || anisotropy <= 0.0) {\n sampling_orthonormal_basis(normal, &tangent, &bitangent);\n }\n else {\n bitangent = normalize(cross(normal, tangent));\n tangent = normalize(cross(bitangent, normal));\n }\n basis.tangent = tangent;\n basis.bitangent = bitangent;\n return basis;\n}\n\nfn openpbr_fresnel_s(\n incidentIor: f32,\n transmittedIor: f32,\n incidentCosine: f32,\n transmittedCosine: f32\n) -> f32 {\n return (incidentIor * incidentCosine - transmittedIor * transmittedCosine)\n / (incidentIor * incidentCosine + transmittedIor * transmittedCosine);\n}\n\nfn openpbr_fresnel_p(\n incidentIor: f32,\n transmittedIor: f32,\n incidentCosine: f32,\n transmittedCosine: f32\n) -> f32 {\n return (transmittedIor * incidentCosine - incidentIor * transmittedCosine)\n / (incidentIor * transmittedCosine + transmittedIor * incidentCosine);\n}\n\nfn openpbr_transmission_s(\n incidentIor: f32,\n transmittedIor: f32,\n incidentCosine: f32,\n transmittedCosine: f32\n) -> f32 {\n return 2.0 * incidentIor * incidentCosine\n / (incidentIor * incidentCosine + transmittedIor * transmittedCosine);\n}\n\nfn openpbr_transmission_p(\n incidentIor: f32,\n transmittedIor: f32,\n incidentCosine: f32,\n transmittedCosine: f32\n) -> f32 {\n return 2.0 * incidentIor * incidentCosine\n / (incidentIor * transmittedCosine + transmittedIor * incidentCosine);\n}\n\nfn openpbr_complex_multiply(a: vec2f, b: vec2f) -> vec2f {\n return vec2f(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);\n}\n\nfn openpbr_complex_divide(a: vec2f, b: vec2f) -> vec2f {\n let denominator = max(dot(b, b), 1e-9);\n return vec2f(\n (a.x * b.x + a.y * b.y) / denominator,\n (a.y * b.x - a.x * b.y) / denominator\n );\n}\n\nfn openpbr_complex_sqrt(value: vec2f) -> vec2f {\n let magnitude = length(value);\n let realPart = sqrt(max(0.0, 0.5 * (magnitude + value.x)));\n let imaginaryMagnitude = sqrt(max(0.0, 0.5 * (magnitude - value.x)));\n return vec2f(realPart, select(-imaginaryMagnitude, imaginaryMagnitude, value.y >= 0.0));\n}\n\nfn openpbr_complex_fresnel_reflection(\n incidentCosine: f32,\n incidentIor: f32,\n transmittedIor: vec2f,\n pPolarized: bool\n) -> vec2f {\n let incidentSine = sqrt(max(0.0, 1.0 - incidentCosine * incidentCosine));\n let iorRatio = openpbr_complex_divide(vec2f(incidentIor, 0.0), transmittedIor);\n let transmittedSine = iorRatio * incidentSine;\n var transmittedCosine = openpbr_complex_sqrt(\n vec2f(1.0, 0.0) - openpbr_complex_multiply(transmittedSine, transmittedSine)\n );\n if (openpbr_complex_multiply(transmittedIor, transmittedCosine).y < 0.0) {\n transmittedCosine = -transmittedCosine;\n }\n\n let incidentIorCosine = vec2f(incidentIor * incidentCosine, 0.0);\n let transmittedIorCosine = openpbr_complex_multiply(transmittedIor, transmittedCosine);\n if (pPolarized) {\n let transmittedIorIncidentCosine = transmittedIor * incidentCosine;\n let incidentIorTransmittedCosine = transmittedCosine * incidentIor;\n return openpbr_complex_divide(\n transmittedIorIncidentCosine - incidentIorTransmittedCosine,\n transmittedIorIncidentCosine + incidentIorTransmittedCosine\n );\n }\n return openpbr_complex_divide(\n incidentIorCosine - transmittedIorCosine,\n incidentIorCosine + transmittedIorCosine\n );\n}\n\nfn openpbr_airy_reflectance(\n reflection12: f32,\n transmission12: f32,\n reflection21: f32,\n transmission21: f32,\n reflection23: vec2f,\n phase: f32\n) -> f32 {\n let phaseRotation = vec2f(cos(phase), sin(phase));\n let reflectedPhase = openpbr_complex_multiply(reflection23, phaseRotation);\n let numerator = reflectedPhase * (transmission12 * transmission21);\n let denominator = vec2f(1.0, 0.0) - reflectedPhase * reflection21;\n let totalReflection = vec2f(reflection12, 0.0)\n + openpbr_complex_divide(numerator, denominator);\n return dot(totalReflection, totalReflection);\n}\n\nfn openpbr_thin_film_reflectance_channel(\n incidentCosine: f32,\n wavelength: f32,\n thickness: f32,\n exteriorIor: f32,\n filmIor: f32,\n substrateIor: vec2f\n) -> f32 {\n let safeIncidentCosine = clamp(abs(incidentCosine), 1e-5, 1.0);\n let filmSineSquared = pow(exteriorIor / filmIor, 2.0)\n * (1.0 - safeIncidentCosine * safeIncidentCosine);\n if (filmSineSquared >= 1.0) {\n return 1.0;\n }\n let filmCosine = sqrt(1.0 - filmSineSquared);\n let reflection12S = openpbr_fresnel_s(\n exteriorIor, filmIor, safeIncidentCosine, filmCosine\n );\n let reflection12P = openpbr_fresnel_p(\n exteriorIor, filmIor, safeIncidentCosine, filmCosine\n );\n let transmission12S = openpbr_transmission_s(\n exteriorIor, filmIor, safeIncidentCosine, filmCosine\n );\n let transmission12P = openpbr_transmission_p(\n exteriorIor, filmIor, safeIncidentCosine, filmCosine\n );\n let transmission21Scale = filmIor * filmCosine\n / max(exteriorIor * safeIncidentCosine, 1e-9);\n let phase = 2.0 * PI * (2.0 * filmIor * thickness * filmCosine) / wavelength;\n var reflectance = 0.0;\n // Both polarizations share Snell's complex solve and the Airy graph.\n for (var polarization = 0u; polarization < (rtUniform.loopUnrollGuard >> 1u); polarization++) {\n let reflection12 = select(reflection12S, reflection12P, polarization == 1u);\n let transmission12 = select(transmission12S, transmission12P, polarization == 1u);\n let reflection23 = openpbr_complex_fresnel_reflection(\n filmCosine, filmIor, substrateIor, polarization == 1u);\n reflectance += openpbr_airy_reflectance(reflection12, transmission12,\n -reflection12, transmission12 * transmission21Scale, reflection23, phase);\n }\n return clamp(0.5 * reflectance, 0.0, 1.0);\n}\n\n\n// OpenPBR 1.1.1 Metal: analytic F82-tint correction to Schlick.\n// Saturation follows Adobe impl/openpbr_lobe_utils.h, openpbr_metal_schlick_with_f82_tint.\nfn openpbr_metal_fresnel(cosine: f32, f0: vec3f, tint: vec3f) -> vec3f {\n let schlick = mix(f0, vec3f(1.0), sampling_schlick_fifth_power(cosine));\n if (all(tint == vec3f(1.0))) { return schlick; }\n let edgeCosine = 1.0 / 7.0;\n let edgeComplement = 1.0 - edgeCosine;\n let edgeFresnel = mix(f0, vec3f(1.0), sampling_schlick_fifth_power(edgeCosine));\n let complement = 1.0 - cosine;\n let correction = cosine * sampling_schlick_fifth_power(cosine) * complement\n / (edgeCosine * pow(edgeComplement, 6.0));\n return clamp(schlick - correction * (edgeFresnel - tint * edgeFresnel), vec3f(0.0), vec3f(1.0));\n}\n\nfn openpbr_metal_complex_ior(f0: f32, f82Tint: f32) -> vec2f {\n let reflectivity = clamp(f0, 0.0, 0.99);\n let sqrtReflectivity = sqrt(reflectivity);\n let minimumIor = (1.0 - reflectivity) / (1.0 + reflectivity);\n let maximumIor = (1.0 + sqrtReflectivity) / max(1.0 - sqrtReflectivity, 1e-9);\n let ior = mix(maximumIor, minimumIor, clamp(f82Tint, 0.0, 1.0));\n let extinctionSquared = (\n (ior + 1.0) * (ior + 1.0) * reflectivity\n - (ior - 1.0) * (ior - 1.0)\n ) / max(1.0 - reflectivity, 1e-9);\n return vec2f(ior, sqrt(max(extinctionSquared, 0.0)));\n}\n\n\nfn openpbr_thin_film_presence(thickness: f32, weight: f32) -> f32 {\n return clamp(weight, 0.0, 1.0) * smoothstep(1e-9, 30.0, thickness);\n}\n\n\n\nfn openpbr_eval_diffuse(\n material: OpenPbrResolvedMaterial,\n color: vec3f,\n viewLocal: vec3f,\n lightLocal: vec3f,\n specularEnergyCompensation: f32,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if (lightLocal.z <= 0.0) {\n return vec3f(0.0);\n }\n\n let roughness = material.diffuseRoughness;\n let fonConstantA = 0.5 - 2.0 / (3.0 * PI);\n let fonConstantB = 2.0 / 3.0 - 28.0 / (15.0 * PI);\n let muInput = lightLocal.z;\n let muOutput = viewLocal.z;\n let directional = dot(lightLocal, viewLocal) - muInput * muOutput;\n let directionalOverTangent = select(\n directional,\n directional / max(max(muInput, muOutput), 1e-7),\n directional > 0.0\n );\n let singleScatterA = 1.0 / (1.0 + fonConstantA * roughness);\n let singleScatter = color * INVPI * singleScatterA\n * (1.0 + roughness * directionalOverTangent);\n\n let inputComplement = 1.0 - muInput;\n let outputComplement = 1.0 - muOutput;\n let inputGOverPi = inputComplement * (\n 0.0571085289 + inputComplement * (\n 0.491881867 + inputComplement * (\n -0.332181442 + inputComplement * 0.0714429953\n )\n )\n );\n let outputGOverPi = outputComplement * (\n 0.0571085289 + outputComplement * (\n 0.491881867 + outputComplement * (\n -0.332181442 + outputComplement * 0.0714429953\n )\n )\n );\n let inputAlbedo = (1.0 + roughness * inputGOverPi)\n / (1.0 + fonConstantA * roughness);\n let outputAlbedo = (1.0 + roughness * outputGOverPi)\n / (1.0 + fonConstantA * roughness);\n let averageAlbedo = singleScatterA * (1.0 + fonConstantB * roughness);\n let multipleScatterAlbedo = color * color * averageAlbedo\n / max(vec3f(1e-7), vec3f(1.0) - color * (1.0 - averageAlbedo));\n let multipleScatter = multipleScatterAlbedo * INVPI\n * max(1e-7, 1.0 - outputAlbedo)\n * max(1e-7, 1.0 - inputAlbedo)\n / max(1e-7, 1.0 - averageAlbedo);\n *pdf = lightLocal.z * INVPI;\n return (singleScatter + multipleScatter) * specularEnergyCompensation;\n}\n\nfn openpbr_fuzz_directional_albedo(viewCosine: f32, roughness: f32) -> f32 {\n let x = clamp(viewCosine, 0.0, 1.0);\n let y = clamp(roughness, 0.01, 1.0);\n let scale = y * (0.0206607 + 1.58491 * y)\n / (0.0379424 + y * (1.32227 + y));\n let mean = y * (-0.193854 + y * (-1.14885 + y * (1.7932 - 0.95943 * y * y)))\n / (0.046391 + y);\n let offset = y * (0.000654023 + (-0.0207818 + 0.119681 * y) * y)\n / (1.26264 + y * (-1.92021 + y));\n let gaussian = exp(-0.5 * pow((x - mean) / scale, 2.0))\n / (scale * sqrt(2.0 * PI));\n return max(gaussian + offset, 0.0);\n}\n\nfn openpbr_eval_fuzz(\n material: OpenPbrResolvedMaterial,\n viewLocal: vec3f,\n lightLocal: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if (viewLocal.z <= 0.0 || lightLocal.z <= 0.0) {\n return vec3f(0.0);\n }\n let albedo = openpbr_fuzz_directional_albedo(\n viewLocal.z,\n material.fuzzRoughness\n );\n *pdf = lightLocal.z * INVPI;\n return material.fuzzColor * albedo * INVPI;\n}\n\nfn openpbr_eval_microfacet_reflection(\n material: OpenPbrResolvedMaterial,\n viewLocal: vec3f,\n lightLocal: vec3f,\n halfVector: vec3f,\n fresnel: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if (lightLocal.z <= 0.0) {\n return vec3f(0.0);\n }\n\n let alpha = vec2f(material.alphaX, material.alphaY);\n let distribution = sampling_ggx_ndf(halfVector, alpha);\n let masking = sampling_ggx_smith_g1(viewLocal, alpha);\n let maskingShadowing = masking\n * sampling_ggx_smith_g1(lightLocal, alpha);\n\n *pdf = masking * distribution / (4.0 * max(viewLocal.z, 1e-8));\n return fresnel * distribution * maskingShadowing\n / (4.0 * max(lightLocal.z * viewLocal.z, 1e-8));\n}\n\nfn openpbr_eval_microfacet_refraction(\n material: OpenPbrResolvedMaterial,\n relativeIor: f32,\n tint: vec3f,\n viewLocal: vec3f,\n lightLocal: vec3f,\n halfVector: vec3f,\n fresnel: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if (lightLocal.z >= 0.0) {\n return vec3f(0.0);\n }\n\n let lightHalf = dot(lightLocal, halfVector);\n let viewHalf = dot(viewLocal, halfVector);\n // A refracting microfacet must face the view and the opposite side of the\n // light. Backfacing configurations have no support in the VNDF proposal.\n if (viewHalf <= 0.0 || lightHalf >= 0.0) {\n return vec3f(0.0);\n }\n let alpha = vec2f(material.alphaX, material.alphaY);\n let distribution = sampling_ggx_ndf(halfVector, alpha);\n let masking = sampling_ggx_smith_g1(viewLocal, alpha);\n let maskingShadowing = masking\n * sampling_ggx_smith_g1(lightLocal, alpha);\n var denominator = lightHalf + viewHalf * relativeIor;\n denominator *= denominator;\n let jacobian = abs(lightHalf) / max(denominator, 1e-8);\n let relativeIorSquared = relativeIor * relativeIor;\n\n *pdf = masking * max(0.0, viewHalf) * distribution * jacobian\n / max(viewLocal.z, 1e-8);\n return tint * (vec3f(1.0) - fresnel) * distribution * maskingShadowing\n * abs(viewHalf) * jacobian * relativeIorSquared\n / max(abs(lightLocal.z * viewLocal.z), 1e-8);\n}\n\nfn openpbr_transmission_tint(material: OpenPbrResolvedMaterial) -> vec3f {\n if (HAS_MEDIUM && material.mediumType > 0) {\n return vec3f(1.0);\n }\n return material.transmissionColor;\n}\n\nfn openpbr_thin_wall_fresnel(singleInterfaceFresnel: f32) -> f32 {\n return 2.0 * singleInterfaceFresnel / (1.0 + singleInterfaceFresnel);\n}\n\nfn openpbr_thin_wall_reflection_coefficient(\n singleInterfaceFresnel: vec3f,\n transmissionWeight: f32\n) -> vec3f {\n let windowFresnel = 2.0 * singleInterfaceFresnel\n / (vec3f(1.0) + singleInterfaceFresnel);\n return mix(\n singleInterfaceFresnel,\n windowFresnel,\n clamp(transmissionWeight, 0.0, 1.0)\n );\n}\n\nfn openpbr_final_transmission_tint(\n material: OpenPbrResolvedMaterial,\n incidentCosine: f32,\n etaTOverEtaI: f32\n) -> vec3f {\n let tint = openpbr_transmission_tint(material);\n if (!material.thinWalled) {\n return tint;\n }\n let incidentSineSquared = max(0.0, 1.0 - incidentCosine * incidentCosine);\n let transmittedSineSquared = incidentSineSquared\n / max(etaTOverEtaI * etaTOverEtaI, 1e-9);\n if (transmittedSineSquared >= 1.0) {\n return vec3f(0.0);\n }\n let transmittedCosine = sqrt(1.0 - transmittedSineSquared);\n return pow(max(tint, vec3f(0.0)), vec3f(1.0 / max(transmittedCosine, 1e-5)));\n}\n\nfn openpbr_eval_subsurface_boundary(\n material: OpenPbrResolvedMaterial,\n viewLocal: vec3f,\n lightLocal: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if (viewLocal.z <= 0.0 || lightLocal.z >= 0.0) {\n return vec3f(0.0);\n }\n let transmittedCosine = -lightLocal.z;\n let entryFresnel = sampling_dielectric_fresnel(\n viewLocal.z,\n material.relativeIor\n );\n *pdf = transmittedCosine * INVPI;\n return vec3f(1.0 - entryFresnel) * INVPI;\n}\n\nfn openpbr_normalize_probabilities(prepared: ptr<function, OpenPbrPreparedBsdf>) {\n // loopUnrollGuard is 4; the dynamic bound covers all seven lobes without\n // asking the driver to unroll array operations across the full BSDF.\n let lobeCount = min(OPENPBR_LOBE_COUNT, i32(rtUniform.loopUnrollGuard * 2u));\n var total = (*prepared).probabilities[OPENPBR_LOBE_FUZZ];\n for (var lobe = 1; lobe < lobeCount; lobe++) {\n total += (*prepared).probabilities[lobe];\n }\n if (total <= 1e-8) {\n for (var lobe = 0; lobe < lobeCount; lobe++) {\n (*prepared).probabilities[lobe] = 0.0;\n }\n return;\n }\n let inverseTotal = 1.0 / total;\n for (var lobe = 0; lobe < lobeCount; lobe++) {\n (*prepared).probabilities[lobe] *= inverseTotal;\n }\n}\n\n// Adapted from Adobe c91aad1 impl/openpbr_lobe_utils.h and\n// impl/openpbr_bsdf.h::openpbr_prepare_lobes (Apache-2.0).\nfn openpbr_average_fresnel(eta: f32) -> f32 {\n if (abs(eta - 1.0) < 1e-6) { return 0.0; }\n if (eta > 1.0) { return (eta - 1.0) / (4.08567 + 1.00071 * eta); }\n return 0.997118 + 0.1014 * eta - 0.965241 * eta * eta - 0.130607 * eta * eta * eta;\n}\n\nfn openpbr_metal_average_fresnel(f0: vec3f, tint: vec3f) -> vec3f {\n let edgeCosine = 1.0 / 7.0;\n let edgeFresnel = mix(f0, vec3f(1.0), sampling_schlick_fifth_power(edgeCosine));\n let b = edgeFresnel * (vec3f(1.0) - tint) / (edgeCosine * pow(1.0 - edgeCosine, 6.0));\n return clamp(f0 + (vec3f(1.0) - f0) / 21.0 - b / 126.0, vec3f(0.0), vec3f(1.0));\n}\n\n// Adobe openpbr_coating_lobe.h: one passage, using the refracted cosine.\n// Partial coverage is mixed separately for each passage, as in the reference.\nfn openpbr_coat_passage(material: OpenPbrResolvedMaterial, cosine: f32) -> vec3f {\n if (cosine <= 0.0) { return vec3f(1.0); }\n let refractedCosine = sqrt(max(0.0, 1.0 - (1.0 - cosine * cosine) / (material.coatIor * material.coatIor)));\n let passage = pow(max(material.coatColor, vec3f(0.0)), vec3f(0.5 / max(refractedCosine, 1e-5)));\n return mix(vec3f(1.0), passage, material.coatWeight);\n}\n\nfn openpbr_coat_energy(material: OpenPbrResolvedMaterial, cosine: f32) -> f32 {\n if (material.coatRoughness <= ROUGHNESS_MIN) {\n return openpbr_dielectric_fresnel(cosine, 1.0 / material.coatIor);\n }\n // The opaque-dielectric table is the complement of coat directional albedo.\n // Adobe uses the original isotropic alpha (r^2) even for anisotropic GGX.\n return clamp(1.0 - openpbr_opaque_energy_complement(material.coatIor,\n material.coatRoughness * material.coatRoughness, cosine), 0.0, 1.0);\n}\n\nfn openpbr_coat_darkening(prepared: OpenPbrPreparedBsdf) -> vec3f {\n let material = prepared.material;\n let dielectric = 1.0 - material.baseMetalness;\n let opaque = dielectric * (1.0 - material.transmissionWeight) * (1.0 - material.subsurfaceWeight);\n let sss = dielectric * (1.0 - material.transmissionWeight) * material.subsurfaceWeight;\n let trans = dielectric * material.transmissionWeight;\n let opaqueSpecularity = openpbr_average_fresnel(prepared.specularEtaTOverEtaI);\n let transSpecularity = openpbr_average_fresnel(1.0 / prepared.transmissionReflectionIor);\n let specularity = clamp(opaque * opaqueSpecularity + (sss + trans) * transSpecularity + material.baseMetalness, 0.0, 1.0);\n let effectiveRoughness = mix(1.0, material.specularRoughness, specularity);\n let ks = openpbr_average_fresnel(material.coatIor);\n let kr = 1.0 - (1.0 - ks) / (material.coatIor * material.coatIor);\n let k = mix(ks, kr, effectiveRoughness);\n let eb = material.baseMetalness * material.specularWeight * openpbr_metal_average_fresnel(material.baseWeight * material.baseColor, material.specularColor)\n + opaque * mix(material.baseWeight * material.baseColor, vec3f(1.0), opaqueSpecularity)\n + sss * material.subsurfaceColor + vec3f(trans);\n let delta = vec3f(1.0 - k) / max(vec3f(1.0) - eb * k, vec3f(1e-6));\n return mix(vec3f(1.0), delta, material.coatWeight * material.coatDarkening);\n}\n\nfn openpbr_coat_base_scale(prepared: OpenPbrPreparedBsdf, lightCosine: f32) -> vec3f {\n if (prepared.material.coatWeight <= 0.0) { return vec3f(1.0); }\n if (prepared.material.inside) {\n // Inside only outgoing transmission crosses the coat; no coat reflection.\n return openpbr_coat_passage(prepared.material, -lightCosine);\n }\n // Default reference mode applies energy partition on the view side only.\n return prepared.coatBaseScale * openpbr_coat_passage(prepared.material, lightCosine);\n}\n\n\n${hasAnisotropyDirections ? /* wgsl */ `\nfn openpbr_direction_basis(fallback: OpenPbrBasis, tangent: vec3f, bitangent: vec3f, direction: vec2f) -> OpenPbrBasis {\n var t = tangent - fallback.normal * dot(fallback.normal, tangent);\n if (dot(t, t) < 1e-8) { t = fallback.tangent; }\n t = normalize(t);\n let b = cross(fallback.normal, t) * select(1.0, -1.0, dot(cross(fallback.normal, t), bitangent) < 0.0);\n t = t * direction.x + b * direction.y;\n if (dot(t, t) < 1e-8) { return fallback; }\n var basis = fallback;\n basis.tangent = normalize(t);\n basis.bitangent = normalize(cross(basis.normal, basis.tangent));\n basis.tangent = normalize(cross(basis.bitangent, basis.normal));\n return basis;\n}\n\n` : /* wgsl */ ``}\nfn openpbr_prepare(\n tangent: vec3f,\n material: OpenPbrResolvedMaterial,\n viewDirection: vec3f,\n normal: vec3f\n) -> OpenPbrPreparedBsdf {\n var prepared: OpenPbrPreparedBsdf;\n prepared.material = material;\n prepared.basis = openpbr_make_basis(\n tangent,\n normal,\n max(material.specularAnisotropy, material.coatAnisotropy)\n );\n${hasAnisotropyDirections ? /* wgsl */ `\n prepared.coatBasis = prepared.basis;\n prepared.directionFrames = false;\n${hasCoatNormals ? /* wgsl */ `\n // Adobe c91aad1 openpbr_prepare_lobes: geometry_coat_basis is independent\n // of the base shading basis; coat passage uses the same coat cosine.\n if (material.coatNormalEnabled) {\n prepared.coatBasis = openpbr_make_basis(material.geometryTangent, material.coatNormal, material.coatAnisotropy);\n prepared.directionFrames = true;\n }\n` : ''}\n if (material.specularAnisotropy > 0.0 && material.anisotropyDirectionEnabled) {\n prepared.basis = openpbr_direction_basis(prepared.basis, material.directionTangent, material.directionBitangent, material.anisotropyDirection);\n prepared.directionFrames = true;\n }\n if (material.coatAnisotropy > 0.0 && material.coatAnisotropyRotation != 0.0) {\n prepared.coatBasis = openpbr_direction_basis(prepared.coatBasis, material.geometryTangent, material.geometryBitangent, vec2f(cos(material.coatAnisotropyRotation), sin(material.coatAnisotropyRotation)));\n prepared.directionFrames = true;\n }\n` : /* wgsl */ ``}\n prepared.viewLocal = openpbr_to_local(prepared.basis, viewDirection);\n${hasCoatNormals ? /* wgsl */ ` prepared.coatViewLocal = openpbr_to_local(prepared.coatBasis, viewDirection);` : ''}\n prepared.diffuseColor = material.baseColor;\n\n let dielectricBaseWeight = 1.0 - material.baseMetalness;\n let opaqueDielectricWeight = dielectricBaseWeight\n * (1.0 - material.transmissionWeight);\n prepared.weights[OPENPBR_LOBE_FUZZ] = material.fuzzWeight;\n prepared.weights[OPENPBR_LOBE_COAT] = material.coatWeight;\n if (material.inside) { prepared.weights[OPENPBR_LOBE_COAT] = 0.0; }\n prepared.weights[OPENPBR_LOBE_METAL] = material.baseMetalness;\n prepared.weights[OPENPBR_LOBE_SPECULAR] = dielectricBaseWeight;\n prepared.weights[OPENPBR_LOBE_TRANSMISSION] = dielectricBaseWeight\n * material.transmissionWeight;\n prepared.weights[OPENPBR_LOBE_DIFFUSE] = opaqueDielectricWeight\n * (1.0 - material.subsurfaceWeight) * material.baseWeight;\n prepared.weights[OPENPBR_LOBE_SUBSURFACE] = opaqueDielectricWeight\n * material.subsurfaceWeight;\n\n prepared.specularEtaTOverEtaI = openpbr_apply_specular_weight_to_ior(\n 1.0 / material.relativeIor,\n material.specularWeight\n );\n prepared.material.relativeIor = 1.0 / prepared.specularEtaTOverEtaI;\n prepared.refractionEtaTOverEtaI = prepared.specularEtaTOverEtaI;\n prepared.transmissionReflectionIor = prepared.material.relativeIor;\n // Adobe openpbr_bsdf.h::openpbr_prepare_lobes: reflect relative to\n // the weighted coat, refract relative to ambient air. Inside ignores coat.\n if (material.coatWeight > 0.0) {\n var weightedCoatIor = 1.0;\n if (!material.inside) { weightedCoatIor = mix(1.0, material.coatIor, material.coatWeight); }\n let weightedBaseIor = weightedCoatIor * openpbr_apply_specular_weight_to_ior(\n material.specularIor / weightedCoatIor, material.specularWeight);\n var reflectionEta = weightedBaseIor / weightedCoatIor;\n if (weightedCoatIor > weightedBaseIor && weightedBaseIor >= 1.0) {\n reflectionEta = 1.0 / reflectionEta;\n }\n prepared.specularEtaTOverEtaI = reflectionEta;\n prepared.transmissionReflectionIor = 1.0 / reflectionEta;\n prepared.refractionEtaTOverEtaI = weightedBaseIor;\n if (material.inside) {\n prepared.refractionEtaTOverEtaI = 1.0 / weightedBaseIor;\n prepared.transmissionReflectionIor = weightedBaseIor;\n }\n prepared.material.relativeIor = 1.0 / prepared.refractionEtaTOverEtaI;\n }\n\n // Adobe openpbr_bsdf.h::openpbr_prepare_lobes: absolute thin-film media.\n // Preserve the existing optical solve for uncoated materials.\n prepared.thinFilmExteriorIor = 1.0;\n prepared.thinFilmBaseIor = prepared.specularEtaTOverEtaI;\n if (material.coatWeight > 0.0) {\n if (material.inside) {\n prepared.thinFilmExteriorIor = 1.0 / prepared.refractionEtaTOverEtaI;\n prepared.thinFilmBaseIor = 1.0;\n }\n else {\n prepared.thinFilmExteriorIor = mix(1.0, material.coatIor, material.coatWeight);\n prepared.thinFilmBaseIor = prepared.refractionEtaTOverEtaI;\n }\n }\n prepared.specularAlpha = material.specularRoughness\n * material.specularRoughness;\n let viewCosine = abs(prepared.viewLocal.z);\n let singleInterfaceFresnel = openpbr_dielectric_fresnel(\n viewCosine,\n 1.0 / prepared.specularEtaTOverEtaI\n );\n let dielectricFresnelEstimate = material.specularColor * select(\n singleInterfaceFresnel,\n mix(\n singleInterfaceFresnel,\n openpbr_thin_wall_fresnel(singleInterfaceFresnel),\n material.transmissionWeight\n ),\n material.thinWalled\n );\n let metalFresnelEstimate = material.specularWeight * openpbr_metal_fresnel(\n viewCosine,\n material.baseWeight * material.baseColor,\n material.specularColor\n );\n let transmissionFresnel = select(\n openpbr_dielectric_fresnel(viewCosine, prepared.transmissionReflectionIor),\n openpbr_thin_wall_fresnel(openpbr_dielectric_fresnel(viewCosine, prepared.transmissionReflectionIor)),\n material.thinWalled\n );\n let transmissionTint = openpbr_final_transmission_tint(\n material,\n viewCosine,\n prepared.refractionEtaTOverEtaI\n );\n let viewEnergyComplement = openpbr_opaque_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha,\n viewCosine\n );\n let averageEnergyComplement = openpbr_opaque_average_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha\n );\n prepared.diffuseSpecularEnergyCompensation = viewEnergyComplement\n / max(averageEnergyComplement, 1e-12);\n\n prepared.probabilities[OPENPBR_LOBE_FUZZ] = prepared.weights[OPENPBR_LOBE_FUZZ]\n * openpbr_luminance(material.fuzzColor)\n * max(openpbr_fuzz_directional_albedo(\n viewCosine,\n material.fuzzRoughness\n ), 0.02);\n prepared.probabilities[OPENPBR_LOBE_COAT] = 0.0;\n prepared.coatBaseScale = vec3f(1.0);\n if (material.coatWeight > 0.0 && !material.inside) {\n ${hasCoatNormals ? /* wgsl */ `let coatViewCosine = select(viewCosine, max(prepared.coatViewLocal.z, 0.0), material.coatNormalEnabled);` : ''}\n let coatEnergy = openpbr_coat_energy(material, ${hasCoatNormals ? 'coatViewCosine' : 'viewCosine'});\n prepared.probabilities[OPENPBR_LOBE_COAT] = prepared.weights[OPENPBR_LOBE_COAT] * coatEnergy;\n prepared.coatBaseScale = openpbr_coat_passage(material, ${hasCoatNormals ? 'coatViewCosine' : 'viewCosine'})\n * (1.0 - prepared.weights[OPENPBR_LOBE_COAT] * coatEnergy) * openpbr_coat_darkening(prepared);\n }\n prepared.probabilities[OPENPBR_LOBE_METAL] = prepared.weights[OPENPBR_LOBE_METAL]\n * openpbr_luminance(metalFresnelEstimate);\n prepared.metalMsScale = vec3f(0.0);\n prepared.metalMsFactor = 0.0;\n prepared.metalMsProbability = 0.0;\n // Reference suppresses loss below roughness .04 and uses the original\n // isotropic alpha even when the single-scattering lobe is anisotropic.\n if (material.specularRoughness >= 0.04 && prepared.weights[OPENPBR_LOBE_METAL] > 0.0) {\n let averageFresnel = openpbr_metal_average_fresnel(material.baseWeight * material.baseColor, material.specularColor);\n prepared.metalMsScale = material.specularWeight * averageFresnel * averageFresnel;\n let viewLoss = openpbr_metal_energy_complement(prepared.specularAlpha, viewCosine);\n prepared.metalMsFactor = viewLoss / max(openpbr_metal_average_energy_complement(prepared.specularAlpha), 1e-12);\n let msEstimate = prepared.weights[OPENPBR_LOBE_METAL] * openpbr_luminance(prepared.metalMsScale) * viewLoss;\n prepared.probabilities[OPENPBR_LOBE_METAL] += msEstimate;\n prepared.metalMsProbability = msEstimate / max(prepared.probabilities[OPENPBR_LOBE_METAL], 1e-12);\n }\n prepared.probabilities[OPENPBR_LOBE_SPECULAR] = prepared.weights[OPENPBR_LOBE_SPECULAR]\n * openpbr_luminance(dielectricFresnelEstimate);\n prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] = prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n * (1.0 - transmissionFresnel)\n * openpbr_luminance(transmissionTint);\n // OpenPBR_DielectricMicrofacetMultipleScatteringLobe, Adobe c91aad1\n // (Apache-2.0). Use the refraction IOR even when coat reflection uses a\n // different ratio. Restrict this addition to solid transmissive materials;\n // opaque/subsurface-only and thin-wall paths retain their existing model.\n // Keep the renderer's exact null boundary discrete: upstream avoids eta=1\n // for refraction, and its tabulation has small nonzero loss there.\n prepared.dielectricMsReflectionScale = vec3f(0.0);\n prepared.dielectricMsTransmissionScale = vec3f(0.0);\n prepared.dielectricMsViewLoss = 0.0;\n prepared.dielectricMsReflectionRatio = 0.0;\n prepared.dielectricMsProbability = 0.0;\n if (!material.thinWalled && material.transmissionWeight > 0.0 &&\n prepared.specularAlpha >= 0.0016 && prepared.weights[OPENPBR_LOBE_TRANSMISSION] > 0.0 &&\n abs(prepared.refractionEtaTOverEtaI - 1.0) > 1e-6) {\n let nonOpaqueWeight = prepared.weights[OPENPBR_LOBE_TRANSMISSION] + prepared.weights[OPENPBR_LOBE_SUBSURFACE];\n prepared.dielectricMsReflectionScale = select(material.specularColor, vec3f(1.0), material.inside) * nonOpaqueWeight;\n prepared.dielectricMsTransmissionScale = transmissionTint * prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n + vec3f(prepared.weights[OPENPBR_LOBE_SUBSURFACE]);\n prepared.dielectricMsReflectionRatio = openpbr_ideal_dielectric_reflection_ratio(prepared.refractionEtaTOverEtaI, prepared.specularAlpha);\n prepared.dielectricMsViewLoss = openpbr_ideal_dielectric_loss(prepared.refractionEtaTOverEtaI, prepared.specularAlpha, viewCosine);\n let averageScale = mix(prepared.dielectricMsTransmissionScale, prepared.dielectricMsReflectionScale, prepared.dielectricMsReflectionRatio);\n let msEstimate = openpbr_luminance(averageScale) * prepared.dielectricMsViewLoss;\n prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] += msEstimate;\n prepared.dielectricMsProbability = msEstimate / max(prepared.probabilities[OPENPBR_LOBE_TRANSMISSION], 1e-12);\n }\n prepared.probabilities[OPENPBR_LOBE_DIFFUSE] = prepared.weights[OPENPBR_LOBE_DIFFUSE]\n * openpbr_luminance(prepared.diffuseColor)\n * prepared.diffuseSpecularEnergyCompensation;\n prepared.probabilities[OPENPBR_LOBE_SUBSURFACE] = prepared.weights[OPENPBR_LOBE_SUBSURFACE]\n * (1.0 - transmissionFresnel)\n * openpbr_luminance(material.subsurfaceColor)\n * prepared.diffuseSpecularEnergyCompensation;\n // Index-matched base interfaces can still reflect through thin film.\n // Keep proposal support without duplicating the Airy optical solve here.\n if (material.coatWeight > 0.0 && HAS_THIN_FILM && material.thinFilmWeight > 0.0) {\n let filmSupport = 0.02 * openpbr_thin_film_presence(material.thinFilmThickness, material.thinFilmWeight);\n prepared.probabilities[OPENPBR_LOBE_SPECULAR] = max(prepared.probabilities[OPENPBR_LOBE_SPECULAR], prepared.weights[OPENPBR_LOBE_SPECULAR] * filmSupport);\n prepared.probabilities[OPENPBR_LOBE_METAL] = max(prepared.probabilities[OPENPBR_LOBE_METAL], prepared.weights[OPENPBR_LOBE_METAL] * material.specularWeight * filmSupport);\n }\n if (material.coatWeight > 0.0 && !material.inside) {\n let baseScale = max(max(prepared.coatBaseScale.x, prepared.coatBaseScale.y), prepared.coatBaseScale.z);\n prepared.probabilities[OPENPBR_LOBE_METAL] *= baseScale;\n prepared.probabilities[OPENPBR_LOBE_SPECULAR] *= baseScale;\n prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] *= baseScale;\n prepared.probabilities[OPENPBR_LOBE_DIFFUSE] *= baseScale;\n prepared.probabilities[OPENPBR_LOBE_SUBSURFACE] *= baseScale;\n }\n openpbr_normalize_probabilities(&prepared);\n return prepared;\n}\n\n\nfn openpbr_substrate_fresnel(prepared: OpenPbrPreparedBsdf, cosine: f32, reflection: bool) -> f32 {\n if (prepared.material.coatWeight <= 0.0) {\n return openpbr_dielectric_fresnel(cosine, prepared.material.relativeIor);\n }\n let transFresnel = openpbr_dielectric_fresnel(cosine, prepared.transmissionReflectionIor);\n if (!reflection) { return transFresnel; }\n let opaqueFresnel = openpbr_dielectric_fresnel(cosine, 1.0 / prepared.specularEtaTOverEtaI);\n let nonOpaque = prepared.material.transmissionWeight + (1.0 - prepared.material.transmissionWeight) * prepared.material.subsurfaceWeight;\n return mix(opaqueFresnel, transFresnel, nonOpaque);\n}\n\nfn openpbr_eval_prepared_lobe(\n prepared: OpenPbrPreparedBsdf,\n lightDirection: vec3f,\n allowedLobe: i32,\n pdf: ptr<function, f32>\n) -> vec3f {\n let material = prepared.material;\n let viewLocal = prepared.viewLocal;\n let lightLocal = openpbr_to_local(prepared.basis, lightDirection);\n let reflection = lightLocal.z * viewLocal.z > 0.0;\n${hasCoatNormals ? /* wgsl */ `\n let independentCoat = material.coatNormalEnabled;\n let geometricReflection = dot(material.geometricNormal, lightDirection) > 0.0;\n let coatLightCosine = dot(prepared.coatBasis.normal, lightDirection);\n let coatReflection = geometricReflection && prepared.coatViewLocal.z > 0.0 && coatLightCosine > 0.0;\n` : ''}\n\n var halfVector: vec3f;\n if (reflection) {\n halfVector = normalize(lightLocal + viewLocal);\n }\n else {\n halfVector = normalize(lightLocal + viewLocal * material.relativeIor);\n }\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n\n var response = vec3f(0.0);\n *pdf = 0.0;\n var lobePdf = 0.0;\n let allowAll = allowedLobe < 0;\n let allowFuzz = allowAll || allowedLobe == OPENPBR_LOBE_FUZZ;\n let allowCoat = allowAll || allowedLobe == OPENPBR_LOBE_COAT;\n let allowMetal = allowAll || allowedLobe == OPENPBR_LOBE_METAL;\n let allowSpecular = allowAll || allowedLobe == OPENPBR_LOBE_SPECULAR;\n let allowTransmission = allowAll ||\n allowedLobe == OPENPBR_LOBE_TRANSMISSION;\n let allowDiffuse = allowAll || allowedLobe == OPENPBR_LOBE_DIFFUSE;\n let allowSubsurface = allowAll ||\n allowedLobe == OPENPBR_LOBE_SUBSURFACE;\n let viewHalf = abs(dot(viewLocal, halfVector));\n // Reflection, solid refraction and thin-wall transmission share the same\n // dielectric optical solve. Select its half angle before evaluating the\n // film, instead of inlining that large solve in each transport branch.\n var thinWallHalf = halfVector;\n var dielectricViewHalf = viewHalf;\n if (!reflection && material.thinWalled) {\n thinWallHalf = normalize(vec3f(lightLocal.xy, -lightLocal.z) + viewLocal);\n if (thinWallHalf.z < 0.0) { thinWallHalf = -thinWallHalf; }\n dielectricViewHalf = abs(dot(viewLocal, thinWallHalf));\n }\n let evaluateDielectric =\n (reflection && prepared.weights[OPENPBR_LOBE_SPECULAR] > 0.0 && allowSpecular) ||\n (!reflection && prepared.weights[OPENPBR_LOBE_TRANSMISSION] > 0.0 && allowTransmission);\n let evaluateMetal = reflection && prepared.weights[OPENPBR_LOBE_METAL] > 0.0 && allowMetal;\n var fresnels: array<vec3f, 2>;\n if (evaluateDielectric) {\n fresnels[0] = vec3f(openpbr_substrate_fresnel(prepared, dielectricViewHalf, reflection));\n }\n if (evaluateMetal) {\n fresnels[1] = openpbr_metal_fresnel(viewHalf, material.baseWeight * material.baseColor, material.specularColor);\n }\n if (HAS_THIN_FILM && material.thinFilmWeight > 0.0) {\n let wavelengths = vec3f(620.0, 540.0, 450.0);\n // Dielectric and conductor substrates share one complete optical graph.\n for (var substrate = 0u; substrate < (rtUniform.loopUnrollGuard >> 1u); substrate++) {\n if (!select(evaluateDielectric, evaluateMetal, substrate == 1u)) { continue; }\n var film = vec3f(0.0);\n let incidentCosine = select(dielectricViewHalf, viewHalf, substrate == 1u);\n for (var channel = 0u; channel + 1u < rtUniform.loopUnrollGuard; channel++) {\n var ior = vec2f(prepared.thinFilmBaseIor, 0.0);\n if (substrate == 1u) {\n ior = openpbr_metal_complex_ior(material.baseWeight * material.baseColor[channel], material.specularColor[channel]);\n }\n film[channel] = openpbr_thin_film_reflectance_channel(incidentCosine,\n wavelengths[channel], material.thinFilmThickness, prepared.thinFilmExteriorIor, material.thinFilmIor, ior);\n }\n fresnels[substrate] = mix(fresnels[substrate], film,\n openpbr_thin_film_presence(material.thinFilmThickness, material.thinFilmWeight));\n }\n }\n let substrateFresnel = fresnels[0];\n let diffuseSpecularEnergyCompensation =\n prepared.diffuseSpecularEnergyCompensation\n * openpbr_opaque_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha,\n abs(lightLocal.z)\n );\n\n${hasCoatNormals ? /* wgsl */ `\n // Each lobe retains its own shading hemisphere; topology is geometric.\n if (!independentCoat || (lightLocal.z != 0.0 && reflection == geometricReflection)) {\n` : ''}\n if (reflection) {\n if (prepared.weights[OPENPBR_LOBE_DIFFUSE] > 0.0 && allowDiffuse) {\n response += prepared.weights[OPENPBR_LOBE_DIFFUSE] * openpbr_eval_diffuse(\n material,\n prepared.diffuseColor,\n viewLocal,\n lightLocal,\n diffuseSpecularEnergyCompensation,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_DIFFUSE] * lobePdf;\n }\n\n if (prepared.weights[OPENPBR_LOBE_SUBSURFACE] > 0.0 && material.thinWalled &&\n allowSubsurface) {\n response += 0.5 * prepared.weights[OPENPBR_LOBE_SUBSURFACE] * openpbr_eval_diffuse(\n material,\n material.subsurfaceColor,\n viewLocal,\n lightLocal,\n diffuseSpecularEnergyCompensation,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_SUBSURFACE] * 0.5 * lobePdf;\n }\n\n if (prepared.weights[OPENPBR_LOBE_SPECULAR] > 0.0 && allowSpecular) {\n var dielectricFresnel = substrateFresnel;\n if (material.thinWalled) {\n dielectricFresnel = openpbr_thin_wall_reflection_coefficient(\n dielectricFresnel,\n material.transmissionWeight\n );\n }\n let fresnel = material.specularColor * dielectricFresnel;\n response += prepared.weights[OPENPBR_LOBE_SPECULAR] * openpbr_eval_microfacet_reflection(\n material,\n viewLocal,\n lightLocal,\n halfVector,\n fresnel,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_SPECULAR] * lobePdf;\n }\n\n if (prepared.weights[OPENPBR_LOBE_METAL] > 0.0 && allowMetal) {\n let fresnel = material.specularWeight * fresnels[1];\n response += prepared.weights[OPENPBR_LOBE_METAL] * openpbr_eval_microfacet_reflection(\n material,\n viewLocal,\n lightLocal,\n halfVector,\n fresnel,\n &lobePdf\n );\n if (prepared.metalMsFactor > 0.0) {\n let factor = min(prepared.metalMsFactor * openpbr_metal_energy_complement(prepared.specularAlpha, lightLocal.z), 1.0 / max(lightLocal.z, 1e-8));\n response += prepared.weights[OPENPBR_LOBE_METAL] * prepared.metalMsScale * factor * INVPI;\n lobePdf = mix(lobePdf, 0.5 * INVPI, prepared.metalMsProbability);\n }\n *pdf += prepared.probabilities[OPENPBR_LOBE_METAL] * lobePdf;\n }\n }\n else {\n let fresnel = substrateFresnel;\n if (prepared.weights[OPENPBR_LOBE_TRANSMISSION] > 0.0 && allowTransmission) {\n if (material.thinWalled) {\n let mirroredLight = vec3f(lightLocal.xy, -lightLocal.z);\n let windowFresnel = openpbr_thin_wall_reflection_coefficient(\n substrateFresnel,\n 1.0\n );\n let tint = openpbr_final_transmission_tint(\n material,\n abs(viewLocal.z),\n prepared.refractionEtaTOverEtaI\n );\n response += prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n * openpbr_eval_microfacet_reflection(\n material,\n viewLocal,\n mirroredLight,\n thinWallHalf,\n tint * (vec3f(1.0) - windowFresnel),\n &lobePdf\n );\n }\n else {\n response += prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n * openpbr_eval_microfacet_refraction(\n material,\n material.relativeIor,\n openpbr_transmission_tint(material),\n viewLocal,\n lightLocal,\n halfVector,\n fresnel,\n &lobePdf\n );\n }\n if (prepared.dielectricMsProbability > 0.0) {\n lobePdf *= 1.0 - prepared.dielectricMsProbability;\n }\n *pdf += prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] * lobePdf;\n }\n if (prepared.weights[OPENPBR_LOBE_SUBSURFACE] > 0.0 && allowSubsurface) {\n if (material.thinWalled) {\n let flippedLight = vec3f(lightLocal.xy, -lightLocal.z);\n response += 0.5 * prepared.weights[OPENPBR_LOBE_SUBSURFACE]\n * openpbr_eval_diffuse(\n material,\n material.subsurfaceColor,\n viewLocal,\n flippedLight,\n diffuseSpecularEnergyCompensation,\n &lobePdf\n );\n lobePdf *= 0.5;\n }\n else {\n response += prepared.weights[OPENPBR_LOBE_SUBSURFACE] * openpbr_eval_subsurface_boundary(\n material,\n viewLocal,\n lightLocal,\n &lobePdf\n );\n }\n *pdf += prepared.probabilities[OPENPBR_LOBE_SUBSURFACE] * lobePdf;\n }\n }\n\n // Reference value includes the hemisphere ratio before the grazing clamp.\n // Reference scale_trans has no extra eta^2 factor; single refraction keeps\n // its existing radiance Jacobian. Both proposals share the aggregate PDF.\n if (prepared.dielectricMsProbability > 0.0 && allowTransmission) {\n let cosine = abs(lightLocal.z);\n if (cosine > 0.0) {\n let ior = select(1.0 / prepared.refractionEtaTOverEtaI, prepared.refractionEtaTOverEtaI, reflection);\n let ratio = select(1.0 - prepared.dielectricMsReflectionRatio, prepared.dielectricMsReflectionRatio, reflection);\n let factors = prepared.dielectricMsViewLoss * openpbr_ideal_dielectric_loss(ior, prepared.specularAlpha, cosine)\n / max(openpbr_ideal_dielectric_average_loss(ior, prepared.specularAlpha), 1e-12);\n let factor = min(factors * ratio, 1.0 / cosine);\n let scale = select(prepared.dielectricMsTransmissionScale, prepared.dielectricMsReflectionScale, reflection);\n response += scale * factor * INVPI;\n *pdf += prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] * prepared.dielectricMsProbability * ratio * 0.5 * INVPI;\n }\n }\n\n${hasCoatNormals ? /* wgsl */ ` } // base hemisphere support` : ''}\n if (material.coatWeight > 0.0) {\n response *= openpbr_coat_base_scale(prepared, ${hasCoatNormals ? 'select(lightLocal.z, coatLightCosine, independentCoat)' : 'lightLocal.z'});\n }\n${hasCoatNormals ? /* wgsl */ `\n // Values are BSDF*cos in world solid angle before the frames are combined.\n if (independentCoat) { response *= abs(lightLocal.z); }\n` : ''}\n\n${hasAnisotropyDirections ? /* wgsl */ `\n if (${hasCoatNormals ? 'select(reflection, coatReflection, independentCoat)' : 'reflection'} && prepared.weights[OPENPBR_LOBE_COAT] > 0.0) {\n var coatView = viewLocal;\n var coatLight = lightLocal;\n var coatHalf = halfVector;\n var coatViewHalf = viewHalf;\n if (prepared.directionFrames) {\n coatView = ${hasCoatNormals ? 'select(openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal)), prepared.coatViewLocal, independentCoat)' : 'openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal))'};\n coatLight = openpbr_to_local(prepared.coatBasis, lightDirection);\n coatHalf = normalize(coatLight + coatView);\n if (coatHalf.z < 0.0) { coatHalf = -coatHalf; }\n coatViewHalf = abs(dot(coatView, coatHalf));\n }\n\n let coatFresnel = vec3f(openpbr_dielectric_fresnel(\n coatViewHalf,\n 1.0 / material.coatIor\n ));\n var coatMaterial = material;\n let coatAlpha = openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy);\n coatMaterial.alphaX = coatAlpha.x;\n coatMaterial.alphaY = coatAlpha.y;\n if (allowCoat) {\n response += prepared.weights[OPENPBR_LOBE_COAT]${hasCoatNormals ? ' * select(1.0, max(coatLightCosine, 0.0), independentCoat)' : ''} * openpbr_eval_microfacet_reflection(\n coatMaterial,\n coatView,\n coatLight,\n coatHalf,\n coatFresnel,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_COAT] * lobePdf;\n }\n }\n` : /* wgsl */ `\n if (${hasCoatNormals ? 'select(reflection, coatReflection, independentCoat)' : 'reflection'} && prepared.weights[OPENPBR_LOBE_COAT] > 0.0) {\n let coatFresnel = vec3f(openpbr_dielectric_fresnel(\n viewHalf,\n 1.0 / material.coatIor\n ));\n var coatMaterial = material;\n let coatAlpha = openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy);\n coatMaterial.alphaX = coatAlpha.x;\n coatMaterial.alphaY = coatAlpha.y;\n if (allowCoat) {\n response += prepared.weights[OPENPBR_LOBE_COAT]${hasCoatNormals ? ' * select(1.0, max(coatLightCosine, 0.0), independentCoat)' : ''} * openpbr_eval_microfacet_reflection(\n coatMaterial,\n viewLocal,\n lightLocal,\n halfVector,\n coatFresnel,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_COAT] * lobePdf;\n }\n }\n`}\n\n if (${hasCoatNormals ? 'select(reflection, geometricReflection, independentCoat)' : 'reflection'} && prepared.weights[OPENPBR_LOBE_FUZZ] > 0.0) {\n let fuzzAlbedo = openpbr_fuzz_directional_albedo(\n abs(viewLocal.z),\n material.fuzzRoughness\n );\n response *= max(\n vec3f(0.0),\n vec3f(1.0 - prepared.weights[OPENPBR_LOBE_FUZZ] * fuzzAlbedo)\n );\n if (allowFuzz) {\n response += prepared.weights[OPENPBR_LOBE_FUZZ]${hasCoatNormals ? ' * select(1.0, max(lightLocal.z, 0.0), independentCoat)' : ''} * openpbr_eval_fuzz(\n material,\n viewLocal,\n lightLocal,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_FUZZ] * lobePdf;\n }\n }\n\n return response * ${hasCoatNormals ? 'select(abs(lightLocal.z), 1.0, independentCoat)' : 'abs(lightLocal.z)'};\n}\n\nfn openpbr_eval_prepared(\n prepared: OpenPbrPreparedBsdf,\n lightDirection: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n return openpbr_eval_prepared_lobe(prepared, lightDirection, -1, pdf);\n}\n\n\n\n\n\nfn openpbr_eval(\n tangent: vec3f,\n material: OpenPbrResolvedMaterial,\n viewDirection: vec3f,\n normal: vec3f,\n lightDirection: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n let prepared = openpbr_prepare(\n tangent,\n material,\n viewDirection,\n normal\n );\n return openpbr_eval_prepared(prepared, lightDirection, pdf);\n}\n\n\n\n\n// Direction-only proposals do not require the layered response or film optics.\nfn openpbr_sample_direction(\n prepared: OpenPbrPreparedBsdf, randomSamples: vec3f,\n lightDirection: ptr<function, vec3f>, isTransmissionEvent: ptr<function, bool>,\n sampledLobeRoughness: ptr<function, f32>, sampledLobeSpecular: ptr<function, bool>,\n sampledLobeDiffuse: ptr<function, bool>, sampledLobeIndex: ptr<function, u32>\n) -> bool {\n let material = prepared.material;\n let viewLocal = prepared.viewLocal;\n var randomX = randomSamples.x;\n let randomY = randomSamples.y;\n let lobeSample = randomSamples.z;\n *isTransmissionEvent = false;\n *sampledLobeRoughness = 1.0;\n *sampledLobeSpecular = false;\n *sampledLobeDiffuse = false;\n *sampledLobeIndex = u32(OPENPBR_LOBE_DIFFUSE);\n *lightDirection = prepared.basis.normal;\n\n var cdf: array<f32, OPENPBR_LOBE_COUNT>;\n cdf[OPENPBR_LOBE_FUZZ] = prepared.probabilities[OPENPBR_LOBE_FUZZ];\n let lobeCount = min(OPENPBR_LOBE_COUNT, i32(rtUniform.loopUnrollGuard * 2u));\n for (var lobe = 1; lobe < lobeCount; lobe++) {\n cdf[lobe] = cdf[lobe - 1] + prepared.probabilities[lobe];\n }\n if (cdf[OPENPBR_LOBE_SUBSURFACE] <= 0.0) {\n return false;\n }\n\n var lightLocal: vec3f;\n if (lobeSample < cdf[OPENPBR_LOBE_FUZZ] || (\n lobeSample >= cdf[OPENPBR_LOBE_TRANSMISSION] && lobeSample < cdf[OPENPBR_LOBE_DIFFUSE]\n )) {\n let sampledFuzz = lobeSample < cdf[OPENPBR_LOBE_FUZZ];\n *sampledLobeRoughness = select(\n 1.0,\n material.fuzzRoughness,\n sampledFuzz\n );\n *sampledLobeDiffuse = !sampledFuzz;\n *sampledLobeIndex = select(\n u32(OPENPBR_LOBE_DIFFUSE),\n u32(OPENPBR_LOBE_FUZZ),\n sampledFuzz\n );\n lightLocal = sampling_cosine_hemisphere(vec2f(randomX, randomY));\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n else if (lobeSample < cdf[OPENPBR_LOBE_COAT]) {\n *sampledLobeRoughness = material.coatRoughness;\n *sampledLobeSpecular = true;\n *sampledLobeIndex = u32(OPENPBR_LOBE_COAT);\n var coatView = viewLocal;\n if (prepared.directionFrames) { coatView = ${hasCoatNormals ? 'select(openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal)), prepared.coatViewLocal, material.coatNormalEnabled)' : 'openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal))'}; }\n var halfVector = sampling_ggx_visible_normal(\n coatView,\n // The VNDF must use the same alpha as the evaluated coat PDF.\n openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy),\n vec2f(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n lightLocal = normalize(reflect(-coatView, halfVector));\n${hasCoatNormals ? /* wgsl */ `\n if (material.coatNormalEnabled) {\n // Validate against the sampled coat frame, never the base normal.\n *lightDirection = openpbr_to_world(prepared.coatBasis, lightLocal);\n return lightLocal.z > 0.0 && dot(material.geometricNormal, *lightDirection) > 0.0;\n }\n` : ''}\n if (prepared.directionFrames) { lightLocal = openpbr_to_local(prepared.basis, openpbr_to_world(prepared.coatBasis, lightLocal)); }\n }\n` : /* wgsl */ `\n else if (lobeSample < cdf[OPENPBR_LOBE_COAT]) {\n *sampledLobeRoughness = material.coatRoughness;\n *sampledLobeSpecular = true;\n *sampledLobeIndex = u32(OPENPBR_LOBE_COAT);\n var halfVector = sampling_ggx_visible_normal(\n viewLocal,\n // The VNDF must use the same alpha as the evaluated coat PDF.\n openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy),\n vec2f(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n lightLocal = normalize(reflect(-viewLocal, halfVector));\n }\n`}\n else if (lobeSample < cdf[OPENPBR_LOBE_SPECULAR]) {\n *sampledLobeRoughness = material.specularRoughness;\n *sampledLobeSpecular = true;\n *sampledLobeIndex = select(\n u32(OPENPBR_LOBE_SPECULAR),\n u32(OPENPBR_LOBE_METAL),\n lobeSample < cdf[OPENPBR_LOBE_METAL]\n );\n let isMetal = lobeSample < cdf[OPENPBR_LOBE_METAL];\n let metalDraw = (lobeSample - cdf[OPENPBR_LOBE_COAT]) / max(prepared.probabilities[OPENPBR_LOBE_METAL], 1e-12);\n if (isMetal && metalDraw < prepared.metalMsProbability) {\n let z = randomX;\n let radial = sqrt(max(0.0, 1.0 - z * z));\n let azimuth = TWOPI * randomY;\n lightLocal = vec3f(radial * cos(azimuth), radial * sin(azimuth), z);\n }\n else {\n var halfVector = sampling_ggx_visible_normal(\n viewLocal,\n vec2f(material.alphaX, material.alphaY),\n vec2f(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n lightLocal = normalize(reflect(-viewLocal, halfVector));\n }\n }\n else if (lobeSample < cdf[OPENPBR_LOBE_TRANSMISSION]) {\n *sampledLobeRoughness = material.specularRoughness;\n *sampledLobeSpecular = true;\n *isTransmissionEvent = true;\n *sampledLobeIndex = u32(OPENPBR_LOBE_TRANSMISSION);\n // Split the existing BSDF_LOBE interval. BSDF_DIRECTION retains its\n // two dimensions; rand.x selects and remaps the reference hemisphere.\n let msDraw = (lobeSample - cdf[OPENPBR_LOBE_SPECULAR]) / max(prepared.probabilities[OPENPBR_LOBE_TRANSMISSION], 1e-12);\n if (prepared.dielectricMsProbability > 0.0 && msDraw < prepared.dielectricMsProbability) {\n let ratio = prepared.dielectricMsReflectionRatio;\n let reflectMs = randomX < ratio;\n var z: f32;\n if (reflectMs) { z = min(randomX / ratio, 0.99999994); }\n else { z = -min((randomX - ratio) / (1.0 - ratio), 0.99999994); }\n let radial = sqrt(max(0.0, 1.0 - z * z));\n let azimuth = TWOPI * randomY;\n lightLocal = vec3f(radial * cos(azimuth), radial * sin(azimuth), z);\n *isTransmissionEvent = !reflectMs;\n *sampledLobeIndex = select(u32(OPENPBR_LOBE_TRANSMISSION), u32(OPENPBR_LOBE_SPECULAR), reflectMs);\n }\n else {\n var halfVector = sampling_ggx_visible_normal(\n viewLocal,\n vec2f(material.alphaX, material.alphaY),\n vec2f(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n if (material.thinWalled) {\n let reflectedLocal = normalize(reflect(-viewLocal, halfVector));\n lightLocal = vec3f(reflectedLocal.xy, -reflectedLocal.z);\n }\n else {\n lightLocal = refract(\n -viewLocal,\n halfVector,\n prepared.material.relativeIor\n );\n if (dot(lightLocal, lightLocal) <= 1e-8) {\n *isTransmissionEvent = false;\n return false;\n }\n lightLocal = normalize(lightLocal);\n }\n }\n }\n else {\n *sampledLobeRoughness = 1.0;\n *sampledLobeDiffuse = true;\n *isTransmissionEvent = true;\n *sampledLobeIndex = u32(OPENPBR_LOBE_SUBSURFACE);\n if (material.thinWalled) {\n let transmit = randomX >= 0.5;\n if (transmit) {\n randomX = (randomX - 0.5) * 2.0;\n }\n else {\n randomX *= 2.0;\n }\n lightLocal = sampling_cosine_hemisphere(vec2f(randomX, randomY));\n if (transmit) {\n lightLocal.z = -lightLocal.z;\n }\n else {\n *isTransmissionEvent = false;\n }\n }\n else {\n lightLocal = sampling_cosine_hemisphere(vec2f(randomX, randomY));\n lightLocal.z = -lightLocal.z;\n }\n }\n\n *lightDirection = openpbr_to_world(prepared.basis, lightLocal);\n // VNDF reflection may land below the macrosurface. A mirrored thin-wall\n // transmission draw can likewise land above it. Reject that draw before\n // aggregate evaluation can accidentally turn it into the opposite lobe.\n if (lightLocal.z == 0.0 || (lightLocal.z < 0.0) != *isTransmissionEvent) {\n return false;\n }\n${hasCoatNormals ? /* wgsl */ `\n if (material.coatNormalEnabled &&\n ((dot(material.geometricNormal, *lightDirection) <= 0.0) != *isTransmissionEvent)) { return false; }\n` : ''}\n return true;\n}\n\nfn openpbr_sample_prepared(\n prepared: OpenPbrPreparedBsdf,\n randomSamples: vec3f,\n lightDirection: ptr<function, vec3f>,\n pdf: ptr<function, f32>,\n isTransmissionEvent: ptr<function, bool>,\n sampledLobeRoughness: ptr<function, f32>,\n sampledLobeSpecular: ptr<function, bool>,\n sampledLobeDiffuse: ptr<function, bool>,\n sampledLobeIndex: ptr<function, u32>,\n marginalPdf: ptr<function, f32>,\n fullResponse: ptr<function, vec3f>\n) -> vec3f {\n *pdf = 0.0;\n *marginalPdf = 0.0;\n *fullResponse = vec3f(0.0);\n // Solid microfacet refraction is singular at exactly eta=1: the usual\n // half vector normalizes light + eta * view, which is zero for straight\n // transmission. Resolve the physically null boundary as a discrete,\n // unit-throughput continuation instead of losing the path to NaNs.\n if (!prepared.material.thinWalled &&\n abs(prepared.material.relativeIor - 1.0) <= 1e-6 &&\n prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] >= 1.0 - 1e-6) {\n *lightDirection = openpbr_to_world(\n prepared.basis,\n -prepared.viewLocal\n );\n let nullBoundaryTint = openpbr_transmission_tint(prepared.material)\n * openpbr_coat_base_scale(prepared, ${hasCoatNormals ? 'select(-prepared.viewLocal.z, dot(prepared.coatBasis.normal, *lightDirection), prepared.material.coatNormalEnabled)' : '-prepared.viewLocal.z'});\n *pdf = 1.0;\n *marginalPdf = 1.0;\n *fullResponse = nullBoundaryTint;\n *isTransmissionEvent = true;\n *sampledLobeRoughness = 0.0;\n *sampledLobeSpecular = true;\n *sampledLobeDiffuse = false;\n *sampledLobeIndex = u32(OPENPBR_LOBE_TRANSMISSION);\n return nullBoundaryTint;\n }\n if (!openpbr_sample_direction(prepared, randomSamples, lightDirection,\n isTransmissionEvent, sampledLobeRoughness, sampledLobeSpecular,\n sampledLobeDiffuse, sampledLobeIndex)) { return vec3f(0.0); }\n let response = openpbr_eval_prepared(prepared, *lightDirection, pdf);\n *marginalPdf = *pdf;\n *fullResponse = response;\n return response;\n}\n\nfn openpbr_sample(\n tangent: vec3f,\n material: OpenPbrResolvedMaterial,\n randomSamples: vec3f,\n viewDirection: vec3f,\n normal: vec3f,\n lightDirection: ptr<function, vec3f>,\n pdf: ptr<function, f32>,\n isTransmissionEvent: ptr<function, bool>,\n sampledLobeRoughness: ptr<function, f32>,\n sampledLobeSpecular: ptr<function, bool>,\n sampledLobeDiffuse: ptr<function, bool>,\n sampledLobeIndex: ptr<function, u32>,\n marginalPdf: ptr<function, f32>\n) -> vec3f {\n let prepared = openpbr_prepare(\n tangent,\n material,\n viewDirection,\n normal\n );\n var fullResponse = vec3f(0.0);\n return openpbr_sample_prepared(\n prepared,\n randomSamples,\n lightDirection,\n pdf,\n isTransmissionEvent,\n sampledLobeRoughness,\n sampledLobeSpecular,\n sampledLobeDiffuse,\n sampledLobeIndex,\n marginalPdf,\n &fullResponse\n );\n}`;\n\n}\n\nexport default createOpenPbrBsdfWgsl();\n"]}
1
+ {"version":3,"file":"openPbrBsdf.wgsl.js","sourceRoot":"","sources":["../../../../src/renderer/wgsl/chunks/openPbrBsdf.wgsl.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * OpenPBR energy-compensation, EON and thin-film portions Copyright 2026 Adobe.\n * Licensed under the Apache License, Version 2.0.\n * https://www.apache.org/licenses/LICENSE-2.0\n */\n\n/*\n * Renderer-specific OpenPBR layered BSDF.\n *\n * The prepare/eval/sample/pdf organization and fixed-lobe composition follow:\n * - https://github.com/adobe/openpbr-bsdf\n * - https://github.com/portsmouth/OpenPBR-viewer\n *\n * This compact GPU implementation reuses the renderer's GGX VNDF and volume\n * integrator. Adobe's opaque-dielectric energy tables are used for the base\n * rough-diffuse/specular coupling; fuzz still uses an analytic approximation.\n */\n\nimport { OPENPBR_METAL_ENERGY_LUT_ROW, OPENPBR_METAL_ENERGY_LUT_AVERAGE_ROW, OPENPBR_DIELECTRIC_ENERGY_LUT_ROW, OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW, OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW } from '../../openPbrEnergyLut.js';\nconst LOBE_INDICES = [0, 1, 2, 3, 4, 5, 6];\n\nexport function createOpenPbrBsdfWgsl(hasAnisotropyDirections = true, hasCoatNormals = false) {\n return /* wgsl */ `\nconst OPENPBR_LOBE_FUZZ: i32 = 0;\nconst OPENPBR_LOBE_COAT: i32 = 1;\nconst OPENPBR_LOBE_METAL: i32 = 2;\nconst OPENPBR_LOBE_SPECULAR: i32 = 3;\nconst OPENPBR_LOBE_TRANSMISSION: i32 = 4;\nconst OPENPBR_LOBE_DIFFUSE: i32 = 5;\nconst OPENPBR_LOBE_SUBSURFACE: i32 = 6;\nconst OPENPBR_LOBE_COUNT: i32 = ${LOBE_INDICES.length};\n\n// Eager boolean operators below combine pure scalar predicates. This avoids\n// multiplying short-circuit control flow when DXC inlines the layered BSDF.\n\nstruct OpenPbrBasis {\n tangent: vec3f,\n bitangent: vec3f,\n normal: vec3f,\n};\n\nstruct OpenPbrPreparedBsdf {\n material: OpenPbrResolvedMaterial,\n basis: OpenPbrBasis,\n${hasAnisotropyDirections ? /* wgsl */ `\n coatBasis: OpenPbrBasis,\n directionFrames: bool,\n` : /* wgsl */ ``}\n${hasCoatNormals ? /* wgsl */ ` coatViewLocal: vec3f,` : ''}\n viewLocal: vec3f,\n diffuseColor: vec3f,\n specularEtaTOverEtaI: f32,\n refractionEtaTOverEtaI: f32,\n transmissionReflectionIor: f32,\n thinFilmExteriorIor: f32,\n thinFilmBaseIor: f32,\n specularAlpha: f32,\n diffuseSpecularEnergyCompensation: f32,\n coatBaseScale: vec3f,\n metalMsScale: vec3f,\n metalMsFactor: f32,\n metalMsProbability: f32,\n\n dielectricMsReflectionScale: vec3f,\n dielectricMsTransmissionScale: vec3f,\n dielectricMsViewLoss: f32,\n dielectricMsReflectionRatio: f32,\n dielectricMsProbability: f32,\n\n weights: array<f32, OPENPBR_LOBE_COUNT>,\n probabilities: array<f32, OPENPBR_LOBE_COUNT>,\n};\n\nfn openpbr_luminance(color: vec3f) -> f32 {\n return dot(color, vec3f(0.212671, 0.715160, 0.072169));\n}\n\nfn openpbr_f0_from_ior(etaTOverEtaI: f32) -> f32 {\n let value = (etaTOverEtaI - 1.0) / (etaTOverEtaI + 1.0);\n return value * value;\n}\n\nfn openpbr_apply_specular_weight_to_ior(\n etaTOverEtaI: f32,\n specularWeight: f32\n) -> f32 {\n let scaledF0 = min(\n openpbr_f0_from_ior(etaTOverEtaI) * specularWeight,\n 0.9999\n );\n let sqrtF0 = sqrt(scaledF0);\n let externalIor = (1.0 + sqrtF0) / (1.0 - sqrtF0);\n return select(externalIor, 1.0 / externalIor, etaTOverEtaI < 1.0);\n}\n\nfn openpbr_dielectric_fresnel(cosTheta: f32, relativeIor: f32) -> f32 {\n if (abs(relativeIor - 1.0) < 1e-6) {\n return 0.0;\n }\n return sampling_dielectric_fresnel(cosTheta, relativeIor);\n}\n\nfn openpbr_energy_ior_index(ior: f32) -> f32 {\n if (ior < 1.0) {\n return 15.0 - ((1.0 / ior - 1.0) / 1.5) * 15.0;\n }\n return 16.0 + ((ior - 1.0) / 1.5) * 15.0;\n}\n\nfn openpbr_energy_fetch(cosIndex: i32, row: i32) -> f32 {\n return textureLoad(openPbrEnergyLut, vec2i(cosIndex, row), 0).r;\n}\n\nfn openpbr_energy_trilinear(\n exactIor: f32,\n exactAlpha: f32,\n exactCosine: f32\n) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let cosineIndex = clamp(exactCosine, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let cosine0 = i32(floor(cosineIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let cosine1 = min(cosine0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let cosineMix = cosineIndex - f32(cosine0);\n\n let v000 = openpbr_energy_fetch(cosine0, ior0 * 32 + alpha0);\n let v001 = openpbr_energy_fetch(cosine1, ior0 * 32 + alpha0);\n let v010 = openpbr_energy_fetch(cosine0, ior0 * 32 + alpha1);\n let v011 = openpbr_energy_fetch(cosine1, ior0 * 32 + alpha1);\n let v100 = openpbr_energy_fetch(cosine0, ior1 * 32 + alpha0);\n let v101 = openpbr_energy_fetch(cosine1, ior1 * 32 + alpha0);\n let v110 = openpbr_energy_fetch(cosine0, ior1 * 32 + alpha1);\n let v111 = openpbr_energy_fetch(cosine1, ior1 * 32 + alpha1);\n let v00 = mix(v000, v100, iorMix);\n let v01 = mix(v001, v101, iorMix);\n let v10 = mix(v010, v110, iorMix);\n let v11 = mix(v011, v111, iorMix);\n return mix(\n mix(v00, v10, alphaMix),\n mix(v01, v11, alphaMix),\n cosineMix\n );\n}\n\nfn openpbr_energy_bilinear(exactIor: f32, exactAlpha: f32) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let v00 = openpbr_energy_fetch(alpha0, 1024 + ior0);\n let v01 = openpbr_energy_fetch(alpha1, 1024 + ior0);\n let v10 = openpbr_energy_fetch(alpha0, 1024 + ior1);\n let v11 = openpbr_energy_fetch(alpha1, 1024 + ior1);\n return mix(\n mix(v00, v10, iorMix),\n mix(v01, v11, iorMix),\n alphaMix\n );\n}\n\n// Adobe c91aad1 ideal dielectric tables clamp IOR indices; unlike opaque\n// dielectric data they do not extrapolate outside [0.4, 2.5].\nfn openpbr_ideal_energy_trilinear(\n exactIor: f32,\n exactAlpha: f32,\n exactCosine: f32\n) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let cosineIndex = clamp(exactCosine, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let cosine0 = i32(floor(cosineIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let cosine1 = min(cosine0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let cosineMix = cosineIndex - f32(cosine0);\n\n let v000 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha0);\n let v001 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha0);\n let v010 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha1);\n let v011 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha1);\n let v100 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha0);\n let v101 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha0);\n let v110 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha1);\n let v111 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha1);\n let v00 = mix(v000, v100, iorMix);\n let v01 = mix(v001, v101, iorMix);\n let v10 = mix(v010, v110, iorMix);\n let v11 = mix(v011, v111, iorMix);\n return mix(\n mix(v00, v10, alphaMix),\n mix(v01, v11, alphaMix),\n cosineMix\n );\n}\n\nfn openpbr_ideal_average_bilinear(exactIor: f32, exactAlpha: f32) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let v00 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior0);\n let v01 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior0);\n let v10 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior1);\n let v11 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior1);\n return mix(\n mix(v00, v10, iorMix),\n mix(v01, v11, iorMix),\n alphaMix\n );\n}\n\nfn openpbr_ideal_ratio_bilinear(exactIor: f32, exactAlpha: f32) -> f32 {\n let iorIndex = clamp(exactIor, 0.0, 31.0);\n let alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n let ior0 = i32(floor(iorIndex));\n let alpha0 = i32(floor(alphaIndex));\n let ior1 = min(ior0 + 1, 31);\n let alpha1 = min(alpha0 + 1, 31);\n let iorMix = iorIndex - f32(ior0);\n let alphaMix = alphaIndex - f32(alpha0);\n let v00 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior0);\n let v01 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior0);\n let v10 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior1);\n let v11 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior1);\n return mix(\n mix(v00, v10, iorMix),\n mix(v01, v11, iorMix),\n alphaMix\n );\n}\n\nfn openpbr_ideal_dielectric_loss(ior: f32, alpha: f32, cosine: f32) -> f32 {\n return openpbr_ideal_energy_trilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0, cosine * 31.0);\n}\nfn openpbr_ideal_dielectric_average_loss(ior: f32, alpha: f32) -> f32 {\n return openpbr_ideal_average_bilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0);\n}\nfn openpbr_ideal_dielectric_reflection_ratio(ior: f32, alpha: f32) -> f32 {\n return openpbr_ideal_ratio_bilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0);\n}\n\n// Adobe openpbr_microfacet_multiple_scattering_{data,lobes}.h (c91aad1):\n// ideal-metal loss E=1-singleScatterEnergy, indexed by sqrt(alpha) and cosine.\nfn openpbr_metal_energy_complement(alpha: f32, cosine: f32) -> f32 {\n let roughnessIndex = clamp(sqrt(alpha) * 31.0, 0.0, 31.0);\n let cosineIndex = clamp(cosine * 31.0, 0.0, 31.0);\n let r0 = i32(floor(roughnessIndex));\n let c0 = i32(floor(cosineIndex));\n let r1 = min(r0 + 1, 31);\n let c1 = min(c0 + 1, 31);\n return mix(\n mix(openpbr_energy_fetch(c0, ${OPENPBR_METAL_ENERGY_LUT_ROW} + r0), openpbr_energy_fetch(c1, ${OPENPBR_METAL_ENERGY_LUT_ROW} + r0), fract(cosineIndex)),\n mix(openpbr_energy_fetch(c0, ${OPENPBR_METAL_ENERGY_LUT_ROW} + r1), openpbr_energy_fetch(c1, ${OPENPBR_METAL_ENERGY_LUT_ROW} + r1), fract(cosineIndex)),\n fract(roughnessIndex));\n}\n\nfn openpbr_metal_average_energy_complement(alpha: f32) -> f32 {\n let index = clamp(sqrt(alpha) * 31.0, 0.0, 31.0);\n let i0 = i32(floor(index));\n return mix(openpbr_energy_fetch(i0, ${OPENPBR_METAL_ENERGY_LUT_AVERAGE_ROW}),\n openpbr_energy_fetch(min(i0 + 1, 31), ${OPENPBR_METAL_ENERGY_LUT_AVERAGE_ROW}), fract(index));\n}\n\nfn openpbr_extrapolate_energy_complement(value: f32, ior: f32) -> f32 {\n if ((ior > 2.5) | (ior < 0.4)) {\n let maxF0 = openpbr_f0_from_ior(2.5);\n let progress = (openpbr_f0_from_ior(ior) - maxF0) / (1.0 - maxF0);\n return (1.0 - progress) * value;\n }\n return value;\n}\n\nfn openpbr_opaque_energy_complement(\n ior: f32,\n alpha: f32,\n cosine: f32\n) -> f32 {\n let value = openpbr_energy_trilinear(\n openpbr_energy_ior_index(ior),\n sqrt(alpha) * 31.0,\n cosine * 31.0\n );\n return openpbr_extrapolate_energy_complement(value, ior);\n}\n\nfn openpbr_opaque_average_energy_complement(ior: f32, alpha: f32) -> f32 {\n let value = openpbr_energy_bilinear(\n openpbr_energy_ior_index(ior),\n sqrt(alpha) * 31.0\n );\n return openpbr_extrapolate_energy_complement(value, ior);\n}\n\nfn openpbr_to_world(basis: OpenPbrBasis, direction: vec3f) -> vec3f {\n return direction.x * basis.tangent\n + direction.y * basis.bitangent\n + direction.z * basis.normal;\n}\n\nfn openpbr_to_local(basis: OpenPbrBasis, direction: vec3f) -> vec3f {\n return vec3f(\n dot(direction, basis.tangent),\n dot(direction, basis.bitangent),\n dot(direction, basis.normal)\n );\n}\n\nfn openpbr_make_basis(\n inputTangent: vec3f,\n normal: vec3f,\n anisotropy: f32\n) -> OpenPbrBasis {\n var basis: OpenPbrBasis;\n basis.normal = normal;\n var tangent = inputTangent;\n var bitangent = vec3f(0.0);\n if ((dot(tangent, tangent) < 0.001) | (anisotropy <= 0.0)) {\n sampling_orthonormal_basis(normal, &tangent, &bitangent);\n }\n else {\n bitangent = normalize(cross(normal, tangent));\n tangent = normalize(cross(bitangent, normal));\n }\n basis.tangent = tangent;\n basis.bitangent = bitangent;\n return basis;\n}\n\nfn openpbr_fresnel_s(\n incidentIor: f32,\n transmittedIor: f32,\n incidentCosine: f32,\n transmittedCosine: f32\n) -> f32 {\n return (incidentIor * incidentCosine - transmittedIor * transmittedCosine)\n / (incidentIor * incidentCosine + transmittedIor * transmittedCosine);\n}\n\nfn openpbr_fresnel_p(\n incidentIor: f32,\n transmittedIor: f32,\n incidentCosine: f32,\n transmittedCosine: f32\n) -> f32 {\n return (transmittedIor * incidentCosine - incidentIor * transmittedCosine)\n / (incidentIor * transmittedCosine + transmittedIor * incidentCosine);\n}\n\nfn openpbr_transmission_s(\n incidentIor: f32,\n transmittedIor: f32,\n incidentCosine: f32,\n transmittedCosine: f32\n) -> f32 {\n return 2.0 * incidentIor * incidentCosine\n / (incidentIor * incidentCosine + transmittedIor * transmittedCosine);\n}\n\nfn openpbr_transmission_p(\n incidentIor: f32,\n transmittedIor: f32,\n incidentCosine: f32,\n transmittedCosine: f32\n) -> f32 {\n return 2.0 * incidentIor * incidentCosine\n / (incidentIor * transmittedCosine + transmittedIor * incidentCosine);\n}\n\nfn openpbr_complex_multiply(a: vec2f, b: vec2f) -> vec2f {\n return vec2f(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);\n}\n\nfn openpbr_complex_divide(a: vec2f, b: vec2f) -> vec2f {\n let denominator = max(dot(b, b), 1e-9);\n return vec2f(\n (a.x * b.x + a.y * b.y) / denominator,\n (a.y * b.x - a.x * b.y) / denominator\n );\n}\n\nfn openpbr_complex_sqrt(value: vec2f) -> vec2f {\n let magnitude = length(value);\n let realPart = sqrt(max(0.0, 0.5 * (magnitude + value.x)));\n let imaginaryMagnitude = sqrt(max(0.0, 0.5 * (magnitude - value.x)));\n return vec2f(realPart, select(-imaginaryMagnitude, imaginaryMagnitude, value.y >= 0.0));\n}\n\nfn openpbr_complex_fresnel_reflection(\n incidentCosine: f32,\n incidentIor: f32,\n transmittedIor: vec2f,\n pPolarized: bool\n) -> vec2f {\n let incidentSine = sqrt(max(0.0, 1.0 - incidentCosine * incidentCosine));\n let iorRatio = openpbr_complex_divide(vec2f(incidentIor, 0.0), transmittedIor);\n let transmittedSine = iorRatio * incidentSine;\n var transmittedCosine = openpbr_complex_sqrt(\n vec2f(1.0, 0.0) - openpbr_complex_multiply(transmittedSine, transmittedSine)\n );\n if (openpbr_complex_multiply(transmittedIor, transmittedCosine).y < 0.0) {\n transmittedCosine = -transmittedCosine;\n }\n\n let incidentIorCosine = vec2f(incidentIor * incidentCosine, 0.0);\n let transmittedIorCosine = openpbr_complex_multiply(transmittedIor, transmittedCosine);\n if (pPolarized) {\n let transmittedIorIncidentCosine = transmittedIor * incidentCosine;\n let incidentIorTransmittedCosine = transmittedCosine * incidentIor;\n return openpbr_complex_divide(\n transmittedIorIncidentCosine - incidentIorTransmittedCosine,\n transmittedIorIncidentCosine + incidentIorTransmittedCosine\n );\n }\n return openpbr_complex_divide(\n incidentIorCosine - transmittedIorCosine,\n incidentIorCosine + transmittedIorCosine\n );\n}\n\nfn openpbr_airy_reflectance(\n reflection12: f32,\n transmission12: f32,\n reflection21: f32,\n transmission21: f32,\n reflection23: vec2f,\n phase: f32\n) -> f32 {\n let phaseRotation = vec2f(cos(phase), sin(phase));\n let reflectedPhase = openpbr_complex_multiply(reflection23, phaseRotation);\n let numerator = reflectedPhase * (transmission12 * transmission21);\n let denominator = vec2f(1.0, 0.0) - reflectedPhase * reflection21;\n let totalReflection = vec2f(reflection12, 0.0)\n + openpbr_complex_divide(numerator, denominator);\n return dot(totalReflection, totalReflection);\n}\n\nfn openpbr_thin_film_reflectance_channel(\n incidentCosine: f32,\n wavelength: f32,\n thickness: f32,\n exteriorIor: f32,\n filmIor: f32,\n substrateIor: vec2f\n) -> f32 {\n let safeIncidentCosine = clamp(abs(incidentCosine), 1e-5, 1.0);\n let filmSineSquared = pow(exteriorIor / filmIor, 2.0)\n * (1.0 - safeIncidentCosine * safeIncidentCosine);\n if (filmSineSquared >= 1.0) {\n return 1.0;\n }\n let filmCosine = sqrt(1.0 - filmSineSquared);\n let reflection12S = openpbr_fresnel_s(\n exteriorIor, filmIor, safeIncidentCosine, filmCosine\n );\n let reflection12P = openpbr_fresnel_p(\n exteriorIor, filmIor, safeIncidentCosine, filmCosine\n );\n let transmission12S = openpbr_transmission_s(\n exteriorIor, filmIor, safeIncidentCosine, filmCosine\n );\n let transmission12P = openpbr_transmission_p(\n exteriorIor, filmIor, safeIncidentCosine, filmCosine\n );\n let transmission21Scale = filmIor * filmCosine\n / max(exteriorIor * safeIncidentCosine, 1e-9);\n let phase = 2.0 * PI * (2.0 * filmIor * thickness * filmCosine) / wavelength;\n var reflectance = 0.0;\n // Both polarizations share Snell's complex solve and the Airy graph.\n for (var polarization = 0u; polarization < (rtUniform.loopUnrollGuard >> 1u); polarization++) {\n let reflection12 = select(reflection12S, reflection12P, polarization == 1u);\n let transmission12 = select(transmission12S, transmission12P, polarization == 1u);\n let reflection23 = openpbr_complex_fresnel_reflection(\n filmCosine, filmIor, substrateIor, polarization == 1u);\n reflectance += openpbr_airy_reflectance(reflection12, transmission12,\n -reflection12, transmission12 * transmission21Scale, reflection23, phase);\n }\n return clamp(0.5 * reflectance, 0.0, 1.0);\n}\n\n\n// OpenPBR 1.1.1 Metal: analytic F82-tint correction to Schlick.\n// Saturation follows Adobe impl/openpbr_lobe_utils.h, openpbr_metal_schlick_with_f82_tint.\nfn openpbr_metal_fresnel(cosine: f32, f0: vec3f, tint: vec3f) -> vec3f {\n let schlick = mix(f0, vec3f(1.0), sampling_schlick_fifth_power(cosine));\n if (all(tint == vec3f(1.0))) { return schlick; }\n let edgeCosine = 1.0 / 7.0;\n let edgeComplement = 1.0 - edgeCosine;\n let edgeFresnel = mix(f0, vec3f(1.0), sampling_schlick_fifth_power(edgeCosine));\n let complement = 1.0 - cosine;\n let correction = cosine * sampling_schlick_fifth_power(cosine) * complement\n / (edgeCosine * pow(edgeComplement, 6.0));\n return clamp(schlick - correction * (edgeFresnel - tint * edgeFresnel), vec3f(0.0), vec3f(1.0));\n}\n\nfn openpbr_metal_complex_ior(f0: f32, f82Tint: f32) -> vec2f {\n let reflectivity = clamp(f0, 0.0, 0.99);\n let sqrtReflectivity = sqrt(reflectivity);\n let minimumIor = (1.0 - reflectivity) / (1.0 + reflectivity);\n let maximumIor = (1.0 + sqrtReflectivity) / max(1.0 - sqrtReflectivity, 1e-9);\n let ior = mix(maximumIor, minimumIor, clamp(f82Tint, 0.0, 1.0));\n let extinctionSquared = (\n (ior + 1.0) * (ior + 1.0) * reflectivity\n - (ior - 1.0) * (ior - 1.0)\n ) / max(1.0 - reflectivity, 1e-9);\n return vec2f(ior, sqrt(max(extinctionSquared, 0.0)));\n}\n\n\nfn openpbr_thin_film_presence(thickness: f32, weight: f32) -> f32 {\n return clamp(weight, 0.0, 1.0) * smoothstep(1e-9, 30.0, thickness);\n}\n\n\n\nfn openpbr_eval_diffuse(\n material: OpenPbrResolvedMaterial,\n color: vec3f,\n viewLocal: vec3f,\n lightLocal: vec3f,\n specularEnergyCompensation: f32,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if (lightLocal.z <= 0.0) {\n return vec3f(0.0);\n }\n\n let roughness = material.diffuseRoughness;\n let fonConstantA = 0.5 - 2.0 / (3.0 * PI);\n let fonConstantB = 2.0 / 3.0 - 28.0 / (15.0 * PI);\n let muInput = lightLocal.z;\n let muOutput = viewLocal.z;\n let directional = dot(lightLocal, viewLocal) - muInput * muOutput;\n let directionalOverTangent = select(\n directional,\n directional / max(max(muInput, muOutput), 1e-7),\n directional > 0.0\n );\n let singleScatterA = 1.0 / (1.0 + fonConstantA * roughness);\n let singleScatter = color * INVPI * singleScatterA\n * (1.0 + roughness * directionalOverTangent);\n\n let inputComplement = 1.0 - muInput;\n let outputComplement = 1.0 - muOutput;\n let inputGOverPi = inputComplement * (\n 0.0571085289 + inputComplement * (\n 0.491881867 + inputComplement * (\n -0.332181442 + inputComplement * 0.0714429953\n )\n )\n );\n let outputGOverPi = outputComplement * (\n 0.0571085289 + outputComplement * (\n 0.491881867 + outputComplement * (\n -0.332181442 + outputComplement * 0.0714429953\n )\n )\n );\n let inputAlbedo = (1.0 + roughness * inputGOverPi)\n / (1.0 + fonConstantA * roughness);\n let outputAlbedo = (1.0 + roughness * outputGOverPi)\n / (1.0 + fonConstantA * roughness);\n let averageAlbedo = singleScatterA * (1.0 + fonConstantB * roughness);\n let multipleScatterAlbedo = color * color * averageAlbedo\n / max(vec3f(1e-7), vec3f(1.0) - color * (1.0 - averageAlbedo));\n let multipleScatter = multipleScatterAlbedo * INVPI\n * max(1e-7, 1.0 - outputAlbedo)\n * max(1e-7, 1.0 - inputAlbedo)\n / max(1e-7, 1.0 - averageAlbedo);\n *pdf = lightLocal.z * INVPI;\n return (singleScatter + multipleScatter) * specularEnergyCompensation;\n}\n\nfn openpbr_fuzz_directional_albedo(viewCosine: f32, roughness: f32) -> f32 {\n let x = clamp(viewCosine, 0.0, 1.0);\n let y = clamp(roughness, 0.01, 1.0);\n let scale = y * (0.0206607 + 1.58491 * y)\n / (0.0379424 + y * (1.32227 + y));\n let mean = y * (-0.193854 + y * (-1.14885 + y * (1.7932 - 0.95943 * y * y)))\n / (0.046391 + y);\n let offset = y * (0.000654023 + (-0.0207818 + 0.119681 * y) * y)\n / (1.26264 + y * (-1.92021 + y));\n let gaussian = exp(-0.5 * pow((x - mean) / scale, 2.0))\n / (scale * sqrt(2.0 * PI));\n return max(gaussian + offset, 0.0);\n}\n\nfn openpbr_eval_fuzz(\n material: OpenPbrResolvedMaterial,\n viewLocal: vec3f,\n lightLocal: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if ((viewLocal.z <= 0.0) | (lightLocal.z <= 0.0)) {\n return vec3f(0.0);\n }\n let albedo = openpbr_fuzz_directional_albedo(\n viewLocal.z,\n material.fuzzRoughness\n );\n *pdf = lightLocal.z * INVPI;\n return material.fuzzColor * albedo * INVPI;\n}\n\nfn openpbr_eval_microfacet_reflection(\n material: OpenPbrResolvedMaterial,\n viewLocal: vec3f,\n lightLocal: vec3f,\n halfVector: vec3f,\n fresnel: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if (lightLocal.z <= 0.0) {\n return vec3f(0.0);\n }\n\n let alpha = vec2f(material.alphaX, material.alphaY);\n let distribution = sampling_ggx_ndf(halfVector, alpha);\n let masking = sampling_ggx_smith_g1(viewLocal, alpha);\n let maskingShadowing = masking\n * sampling_ggx_smith_g1(lightLocal, alpha);\n\n *pdf = masking * distribution / (4.0 * max(viewLocal.z, 1e-8));\n return fresnel * distribution * maskingShadowing\n / (4.0 * max(lightLocal.z * viewLocal.z, 1e-8));\n}\n\nfn openpbr_eval_microfacet_refraction(\n material: OpenPbrResolvedMaterial,\n relativeIor: f32,\n tint: vec3f,\n viewLocal: vec3f,\n lightLocal: vec3f,\n halfVector: vec3f,\n fresnel: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if (lightLocal.z >= 0.0) {\n return vec3f(0.0);\n }\n\n let lightHalf = dot(lightLocal, halfVector);\n let viewHalf = dot(viewLocal, halfVector);\n // A refracting microfacet must face the view and the opposite side of the\n // light. Backfacing configurations have no support in the VNDF proposal.\n if ((viewHalf <= 0.0) | (lightHalf >= 0.0)) {\n return vec3f(0.0);\n }\n let alpha = vec2f(material.alphaX, material.alphaY);\n let distribution = sampling_ggx_ndf(halfVector, alpha);\n let masking = sampling_ggx_smith_g1(viewLocal, alpha);\n let maskingShadowing = masking\n * sampling_ggx_smith_g1(lightLocal, alpha);\n var denominator = lightHalf + viewHalf * relativeIor;\n denominator *= denominator;\n let jacobian = abs(lightHalf) / max(denominator, 1e-8);\n let relativeIorSquared = relativeIor * relativeIor;\n\n *pdf = masking * max(0.0, viewHalf) * distribution * jacobian\n / max(viewLocal.z, 1e-8);\n return tint * (vec3f(1.0) - fresnel) * distribution * maskingShadowing\n * abs(viewHalf) * jacobian * relativeIorSquared\n / max(abs(lightLocal.z * viewLocal.z), 1e-8);\n}\n\nfn openpbr_transmission_tint(material: OpenPbrResolvedMaterial) -> vec3f {\n if (HAS_MEDIUM & (material.mediumType > 0)) {\n return vec3f(1.0);\n }\n return material.transmissionColor;\n}\n\nfn openpbr_thin_wall_fresnel(singleInterfaceFresnel: f32) -> f32 {\n return 2.0 * singleInterfaceFresnel / (1.0 + singleInterfaceFresnel);\n}\n\nfn openpbr_thin_wall_reflection_coefficient(\n singleInterfaceFresnel: vec3f,\n transmissionWeight: f32\n) -> vec3f {\n let windowFresnel = 2.0 * singleInterfaceFresnel\n / (vec3f(1.0) + singleInterfaceFresnel);\n return mix(\n singleInterfaceFresnel,\n windowFresnel,\n clamp(transmissionWeight, 0.0, 1.0)\n );\n}\n\nfn openpbr_final_transmission_tint(\n material: OpenPbrResolvedMaterial,\n incidentCosine: f32,\n etaTOverEtaI: f32\n) -> vec3f {\n let tint = openpbr_transmission_tint(material);\n if (!material.thinWalled) {\n return tint;\n }\n let incidentSineSquared = max(0.0, 1.0 - incidentCosine * incidentCosine);\n let transmittedSineSquared = incidentSineSquared\n / max(etaTOverEtaI * etaTOverEtaI, 1e-9);\n if (transmittedSineSquared >= 1.0) {\n return vec3f(0.0);\n }\n let transmittedCosine = sqrt(1.0 - transmittedSineSquared);\n return pow(max(tint, vec3f(0.0)), vec3f(1.0 / max(transmittedCosine, 1e-5)));\n}\n\nfn openpbr_eval_subsurface_boundary(\n material: OpenPbrResolvedMaterial,\n viewLocal: vec3f,\n lightLocal: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n *pdf = 0.0;\n if ((viewLocal.z <= 0.0) | (lightLocal.z >= 0.0)) {\n return vec3f(0.0);\n }\n let transmittedCosine = -lightLocal.z;\n let entryFresnel = sampling_dielectric_fresnel(\n viewLocal.z,\n material.relativeIor\n );\n *pdf = transmittedCosine * INVPI;\n return vec3f(1.0 - entryFresnel) * INVPI;\n}\n\nfn openpbr_normalize_probabilities(prepared: ptr<function, OpenPbrPreparedBsdf>) {\n // Keep the loops dynamically bounded: explicit unrolling regresses Metal\n // rendering performance. The uniform guard is four, covering all lobes.\n let lobeCount = min(OPENPBR_LOBE_COUNT, i32(rtUniform.loopUnrollGuard * 2u));\n var total = (*prepared).probabilities[OPENPBR_LOBE_FUZZ];\n for (var lobe = 1; lobe < lobeCount; lobe++) {\n total += (*prepared).probabilities[lobe];\n }\n if (total <= 1e-8) {\n for (var lobe = 0; lobe < lobeCount; lobe++) {\n (*prepared).probabilities[lobe] = 0.0;\n }\n return;\n }\n let inverseTotal = 1.0 / total;\n for (var lobe = 0; lobe < lobeCount; lobe++) {\n (*prepared).probabilities[lobe] *= inverseTotal;\n }\n}\n\n// Adapted from Adobe c91aad1 impl/openpbr_lobe_utils.h and\n// impl/openpbr_bsdf.h::openpbr_prepare_lobes (Apache-2.0).\nfn openpbr_average_fresnel(eta: f32) -> f32 {\n if (abs(eta - 1.0) < 1e-6) { return 0.0; }\n if (eta > 1.0) { return (eta - 1.0) / (4.08567 + 1.00071 * eta); }\n return 0.997118 + 0.1014 * eta - 0.965241 * eta * eta - 0.130607 * eta * eta * eta;\n}\n\nfn openpbr_metal_average_fresnel(f0: vec3f, tint: vec3f) -> vec3f {\n let edgeCosine = 1.0 / 7.0;\n let edgeFresnel = mix(f0, vec3f(1.0), sampling_schlick_fifth_power(edgeCosine));\n let b = edgeFresnel * (vec3f(1.0) - tint) / (edgeCosine * pow(1.0 - edgeCosine, 6.0));\n return clamp(f0 + (vec3f(1.0) - f0) / 21.0 - b / 126.0, vec3f(0.0), vec3f(1.0));\n}\n\n// Adobe openpbr_coating_lobe.h: one passage, using the refracted cosine.\n// Partial coverage is mixed separately for each passage, as in the reference.\nfn openpbr_coat_passage(material: OpenPbrResolvedMaterial, cosine: f32) -> vec3f {\n if (cosine <= 0.0) { return vec3f(1.0); }\n let refractedCosine = sqrt(max(0.0, 1.0 - (1.0 - cosine * cosine) / (material.coatIor * material.coatIor)));\n let passage = pow(max(material.coatColor, vec3f(0.0)), vec3f(0.5 / max(refractedCosine, 1e-5)));\n return mix(vec3f(1.0), passage, material.coatWeight);\n}\n\nfn openpbr_coat_energy(material: OpenPbrResolvedMaterial, cosine: f32) -> f32 {\n if (material.coatRoughness <= ROUGHNESS_MIN) {\n return openpbr_dielectric_fresnel(cosine, 1.0 / material.coatIor);\n }\n // The opaque-dielectric table is the complement of coat directional albedo.\n // Adobe uses the original isotropic alpha (r^2) even for anisotropic GGX.\n return clamp(1.0 - openpbr_opaque_energy_complement(material.coatIor,\n material.coatRoughness * material.coatRoughness, cosine), 0.0, 1.0);\n}\n\nfn openpbr_coat_darkening(prepared: OpenPbrPreparedBsdf) -> vec3f {\n let material = prepared.material;\n let dielectric = 1.0 - material.baseMetalness;\n let opaque = dielectric * (1.0 - material.transmissionWeight) * (1.0 - material.subsurfaceWeight);\n let sss = dielectric * (1.0 - material.transmissionWeight) * material.subsurfaceWeight;\n let trans = dielectric * material.transmissionWeight;\n let opaqueSpecularity = openpbr_average_fresnel(prepared.specularEtaTOverEtaI);\n let transSpecularity = openpbr_average_fresnel(1.0 / prepared.transmissionReflectionIor);\n let specularity = clamp(opaque * opaqueSpecularity + (sss + trans) * transSpecularity + material.baseMetalness, 0.0, 1.0);\n let effectiveRoughness = mix(1.0, material.specularRoughness, specularity);\n let ks = openpbr_average_fresnel(material.coatIor);\n let kr = 1.0 - (1.0 - ks) / (material.coatIor * material.coatIor);\n let k = mix(ks, kr, effectiveRoughness);\n let eb = material.baseMetalness * material.specularWeight * openpbr_metal_average_fresnel(material.baseWeight * material.baseColor, material.specularColor)\n + opaque * mix(material.baseWeight * material.baseColor, vec3f(1.0), opaqueSpecularity)\n + sss * material.subsurfaceColor + vec3f(trans);\n let delta = vec3f(1.0 - k) / max(vec3f(1.0) - eb * k, vec3f(1e-6));\n return mix(vec3f(1.0), delta, material.coatWeight * material.coatDarkening);\n}\n\nfn openpbr_coat_base_scale(prepared: OpenPbrPreparedBsdf, lightCosine: f32) -> vec3f {\n if (prepared.material.coatWeight <= 0.0) { return vec3f(1.0); }\n if (prepared.material.inside) {\n // Inside only outgoing transmission crosses the coat; no coat reflection.\n return openpbr_coat_passage(prepared.material, -lightCosine);\n }\n // Default reference mode applies energy partition on the view side only.\n return prepared.coatBaseScale * openpbr_coat_passage(prepared.material, lightCosine);\n}\n\n\n${hasAnisotropyDirections ? /* wgsl */ `\nfn openpbr_direction_basis(fallback: OpenPbrBasis, tangent: vec3f, bitangent: vec3f, direction: vec2f) -> OpenPbrBasis {\n var t = tangent - fallback.normal * dot(fallback.normal, tangent);\n if (dot(t, t) < 1e-8) { t = fallback.tangent; }\n t = normalize(t);\n let b = cross(fallback.normal, t) * select(1.0, -1.0, dot(cross(fallback.normal, t), bitangent) < 0.0);\n t = t * direction.x + b * direction.y;\n if (dot(t, t) < 1e-8) { return fallback; }\n var basis = fallback;\n basis.tangent = normalize(t);\n basis.bitangent = normalize(cross(basis.normal, basis.tangent));\n basis.tangent = normalize(cross(basis.bitangent, basis.normal));\n return basis;\n}\n\n` : /* wgsl */ ``}\nfn openpbr_prepare(\n tangent: vec3f,\n material: OpenPbrResolvedMaterial,\n viewDirection: vec3f,\n normal: vec3f\n) -> OpenPbrPreparedBsdf {\n var prepared: OpenPbrPreparedBsdf;\n prepared.material = material;\n prepared.basis = openpbr_make_basis(\n tangent,\n normal,\n max(material.specularAnisotropy, material.coatAnisotropy)\n );\n${hasAnisotropyDirections ? /* wgsl */ `\n prepared.coatBasis = prepared.basis;\n prepared.directionFrames = false;\n${hasCoatNormals ? /* wgsl */ `\n // Adobe c91aad1 openpbr_prepare_lobes: geometry_coat_basis is independent\n // of the base shading basis; coat passage uses the same coat cosine.\n if (material.coatNormalEnabled) {\n prepared.coatBasis = openpbr_make_basis(material.geometryTangent, material.coatNormal, material.coatAnisotropy);\n prepared.directionFrames = true;\n }\n` : ''}\n if ((material.specularAnisotropy > 0.0) & material.anisotropyDirectionEnabled) {\n prepared.basis = openpbr_direction_basis(prepared.basis, material.directionTangent, material.directionBitangent, material.anisotropyDirection);\n prepared.directionFrames = true;\n }\n if ((material.coatAnisotropy > 0.0) & (material.coatAnisotropyRotation != 0.0)) {\n prepared.coatBasis = openpbr_direction_basis(prepared.coatBasis, material.geometryTangent, material.geometryBitangent, vec2f(cos(material.coatAnisotropyRotation), sin(material.coatAnisotropyRotation)));\n prepared.directionFrames = true;\n }\n` : /* wgsl */ ``}\n prepared.viewLocal = openpbr_to_local(prepared.basis, viewDirection);\n${hasCoatNormals ? /* wgsl */ ` prepared.coatViewLocal = openpbr_to_local(prepared.coatBasis, viewDirection);` : ''}\n prepared.diffuseColor = material.baseColor;\n\n let dielectricBaseWeight = 1.0 - material.baseMetalness;\n let opaqueDielectricWeight = dielectricBaseWeight\n * (1.0 - material.transmissionWeight);\n prepared.weights[OPENPBR_LOBE_FUZZ] = material.fuzzWeight;\n prepared.weights[OPENPBR_LOBE_COAT] = material.coatWeight;\n if (material.inside) { prepared.weights[OPENPBR_LOBE_COAT] = 0.0; }\n prepared.weights[OPENPBR_LOBE_METAL] = material.baseMetalness;\n prepared.weights[OPENPBR_LOBE_SPECULAR] = dielectricBaseWeight;\n prepared.weights[OPENPBR_LOBE_TRANSMISSION] = dielectricBaseWeight\n * material.transmissionWeight;\n prepared.weights[OPENPBR_LOBE_DIFFUSE] = opaqueDielectricWeight\n * (1.0 - material.subsurfaceWeight) * material.baseWeight;\n prepared.weights[OPENPBR_LOBE_SUBSURFACE] = opaqueDielectricWeight\n * material.subsurfaceWeight;\n\n prepared.specularEtaTOverEtaI = openpbr_apply_specular_weight_to_ior(\n 1.0 / material.relativeIor,\n material.specularWeight\n );\n prepared.material.relativeIor = 1.0 / prepared.specularEtaTOverEtaI;\n prepared.refractionEtaTOverEtaI = prepared.specularEtaTOverEtaI;\n prepared.transmissionReflectionIor = prepared.material.relativeIor;\n // Adobe openpbr_bsdf.h::openpbr_prepare_lobes: reflect relative to\n // the weighted coat, refract relative to ambient air. Inside ignores coat.\n if (material.coatWeight > 0.0) {\n var weightedCoatIor = 1.0;\n if (!material.inside) { weightedCoatIor = mix(1.0, material.coatIor, material.coatWeight); }\n let weightedBaseIor = weightedCoatIor * openpbr_apply_specular_weight_to_ior(\n material.specularIor / weightedCoatIor, material.specularWeight);\n var reflectionEta = weightedBaseIor / weightedCoatIor;\n if ((weightedCoatIor > weightedBaseIor) & (weightedBaseIor >= 1.0)) {\n reflectionEta = 1.0 / reflectionEta;\n }\n prepared.specularEtaTOverEtaI = reflectionEta;\n prepared.transmissionReflectionIor = 1.0 / reflectionEta;\n prepared.refractionEtaTOverEtaI = weightedBaseIor;\n if (material.inside) {\n prepared.refractionEtaTOverEtaI = 1.0 / weightedBaseIor;\n prepared.transmissionReflectionIor = weightedBaseIor;\n }\n prepared.material.relativeIor = 1.0 / prepared.refractionEtaTOverEtaI;\n }\n\n // Adobe openpbr_bsdf.h::openpbr_prepare_lobes: absolute thin-film media.\n // Preserve the existing optical solve for uncoated materials.\n prepared.thinFilmExteriorIor = 1.0;\n prepared.thinFilmBaseIor = prepared.specularEtaTOverEtaI;\n if (material.coatWeight > 0.0) {\n if (material.inside) {\n prepared.thinFilmExteriorIor = 1.0 / prepared.refractionEtaTOverEtaI;\n prepared.thinFilmBaseIor = 1.0;\n }\n else {\n prepared.thinFilmExteriorIor = mix(1.0, material.coatIor, material.coatWeight);\n prepared.thinFilmBaseIor = prepared.refractionEtaTOverEtaI;\n }\n }\n prepared.specularAlpha = material.specularRoughness\n * material.specularRoughness;\n let viewCosine = abs(prepared.viewLocal.z);\n let singleInterfaceFresnel = openpbr_dielectric_fresnel(\n viewCosine,\n 1.0 / prepared.specularEtaTOverEtaI\n );\n let dielectricFresnelEstimate = material.specularColor * select(\n singleInterfaceFresnel,\n mix(\n singleInterfaceFresnel,\n openpbr_thin_wall_fresnel(singleInterfaceFresnel),\n material.transmissionWeight\n ),\n material.thinWalled\n );\n let metalFresnelEstimate = material.specularWeight * openpbr_metal_fresnel(\n viewCosine,\n material.baseWeight * material.baseColor,\n material.specularColor\n );\n let transmissionFresnel = select(\n openpbr_dielectric_fresnel(viewCosine, prepared.transmissionReflectionIor),\n openpbr_thin_wall_fresnel(openpbr_dielectric_fresnel(viewCosine, prepared.transmissionReflectionIor)),\n material.thinWalled\n );\n let transmissionTint = openpbr_final_transmission_tint(\n material,\n viewCosine,\n prepared.refractionEtaTOverEtaI\n );\n let viewEnergyComplement = openpbr_opaque_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha,\n viewCosine\n );\n let averageEnergyComplement = openpbr_opaque_average_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha\n );\n prepared.diffuseSpecularEnergyCompensation = viewEnergyComplement\n / max(averageEnergyComplement, 1e-12);\n\n prepared.probabilities[OPENPBR_LOBE_FUZZ] = prepared.weights[OPENPBR_LOBE_FUZZ]\n * openpbr_luminance(material.fuzzColor)\n * max(openpbr_fuzz_directional_albedo(\n viewCosine,\n material.fuzzRoughness\n ), 0.02);\n prepared.probabilities[OPENPBR_LOBE_COAT] = 0.0;\n prepared.coatBaseScale = vec3f(1.0);\n if ((material.coatWeight > 0.0) & !material.inside) {\n ${hasCoatNormals ? /* wgsl */ `let coatViewCosine = select(viewCosine, max(prepared.coatViewLocal.z, 0.0), material.coatNormalEnabled);` : ''}\n let coatEnergy = openpbr_coat_energy(material, ${hasCoatNormals ? 'coatViewCosine' : 'viewCosine'});\n prepared.probabilities[OPENPBR_LOBE_COAT] = prepared.weights[OPENPBR_LOBE_COAT] * coatEnergy;\n prepared.coatBaseScale = openpbr_coat_passage(material, ${hasCoatNormals ? 'coatViewCosine' : 'viewCosine'})\n * (1.0 - prepared.weights[OPENPBR_LOBE_COAT] * coatEnergy) * openpbr_coat_darkening(prepared);\n }\n prepared.probabilities[OPENPBR_LOBE_METAL] = prepared.weights[OPENPBR_LOBE_METAL]\n * openpbr_luminance(metalFresnelEstimate);\n prepared.metalMsScale = vec3f(0.0);\n prepared.metalMsFactor = 0.0;\n prepared.metalMsProbability = 0.0;\n // Reference suppresses loss below roughness .04 and uses the original\n // isotropic alpha even when the single-scattering lobe is anisotropic.\n if ((material.specularRoughness >= 0.04) & (prepared.weights[OPENPBR_LOBE_METAL] > 0.0)) {\n let averageFresnel = openpbr_metal_average_fresnel(material.baseWeight * material.baseColor, material.specularColor);\n prepared.metalMsScale = material.specularWeight * averageFresnel * averageFresnel;\n let viewLoss = openpbr_metal_energy_complement(prepared.specularAlpha, viewCosine);\n prepared.metalMsFactor = viewLoss / max(openpbr_metal_average_energy_complement(prepared.specularAlpha), 1e-12);\n let msEstimate = prepared.weights[OPENPBR_LOBE_METAL] * openpbr_luminance(prepared.metalMsScale) * viewLoss;\n prepared.probabilities[OPENPBR_LOBE_METAL] += msEstimate;\n prepared.metalMsProbability = msEstimate / max(prepared.probabilities[OPENPBR_LOBE_METAL], 1e-12);\n }\n prepared.probabilities[OPENPBR_LOBE_SPECULAR] = prepared.weights[OPENPBR_LOBE_SPECULAR]\n * openpbr_luminance(dielectricFresnelEstimate);\n prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] = prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n * (1.0 - transmissionFresnel)\n * openpbr_luminance(transmissionTint);\n // OpenPBR_DielectricMicrofacetMultipleScatteringLobe, Adobe c91aad1\n // (Apache-2.0). Use the refraction IOR even when coat reflection uses a\n // different ratio. Restrict this addition to solid transmissive materials;\n // opaque/subsurface-only and thin-wall paths retain their existing model.\n // Keep the renderer's exact null boundary discrete: upstream avoids eta=1\n // for refraction, and its tabulation has small nonzero loss there.\n prepared.dielectricMsReflectionScale = vec3f(0.0);\n prepared.dielectricMsTransmissionScale = vec3f(0.0);\n prepared.dielectricMsViewLoss = 0.0;\n prepared.dielectricMsReflectionRatio = 0.0;\n prepared.dielectricMsProbability = 0.0;\n if (!material.thinWalled & (material.transmissionWeight > 0.0) &\n (prepared.specularAlpha >= 0.0016) & (prepared.weights[OPENPBR_LOBE_TRANSMISSION] > 0.0) &\n (abs(prepared.refractionEtaTOverEtaI - 1.0) > 1e-6)) {\n let nonOpaqueWeight = prepared.weights[OPENPBR_LOBE_TRANSMISSION] + prepared.weights[OPENPBR_LOBE_SUBSURFACE];\n prepared.dielectricMsReflectionScale = select(material.specularColor, vec3f(1.0), material.inside) * nonOpaqueWeight;\n prepared.dielectricMsTransmissionScale = transmissionTint * prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n + vec3f(prepared.weights[OPENPBR_LOBE_SUBSURFACE]);\n prepared.dielectricMsReflectionRatio = openpbr_ideal_dielectric_reflection_ratio(prepared.refractionEtaTOverEtaI, prepared.specularAlpha);\n prepared.dielectricMsViewLoss = openpbr_ideal_dielectric_loss(prepared.refractionEtaTOverEtaI, prepared.specularAlpha, viewCosine);\n let averageScale = mix(prepared.dielectricMsTransmissionScale, prepared.dielectricMsReflectionScale, prepared.dielectricMsReflectionRatio);\n let msEstimate = openpbr_luminance(averageScale) * prepared.dielectricMsViewLoss;\n prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] += msEstimate;\n prepared.dielectricMsProbability = msEstimate / max(prepared.probabilities[OPENPBR_LOBE_TRANSMISSION], 1e-12);\n }\n prepared.probabilities[OPENPBR_LOBE_DIFFUSE] = prepared.weights[OPENPBR_LOBE_DIFFUSE]\n * openpbr_luminance(prepared.diffuseColor)\n * prepared.diffuseSpecularEnergyCompensation;\n prepared.probabilities[OPENPBR_LOBE_SUBSURFACE] = prepared.weights[OPENPBR_LOBE_SUBSURFACE]\n * (1.0 - transmissionFresnel)\n * openpbr_luminance(material.subsurfaceColor)\n * prepared.diffuseSpecularEnergyCompensation;\n // Index-matched base interfaces can still reflect through thin film.\n // Keep proposal support without duplicating the Airy optical solve here.\n if ((material.coatWeight > 0.0) & HAS_THIN_FILM & (material.thinFilmWeight > 0.0)) {\n let filmSupport = 0.02 * openpbr_thin_film_presence(material.thinFilmThickness, material.thinFilmWeight);\n prepared.probabilities[OPENPBR_LOBE_SPECULAR] = max(prepared.probabilities[OPENPBR_LOBE_SPECULAR], prepared.weights[OPENPBR_LOBE_SPECULAR] * filmSupport);\n prepared.probabilities[OPENPBR_LOBE_METAL] = max(prepared.probabilities[OPENPBR_LOBE_METAL], prepared.weights[OPENPBR_LOBE_METAL] * material.specularWeight * filmSupport);\n }\n if ((material.coatWeight > 0.0) & !material.inside) {\n let baseScale = max(max(prepared.coatBaseScale.x, prepared.coatBaseScale.y), prepared.coatBaseScale.z);\n prepared.probabilities[OPENPBR_LOBE_METAL] *= baseScale;\n prepared.probabilities[OPENPBR_LOBE_SPECULAR] *= baseScale;\n prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] *= baseScale;\n prepared.probabilities[OPENPBR_LOBE_DIFFUSE] *= baseScale;\n prepared.probabilities[OPENPBR_LOBE_SUBSURFACE] *= baseScale;\n }\n openpbr_normalize_probabilities(&prepared);\n return prepared;\n}\n\n\nfn openpbr_substrate_fresnel(prepared: OpenPbrPreparedBsdf, cosine: f32, reflection: bool) -> f32 {\n if (prepared.material.coatWeight <= 0.0) {\n return openpbr_dielectric_fresnel(cosine, prepared.material.relativeIor);\n }\n let transFresnel = openpbr_dielectric_fresnel(cosine, prepared.transmissionReflectionIor);\n if (!reflection) { return transFresnel; }\n let opaqueFresnel = openpbr_dielectric_fresnel(cosine, 1.0 / prepared.specularEtaTOverEtaI);\n let nonOpaque = prepared.material.transmissionWeight + (1.0 - prepared.material.transmissionWeight) * prepared.material.subsurfaceWeight;\n return mix(opaqueFresnel, transFresnel, nonOpaque);\n}\n\nfn openpbr_eval_prepared_lobe(\n prepared: OpenPbrPreparedBsdf,\n lightDirection: vec3f,\n allowedLobe: i32,\n pdf: ptr<function, f32>\n) -> vec3f {\n let material = prepared.material;\n let viewLocal = prepared.viewLocal;\n let lightLocal = openpbr_to_local(prepared.basis, lightDirection);\n let reflection = lightLocal.z * viewLocal.z > 0.0;\n${hasCoatNormals ? /* wgsl */ `\n let independentCoat = material.coatNormalEnabled;\n let geometricReflection = dot(material.geometricNormal, lightDirection) > 0.0;\n let coatLightCosine = dot(prepared.coatBasis.normal, lightDirection);\n let coatReflection = geometricReflection & (prepared.coatViewLocal.z > 0.0) & (coatLightCosine > 0.0);\n` : ''}\n\n var halfVector: vec3f;\n if (reflection) {\n halfVector = normalize(lightLocal + viewLocal);\n }\n else {\n halfVector = normalize(lightLocal + viewLocal * material.relativeIor);\n }\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n\n var response = vec3f(0.0);\n *pdf = 0.0;\n var lobePdf = 0.0;\n let allowAll = allowedLobe < 0;\n let allowFuzz = allowAll | (allowedLobe == OPENPBR_LOBE_FUZZ);\n let allowCoat = allowAll | (allowedLobe == OPENPBR_LOBE_COAT);\n let allowMetal = allowAll | (allowedLobe == OPENPBR_LOBE_METAL);\n let allowSpecular = allowAll | (allowedLobe == OPENPBR_LOBE_SPECULAR);\n let allowTransmission = allowAll | (allowedLobe == OPENPBR_LOBE_TRANSMISSION);\n let allowDiffuse = allowAll | (allowedLobe == OPENPBR_LOBE_DIFFUSE);\n let allowSubsurface = allowAll | (allowedLobe == OPENPBR_LOBE_SUBSURFACE);\n let viewHalf = abs(dot(viewLocal, halfVector));\n // Reflection, solid refraction and thin-wall transmission share the same\n // dielectric optical solve. Select its half angle before evaluating the\n // film, instead of inlining that large solve in each transport branch.\n var thinWallHalf = halfVector;\n var dielectricViewHalf = viewHalf;\n if (!reflection & material.thinWalled) {\n thinWallHalf = normalize(vec3f(lightLocal.xy, -lightLocal.z) + viewLocal);\n if (thinWallHalf.z < 0.0) { thinWallHalf = -thinWallHalf; }\n dielectricViewHalf = abs(dot(viewLocal, thinWallHalf));\n }\n let evaluateDielectric =\n (reflection & (prepared.weights[OPENPBR_LOBE_SPECULAR] > 0.0) & allowSpecular) |\n (!reflection & (prepared.weights[OPENPBR_LOBE_TRANSMISSION] > 0.0) & allowTransmission);\n let evaluateMetal = reflection & (prepared.weights[OPENPBR_LOBE_METAL] > 0.0) & allowMetal;\n var fresnels: array<vec3f, 2>;\n if (evaluateDielectric) {\n fresnels[0] = vec3f(openpbr_substrate_fresnel(prepared, dielectricViewHalf, reflection));\n }\n if (evaluateMetal) {\n fresnels[1] = openpbr_metal_fresnel(viewHalf, material.baseWeight * material.baseColor, material.specularColor);\n }\n if (HAS_THIN_FILM & (material.thinFilmWeight > 0.0)) {\n let wavelengths = vec3f(620.0, 540.0, 450.0);\n // Dielectric and conductor substrates share one complete optical graph.\n for (var substrate = 0u; substrate < (rtUniform.loopUnrollGuard >> 1u); substrate++) {\n if (!select(evaluateDielectric, evaluateMetal, substrate == 1u)) { continue; }\n var film = vec3f(0.0);\n let incidentCosine = select(dielectricViewHalf, viewHalf, substrate == 1u);\n for (var channel = 0u; channel + 1u < rtUniform.loopUnrollGuard; channel++) {\n var ior = vec2f(prepared.thinFilmBaseIor, 0.0);\n if (substrate == 1u) {\n ior = openpbr_metal_complex_ior(material.baseWeight * material.baseColor[channel], material.specularColor[channel]);\n }\n film[channel] = openpbr_thin_film_reflectance_channel(incidentCosine,\n wavelengths[channel], material.thinFilmThickness, prepared.thinFilmExteriorIor, material.thinFilmIor, ior);\n }\n fresnels[substrate] = mix(fresnels[substrate], film,\n openpbr_thin_film_presence(material.thinFilmThickness, material.thinFilmWeight));\n }\n }\n let substrateFresnel = fresnels[0];\n let diffuseSpecularEnergyCompensation =\n prepared.diffuseSpecularEnergyCompensation\n * openpbr_opaque_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha,\n abs(lightLocal.z)\n );\n\n${hasCoatNormals ? /* wgsl */ `\n // Each lobe retains its own shading hemisphere; topology is geometric.\n if (!independentCoat | ((lightLocal.z != 0.0) & (reflection == geometricReflection))) {\n` : ''}\n if (reflection) {\n if ((prepared.weights[OPENPBR_LOBE_DIFFUSE] > 0.0) & allowDiffuse) {\n response += prepared.weights[OPENPBR_LOBE_DIFFUSE] * openpbr_eval_diffuse(\n material,\n prepared.diffuseColor,\n viewLocal,\n lightLocal,\n diffuseSpecularEnergyCompensation,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_DIFFUSE] * lobePdf;\n }\n\n if ((prepared.weights[OPENPBR_LOBE_SUBSURFACE] > 0.0) & material.thinWalled & allowSubsurface) {\n response += 0.5 * prepared.weights[OPENPBR_LOBE_SUBSURFACE] * openpbr_eval_diffuse(\n material,\n material.subsurfaceColor,\n viewLocal,\n lightLocal,\n diffuseSpecularEnergyCompensation,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_SUBSURFACE] * 0.5 * lobePdf;\n }\n\n if ((prepared.weights[OPENPBR_LOBE_SPECULAR] > 0.0) & allowSpecular) {\n var dielectricFresnel = substrateFresnel;\n if (material.thinWalled) {\n dielectricFresnel = openpbr_thin_wall_reflection_coefficient(\n dielectricFresnel,\n material.transmissionWeight\n );\n }\n let fresnel = material.specularColor * dielectricFresnel;\n response += prepared.weights[OPENPBR_LOBE_SPECULAR] * openpbr_eval_microfacet_reflection(\n material,\n viewLocal,\n lightLocal,\n halfVector,\n fresnel,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_SPECULAR] * lobePdf;\n }\n\n if ((prepared.weights[OPENPBR_LOBE_METAL] > 0.0) & allowMetal) {\n let fresnel = material.specularWeight * fresnels[1];\n response += prepared.weights[OPENPBR_LOBE_METAL] * openpbr_eval_microfacet_reflection(\n material,\n viewLocal,\n lightLocal,\n halfVector,\n fresnel,\n &lobePdf\n );\n if (prepared.metalMsFactor > 0.0) {\n let factor = min(prepared.metalMsFactor * openpbr_metal_energy_complement(prepared.specularAlpha, lightLocal.z), 1.0 / max(lightLocal.z, 1e-8));\n response += prepared.weights[OPENPBR_LOBE_METAL] * prepared.metalMsScale * factor * INVPI;\n lobePdf = mix(lobePdf, 0.5 * INVPI, prepared.metalMsProbability);\n }\n *pdf += prepared.probabilities[OPENPBR_LOBE_METAL] * lobePdf;\n }\n }\n else {\n let fresnel = substrateFresnel;\n if ((prepared.weights[OPENPBR_LOBE_TRANSMISSION] > 0.0) & allowTransmission) {\n if (material.thinWalled) {\n let mirroredLight = vec3f(lightLocal.xy, -lightLocal.z);\n let windowFresnel = openpbr_thin_wall_reflection_coefficient(\n substrateFresnel,\n 1.0\n );\n let tint = openpbr_final_transmission_tint(\n material,\n abs(viewLocal.z),\n prepared.refractionEtaTOverEtaI\n );\n response += prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n * openpbr_eval_microfacet_reflection(\n material,\n viewLocal,\n mirroredLight,\n thinWallHalf,\n tint * (vec3f(1.0) - windowFresnel),\n &lobePdf\n );\n }\n else {\n response += prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n * openpbr_eval_microfacet_refraction(\n material,\n material.relativeIor,\n openpbr_transmission_tint(material),\n viewLocal,\n lightLocal,\n halfVector,\n fresnel,\n &lobePdf\n );\n }\n if (prepared.dielectricMsProbability > 0.0) {\n lobePdf *= 1.0 - prepared.dielectricMsProbability;\n }\n *pdf += prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] * lobePdf;\n }\n if ((prepared.weights[OPENPBR_LOBE_SUBSURFACE] > 0.0) & allowSubsurface) {\n if (material.thinWalled) {\n let flippedLight = vec3f(lightLocal.xy, -lightLocal.z);\n response += 0.5 * prepared.weights[OPENPBR_LOBE_SUBSURFACE]\n * openpbr_eval_diffuse(\n material,\n material.subsurfaceColor,\n viewLocal,\n flippedLight,\n diffuseSpecularEnergyCompensation,\n &lobePdf\n );\n lobePdf *= 0.5;\n }\n else {\n response += prepared.weights[OPENPBR_LOBE_SUBSURFACE] * openpbr_eval_subsurface_boundary(\n material,\n viewLocal,\n lightLocal,\n &lobePdf\n );\n }\n *pdf += prepared.probabilities[OPENPBR_LOBE_SUBSURFACE] * lobePdf;\n }\n }\n\n // Reference value includes the hemisphere ratio before the grazing clamp.\n // Reference scale_trans has no extra eta^2 factor; single refraction keeps\n // its existing radiance Jacobian. Both proposals share the aggregate PDF.\n if ((prepared.dielectricMsProbability > 0.0) & allowTransmission) {\n let cosine = abs(lightLocal.z);\n if (cosine > 0.0) {\n let ior = select(1.0 / prepared.refractionEtaTOverEtaI, prepared.refractionEtaTOverEtaI, reflection);\n let ratio = select(1.0 - prepared.dielectricMsReflectionRatio, prepared.dielectricMsReflectionRatio, reflection);\n let factors = prepared.dielectricMsViewLoss * openpbr_ideal_dielectric_loss(ior, prepared.specularAlpha, cosine)\n / max(openpbr_ideal_dielectric_average_loss(ior, prepared.specularAlpha), 1e-12);\n let factor = min(factors * ratio, 1.0 / cosine);\n let scale = select(prepared.dielectricMsTransmissionScale, prepared.dielectricMsReflectionScale, reflection);\n response += scale * factor * INVPI;\n *pdf += prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] * prepared.dielectricMsProbability * ratio * 0.5 * INVPI;\n }\n }\n\n${hasCoatNormals ? /* wgsl */ ` } // base hemisphere support` : ''}\n if (material.coatWeight > 0.0) {\n response *= openpbr_coat_base_scale(prepared, ${hasCoatNormals ? 'select(lightLocal.z, coatLightCosine, independentCoat)' : 'lightLocal.z'});\n }\n${hasCoatNormals ? /* wgsl */ `\n // Values are BSDF*cos in world solid angle before the frames are combined.\n if (independentCoat) { response *= abs(lightLocal.z); }\n` : ''}\n\n${hasAnisotropyDirections ? /* wgsl */ `\n if (${hasCoatNormals ? 'select(reflection, coatReflection, independentCoat)' : 'reflection'} & (prepared.weights[OPENPBR_LOBE_COAT] > 0.0)) {\n var coatView = viewLocal;\n var coatLight = lightLocal;\n var coatHalf = halfVector;\n var coatViewHalf = viewHalf;\n if (prepared.directionFrames) {\n coatView = ${hasCoatNormals ? 'select(openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal)), prepared.coatViewLocal, independentCoat)' : 'openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal))'};\n coatLight = openpbr_to_local(prepared.coatBasis, lightDirection);\n coatHalf = normalize(coatLight + coatView);\n if (coatHalf.z < 0.0) { coatHalf = -coatHalf; }\n coatViewHalf = abs(dot(coatView, coatHalf));\n }\n\n let coatFresnel = vec3f(openpbr_dielectric_fresnel(\n coatViewHalf,\n 1.0 / material.coatIor\n ));\n var coatMaterial = material;\n let coatAlpha = openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy);\n coatMaterial.alphaX = coatAlpha.x;\n coatMaterial.alphaY = coatAlpha.y;\n if (allowCoat) {\n response += prepared.weights[OPENPBR_LOBE_COAT]${hasCoatNormals ? ' * select(1.0, max(coatLightCosine, 0.0), independentCoat)' : ''} * openpbr_eval_microfacet_reflection(\n coatMaterial,\n coatView,\n coatLight,\n coatHalf,\n coatFresnel,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_COAT] * lobePdf;\n }\n }\n` : /* wgsl */ `\n if (${hasCoatNormals ? 'select(reflection, coatReflection, independentCoat)' : 'reflection'} & (prepared.weights[OPENPBR_LOBE_COAT] > 0.0)) {\n let coatFresnel = vec3f(openpbr_dielectric_fresnel(\n viewHalf,\n 1.0 / material.coatIor\n ));\n var coatMaterial = material;\n let coatAlpha = openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy);\n coatMaterial.alphaX = coatAlpha.x;\n coatMaterial.alphaY = coatAlpha.y;\n if (allowCoat) {\n response += prepared.weights[OPENPBR_LOBE_COAT]${hasCoatNormals ? ' * select(1.0, max(coatLightCosine, 0.0), independentCoat)' : ''} * openpbr_eval_microfacet_reflection(\n coatMaterial,\n viewLocal,\n lightLocal,\n halfVector,\n coatFresnel,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_COAT] * lobePdf;\n }\n }\n`}\n\n if (${hasCoatNormals ? 'select(reflection, geometricReflection, independentCoat)' : 'reflection'} & (prepared.weights[OPENPBR_LOBE_FUZZ] > 0.0)) {\n let fuzzAlbedo = openpbr_fuzz_directional_albedo(\n abs(viewLocal.z),\n material.fuzzRoughness\n );\n response *= max(\n vec3f(0.0),\n vec3f(1.0 - prepared.weights[OPENPBR_LOBE_FUZZ] * fuzzAlbedo)\n );\n if (allowFuzz) {\n response += prepared.weights[OPENPBR_LOBE_FUZZ]${hasCoatNormals ? ' * select(1.0, max(lightLocal.z, 0.0), independentCoat)' : ''} * openpbr_eval_fuzz(\n material,\n viewLocal,\n lightLocal,\n &lobePdf\n );\n *pdf += prepared.probabilities[OPENPBR_LOBE_FUZZ] * lobePdf;\n }\n }\n\n return response * ${hasCoatNormals ? 'select(abs(lightLocal.z), 1.0, independentCoat)' : 'abs(lightLocal.z)'};\n}\n\nfn openpbr_eval_prepared(\n prepared: OpenPbrPreparedBsdf,\n lightDirection: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n return openpbr_eval_prepared_lobe(prepared, lightDirection, -1, pdf);\n}\n\n\n\n\n\nfn openpbr_eval(\n tangent: vec3f,\n material: OpenPbrResolvedMaterial,\n viewDirection: vec3f,\n normal: vec3f,\n lightDirection: vec3f,\n pdf: ptr<function, f32>\n) -> vec3f {\n let prepared = openpbr_prepare(\n tangent,\n material,\n viewDirection,\n normal\n );\n return openpbr_eval_prepared(prepared, lightDirection, pdf);\n}\n\n\n\n\n// Direction-only proposals do not require the layered response or film optics.\nfn openpbr_sample_direction(\n prepared: OpenPbrPreparedBsdf, randomSamples: vec3f,\n lightDirection: ptr<function, vec3f>, isTransmissionEvent: ptr<function, bool>,\n sampledLobeRoughness: ptr<function, f32>, sampledLobeSpecular: ptr<function, bool>,\n sampledLobeDiffuse: ptr<function, bool>, sampledLobeIndex: ptr<function, u32>\n) -> bool {\n let material = prepared.material;\n let viewLocal = prepared.viewLocal;\n var randomX = randomSamples.x;\n let randomY = randomSamples.y;\n let lobeSample = randomSamples.z;\n *isTransmissionEvent = false;\n *sampledLobeRoughness = 1.0;\n *sampledLobeSpecular = false;\n *sampledLobeDiffuse = false;\n *sampledLobeIndex = u32(OPENPBR_LOBE_DIFFUSE);\n *lightDirection = prepared.basis.normal;\n\n var cdf: array<f32, OPENPBR_LOBE_COUNT>;\n cdf[OPENPBR_LOBE_FUZZ] = prepared.probabilities[OPENPBR_LOBE_FUZZ];\n ${LOBE_INDICES.slice(1).map(i => /* wgsl */ `cdf[${i}] = cdf[${i - 1}] + prepared.probabilities[${i}];`).join('\\n ')}\n if (cdf[OPENPBR_LOBE_SUBSURFACE] <= 0.0) {\n return false;\n }\n\n var lightLocal: vec3f;\n if ((lobeSample < cdf[OPENPBR_LOBE_FUZZ]) | (\n (lobeSample >= cdf[OPENPBR_LOBE_TRANSMISSION]) & (lobeSample < cdf[OPENPBR_LOBE_DIFFUSE])\n )) {\n let sampledFuzz = lobeSample < cdf[OPENPBR_LOBE_FUZZ];\n *sampledLobeRoughness = select(\n 1.0,\n material.fuzzRoughness,\n sampledFuzz\n );\n *sampledLobeDiffuse = !sampledFuzz;\n *sampledLobeIndex = select(\n u32(OPENPBR_LOBE_DIFFUSE),\n u32(OPENPBR_LOBE_FUZZ),\n sampledFuzz\n );\n lightLocal = sampling_cosine_hemisphere(vec2f(randomX, randomY));\n }\n${hasAnisotropyDirections ? /* wgsl */ `\n else if (lobeSample < cdf[OPENPBR_LOBE_COAT]) {\n *sampledLobeRoughness = material.coatRoughness;\n *sampledLobeSpecular = true;\n *sampledLobeIndex = u32(OPENPBR_LOBE_COAT);\n var coatView = viewLocal;\n if (prepared.directionFrames) { coatView = ${hasCoatNormals ? 'select(openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal)), prepared.coatViewLocal, material.coatNormalEnabled)' : 'openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal))'}; }\n var halfVector = sampling_ggx_visible_normal(\n coatView,\n // The VNDF must use the same alpha as the evaluated coat PDF.\n openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy),\n vec2f(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n lightLocal = normalize(reflect(-coatView, halfVector));\n${hasCoatNormals ? /* wgsl */ `\n if (material.coatNormalEnabled) {\n // Validate against the sampled coat frame, never the base normal.\n *lightDirection = openpbr_to_world(prepared.coatBasis, lightLocal);\n return (lightLocal.z > 0.0) & (dot(material.geometricNormal, *lightDirection) > 0.0);\n }\n` : ''}\n if (prepared.directionFrames) { lightLocal = openpbr_to_local(prepared.basis, openpbr_to_world(prepared.coatBasis, lightLocal)); }\n }\n` : /* wgsl */ `\n else if (lobeSample < cdf[OPENPBR_LOBE_COAT]) {\n *sampledLobeRoughness = material.coatRoughness;\n *sampledLobeSpecular = true;\n *sampledLobeIndex = u32(OPENPBR_LOBE_COAT);\n var halfVector = sampling_ggx_visible_normal(\n viewLocal,\n // The VNDF must use the same alpha as the evaluated coat PDF.\n openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy),\n vec2f(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n lightLocal = normalize(reflect(-viewLocal, halfVector));\n }\n`}\n else if (lobeSample < cdf[OPENPBR_LOBE_SPECULAR]) {\n *sampledLobeRoughness = material.specularRoughness;\n *sampledLobeSpecular = true;\n *sampledLobeIndex = select(\n u32(OPENPBR_LOBE_SPECULAR),\n u32(OPENPBR_LOBE_METAL),\n lobeSample < cdf[OPENPBR_LOBE_METAL]\n );\n let isMetal = lobeSample < cdf[OPENPBR_LOBE_METAL];\n let metalDraw = (lobeSample - cdf[OPENPBR_LOBE_COAT]) / max(prepared.probabilities[OPENPBR_LOBE_METAL], 1e-12);\n if (isMetal & (metalDraw < prepared.metalMsProbability)) {\n let z = randomX;\n let radial = sqrt(max(0.0, 1.0 - z * z));\n let azimuth = TWOPI * randomY;\n lightLocal = vec3f(radial * cos(azimuth), radial * sin(azimuth), z);\n }\n else {\n var halfVector = sampling_ggx_visible_normal(\n viewLocal,\n vec2f(material.alphaX, material.alphaY),\n vec2f(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n lightLocal = normalize(reflect(-viewLocal, halfVector));\n }\n }\n else if (lobeSample < cdf[OPENPBR_LOBE_TRANSMISSION]) {\n *sampledLobeRoughness = material.specularRoughness;\n *sampledLobeSpecular = true;\n *isTransmissionEvent = true;\n *sampledLobeIndex = u32(OPENPBR_LOBE_TRANSMISSION);\n // Split the existing BSDF_LOBE interval. BSDF_DIRECTION retains its\n // two dimensions; rand.x selects and remaps the reference hemisphere.\n let msDraw = (lobeSample - cdf[OPENPBR_LOBE_SPECULAR]) / max(prepared.probabilities[OPENPBR_LOBE_TRANSMISSION], 1e-12);\n if ((prepared.dielectricMsProbability > 0.0) & (msDraw < prepared.dielectricMsProbability)) {\n let ratio = prepared.dielectricMsReflectionRatio;\n let reflectMs = randomX < ratio;\n var z: f32;\n if (reflectMs) { z = min(randomX / ratio, 0.99999994); }\n else { z = -min((randomX - ratio) / (1.0 - ratio), 0.99999994); }\n let radial = sqrt(max(0.0, 1.0 - z * z));\n let azimuth = TWOPI * randomY;\n lightLocal = vec3f(radial * cos(azimuth), radial * sin(azimuth), z);\n *isTransmissionEvent = !reflectMs;\n *sampledLobeIndex = select(u32(OPENPBR_LOBE_TRANSMISSION), u32(OPENPBR_LOBE_SPECULAR), reflectMs);\n }\n else {\n var halfVector = sampling_ggx_visible_normal(\n viewLocal,\n vec2f(material.alphaX, material.alphaY),\n vec2f(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n if (material.thinWalled) {\n let reflectedLocal = normalize(reflect(-viewLocal, halfVector));\n lightLocal = vec3f(reflectedLocal.xy, -reflectedLocal.z);\n }\n else {\n lightLocal = refract(\n -viewLocal,\n halfVector,\n prepared.material.relativeIor\n );\n if (dot(lightLocal, lightLocal) <= 1e-8) {\n *isTransmissionEvent = false;\n return false;\n }\n lightLocal = normalize(lightLocal);\n }\n }\n }\n else {\n *sampledLobeRoughness = 1.0;\n *sampledLobeDiffuse = true;\n *isTransmissionEvent = true;\n *sampledLobeIndex = u32(OPENPBR_LOBE_SUBSURFACE);\n if (material.thinWalled) {\n let transmit = randomX >= 0.5;\n if (transmit) {\n randomX = (randomX - 0.5) * 2.0;\n }\n else {\n randomX *= 2.0;\n }\n lightLocal = sampling_cosine_hemisphere(vec2f(randomX, randomY));\n if (transmit) {\n lightLocal.z = -lightLocal.z;\n }\n else {\n *isTransmissionEvent = false;\n }\n }\n else {\n lightLocal = sampling_cosine_hemisphere(vec2f(randomX, randomY));\n lightLocal.z = -lightLocal.z;\n }\n }\n\n *lightDirection = openpbr_to_world(prepared.basis, lightLocal);\n // VNDF reflection may land below the macrosurface. A mirrored thin-wall\n // transmission draw can likewise land above it. Reject that draw before\n // aggregate evaluation can accidentally turn it into the opposite lobe.\n if ((lightLocal.z == 0.0) | ((lightLocal.z < 0.0) != *isTransmissionEvent)) {\n return false;\n }\n${hasCoatNormals ? /* wgsl */ `\n if (material.coatNormalEnabled &\n ((dot(material.geometricNormal, *lightDirection) <= 0.0) != *isTransmissionEvent)) { return false; }\n` : ''}\n return true;\n}\n\nfn openpbr_sample_prepared(\n prepared: OpenPbrPreparedBsdf,\n randomSamples: vec3f,\n lightDirection: ptr<function, vec3f>,\n pdf: ptr<function, f32>,\n isTransmissionEvent: ptr<function, bool>,\n sampledLobeRoughness: ptr<function, f32>,\n sampledLobeSpecular: ptr<function, bool>,\n sampledLobeDiffuse: ptr<function, bool>,\n sampledLobeIndex: ptr<function, u32>,\n marginalPdf: ptr<function, f32>,\n fullResponse: ptr<function, vec3f>\n) -> vec3f {\n *pdf = 0.0;\n *marginalPdf = 0.0;\n *fullResponse = vec3f(0.0);\n // Solid microfacet refraction is singular at exactly eta=1: the usual\n // half vector normalizes light + eta * view, which is zero for straight\n // transmission. Resolve the physically null boundary as a discrete,\n // unit-throughput continuation instead of losing the path to NaNs.\n if (!prepared.material.thinWalled &\n (abs(prepared.material.relativeIor - 1.0) <= 1e-6) &\n (prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] >= 1.0 - 1e-6)) {\n *lightDirection = openpbr_to_world(\n prepared.basis,\n -prepared.viewLocal\n );\n let nullBoundaryTint = openpbr_transmission_tint(prepared.material)\n * openpbr_coat_base_scale(prepared, ${hasCoatNormals ? 'select(-prepared.viewLocal.z, dot(prepared.coatBasis.normal, *lightDirection), prepared.material.coatNormalEnabled)' : '-prepared.viewLocal.z'});\n *pdf = 1.0;\n *marginalPdf = 1.0;\n *fullResponse = nullBoundaryTint;\n *isTransmissionEvent = true;\n *sampledLobeRoughness = 0.0;\n *sampledLobeSpecular = true;\n *sampledLobeDiffuse = false;\n *sampledLobeIndex = u32(OPENPBR_LOBE_TRANSMISSION);\n return nullBoundaryTint;\n }\n if (!openpbr_sample_direction(prepared, randomSamples, lightDirection,\n isTransmissionEvent, sampledLobeRoughness, sampledLobeSpecular,\n sampledLobeDiffuse, sampledLobeIndex)) { return vec3f(0.0); }\n let response = openpbr_eval_prepared(prepared, *lightDirection, pdf);\n *marginalPdf = *pdf;\n *fullResponse = response;\n return response;\n}\n\nfn openpbr_sample(\n tangent: vec3f,\n material: OpenPbrResolvedMaterial,\n randomSamples: vec3f,\n viewDirection: vec3f,\n normal: vec3f,\n lightDirection: ptr<function, vec3f>,\n pdf: ptr<function, f32>,\n isTransmissionEvent: ptr<function, bool>,\n sampledLobeRoughness: ptr<function, f32>,\n sampledLobeSpecular: ptr<function, bool>,\n sampledLobeDiffuse: ptr<function, bool>,\n sampledLobeIndex: ptr<function, u32>,\n marginalPdf: ptr<function, f32>\n) -> vec3f {\n let prepared = openpbr_prepare(\n tangent,\n material,\n viewDirection,\n normal\n );\n var fullResponse = vec3f(0.0);\n return openpbr_sample_prepared(\n prepared,\n randomSamples,\n lightDirection,\n pdf,\n isTransmissionEvent,\n sampledLobeRoughness,\n sampledLobeSpecular,\n sampledLobeDiffuse,\n sampledLobeIndex,\n marginalPdf,\n &fullResponse\n );\n}`;\n\n}\n\nexport default createOpenPbrBsdfWgsl();\n"]}