@meimu/mutracer 0.1.4 → 0.1.5
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.
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +1425 -1421
- package/docs/api.md +2 -1
- package/docs/materials.md +17 -26
- package/docs/threejs.md +5 -5
- package/docs/viewer.md +36 -3
- package/lib/oidn.d.ts +1 -1
- package/lib/oidnBundledWeights.cdn.d.ts +2 -2
- package/lib/renderer/WGPUFullQuadPass.js +3 -3
- package/lib/renderer/WGPUJNLMDenoiser.js +35 -35
- package/lib/renderer/WGPURayTraceOutput.js +4 -4
- package/lib/renderer/WGPURayTracePass.js +5 -3
- package/lib/renderer/WGPURayTracePass.js.map +1 -1
- package/lib/renderer/WGPURenderingPipeline.js +7 -7
- package/lib/renderer/WebGLJNLMDenoiser.js +36 -36
- package/lib/renderer/WebGLRayTracePass.js +1 -0
- package/lib/renderer/WebGLRayTracePass.js.map +1 -1
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js +8 -8
- package/lib/renderer/glsl/chunks/envMap.glsl.js +26 -26
- package/lib/renderer/glsl/chunks/intersect.glsl.js +141 -139
- package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +56 -55
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +144 -144
- package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/random.glsl.js +1 -1
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +14 -14
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js +19 -19
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +46 -46
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +6 -6
- package/lib/renderer/glsl/chunks/sampling.glsl.js +33 -33
- package/lib/renderer/glsl/chunks/sobol.glsl.js +7 -7
- package/lib/renderer/glsl/chunks/uvProjection.glsl.js +1 -1
- package/lib/renderer/glsl/depthQuery.frag.js +2 -2
- package/lib/renderer/glsl/rayTrace.frag.js +67 -67
- package/lib/renderer/glsl/shadowCatcher.frag.js +3 -3
- package/lib/renderer/glsl/webglPipeline.frag.js +26 -26
- package/lib/renderer/scratchSampling.js +16 -16
- package/lib/renderer/wgsl/chunks/envMap.wgsl.js +14 -14
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js +178 -178
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +24 -23
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +160 -160
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +12 -12
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +5 -5
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +44 -44
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +69 -69
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js +33 -33
- package/lib/renderer/wgsl/chunks/sobol.wgsl.js +7 -7
- package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +1 -1
- package/lib/renderer/wgsl/compose.frag.wgsl.js +54 -54
- package/lib/renderer/wgsl/customDenoise.wgsl.js +6 -6
- package/lib/renderer/wgsl/depthQuery.wgsl.js +2 -2
- package/lib/renderer/wgsl/offlineAdaptive.compute.js +5 -5
- package/lib/renderer/wgsl/rayTrace.compute.js +103 -103
- package/lib/scene/OpenPBRMaterial.d.ts +1 -1
- package/lib/scene/OpenPBRMaterial.js +1 -1
- package/lib/scene/OpenPBRMaterial.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openPbrBsdf.glsl.js","sourceRoot":"","sources":["../../../../src/renderer/glsl/chunks/openPbrBsdf.glsl.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEvC,OAAO,EAAE,4BAA4B,EAAE,oCAAoC,EAAE,iCAAiC,EAAE,yCAAyC,EAAE,2CAA2C,EAAE,MAAM,2BAA2B,CAAC;AAE1O,eAAe,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAqK8B,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;;;;;;;;;;;;;;;;;;;;;oBAqBnC,yCAAyC;oBACzC,yCAAyC;oBACzC,yCAAyC;oBACzC,yCAAyC;;;;;;;;;;;;;;;;;oBAiBzC,2CAA2C;oBAC3C,2CAA2C;oBAC3C,2CAA2C;oBAC3C,2CAA2C;;;;;;;;;;;;;;;;;;;;;;;;;;;YA2BnD,4BAA4B,iBAAoC,4BAA4B;YAC5F,4BAA4B,iBAAoC,4BAA4B;;;;;;;mBAOzF,oCAAoC;qBAC9B,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAu9ClF,CAAC","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 */\nimport { glsl } from '../../shader.js';\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 default glsl`\nconst int OPENPBR_LOBE_FUZZ = 0;\nconst int OPENPBR_LOBE_COAT = 1;\nconst int OPENPBR_LOBE_METAL = 2;\nconst int OPENPBR_LOBE_SPECULAR = 3;\nconst int OPENPBR_LOBE_TRANSMISSION = 4;\nconst int OPENPBR_LOBE_DIFFUSE = 5;\nconst int OPENPBR_LOBE_SUBSURFACE = 6;\nconst int OPENPBR_LOBE_COUNT = 7;\n\nstruct OpenPbrBasis {\n vec3 tangent;\n vec3 bitangent;\n vec3 normal;\n};\n\nstruct OpenPbrPreparedBsdf {\n OpenPbrResolvedMaterial material;\n OpenPbrBasis basis;\n OpenPbrBasis coatBasis;\n bool directionFrames;\n#ifdef NUM_COAT_NORMAL_MAPS\n vec3 coatViewLocal;\n#endif\n vec3 viewLocal;\n vec3 diffuseColor;\n float specularEtaTOverEtaI;\n float refractionEtaTOverEtaI;\n float transmissionReflectionIor;\n float thinFilmExteriorIor;\n float thinFilmBaseIor;\n float specularAlpha;\n float diffuseSpecularEnergyCompensation;\n vec3 coatBaseScale;\n vec3 metalMsScale;\n float metalMsFactor;\n float metalMsProbability;\n\n vec3 dielectricMsReflectionScale;\n vec3 dielectricMsTransmissionScale;\n float dielectricMsViewLoss;\n float dielectricMsReflectionRatio;\n float dielectricMsProbability;\n\n float weights[OPENPBR_LOBE_COUNT];\n float probabilities[OPENPBR_LOBE_COUNT];\n};\n\nfloat openpbr_luminance(vec3 color) {\n return dot(color, vec3(0.212671, 0.715160, 0.072169));\n}\n\nfloat openpbr_f0_from_ior(float etaTOverEtaI) {\n float value = (etaTOverEtaI - 1.0) / (etaTOverEtaI + 1.0);\n return value * value;\n}\n\nfloat openpbr_apply_specular_weight_to_ior(\n float etaTOverEtaI,\n float specularWeight\n) {\n float scaledF0 = min(\n openpbr_f0_from_ior(etaTOverEtaI) * specularWeight,\n 0.9999\n );\n float sqrtF0 = sqrt(scaledF0);\n float externalIor = (1.0 + sqrtF0) / (1.0 - sqrtF0);\n return etaTOverEtaI < 1.0 ? 1.0 / externalIor : externalIor;\n}\n\nfloat openpbr_dielectric_fresnel(float cosTheta, float relativeIor) {\n if (abs(relativeIor - 1.0) < 1e-6) {\n return 0.0;\n }\n return sampling_dielectric_fresnel(cosTheta, relativeIor);\n}\n\nfloat openpbr_energy_ior_index(float ior) {\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\nfloat openpbr_energy_fetch(int cosIndex, int row) {\n return texelFetch(openPbrEnergyLut, ivec2(cosIndex, row), 0).r;\n}\n\nfloat openpbr_energy_trilinear(\n float exactIor,\n float exactAlpha,\n float exactCosine\n) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n float cosineIndex = clamp(exactCosine, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int cosine0 = int(floor(cosineIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n int cosine1 = min(cosine0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float cosineMix = cosineIndex - float(cosine0);\n\n float v000 = openpbr_energy_fetch(cosine0, ior0 * 32 + alpha0);\n float v001 = openpbr_energy_fetch(cosine1, ior0 * 32 + alpha0);\n float v010 = openpbr_energy_fetch(cosine0, ior0 * 32 + alpha1);\n float v011 = openpbr_energy_fetch(cosine1, ior0 * 32 + alpha1);\n float v100 = openpbr_energy_fetch(cosine0, ior1 * 32 + alpha0);\n float v101 = openpbr_energy_fetch(cosine1, ior1 * 32 + alpha0);\n float v110 = openpbr_energy_fetch(cosine0, ior1 * 32 + alpha1);\n float v111 = openpbr_energy_fetch(cosine1, ior1 * 32 + alpha1);\n float v00 = mix(v000, v100, iorMix);\n float v01 = mix(v001, v101, iorMix);\n float v10 = mix(v010, v110, iorMix);\n float v11 = mix(v011, v111, iorMix);\n return mix(\n mix(v00, v10, alphaMix),\n mix(v01, v11, alphaMix),\n cosineMix\n );\n}\n\nfloat openpbr_energy_bilinear(float exactIor, float exactAlpha) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float v00 = openpbr_energy_fetch(alpha0, 1024 + ior0);\n float v01 = openpbr_energy_fetch(alpha1, 1024 + ior0);\n float v10 = openpbr_energy_fetch(alpha0, 1024 + ior1);\n float 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].\nfloat openpbr_ideal_energy_trilinear(\n float exactIor,\n float exactAlpha,\n float exactCosine\n) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n float cosineIndex = clamp(exactCosine, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int cosine0 = int(floor(cosineIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n int cosine1 = min(cosine0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float cosineMix = cosineIndex - float(cosine0);\n\n float v000 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha0);\n float v001 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha0);\n float v010 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha1);\n float v011 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha1);\n float v100 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha0);\n float v101 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha0);\n float v110 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha1);\n float v111 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha1);\n float v00 = mix(v000, v100, iorMix);\n float v01 = mix(v001, v101, iorMix);\n float v10 = mix(v010, v110, iorMix);\n float v11 = mix(v011, v111, iorMix);\n return mix(\n mix(v00, v10, alphaMix),\n mix(v01, v11, alphaMix),\n cosineMix\n );\n}\n\nfloat openpbr_ideal_average_bilinear(float exactIor, float exactAlpha) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float v00 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior0);\n float v01 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior0);\n float v10 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior1);\n float 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\nfloat openpbr_ideal_ratio_bilinear(float exactIor, float exactAlpha) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float v00 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior0);\n float v01 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior0);\n float v10 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior1);\n float 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\nfloat openpbr_ideal_dielectric_loss(float ior, float alpha, float cosine) {\n return openpbr_ideal_energy_trilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0, cosine * 31.0);\n}\nfloat openpbr_ideal_dielectric_average_loss(float ior, float alpha) {\n return openpbr_ideal_average_bilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0);\n}\nfloat openpbr_ideal_dielectric_reflection_ratio(float ior, float alpha) {\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).\nfloat openpbr_metal_energy_complement(float alpha, float cosine) {\n float roughnessIndex = clamp(sqrt(alpha) * 31.0, 0.0, 31.0);\n float cosineIndex = clamp(cosine * 31.0, 0.0, 31.0);\n int r0 = int(floor(roughnessIndex));\n int c0 = int(floor(cosineIndex));\n int r1 = min(r0 + 1, 31);\n int 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\nfloat openpbr_metal_average_energy_complement(float alpha) {\n float index = clamp(sqrt(alpha) * 31.0, 0.0, 31.0);\n int i0 = int(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\nfloat openpbr_extrapolate_energy_complement(float value, float ior) {\n if (ior > 2.5 || ior < 0.4) {\n float maxF0 = openpbr_f0_from_ior(2.5);\n float progress = (openpbr_f0_from_ior(ior) - maxF0) / (1.0 - maxF0);\n return (1.0 - progress) * value;\n }\n return value;\n}\n\nfloat openpbr_opaque_energy_complement(\n float ior,\n float alpha,\n float cosine\n) {\n float 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\nfloat openpbr_opaque_average_energy_complement(float ior, float alpha) {\n float 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\nvec3 openpbr_to_world(OpenPbrBasis basis, vec3 direction) {\n return direction.x * basis.tangent\n + direction.y * basis.bitangent\n + direction.z * basis.normal;\n}\n\nvec3 openpbr_to_local(OpenPbrBasis basis, vec3 direction) {\n return vec3(\n dot(direction, basis.tangent),\n dot(direction, basis.bitangent),\n dot(direction, basis.normal)\n );\n}\n\nOpenPbrBasis openpbr_make_basis(\n vec3 inputTangent,\n vec3 normal,\n float anisotropy\n) {\n OpenPbrBasis basis;\n basis.normal = normal;\n basis.tangent = vec3(0.0);\n#ifdef USE_TANGENT\n basis.tangent = inputTangent;\n#endif\n if (dot(basis.tangent, basis.tangent) < 0.001 || anisotropy <= 0.0) {\n sampling_orthonormal_basis(normal, basis.tangent, basis.bitangent);\n }\n else {\n basis.bitangent = normalize(cross(normal, basis.tangent));\n basis.tangent = normalize(cross(basis.bitangent, normal));\n }\n return basis;\n}\n\nfloat openpbr_fresnel_s(\n float incidentIor,\n float transmittedIor,\n float incidentCosine,\n float transmittedCosine\n) {\n return (incidentIor * incidentCosine - transmittedIor * transmittedCosine)\n / (incidentIor * incidentCosine + transmittedIor * transmittedCosine);\n}\n\nfloat openpbr_fresnel_p(\n float incidentIor,\n float transmittedIor,\n float incidentCosine,\n float transmittedCosine\n) {\n return (transmittedIor * incidentCosine - incidentIor * transmittedCosine)\n / (incidentIor * transmittedCosine + transmittedIor * incidentCosine);\n}\n\nfloat openpbr_transmission_s(\n float incidentIor,\n float transmittedIor,\n float incidentCosine,\n float transmittedCosine\n) {\n return 2.0 * incidentIor * incidentCosine\n / (incidentIor * incidentCosine + transmittedIor * transmittedCosine);\n}\n\nfloat openpbr_transmission_p(\n float incidentIor,\n float transmittedIor,\n float incidentCosine,\n float transmittedCosine\n) {\n return 2.0 * incidentIor * incidentCosine\n / (incidentIor * transmittedCosine + transmittedIor * incidentCosine);\n}\n\nvec2 openpbr_complex_multiply(vec2 a, vec2 b) {\n return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);\n}\n\nvec2 openpbr_complex_divide(vec2 a, vec2 b) {\n float denominator = max(dot(b, b), 1e-9);\n return vec2(\n (a.x * b.x + a.y * b.y) / denominator,\n (a.y * b.x - a.x * b.y) / denominator\n );\n}\n\nvec2 openpbr_complex_sqrt(vec2 value) {\n float magnitude = length(value);\n float realPart = sqrt(max(0.0, 0.5 * (magnitude + value.x)));\n float imaginaryMagnitude = sqrt(max(0.0, 0.5 * (magnitude - value.x)));\n return vec2(realPart, value.y >= 0.0 ? imaginaryMagnitude : -imaginaryMagnitude);\n}\n\nvec2 openpbr_complex_fresnel_reflection(\n float incidentCosine,\n float incidentIor,\n vec2 transmittedIor,\n bool pPolarized\n) {\n float incidentSine = sqrt(max(0.0, 1.0 - incidentCosine * incidentCosine));\n vec2 iorRatio = openpbr_complex_divide(vec2(incidentIor, 0.0), transmittedIor);\n vec2 transmittedSine = iorRatio * incidentSine;\n vec2 transmittedCosine = openpbr_complex_sqrt(\n vec2(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 vec2 incidentIorCosine = vec2(incidentIor * incidentCosine, 0.0);\n vec2 transmittedIorCosine = openpbr_complex_multiply(transmittedIor, transmittedCosine);\n if (pPolarized) {\n vec2 transmittedIorIncidentCosine = transmittedIor * incidentCosine;\n vec2 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\nfloat openpbr_airy_reflectance(\n float reflection12,\n float transmission12,\n float reflection21,\n float transmission21,\n vec2 reflection23,\n float phase\n) {\n vec2 phaseRotation = vec2(cos(phase), sin(phase));\n vec2 reflectedPhase = openpbr_complex_multiply(reflection23, phaseRotation);\n vec2 numerator = reflectedPhase * (transmission12 * transmission21);\n vec2 denominator = vec2(1.0, 0.0) - reflectedPhase * reflection21;\n vec2 totalReflection = vec2(reflection12, 0.0)\n + openpbr_complex_divide(numerator, denominator);\n return dot(totalReflection, totalReflection);\n}\n\nfloat openpbr_thin_film_reflectance_channel(\n float incidentCosine,\n float wavelength,\n float thickness,\n float exteriorIor,\n float filmIor,\n vec2 substrateIor\n) {\n incidentCosine = clamp(abs(incidentCosine), 1e-5, 1.0);\n float filmSineSquared = pow(exteriorIor / filmIor, 2.0)\n * (1.0 - incidentCosine * incidentCosine);\n if (filmSineSquared >= 1.0) {\n return 1.0;\n }\n float filmCosine = sqrt(1.0 - filmSineSquared);\n float reflection12S = openpbr_fresnel_s(\n exteriorIor, filmIor, incidentCosine, filmCosine\n );\n float reflection12P = openpbr_fresnel_p(\n exteriorIor, filmIor, incidentCosine, filmCosine\n );\n float transmission12S = openpbr_transmission_s(\n exteriorIor, filmIor, incidentCosine, filmCosine\n );\n float transmission12P = openpbr_transmission_p(\n exteriorIor, filmIor, incidentCosine, filmCosine\n );\n float transmission21Scale = filmIor * filmCosine\n / max(exteriorIor * incidentCosine, 1e-9);\n vec2 reflection23S = openpbr_complex_fresnel_reflection(\n filmCosine, filmIor, substrateIor, false\n );\n vec2 reflection23P = openpbr_complex_fresnel_reflection(\n filmCosine, filmIor, substrateIor, true\n );\n float phase = 2.0 * PI * (2.0 * filmIor * thickness * filmCosine) / wavelength;\n float reflectanceS = openpbr_airy_reflectance(\n reflection12S,\n transmission12S,\n -reflection12S,\n transmission12S * transmission21Scale,\n reflection23S,\n phase\n );\n float reflectanceP = openpbr_airy_reflectance(\n reflection12P,\n transmission12P,\n -reflection12P,\n transmission12P * transmission21Scale,\n reflection23P,\n phase\n );\n return clamp(0.5 * (reflectanceS + reflectanceP), 0.0, 1.0);\n}\n\nvec3 openpbr_thin_film_dielectric_reflectance(\n float incidentCosine,\n float thickness,\n float filmIor,\n float substrateIor,\n float exteriorIor\n) {\n return vec3(\n openpbr_thin_film_reflectance_channel(\n incidentCosine, 620.0, thickness, exteriorIor, filmIor, vec2(substrateIor, 0.0)\n ),\n openpbr_thin_film_reflectance_channel(\n incidentCosine, 540.0, thickness, exteriorIor, filmIor, vec2(substrateIor, 0.0)\n ),\n openpbr_thin_film_reflectance_channel(\n incidentCosine, 450.0, thickness, exteriorIor, filmIor, vec2(substrateIor, 0.0)\n )\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.\nvec3 openpbr_metal_fresnel(float cosine, vec3 f0, vec3 tint) {\n vec3 schlick = mix(f0, vec3(1.0), sampling_schlick_fifth_power(cosine));\n if (all(equal(tint, vec3(1.0)))) { return schlick; }\n float edgeCosine = 1.0 / 7.0;\n float edgeComplement = 1.0 - edgeCosine;\n vec3 edgeFresnel = mix(f0, vec3(1.0), sampling_schlick_fifth_power(edgeCosine));\n float complement = 1.0 - cosine;\n float correction = cosine * sampling_schlick_fifth_power(cosine) * complement\n / (edgeCosine * pow(edgeComplement, 6.0));\n return clamp(schlick - correction * (edgeFresnel - tint * edgeFresnel), vec3(0.0), vec3(1.0));\n}\n\nvec2 openpbr_metal_complex_ior(float f0, float f82Tint) {\n float reflectivity = clamp(f0, 0.0, 0.99);\n float sqrtReflectivity = sqrt(reflectivity);\n float minimumIor = (1.0 - reflectivity) / (1.0 + reflectivity);\n float maximumIor = (1.0 + sqrtReflectivity) / max(1.0 - sqrtReflectivity, 1e-9);\n float ior = mix(maximumIor, minimumIor, clamp(f82Tint, 0.0, 1.0));\n float 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 vec2(ior, sqrt(max(extinctionSquared, 0.0)));\n}\n\nvec3 openpbr_thin_film_metal_reflectance(\n float incidentCosine,\n float thickness,\n float filmIor,\n vec3 metalF0,\n vec3 metalF82Tint,\n float exteriorIor\n) {\n return vec3(\n openpbr_thin_film_reflectance_channel(\n incidentCosine,\n 620.0,\n thickness,\n exteriorIor,\n filmIor,\n openpbr_metal_complex_ior(metalF0.r, metalF82Tint.r)\n ),\n openpbr_thin_film_reflectance_channel(\n incidentCosine,\n 540.0,\n thickness,\n exteriorIor,\n filmIor,\n openpbr_metal_complex_ior(metalF0.g, metalF82Tint.g)\n ),\n openpbr_thin_film_reflectance_channel(\n incidentCosine,\n 450.0,\n thickness,\n exteriorIor,\n filmIor,\n openpbr_metal_complex_ior(metalF0.b, metalF82Tint.b)\n )\n );\n}\n\nfloat openpbr_thin_film_presence(float thickness, float weight) {\n return clamp(weight, 0.0, 1.0) * smoothstep(1e-9, 30.0, thickness);\n}\n\nvec3 openpbr_apply_dielectric_thin_film(\n vec3 substrateFresnel,\n float incidentCosine,\n float thickness,\n float filmIor,\n float substrateIor,\n float weight,\n float exteriorIor\n) {\n vec3 filmFresnel = openpbr_thin_film_dielectric_reflectance(\n incidentCosine,\n thickness,\n filmIor,\n substrateIor,\n exteriorIor\n );\n return mix(\n substrateFresnel,\n filmFresnel,\n openpbr_thin_film_presence(thickness, weight)\n );\n}\n\nvec3 openpbr_apply_metal_thin_film(\n vec3 substrateFresnel,\n float incidentCosine,\n float thickness,\n float filmIor,\n vec3 metalF0,\n vec3 metalF82Tint,\n float weight,\n float exteriorIor\n) {\n vec3 filmFresnel = openpbr_thin_film_metal_reflectance(\n incidentCosine,\n thickness,\n filmIor,\n metalF0,\n metalF82Tint,\n exteriorIor\n );\n return mix(\n substrateFresnel,\n filmFresnel,\n openpbr_thin_film_presence(thickness, weight)\n );\n}\n\nvec3 openpbr_eval_diffuse(\n OpenPbrResolvedMaterial material,\n vec3 color,\n vec3 viewLocal,\n vec3 lightLocal,\n float specularEnergyCompensation,\n out float pdf\n) {\n pdf = 0.0;\n if (lightLocal.z <= 0.0) {\n return vec3(0.0);\n }\n\n float roughness = material.diffuseRoughness;\n float fonConstantA = 0.5 - 2.0 / (3.0 * PI);\n float fonConstantB = 2.0 / 3.0 - 28.0 / (15.0 * PI);\n float muInput = lightLocal.z;\n float muOutput = viewLocal.z;\n float directional = dot(lightLocal, viewLocal) - muInput * muOutput;\n float directionalOverTangent = directional > 0.0\n ? directional / max(max(muInput, muOutput), 1e-7)\n : directional;\n float singleScatterA = 1.0 / (1.0 + fonConstantA * roughness);\n vec3 singleScatter = color * INVPI * singleScatterA\n * (1.0 + roughness * directionalOverTangent);\n\n float inputComplement = 1.0 - muInput;\n float outputComplement = 1.0 - muOutput;\n float inputGOverPi = inputComplement * (\n 0.0571085289 + inputComplement * (\n 0.491881867 + inputComplement * (\n -0.332181442 + inputComplement * 0.0714429953\n )\n )\n );\n float outputGOverPi = outputComplement * (\n 0.0571085289 + outputComplement * (\n 0.491881867 + outputComplement * (\n -0.332181442 + outputComplement * 0.0714429953\n )\n )\n );\n float inputAlbedo = (1.0 + roughness * inputGOverPi)\n / (1.0 + fonConstantA * roughness);\n float outputAlbedo = (1.0 + roughness * outputGOverPi)\n / (1.0 + fonConstantA * roughness);\n float averageAlbedo = singleScatterA * (1.0 + fonConstantB * roughness);\n vec3 multipleScatterAlbedo = color * color * averageAlbedo\n / max(vec3(1e-7), vec3(1.0) - color * (1.0 - averageAlbedo));\n vec3 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\nfloat openpbr_fuzz_directional_albedo(float viewCosine, float roughness) {\n float x = clamp(viewCosine, 0.0, 1.0);\n float y = clamp(roughness, 0.01, 1.0);\n float scale = y * (0.0206607 + 1.58491 * y)\n / (0.0379424 + y * (1.32227 + y));\n float mean = y * (-0.193854 + y * (-1.14885 + y * (1.7932 - 0.95943 * y * y)))\n / (0.046391 + y);\n float offset = y * (0.000654023 + (-0.0207818 + 0.119681 * y) * y)\n / (1.26264 + y * (-1.92021 + y));\n float 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\nvec3 openpbr_eval_fuzz(\n OpenPbrResolvedMaterial material,\n vec3 viewLocal,\n vec3 lightLocal,\n out float pdf\n) {\n pdf = 0.0;\n if (viewLocal.z <= 0.0 || lightLocal.z <= 0.0) {\n return vec3(0.0);\n }\n float 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\nvec3 openpbr_eval_microfacet_reflection(\n OpenPbrResolvedMaterial material,\n vec3 viewLocal,\n vec3 lightLocal,\n vec3 halfVector,\n vec3 fresnel,\n out float pdf\n) {\n pdf = 0.0;\n if (lightLocal.z <= 0.0) {\n return vec3(0.0);\n }\n\n vec2 alpha = vec2(material.alphaX, material.alphaY);\n float distribution = sampling_ggx_ndf(halfVector, alpha);\n float masking = sampling_ggx_smith_g1(viewLocal, alpha);\n float 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\nvec3 openpbr_eval_microfacet_refraction(\n OpenPbrResolvedMaterial material,\n float relativeIor,\n vec3 tint,\n vec3 viewLocal,\n vec3 lightLocal,\n vec3 halfVector,\n vec3 fresnel,\n out float pdf\n) {\n pdf = 0.0;\n if (lightLocal.z >= 0.0) {\n return vec3(0.0);\n }\n\n float lightHalf = dot(lightLocal, halfVector);\n float 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 vec3(0.0);\n }\n vec2 alpha = vec2(material.alphaX, material.alphaY);\n float distribution = sampling_ggx_ndf(halfVector, alpha);\n float masking = sampling_ggx_smith_g1(viewLocal, alpha);\n float maskingShadowing = masking\n * sampling_ggx_smith_g1(lightLocal, alpha);\n float denominator = lightHalf + viewHalf * relativeIor;\n denominator *= denominator;\n float jacobian = abs(lightHalf) / max(denominator, 1e-8);\n float relativeIorSquared = relativeIor * relativeIor;\n\n pdf = masking * max(0.0, viewHalf) * distribution * jacobian\n / max(viewLocal.z, 1e-8);\n return tint * (vec3(1.0) - fresnel) * distribution * maskingShadowing\n * abs(viewHalf) * jacobian * relativeIorSquared\n / max(abs(lightLocal.z * viewLocal.z), 1e-8);\n}\n\nvec3 openpbr_transmission_tint(OpenPbrResolvedMaterial material) {\n#ifdef HAS_MEDIUM\n if (material.mediumType > 0) {\n return vec3(1.0);\n }\n#endif\n return material.transmissionColor;\n}\n\nfloat openpbr_thin_wall_fresnel(float singleInterfaceFresnel) {\n return 2.0 * singleInterfaceFresnel / (1.0 + singleInterfaceFresnel);\n}\n\nvec3 openpbr_thin_wall_reflection_coefficient(\n vec3 singleInterfaceFresnel,\n float transmissionWeight\n) {\n vec3 windowFresnel = 2.0 * singleInterfaceFresnel\n / (vec3(1.0) + singleInterfaceFresnel);\n return mix(\n singleInterfaceFresnel,\n windowFresnel,\n clamp(transmissionWeight, 0.0, 1.0)\n );\n}\n\nvec3 openpbr_final_transmission_tint(\n OpenPbrResolvedMaterial material,\n float incidentCosine,\n float etaTOverEtaI\n) {\n vec3 tint = openpbr_transmission_tint(material);\n if (!material.thinWalled) {\n return tint;\n }\n float incidentSineSquared = max(0.0, 1.0 - incidentCosine * incidentCosine);\n float transmittedSineSquared = incidentSineSquared\n / max(etaTOverEtaI * etaTOverEtaI, 1e-9);\n if (transmittedSineSquared >= 1.0) {\n return vec3(0.0);\n }\n float transmittedCosine = sqrt(1.0 - transmittedSineSquared);\n return pow(max(tint, vec3(0.0)), vec3(1.0 / max(transmittedCosine, 1e-5)));\n}\n\nvec3 openpbr_eval_subsurface_boundary(\n OpenPbrResolvedMaterial material,\n vec3 viewLocal,\n vec3 lightLocal,\n out float pdf\n) {\n pdf = 0.0;\n if (viewLocal.z <= 0.0 || lightLocal.z >= 0.0) {\n return vec3(0.0);\n }\n float transmittedCosine = -lightLocal.z;\n float entryFresnel = sampling_dielectric_fresnel(\n viewLocal.z,\n material.relativeIor\n );\n pdf = transmittedCosine * INVPI;\n return vec3(1.0 - entryFresnel) * INVPI;\n}\n\nvoid openpbr_normalize_probabilities(inout OpenPbrPreparedBsdf prepared) {\n float total = prepared.probabilities[OPENPBR_LOBE_FUZZ];\n for (int lobe = 1; lobe < OPENPBR_LOBE_COUNT; lobe++) {\n total += prepared.probabilities[lobe];\n }\n if (total <= 1e-8) {\n for (int lobe = 0; lobe < OPENPBR_LOBE_COUNT; lobe++) {\n prepared.probabilities[lobe] = 0.0;\n }\n return;\n }\n float inverseTotal = 1.0 / total;\n for (int lobe = 0; lobe < OPENPBR_LOBE_COUNT; 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).\nfloat openpbr_average_fresnel(float eta) {\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\nvec3 openpbr_metal_average_fresnel(vec3 f0, vec3 tint) {\n float edgeCosine = 1.0 / 7.0;\n vec3 edgeFresnel = mix(f0, vec3(1.0), sampling_schlick_fifth_power(edgeCosine));\n vec3 b = edgeFresnel * (vec3(1.0) - tint) / (edgeCosine * pow(1.0 - edgeCosine, 6.0));\n return clamp(f0 + (vec3(1.0) - f0) / 21.0 - b / 126.0, vec3(0.0), vec3(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.\nvec3 openpbr_coat_passage(OpenPbrResolvedMaterial material, float cosine) {\n if (cosine <= 0.0) { return vec3(1.0); }\n float refractedCosine = sqrt(max(0.0, 1.0 - (1.0 - cosine * cosine) / (material.coatIor * material.coatIor)));\n vec3 passage = pow(max(material.coatColor, vec3(0.0)), vec3(0.5 / max(refractedCosine, 1e-5)));\n return mix(vec3(1.0), passage, material.coatWeight);\n}\n\nfloat openpbr_coat_energy(OpenPbrResolvedMaterial material, float cosine) {\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\nvec3 openpbr_coat_darkening(OpenPbrPreparedBsdf prepared) {\n OpenPbrResolvedMaterial material = prepared.material;\n float dielectric = 1.0 - material.baseMetalness;\n float opaque = dielectric * (1.0 - material.transmissionWeight) * (1.0 - material.subsurfaceWeight);\n float sss = dielectric * (1.0 - material.transmissionWeight) * material.subsurfaceWeight;\n float trans = dielectric * material.transmissionWeight;\n float opaqueSpecularity = openpbr_average_fresnel(prepared.specularEtaTOverEtaI);\n float transSpecularity = openpbr_average_fresnel(1.0 / prepared.transmissionReflectionIor);\n float specularity = clamp(opaque * opaqueSpecularity + (sss + trans) * transSpecularity + material.baseMetalness, 0.0, 1.0);\n float effectiveRoughness = mix(1.0, material.specularRoughness, specularity);\n float ks = openpbr_average_fresnel(material.coatIor);\n float kr = 1.0 - (1.0 - ks) / (material.coatIor * material.coatIor);\n float k = mix(ks, kr, effectiveRoughness);\n vec3 eb = material.baseMetalness * material.specularWeight * openpbr_metal_average_fresnel(material.baseWeight * material.baseColor, material.specularColor)\n + opaque * mix(material.baseWeight * material.baseColor, vec3(1.0), opaqueSpecularity)\n + sss * material.subsurfaceColor + vec3(trans);\n vec3 delta = vec3(1.0 - k) / max(vec3(1.0) - eb * k, vec3(1e-6));\n return mix(vec3(1.0), delta, material.coatWeight * material.coatDarkening);\n}\n\nvec3 openpbr_coat_base_scale(OpenPbrPreparedBsdf prepared, float lightCosine) {\n if (prepared.material.coatWeight <= 0.0) { return vec3(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\nOpenPbrBasis openpbr_direction_basis(OpenPbrBasis fallback, vec3 tangent, vec3 bitangent, vec2 direction) {\n vec3 t = tangent - fallback.normal * dot(fallback.normal, tangent);\n if (dot(t, t) < 1e-8) { t = fallback.tangent; }\n t = normalize(t);\n vec3 b = cross(fallback.normal, t) * (dot(cross(fallback.normal, t), bitangent) < 0.0 ? -1.0 : 1.0);\n t = t * direction.x + b * direction.y;\n if (dot(t, t) < 1e-8) { return fallback; }\n OpenPbrBasis 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\nOpenPbrPreparedBsdf openpbr_prepare(\n vec3 tangent,\n OpenPbrResolvedMaterial material,\n vec3 viewDirection,\n vec3 normal\n) {\n OpenPbrPreparedBsdf prepared;\n prepared.material = material;\n prepared.basis = openpbr_make_basis(\n tangent,\n normal,\n max(material.specularAnisotropy, material.coatAnisotropy)\n );\n prepared.coatBasis = prepared.basis;\n prepared.directionFrames = false;\n#ifdef NUM_COAT_NORMAL_MAPS\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#endif\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, vec2(cos(material.coatAnisotropyRotation), sin(material.coatAnisotropyRotation)));\n prepared.directionFrames = true;\n }\n prepared.viewLocal = openpbr_to_local(prepared.basis, viewDirection);\n#ifdef NUM_COAT_NORMAL_MAPS\n prepared.coatViewLocal = openpbr_to_local(prepared.coatBasis, viewDirection);\n#endif\n prepared.diffuseColor = material.baseColor;\n\n float dielectricBaseWeight = 1.0 - material.baseMetalness;\n float 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 float weightedCoatIor = 1.0;\n if (!material.inside) { weightedCoatIor = mix(1.0, material.coatIor, material.coatWeight); }\n float weightedBaseIor = weightedCoatIor * openpbr_apply_specular_weight_to_ior(\n material.specularIor / weightedCoatIor, material.specularWeight);\n float 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 float viewCosine = abs(prepared.viewLocal.z);\n float singleInterfaceFresnel = openpbr_dielectric_fresnel(\n viewCosine,\n 1.0 / prepared.specularEtaTOverEtaI\n );\n vec3 dielectricFresnelEstimate = material.specularColor * (\n material.thinWalled\n ? mix(\n singleInterfaceFresnel,\n openpbr_thin_wall_fresnel(singleInterfaceFresnel),\n material.transmissionWeight\n )\n : singleInterfaceFresnel\n );\n vec3 metalFresnelEstimate = material.specularWeight * openpbr_metal_fresnel(\n viewCosine,\n material.baseWeight * material.baseColor,\n material.specularColor\n );\n float transmissionFresnel = material.thinWalled\n ? openpbr_thin_wall_fresnel(openpbr_dielectric_fresnel(viewCosine, prepared.transmissionReflectionIor))\n : openpbr_dielectric_fresnel(viewCosine, prepared.transmissionReflectionIor);\n vec3 transmissionTint = openpbr_final_transmission_tint(\n material,\n viewCosine,\n prepared.refractionEtaTOverEtaI\n );\n float viewEnergyComplement = openpbr_opaque_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha,\n viewCosine\n );\n float 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 = vec3(1.0);\n if (material.coatWeight > 0.0 && !material.inside) {\n#ifdef NUM_COAT_NORMAL_MAPS\n float coatViewCosine = material.coatNormalEnabled ? max(prepared.coatViewLocal.z, 0.0) : viewCosine;\n float coatEnergy = openpbr_coat_energy(material, coatViewCosine);\n#else\n float coatEnergy = openpbr_coat_energy(material, viewCosine);\n#endif\n prepared.probabilities[OPENPBR_LOBE_COAT] = prepared.weights[OPENPBR_LOBE_COAT] * coatEnergy;\n prepared.coatBaseScale = openpbr_coat_passage(material,\n#ifdef NUM_COAT_NORMAL_MAPS\n coatViewCosine\n#else\n viewCosine\n#endif\n )\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 = vec3(0.0);\n prepared.metalMsFactor = 0.0;\n prepared.metalMsProbability = 0.0;\n if (material.specularRoughness >= 0.04 && prepared.weights[OPENPBR_LOBE_METAL] > 0.0) {\n vec3 averageFresnel = openpbr_metal_average_fresnel(material.baseWeight * material.baseColor, material.specularColor);\n prepared.metalMsScale = material.specularWeight * averageFresnel * averageFresnel;\n float viewLoss = openpbr_metal_energy_complement(prepared.specularAlpha, viewCosine);\n prepared.metalMsFactor = viewLoss / max(openpbr_metal_average_energy_complement(prepared.specularAlpha), 1e-12);\n float 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 = vec3(0.0);\n prepared.dielectricMsTransmissionScale = vec3(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 float nonOpaqueWeight = prepared.weights[OPENPBR_LOBE_TRANSMISSION] + prepared.weights[OPENPBR_LOBE_SUBSURFACE];\n prepared.dielectricMsReflectionScale = (material.inside ? vec3(1.0) : material.specularColor) * nonOpaqueWeight;\n prepared.dielectricMsTransmissionScale = transmissionTint * prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n + vec3(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 vec3 averageScale = mix(prepared.dielectricMsTransmissionScale, prepared.dielectricMsReflectionScale, prepared.dielectricMsReflectionRatio);\n float 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 && material.thinFilmWeight > 0.0) {\n float 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 float 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\nfloat openpbr_substrate_fresnel(OpenPbrPreparedBsdf prepared, float cosine, bool reflection) {\n if (prepared.material.coatWeight <= 0.0) {\n return openpbr_dielectric_fresnel(cosine, prepared.material.relativeIor);\n }\n float transFresnel = openpbr_dielectric_fresnel(cosine, prepared.transmissionReflectionIor);\n if (!reflection) { return transFresnel; }\n float opaqueFresnel = openpbr_dielectric_fresnel(cosine, 1.0 / prepared.specularEtaTOverEtaI);\n float nonOpaque = prepared.material.transmissionWeight + (1.0 - prepared.material.transmissionWeight) * prepared.material.subsurfaceWeight;\n return mix(opaqueFresnel, transFresnel, nonOpaque);\n}\n\nvec3 openpbr_eval_prepared(\n OpenPbrPreparedBsdf prepared,\n vec3 lightDirection,\n out float pdf\n) {\n OpenPbrResolvedMaterial material = prepared.material;\n vec3 viewLocal = prepared.viewLocal;\n vec3 lightLocal = openpbr_to_local(prepared.basis, lightDirection);\n bool reflection = lightLocal.z * viewLocal.z > 0.0;\n#ifdef NUM_COAT_NORMAL_MAPS\n bool independentCoat = material.coatNormalEnabled;\n bool geometricReflection = dot(material.geometricNormal, lightDirection) > 0.0;\n float coatLightCosine = dot(prepared.coatBasis.normal, lightDirection);\n bool coatReflection = geometricReflection && prepared.coatViewLocal.z > 0.0 && coatLightCosine > 0.0;\n#endif\n\n vec3 halfVector;\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 vec3 response = vec3(0.0);\n pdf = 0.0;\n float lobePdf = 0.0;\n float viewHalf = abs(dot(viewLocal, halfVector));\n float diffuseSpecularEnergyCompensation =\n prepared.diffuseSpecularEnergyCompensation\n * openpbr_opaque_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha,\n abs(lightLocal.z)\n );\n\n#ifdef NUM_COAT_NORMAL_MAPS\n if (!independentCoat || (lightLocal.z != 0.0 && reflection == geometricReflection)) {\n#endif\n if (reflection) {\n if (prepared.weights[OPENPBR_LOBE_DIFFUSE] > 0.0) {\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 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) {\n vec3 dielectricFresnel = vec3(openpbr_substrate_fresnel(prepared, viewHalf, true));\n if (material.thinFilmWeight > 0.0) {\n dielectricFresnel = openpbr_apply_dielectric_thin_film(\n dielectricFresnel,\n viewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n prepared.thinFilmBaseIor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n if (material.thinWalled) {\n dielectricFresnel = openpbr_thin_wall_reflection_coefficient(\n dielectricFresnel,\n material.transmissionWeight\n );\n }\n vec3 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) {\n vec3 fresnel = openpbr_metal_fresnel(\n viewHalf,\n material.baseWeight * material.baseColor,\n material.specularColor\n );\n if (material.thinFilmWeight > 0.0) {\n fresnel = openpbr_apply_metal_thin_film(\n fresnel,\n viewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n material.baseWeight * material.baseColor,\n material.specularColor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n fresnel *= material.specularWeight;\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 float 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 vec3 fresnel = vec3(openpbr_substrate_fresnel(prepared, viewHalf, false));\n if (material.thinFilmWeight > 0.0) {\n fresnel = openpbr_apply_dielectric_thin_film(\n fresnel,\n viewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n prepared.thinFilmBaseIor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n if (prepared.weights[OPENPBR_LOBE_TRANSMISSION] > 0.0) {\n if (material.thinWalled) {\n vec3 mirroredLight = vec3(lightLocal.xy, -lightLocal.z);\n vec3 thinWallHalf = normalize(mirroredLight + viewLocal);\n if (thinWallHalf.z < 0.0) {\n thinWallHalf = -thinWallHalf;\n }\n float thinWallViewHalf = abs(dot(viewLocal, thinWallHalf));\n vec3 thinWallFresnel = vec3(openpbr_substrate_fresnel(prepared, thinWallViewHalf, false));\n if (material.thinFilmWeight > 0.0) {\n thinWallFresnel = openpbr_apply_dielectric_thin_film(\n thinWallFresnel,\n thinWallViewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n prepared.thinFilmBaseIor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n vec3 windowFresnel = openpbr_thin_wall_reflection_coefficient(\n thinWallFresnel,\n 1.0\n );\n vec3 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 * (vec3(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) {\n if (material.thinWalled) {\n vec3 flippedLight = vec3(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) {\n float cosine = abs(lightLocal.z);\n if (cosine > 0.0) {\n float ior = (reflection ? prepared.refractionEtaTOverEtaI : 1.0 / prepared.refractionEtaTOverEtaI);\n float ratio = (reflection ? prepared.dielectricMsReflectionRatio : 1.0 - prepared.dielectricMsReflectionRatio);\n float factors = prepared.dielectricMsViewLoss * openpbr_ideal_dielectric_loss(ior, prepared.specularAlpha, cosine)\n / max(openpbr_ideal_dielectric_average_loss(ior, prepared.specularAlpha), 1e-12);\n float factor = min(factors * ratio, 1.0 / cosine);\n vec3 scale = (reflection ? prepared.dielectricMsReflectionScale : prepared.dielectricMsTransmissionScale);\n response += scale * factor * INVPI;\n pdf += prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] * prepared.dielectricMsProbability * ratio * 0.5 * INVPI;\n }\n }\n\n#ifdef NUM_COAT_NORMAL_MAPS\n } // base hemisphere support\n#endif\n if (material.coatWeight > 0.0) {\n response *= openpbr_coat_base_scale(prepared,\n#ifdef NUM_COAT_NORMAL_MAPS\n independentCoat ? coatLightCosine : lightLocal.z\n#else\n lightLocal.z\n#endif\n );\n }\n#ifdef NUM_COAT_NORMAL_MAPS\n if (independentCoat) { response *= abs(lightLocal.z); }\n#endif\n\n if (\n#ifdef NUM_COAT_NORMAL_MAPS\n (independentCoat ? coatReflection : reflection)\n#else\n reflection\n#endif\n && prepared.weights[OPENPBR_LOBE_COAT] > 0.0) {\n vec3 coatView = viewLocal;\n vec3 coatLight = lightLocal;\n vec3 coatHalf = halfVector;\n float coatViewHalf = viewHalf;\n if (prepared.directionFrames) {\n coatView = openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal));\n#ifdef NUM_COAT_NORMAL_MAPS\n if (independentCoat) { coatView = prepared.coatViewLocal; }\n#endif\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 vec3 coatFresnel = vec3(openpbr_dielectric_fresnel(\n coatViewHalf,\n 1.0 / material.coatIor\n ));\n OpenPbrResolvedMaterial coatMaterial = material;\n vec2 coatAlpha = openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy);\n coatMaterial.alphaX = coatAlpha.x;\n coatMaterial.alphaY = coatAlpha.y;\n response += prepared.weights[OPENPBR_LOBE_COAT]\n#ifdef NUM_COAT_NORMAL_MAPS\n * (independentCoat ? max(coatLightCosine, 0.0) : 1.0)\n#endif\n * 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 if (\n#ifdef NUM_COAT_NORMAL_MAPS\n (independentCoat ? geometricReflection : reflection)\n#else\n reflection\n#endif\n && prepared.weights[OPENPBR_LOBE_FUZZ] > 0.0) {\n float fuzzAlbedo = openpbr_fuzz_directional_albedo(\n abs(viewLocal.z),\n material.fuzzRoughness\n );\n response *= max(\n vec3(0.0),\n vec3(1.0 - prepared.weights[OPENPBR_LOBE_FUZZ] * fuzzAlbedo)\n );\n response += prepared.weights[OPENPBR_LOBE_FUZZ]\n#ifdef NUM_COAT_NORMAL_MAPS\n * (independentCoat ? max(lightLocal.z, 0.0) : 1.0)\n#endif\n * openpbr_eval_fuzz(\n material,\n viewLocal,\n lightLocal,\n lobePdf\n );\n pdf += prepared.probabilities[OPENPBR_LOBE_FUZZ] * lobePdf;\n }\n\n#ifdef NUM_COAT_NORMAL_MAPS\n if (independentCoat) { return response; }\n#endif\n return response * abs(lightLocal.z);\n}\n\nvec3 openpbr_eval(\n vec3 tangent,\n OpenPbrResolvedMaterial material,\n vec3 viewDirection,\n vec3 normal,\n vec3 lightDirection,\n out float pdf\n) {\n OpenPbrPreparedBsdf prepared = openpbr_prepare(\n tangent,\n material,\n viewDirection,\n normal\n );\n return openpbr_eval_prepared(prepared, lightDirection, pdf);\n}\n\nfloat openpbr_pdf(\n vec3 tangent,\n OpenPbrResolvedMaterial material,\n vec3 viewDirection,\n vec3 normal,\n vec3 lightDirection\n) {\n float pdf;\n openpbr_eval(\n tangent,\n material,\n viewDirection,\n normal,\n lightDirection,\n pdf\n );\n return pdf;\n}\n\nvec3 openpbr_sample_prepared(\n OpenPbrPreparedBsdf prepared,\n vec3 randomSamples,\n out vec3 lightDirection,\n out float pdf,\n out bool isTransmissionEvent\n) {\n OpenPbrResolvedMaterial material = prepared.material;\n vec3 viewLocal = prepared.viewLocal;\n float randomX = randomSamples.x;\n float randomY = randomSamples.y;\n float lobeSample = randomSamples.z;\n pdf = 0.0;\n isTransmissionEvent = false;\n lightDirection = prepared.basis.normal;\n\n // At eta=1 the solid-refraction half vector is the zero vector. Treat the\n // physically null interface as straight, unit-throughput transmission.\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(prepared.basis, -viewLocal);\n pdf = 1.0;\n isTransmissionEvent = true;\n return (openpbr_transmission_tint(prepared.material) * openpbr_coat_base_scale(prepared,\n#ifdef NUM_COAT_NORMAL_MAPS\n prepared.material.coatNormalEnabled ? dot(prepared.coatBasis.normal, lightDirection) : -prepared.viewLocal.z\n#else\n -prepared.viewLocal.z\n#endif\n ));\n }\n\n float cdf[OPENPBR_LOBE_COUNT];\n cdf[OPENPBR_LOBE_FUZZ] = prepared.probabilities[OPENPBR_LOBE_FUZZ];\n for (int lobe = 1; lobe < OPENPBR_LOBE_COUNT; lobe++) {\n cdf[lobe] = cdf[lobe - 1] + prepared.probabilities[lobe];\n }\n if (cdf[OPENPBR_LOBE_SUBSURFACE] <= 0.0) {\n return vec3(0.0);\n }\n\n vec3 lightLocal;\n if (lobeSample < cdf[OPENPBR_LOBE_FUZZ] || (\n lobeSample >= cdf[OPENPBR_LOBE_TRANSMISSION] && lobeSample < cdf[OPENPBR_LOBE_DIFFUSE]\n )) {\n lightLocal = sampling_cosine_hemisphere(vec2(randomX, randomY));\n }\n else if (lobeSample < cdf[OPENPBR_LOBE_COAT]) {\n vec3 coatView = viewLocal;\n if (prepared.directionFrames) { coatView = openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal)); }\n#ifdef NUM_COAT_NORMAL_MAPS\n if (material.coatNormalEnabled) { coatView = prepared.coatViewLocal; }\n#endif\n vec3 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 vec2(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n lightLocal = normalize(reflect(-coatView, halfVector));\n#ifdef NUM_COAT_NORMAL_MAPS\n if (material.coatNormalEnabled) {\n lightDirection = openpbr_to_world(prepared.coatBasis, lightLocal);\n if (lightLocal.z <= 0.0 || dot(material.geometricNormal, lightDirection) <= 0.0) { return vec3(0.0); }\n return openpbr_eval_prepared(prepared, lightDirection, pdf);\n }\n#endif\n if (prepared.directionFrames) { lightLocal = openpbr_to_local(prepared.basis, openpbr_to_world(prepared.coatBasis, lightLocal)); }\n }\n else if (lobeSample < cdf[OPENPBR_LOBE_SPECULAR]) {\n bool isMetal = lobeSample < cdf[OPENPBR_LOBE_METAL];\n float metalDraw = (lobeSample - cdf[OPENPBR_LOBE_COAT]) / max(prepared.probabilities[OPENPBR_LOBE_METAL], 1e-12);\n if (isMetal && metalDraw < prepared.metalMsProbability) {\n float z = randomX;\n float radial = sqrt(max(0.0, 1.0 - z * z));\n float azimuth = TWOPI * randomY;\n lightLocal = vec3(radial * cos(azimuth), radial * sin(azimuth), z);\n }\n else {\n vec3 halfVector = sampling_ggx_visible_normal(\n viewLocal,\n vec2(material.alphaX, material.alphaY),\n vec2(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 isTransmissionEvent = true;\n // Split the existing BSDF_LOBE interval. BSDF_DIRECTION retains its\n // two dimensions; rand.x selects and remaps the reference hemisphere.\n float msDraw = (lobeSample - cdf[OPENPBR_LOBE_SPECULAR]) / max(prepared.probabilities[OPENPBR_LOBE_TRANSMISSION], 1e-12);\n if (prepared.dielectricMsProbability > 0.0 && msDraw < prepared.dielectricMsProbability) {\n float ratio = prepared.dielectricMsReflectionRatio;\n bool reflectMs = randomX < ratio;\n float z;\n if (reflectMs) { z = min(randomX / ratio, 0.99999994); }\n else { z = -min((randomX - ratio) / (1.0 - ratio), 0.99999994); }\n float radial = sqrt(max(0.0, 1.0 - z * z));\n float azimuth = TWOPI * randomY;\n lightLocal = vec3(radial * cos(azimuth), radial * sin(azimuth), z);\n isTransmissionEvent = !reflectMs;\n }\n else {\n vec3 halfVector = sampling_ggx_visible_normal(\n viewLocal,\n vec2(material.alphaX, material.alphaY),\n vec2(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n float viewHalf = abs(dot(viewLocal, halfVector));\n vec3 fresnel = vec3(openpbr_dielectric_fresnel(\n viewHalf,\n prepared.material.relativeIor\n ));\n if (material.thinFilmWeight > 0.0) {\n fresnel = openpbr_apply_dielectric_thin_film(\n fresnel,\n viewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n prepared.thinFilmBaseIor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n if (material.thinWalled) {\n vec3 reflectedLocal = normalize(reflect(-viewLocal, halfVector));\n lightLocal = vec3(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 vec3(0.0);\n }\n lightLocal = normalize(lightLocal);\n }\n }\n }\n else {\n isTransmissionEvent = true;\n if (material.thinWalled) {\n float hemisphereSample = randomX;\n bool transmit = hemisphereSample >= 0.5;\n randomX = transmit\n ? (hemisphereSample - 0.5) * 2.0\n : hemisphereSample * 2.0;\n lightLocal = sampling_cosine_hemisphere(vec2(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(vec2(randomX, randomY));\n lightLocal.z = -lightLocal.z;\n }\n }\n\n lightDirection = openpbr_to_world(prepared.basis, lightLocal);\n // Reject VNDF draws on the wrong side before aggregate BSDF evaluation.\n if (lightLocal.z == 0.0 || (lightLocal.z < 0.0) != isTransmissionEvent) {\n return vec3(0.0);\n }\n#ifdef NUM_COAT_NORMAL_MAPS\n if (material.coatNormalEnabled &&\n ((dot(material.geometricNormal, lightDirection) <= 0.0) != isTransmissionEvent)) { return vec3(0.0); }\n#endif\n return openpbr_eval_prepared(prepared, lightDirection, pdf);\n}\n\nvec3 openpbr_sample(\n vec3 tangent,\n OpenPbrResolvedMaterial material,\n vec3 randomSamples,\n vec3 viewDirection,\n vec3 normal,\n out vec3 lightDirection,\n out float pdf,\n out bool isTransmissionEvent\n) {\n OpenPbrPreparedBsdf prepared = openpbr_prepare(tangent, material, viewDirection, normal);\n return openpbr_sample_prepared(prepared, randomSamples, lightDirection, pdf, isTransmissionEvent);\n}`;\n"]}
|
|
1
|
+
{"version":3,"file":"openPbrBsdf.glsl.js","sourceRoot":"","sources":["../../../../src/renderer/glsl/chunks/openPbrBsdf.glsl.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEvC,OAAO,EAAE,4BAA4B,EAAE,oCAAoC,EAAE,iCAAiC,EAAE,yCAAyC,EAAE,2CAA2C,EAAE,MAAM,2BAA2B,CAAC;AAE1O,eAAe,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAqK8B,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;mBACjC,iCAAiC;;;;;;;;;;;;;;;;;;;;;oBAqBnC,yCAAyC;oBACzC,yCAAyC;oBACzC,yCAAyC;oBACzC,yCAAyC;;;;;;;;;;;;;;;;;oBAiBzC,2CAA2C;oBAC3C,2CAA2C;oBAC3C,2CAA2C;oBAC3C,2CAA2C;;;;;;;;;;;;;;;;;;;;;;;;;;;YA2BnD,4BAA4B,iBAAoC,4BAA4B;YAC5F,4BAA4B,iBAAoC,4BAA4B;;;;;;;mBAOzF,oCAAoC;qBAC9B,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAu9ClF,CAAC","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 */\nimport { glsl } from '../../shader.js';\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 default glsl`\nconst int OPENPBR_LOBE_FUZZ = 0;\nconst int OPENPBR_LOBE_COAT = 1;\nconst int OPENPBR_LOBE_METAL = 2;\nconst int OPENPBR_LOBE_SPECULAR = 3;\nconst int OPENPBR_LOBE_TRANSMISSION = 4;\nconst int OPENPBR_LOBE_DIFFUSE = 5;\nconst int OPENPBR_LOBE_SUBSURFACE = 6;\nconst int OPENPBR_LOBE_COUNT = 7;\n\nstruct OpenPbrBasis {\n vec3 tangent;\n vec3 bitangent;\n vec3 normal;\n};\n\nstruct OpenPbrPreparedBsdf {\n OpenPbrResolvedMaterial material;\n OpenPbrBasis basis;\n OpenPbrBasis coatBasis;\n bool directionFrames;\n#ifdef USE_COAT_NORMAL_FRAMES\n vec3 coatViewLocal;\n#endif\n vec3 viewLocal;\n vec3 diffuseColor;\n float specularEtaTOverEtaI;\n float refractionEtaTOverEtaI;\n float transmissionReflectionIor;\n float thinFilmExteriorIor;\n float thinFilmBaseIor;\n float specularAlpha;\n float diffuseSpecularEnergyCompensation;\n vec3 coatBaseScale;\n vec3 metalMsScale;\n float metalMsFactor;\n float metalMsProbability;\n\n vec3 dielectricMsReflectionScale;\n vec3 dielectricMsTransmissionScale;\n float dielectricMsViewLoss;\n float dielectricMsReflectionRatio;\n float dielectricMsProbability;\n\n float weights[OPENPBR_LOBE_COUNT];\n float probabilities[OPENPBR_LOBE_COUNT];\n};\n\nfloat openpbr_luminance(vec3 color) {\n return dot(color, vec3(0.212671, 0.715160, 0.072169));\n}\n\nfloat openpbr_f0_from_ior(float etaTOverEtaI) {\n float value = (etaTOverEtaI - 1.0) / (etaTOverEtaI + 1.0);\n return value * value;\n}\n\nfloat openpbr_apply_specular_weight_to_ior(\n float etaTOverEtaI,\n float specularWeight\n) {\n float scaledF0 = min(\n openpbr_f0_from_ior(etaTOverEtaI) * specularWeight,\n 0.9999\n );\n float sqrtF0 = sqrt(scaledF0);\n float externalIor = (1.0 + sqrtF0) / (1.0 - sqrtF0);\n return etaTOverEtaI < 1.0 ? 1.0 / externalIor : externalIor;\n}\n\nfloat openpbr_dielectric_fresnel(float cosTheta, float relativeIor) {\n if (abs(relativeIor - 1.0) < 1e-6) {\n return 0.0;\n }\n return sampling_dielectric_fresnel(cosTheta, relativeIor);\n}\n\nfloat openpbr_energy_ior_index(float ior) {\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\nfloat openpbr_energy_fetch(int cosIndex, int row) {\n return texelFetch(openPbrEnergyLut, ivec2(cosIndex, row), 0).r;\n}\n\nfloat openpbr_energy_trilinear(\n float exactIor,\n float exactAlpha,\n float exactCosine\n) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n float cosineIndex = clamp(exactCosine, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int cosine0 = int(floor(cosineIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n int cosine1 = min(cosine0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float cosineMix = cosineIndex - float(cosine0);\n\n float v000 = openpbr_energy_fetch(cosine0, ior0 * 32 + alpha0);\n float v001 = openpbr_energy_fetch(cosine1, ior0 * 32 + alpha0);\n float v010 = openpbr_energy_fetch(cosine0, ior0 * 32 + alpha1);\n float v011 = openpbr_energy_fetch(cosine1, ior0 * 32 + alpha1);\n float v100 = openpbr_energy_fetch(cosine0, ior1 * 32 + alpha0);\n float v101 = openpbr_energy_fetch(cosine1, ior1 * 32 + alpha0);\n float v110 = openpbr_energy_fetch(cosine0, ior1 * 32 + alpha1);\n float v111 = openpbr_energy_fetch(cosine1, ior1 * 32 + alpha1);\n float v00 = mix(v000, v100, iorMix);\n float v01 = mix(v001, v101, iorMix);\n float v10 = mix(v010, v110, iorMix);\n float v11 = mix(v011, v111, iorMix);\n return mix(\n mix(v00, v10, alphaMix),\n mix(v01, v11, alphaMix),\n cosineMix\n );\n}\n\nfloat openpbr_energy_bilinear(float exactIor, float exactAlpha) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float v00 = openpbr_energy_fetch(alpha0, 1024 + ior0);\n float v01 = openpbr_energy_fetch(alpha1, 1024 + ior0);\n float v10 = openpbr_energy_fetch(alpha0, 1024 + ior1);\n float 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].\nfloat openpbr_ideal_energy_trilinear(\n float exactIor,\n float exactAlpha,\n float exactCosine\n) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n float cosineIndex = clamp(exactCosine, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int cosine0 = int(floor(cosineIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n int cosine1 = min(cosine0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float cosineMix = cosineIndex - float(cosine0);\n\n float v000 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha0);\n float v001 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha0);\n float v010 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha1);\n float v011 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior0 * 32 + alpha1);\n float v100 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha0);\n float v101 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha0);\n float v110 = openpbr_energy_fetch(cosine0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha1);\n float v111 = openpbr_energy_fetch(cosine1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_ROW} + ior1 * 32 + alpha1);\n float v00 = mix(v000, v100, iorMix);\n float v01 = mix(v001, v101, iorMix);\n float v10 = mix(v010, v110, iorMix);\n float v11 = mix(v011, v111, iorMix);\n return mix(\n mix(v00, v10, alphaMix),\n mix(v01, v11, alphaMix),\n cosineMix\n );\n}\n\nfloat openpbr_ideal_average_bilinear(float exactIor, float exactAlpha) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float v00 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior0);\n float v01 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior0);\n float v10 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_ENERGY_LUT_AVERAGE_ROW} + ior1);\n float 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\nfloat openpbr_ideal_ratio_bilinear(float exactIor, float exactAlpha) {\n float iorIndex = clamp(exactIor, 0.0, 31.0);\n float alphaIndex = clamp(exactAlpha, 0.0, 31.0);\n int ior0 = int(floor(iorIndex));\n int alpha0 = int(floor(alphaIndex));\n int ior1 = min(ior0 + 1, 31);\n int alpha1 = min(alpha0 + 1, 31);\n float iorMix = iorIndex - float(ior0);\n float alphaMix = alphaIndex - float(alpha0);\n float v00 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior0);\n float v01 = openpbr_energy_fetch(alpha1, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior0);\n float v10 = openpbr_energy_fetch(alpha0, ${OPENPBR_DIELECTRIC_REFLECTION_RATIO_LUT_ROW} + ior1);\n float 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\nfloat openpbr_ideal_dielectric_loss(float ior, float alpha, float cosine) {\n return openpbr_ideal_energy_trilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0, cosine * 31.0);\n}\nfloat openpbr_ideal_dielectric_average_loss(float ior, float alpha) {\n return openpbr_ideal_average_bilinear(openpbr_energy_ior_index(ior), sqrt(alpha) * 31.0);\n}\nfloat openpbr_ideal_dielectric_reflection_ratio(float ior, float alpha) {\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).\nfloat openpbr_metal_energy_complement(float alpha, float cosine) {\n float roughnessIndex = clamp(sqrt(alpha) * 31.0, 0.0, 31.0);\n float cosineIndex = clamp(cosine * 31.0, 0.0, 31.0);\n int r0 = int(floor(roughnessIndex));\n int c0 = int(floor(cosineIndex));\n int r1 = min(r0 + 1, 31);\n int 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\nfloat openpbr_metal_average_energy_complement(float alpha) {\n float index = clamp(sqrt(alpha) * 31.0, 0.0, 31.0);\n int i0 = int(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\nfloat openpbr_extrapolate_energy_complement(float value, float ior) {\n if (ior > 2.5 || ior < 0.4) {\n float maxF0 = openpbr_f0_from_ior(2.5);\n float progress = (openpbr_f0_from_ior(ior) - maxF0) / (1.0 - maxF0);\n return (1.0 - progress) * value;\n }\n return value;\n}\n\nfloat openpbr_opaque_energy_complement(\n float ior,\n float alpha,\n float cosine\n) {\n float 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\nfloat openpbr_opaque_average_energy_complement(float ior, float alpha) {\n float 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\nvec3 openpbr_to_world(OpenPbrBasis basis, vec3 direction) {\n return direction.x * basis.tangent\n + direction.y * basis.bitangent\n + direction.z * basis.normal;\n}\n\nvec3 openpbr_to_local(OpenPbrBasis basis, vec3 direction) {\n return vec3(\n dot(direction, basis.tangent),\n dot(direction, basis.bitangent),\n dot(direction, basis.normal)\n );\n}\n\nOpenPbrBasis openpbr_make_basis(\n vec3 inputTangent,\n vec3 normal,\n float anisotropy\n) {\n OpenPbrBasis basis;\n basis.normal = normal;\n basis.tangent = vec3(0.0);\n#ifdef USE_TANGENT\n basis.tangent = inputTangent;\n#endif\n if (dot(basis.tangent, basis.tangent) < 0.001 || anisotropy <= 0.0) {\n sampling_orthonormal_basis(normal, basis.tangent, basis.bitangent);\n }\n else {\n basis.bitangent = normalize(cross(normal, basis.tangent));\n basis.tangent = normalize(cross(basis.bitangent, normal));\n }\n return basis;\n}\n\nfloat openpbr_fresnel_s(\n float incidentIor,\n float transmittedIor,\n float incidentCosine,\n float transmittedCosine\n) {\n return (incidentIor * incidentCosine - transmittedIor * transmittedCosine)\n / (incidentIor * incidentCosine + transmittedIor * transmittedCosine);\n}\n\nfloat openpbr_fresnel_p(\n float incidentIor,\n float transmittedIor,\n float incidentCosine,\n float transmittedCosine\n) {\n return (transmittedIor * incidentCosine - incidentIor * transmittedCosine)\n / (incidentIor * transmittedCosine + transmittedIor * incidentCosine);\n}\n\nfloat openpbr_transmission_s(\n float incidentIor,\n float transmittedIor,\n float incidentCosine,\n float transmittedCosine\n) {\n return 2.0 * incidentIor * incidentCosine\n / (incidentIor * incidentCosine + transmittedIor * transmittedCosine);\n}\n\nfloat openpbr_transmission_p(\n float incidentIor,\n float transmittedIor,\n float incidentCosine,\n float transmittedCosine\n) {\n return 2.0 * incidentIor * incidentCosine\n / (incidentIor * transmittedCosine + transmittedIor * incidentCosine);\n}\n\nvec2 openpbr_complex_multiply(vec2 a, vec2 b) {\n return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);\n}\n\nvec2 openpbr_complex_divide(vec2 a, vec2 b) {\n float denominator = max(dot(b, b), 1e-9);\n return vec2(\n (a.x * b.x + a.y * b.y) / denominator,\n (a.y * b.x - a.x * b.y) / denominator\n );\n}\n\nvec2 openpbr_complex_sqrt(vec2 value) {\n float magnitude = length(value);\n float realPart = sqrt(max(0.0, 0.5 * (magnitude + value.x)));\n float imaginaryMagnitude = sqrt(max(0.0, 0.5 * (magnitude - value.x)));\n return vec2(realPart, value.y >= 0.0 ? imaginaryMagnitude : -imaginaryMagnitude);\n}\n\nvec2 openpbr_complex_fresnel_reflection(\n float incidentCosine,\n float incidentIor,\n vec2 transmittedIor,\n bool pPolarized\n) {\n float incidentSine = sqrt(max(0.0, 1.0 - incidentCosine * incidentCosine));\n vec2 iorRatio = openpbr_complex_divide(vec2(incidentIor, 0.0), transmittedIor);\n vec2 transmittedSine = iorRatio * incidentSine;\n vec2 transmittedCosine = openpbr_complex_sqrt(\n vec2(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 vec2 incidentIorCosine = vec2(incidentIor * incidentCosine, 0.0);\n vec2 transmittedIorCosine = openpbr_complex_multiply(transmittedIor, transmittedCosine);\n if (pPolarized) {\n vec2 transmittedIorIncidentCosine = transmittedIor * incidentCosine;\n vec2 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\nfloat openpbr_airy_reflectance(\n float reflection12,\n float transmission12,\n float reflection21,\n float transmission21,\n vec2 reflection23,\n float phase\n) {\n vec2 phaseRotation = vec2(cos(phase), sin(phase));\n vec2 reflectedPhase = openpbr_complex_multiply(reflection23, phaseRotation);\n vec2 numerator = reflectedPhase * (transmission12 * transmission21);\n vec2 denominator = vec2(1.0, 0.0) - reflectedPhase * reflection21;\n vec2 totalReflection = vec2(reflection12, 0.0)\n + openpbr_complex_divide(numerator, denominator);\n return dot(totalReflection, totalReflection);\n}\n\nfloat openpbr_thin_film_reflectance_channel(\n float incidentCosine,\n float wavelength,\n float thickness,\n float exteriorIor,\n float filmIor,\n vec2 substrateIor\n) {\n incidentCosine = clamp(abs(incidentCosine), 1e-5, 1.0);\n float filmSineSquared = pow(exteriorIor / filmIor, 2.0)\n * (1.0 - incidentCosine * incidentCosine);\n if (filmSineSquared >= 1.0) {\n return 1.0;\n }\n float filmCosine = sqrt(1.0 - filmSineSquared);\n float reflection12S = openpbr_fresnel_s(\n exteriorIor, filmIor, incidentCosine, filmCosine\n );\n float reflection12P = openpbr_fresnel_p(\n exteriorIor, filmIor, incidentCosine, filmCosine\n );\n float transmission12S = openpbr_transmission_s(\n exteriorIor, filmIor, incidentCosine, filmCosine\n );\n float transmission12P = openpbr_transmission_p(\n exteriorIor, filmIor, incidentCosine, filmCosine\n );\n float transmission21Scale = filmIor * filmCosine\n / max(exteriorIor * incidentCosine, 1e-9);\n vec2 reflection23S = openpbr_complex_fresnel_reflection(\n filmCosine, filmIor, substrateIor, false\n );\n vec2 reflection23P = openpbr_complex_fresnel_reflection(\n filmCosine, filmIor, substrateIor, true\n );\n float phase = 2.0 * PI * (2.0 * filmIor * thickness * filmCosine) / wavelength;\n float reflectanceS = openpbr_airy_reflectance(\n reflection12S,\n transmission12S,\n -reflection12S,\n transmission12S * transmission21Scale,\n reflection23S,\n phase\n );\n float reflectanceP = openpbr_airy_reflectance(\n reflection12P,\n transmission12P,\n -reflection12P,\n transmission12P * transmission21Scale,\n reflection23P,\n phase\n );\n return clamp(0.5 * (reflectanceS + reflectanceP), 0.0, 1.0);\n}\n\nvec3 openpbr_thin_film_dielectric_reflectance(\n float incidentCosine,\n float thickness,\n float filmIor,\n float substrateIor,\n float exteriorIor\n) {\n return vec3(\n openpbr_thin_film_reflectance_channel(\n incidentCosine, 620.0, thickness, exteriorIor, filmIor, vec2(substrateIor, 0.0)\n ),\n openpbr_thin_film_reflectance_channel(\n incidentCosine, 540.0, thickness, exteriorIor, filmIor, vec2(substrateIor, 0.0)\n ),\n openpbr_thin_film_reflectance_channel(\n incidentCosine, 450.0, thickness, exteriorIor, filmIor, vec2(substrateIor, 0.0)\n )\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.\nvec3 openpbr_metal_fresnel(float cosine, vec3 f0, vec3 tint) {\n vec3 schlick = mix(f0, vec3(1.0), sampling_schlick_fifth_power(cosine));\n if (all(equal(tint, vec3(1.0)))) { return schlick; }\n float edgeCosine = 1.0 / 7.0;\n float edgeComplement = 1.0 - edgeCosine;\n vec3 edgeFresnel = mix(f0, vec3(1.0), sampling_schlick_fifth_power(edgeCosine));\n float complement = 1.0 - cosine;\n float correction = cosine * sampling_schlick_fifth_power(cosine) * complement\n / (edgeCosine * pow(edgeComplement, 6.0));\n return clamp(schlick - correction * (edgeFresnel - tint * edgeFresnel), vec3(0.0), vec3(1.0));\n}\n\nvec2 openpbr_metal_complex_ior(float f0, float f82Tint) {\n float reflectivity = clamp(f0, 0.0, 0.99);\n float sqrtReflectivity = sqrt(reflectivity);\n float minimumIor = (1.0 - reflectivity) / (1.0 + reflectivity);\n float maximumIor = (1.0 + sqrtReflectivity) / max(1.0 - sqrtReflectivity, 1e-9);\n float ior = mix(maximumIor, minimumIor, clamp(f82Tint, 0.0, 1.0));\n float 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 vec2(ior, sqrt(max(extinctionSquared, 0.0)));\n}\n\nvec3 openpbr_thin_film_metal_reflectance(\n float incidentCosine,\n float thickness,\n float filmIor,\n vec3 metalF0,\n vec3 metalF82Tint,\n float exteriorIor\n) {\n return vec3(\n openpbr_thin_film_reflectance_channel(\n incidentCosine,\n 620.0,\n thickness,\n exteriorIor,\n filmIor,\n openpbr_metal_complex_ior(metalF0.r, metalF82Tint.r)\n ),\n openpbr_thin_film_reflectance_channel(\n incidentCosine,\n 540.0,\n thickness,\n exteriorIor,\n filmIor,\n openpbr_metal_complex_ior(metalF0.g, metalF82Tint.g)\n ),\n openpbr_thin_film_reflectance_channel(\n incidentCosine,\n 450.0,\n thickness,\n exteriorIor,\n filmIor,\n openpbr_metal_complex_ior(metalF0.b, metalF82Tint.b)\n )\n );\n}\n\nfloat openpbr_thin_film_presence(float thickness, float weight) {\n return clamp(weight, 0.0, 1.0) * smoothstep(1e-9, 30.0, thickness);\n}\n\nvec3 openpbr_apply_dielectric_thin_film(\n vec3 substrateFresnel,\n float incidentCosine,\n float thickness,\n float filmIor,\n float substrateIor,\n float weight,\n float exteriorIor\n) {\n vec3 filmFresnel = openpbr_thin_film_dielectric_reflectance(\n incidentCosine,\n thickness,\n filmIor,\n substrateIor,\n exteriorIor\n );\n return mix(\n substrateFresnel,\n filmFresnel,\n openpbr_thin_film_presence(thickness, weight)\n );\n}\n\nvec3 openpbr_apply_metal_thin_film(\n vec3 substrateFresnel,\n float incidentCosine,\n float thickness,\n float filmIor,\n vec3 metalF0,\n vec3 metalF82Tint,\n float weight,\n float exteriorIor\n) {\n vec3 filmFresnel = openpbr_thin_film_metal_reflectance(\n incidentCosine,\n thickness,\n filmIor,\n metalF0,\n metalF82Tint,\n exteriorIor\n );\n return mix(\n substrateFresnel,\n filmFresnel,\n openpbr_thin_film_presence(thickness, weight)\n );\n}\n\nvec3 openpbr_eval_diffuse(\n OpenPbrResolvedMaterial material,\n vec3 color,\n vec3 viewLocal,\n vec3 lightLocal,\n float specularEnergyCompensation,\n out float pdf\n) {\n pdf = 0.0;\n if (lightLocal.z <= 0.0) {\n return vec3(0.0);\n }\n\n float roughness = material.diffuseRoughness;\n float fonConstantA = 0.5 - 2.0 / (3.0 * PI);\n float fonConstantB = 2.0 / 3.0 - 28.0 / (15.0 * PI);\n float muInput = lightLocal.z;\n float muOutput = viewLocal.z;\n float directional = dot(lightLocal, viewLocal) - muInput * muOutput;\n float directionalOverTangent = directional > 0.0\n ? directional / max(max(muInput, muOutput), 1e-7)\n : directional;\n float singleScatterA = 1.0 / (1.0 + fonConstantA * roughness);\n vec3 singleScatter = color * INVPI * singleScatterA\n * (1.0 + roughness * directionalOverTangent);\n\n float inputComplement = 1.0 - muInput;\n float outputComplement = 1.0 - muOutput;\n float inputGOverPi = inputComplement * (\n 0.0571085289 + inputComplement * (\n 0.491881867 + inputComplement * (\n -0.332181442 + inputComplement * 0.0714429953\n )\n )\n );\n float outputGOverPi = outputComplement * (\n 0.0571085289 + outputComplement * (\n 0.491881867 + outputComplement * (\n -0.332181442 + outputComplement * 0.0714429953\n )\n )\n );\n float inputAlbedo = (1.0 + roughness * inputGOverPi)\n / (1.0 + fonConstantA * roughness);\n float outputAlbedo = (1.0 + roughness * outputGOverPi)\n / (1.0 + fonConstantA * roughness);\n float averageAlbedo = singleScatterA * (1.0 + fonConstantB * roughness);\n vec3 multipleScatterAlbedo = color * color * averageAlbedo\n / max(vec3(1e-7), vec3(1.0) - color * (1.0 - averageAlbedo));\n vec3 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\nfloat openpbr_fuzz_directional_albedo(float viewCosine, float roughness) {\n float x = clamp(viewCosine, 0.0, 1.0);\n float y = clamp(roughness, 0.01, 1.0);\n float scale = y * (0.0206607 + 1.58491 * y)\n / (0.0379424 + y * (1.32227 + y));\n float mean = y * (-0.193854 + y * (-1.14885 + y * (1.7932 - 0.95943 * y * y)))\n / (0.046391 + y);\n float offset = y * (0.000654023 + (-0.0207818 + 0.119681 * y) * y)\n / (1.26264 + y * (-1.92021 + y));\n float 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\nvec3 openpbr_eval_fuzz(\n OpenPbrResolvedMaterial material,\n vec3 viewLocal,\n vec3 lightLocal,\n out float pdf\n) {\n pdf = 0.0;\n if (viewLocal.z <= 0.0 || lightLocal.z <= 0.0) {\n return vec3(0.0);\n }\n float 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\nvec3 openpbr_eval_microfacet_reflection(\n OpenPbrResolvedMaterial material,\n vec3 viewLocal,\n vec3 lightLocal,\n vec3 halfVector,\n vec3 fresnel,\n out float pdf\n) {\n pdf = 0.0;\n if (lightLocal.z <= 0.0) {\n return vec3(0.0);\n }\n\n vec2 alpha = vec2(material.alphaX, material.alphaY);\n float distribution = sampling_ggx_ndf(halfVector, alpha);\n float masking = sampling_ggx_smith_g1(viewLocal, alpha);\n float 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\nvec3 openpbr_eval_microfacet_refraction(\n OpenPbrResolvedMaterial material,\n float relativeIor,\n vec3 tint,\n vec3 viewLocal,\n vec3 lightLocal,\n vec3 halfVector,\n vec3 fresnel,\n out float pdf\n) {\n pdf = 0.0;\n if (lightLocal.z >= 0.0) {\n return vec3(0.0);\n }\n\n float lightHalf = dot(lightLocal, halfVector);\n float 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 vec3(0.0);\n }\n vec2 alpha = vec2(material.alphaX, material.alphaY);\n float distribution = sampling_ggx_ndf(halfVector, alpha);\n float masking = sampling_ggx_smith_g1(viewLocal, alpha);\n float maskingShadowing = masking\n * sampling_ggx_smith_g1(lightLocal, alpha);\n float denominator = lightHalf + viewHalf * relativeIor;\n denominator *= denominator;\n float jacobian = abs(lightHalf) / max(denominator, 1e-8);\n float relativeIorSquared = relativeIor * relativeIor;\n\n pdf = masking * max(0.0, viewHalf) * distribution * jacobian\n / max(viewLocal.z, 1e-8);\n return tint * (vec3(1.0) - fresnel) * distribution * maskingShadowing\n * abs(viewHalf) * jacobian * relativeIorSquared\n / max(abs(lightLocal.z * viewLocal.z), 1e-8);\n}\n\nvec3 openpbr_transmission_tint(OpenPbrResolvedMaterial material) {\n#ifdef HAS_MEDIUM\n if (material.mediumType > 0) {\n return vec3(1.0);\n }\n#endif\n return material.transmissionColor;\n}\n\nfloat openpbr_thin_wall_fresnel(float singleInterfaceFresnel) {\n return 2.0 * singleInterfaceFresnel / (1.0 + singleInterfaceFresnel);\n}\n\nvec3 openpbr_thin_wall_reflection_coefficient(\n vec3 singleInterfaceFresnel,\n float transmissionWeight\n) {\n vec3 windowFresnel = 2.0 * singleInterfaceFresnel\n / (vec3(1.0) + singleInterfaceFresnel);\n return mix(\n singleInterfaceFresnel,\n windowFresnel,\n clamp(transmissionWeight, 0.0, 1.0)\n );\n}\n\nvec3 openpbr_final_transmission_tint(\n OpenPbrResolvedMaterial material,\n float incidentCosine,\n float etaTOverEtaI\n) {\n vec3 tint = openpbr_transmission_tint(material);\n if (!material.thinWalled) {\n return tint;\n }\n float incidentSineSquared = max(0.0, 1.0 - incidentCosine * incidentCosine);\n float transmittedSineSquared = incidentSineSquared\n / max(etaTOverEtaI * etaTOverEtaI, 1e-9);\n if (transmittedSineSquared >= 1.0) {\n return vec3(0.0);\n }\n float transmittedCosine = sqrt(1.0 - transmittedSineSquared);\n return pow(max(tint, vec3(0.0)), vec3(1.0 / max(transmittedCosine, 1e-5)));\n}\n\nvec3 openpbr_eval_subsurface_boundary(\n OpenPbrResolvedMaterial material,\n vec3 viewLocal,\n vec3 lightLocal,\n out float pdf\n) {\n pdf = 0.0;\n if (viewLocal.z <= 0.0 || lightLocal.z >= 0.0) {\n return vec3(0.0);\n }\n float transmittedCosine = -lightLocal.z;\n float entryFresnel = sampling_dielectric_fresnel(\n viewLocal.z,\n material.relativeIor\n );\n pdf = transmittedCosine * INVPI;\n return vec3(1.0 - entryFresnel) * INVPI;\n}\n\nvoid openpbr_normalize_probabilities(inout OpenPbrPreparedBsdf prepared) {\n float total = prepared.probabilities[OPENPBR_LOBE_FUZZ];\n for (int lobe = 1; lobe < OPENPBR_LOBE_COUNT; lobe++) {\n total += prepared.probabilities[lobe];\n }\n if (total <= 1e-8) {\n for (int lobe = 0; lobe < OPENPBR_LOBE_COUNT; lobe++) {\n prepared.probabilities[lobe] = 0.0;\n }\n return;\n }\n float inverseTotal = 1.0 / total;\n for (int lobe = 0; lobe < OPENPBR_LOBE_COUNT; 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).\nfloat openpbr_average_fresnel(float eta) {\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\nvec3 openpbr_metal_average_fresnel(vec3 f0, vec3 tint) {\n float edgeCosine = 1.0 / 7.0;\n vec3 edgeFresnel = mix(f0, vec3(1.0), sampling_schlick_fifth_power(edgeCosine));\n vec3 b = edgeFresnel * (vec3(1.0) - tint) / (edgeCosine * pow(1.0 - edgeCosine, 6.0));\n return clamp(f0 + (vec3(1.0) - f0) / 21.0 - b / 126.0, vec3(0.0), vec3(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.\nvec3 openpbr_coat_passage(OpenPbrResolvedMaterial material, float cosine) {\n if (cosine <= 0.0) { return vec3(1.0); }\n float refractedCosine = sqrt(max(0.0, 1.0 - (1.0 - cosine * cosine) / (material.coatIor * material.coatIor)));\n vec3 passage = pow(max(material.coatColor, vec3(0.0)), vec3(0.5 / max(refractedCosine, 1e-5)));\n return mix(vec3(1.0), passage, material.coatWeight);\n}\n\nfloat openpbr_coat_energy(OpenPbrResolvedMaterial material, float cosine) {\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\nvec3 openpbr_coat_darkening(OpenPbrPreparedBsdf prepared) {\n OpenPbrResolvedMaterial material = prepared.material;\n float dielectric = 1.0 - material.baseMetalness;\n float opaque = dielectric * (1.0 - material.transmissionWeight) * (1.0 - material.subsurfaceWeight);\n float sss = dielectric * (1.0 - material.transmissionWeight) * material.subsurfaceWeight;\n float trans = dielectric * material.transmissionWeight;\n float opaqueSpecularity = openpbr_average_fresnel(prepared.specularEtaTOverEtaI);\n float transSpecularity = openpbr_average_fresnel(1.0 / prepared.transmissionReflectionIor);\n float specularity = clamp(opaque * opaqueSpecularity + (sss + trans) * transSpecularity + material.baseMetalness, 0.0, 1.0);\n float effectiveRoughness = mix(1.0, material.specularRoughness, specularity);\n float ks = openpbr_average_fresnel(material.coatIor);\n float kr = 1.0 - (1.0 - ks) / (material.coatIor * material.coatIor);\n float k = mix(ks, kr, effectiveRoughness);\n vec3 eb = material.baseMetalness * material.specularWeight * openpbr_metal_average_fresnel(material.baseWeight * material.baseColor, material.specularColor)\n + opaque * mix(material.baseWeight * material.baseColor, vec3(1.0), opaqueSpecularity)\n + sss * material.subsurfaceColor + vec3(trans);\n vec3 delta = vec3(1.0 - k) / max(vec3(1.0) - eb * k, vec3(1e-6));\n return mix(vec3(1.0), delta, material.coatWeight * material.coatDarkening);\n}\n\nvec3 openpbr_coat_base_scale(OpenPbrPreparedBsdf prepared, float lightCosine) {\n if (prepared.material.coatWeight <= 0.0) { return vec3(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\nOpenPbrBasis openpbr_direction_basis(OpenPbrBasis fallback, vec3 tangent, vec3 bitangent, vec2 direction) {\n vec3 t = tangent - fallback.normal * dot(fallback.normal, tangent);\n if (dot(t, t) < 1e-8) { t = fallback.tangent; }\n t = normalize(t);\n vec3 b = cross(fallback.normal, t) * (dot(cross(fallback.normal, t), bitangent) < 0.0 ? -1.0 : 1.0);\n t = t * direction.x + b * direction.y;\n if (dot(t, t) < 1e-8) { return fallback; }\n OpenPbrBasis 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\nOpenPbrPreparedBsdf openpbr_prepare(\n vec3 tangent,\n OpenPbrResolvedMaterial material,\n vec3 viewDirection,\n vec3 normal\n) {\n OpenPbrPreparedBsdf prepared;\n prepared.material = material;\n prepared.basis = openpbr_make_basis(\n tangent,\n normal,\n max(material.specularAnisotropy, material.coatAnisotropy)\n );\n prepared.coatBasis = prepared.basis;\n prepared.directionFrames = false;\n#ifdef USE_COAT_NORMAL_FRAMES\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#endif\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, vec2(cos(material.coatAnisotropyRotation), sin(material.coatAnisotropyRotation)));\n prepared.directionFrames = true;\n }\n prepared.viewLocal = openpbr_to_local(prepared.basis, viewDirection);\n#ifdef USE_COAT_NORMAL_FRAMES\n prepared.coatViewLocal = openpbr_to_local(prepared.coatBasis, viewDirection);\n#endif\n prepared.diffuseColor = material.baseColor;\n\n float dielectricBaseWeight = 1.0 - material.baseMetalness;\n float 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 float weightedCoatIor = 1.0;\n if (!material.inside) { weightedCoatIor = mix(1.0, material.coatIor, material.coatWeight); }\n float weightedBaseIor = weightedCoatIor * openpbr_apply_specular_weight_to_ior(\n material.specularIor / weightedCoatIor, material.specularWeight);\n float 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 float viewCosine = abs(prepared.viewLocal.z);\n float singleInterfaceFresnel = openpbr_dielectric_fresnel(\n viewCosine,\n 1.0 / prepared.specularEtaTOverEtaI\n );\n vec3 dielectricFresnelEstimate = material.specularColor * (\n material.thinWalled\n ? mix(\n singleInterfaceFresnel,\n openpbr_thin_wall_fresnel(singleInterfaceFresnel),\n material.transmissionWeight\n )\n : singleInterfaceFresnel\n );\n vec3 metalFresnelEstimate = material.specularWeight * openpbr_metal_fresnel(\n viewCosine,\n material.baseWeight * material.baseColor,\n material.specularColor\n );\n float transmissionFresnel = material.thinWalled\n ? openpbr_thin_wall_fresnel(openpbr_dielectric_fresnel(viewCosine, prepared.transmissionReflectionIor))\n : openpbr_dielectric_fresnel(viewCosine, prepared.transmissionReflectionIor);\n vec3 transmissionTint = openpbr_final_transmission_tint(\n material,\n viewCosine,\n prepared.refractionEtaTOverEtaI\n );\n float viewEnergyComplement = openpbr_opaque_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha,\n viewCosine\n );\n float 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 = vec3(1.0);\n if (material.coatWeight > 0.0 && !material.inside) {\n#ifdef USE_COAT_NORMAL_FRAMES\n float coatViewCosine = material.coatNormalEnabled ? max(prepared.coatViewLocal.z, 0.0) : viewCosine;\n float coatEnergy = openpbr_coat_energy(material, coatViewCosine);\n#else\n float coatEnergy = openpbr_coat_energy(material, viewCosine);\n#endif\n prepared.probabilities[OPENPBR_LOBE_COAT] = prepared.weights[OPENPBR_LOBE_COAT] * coatEnergy;\n prepared.coatBaseScale = openpbr_coat_passage(material,\n#ifdef USE_COAT_NORMAL_FRAMES\n coatViewCosine\n#else\n viewCosine\n#endif\n )\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 = vec3(0.0);\n prepared.metalMsFactor = 0.0;\n prepared.metalMsProbability = 0.0;\n if (material.specularRoughness >= 0.04 && prepared.weights[OPENPBR_LOBE_METAL] > 0.0) {\n vec3 averageFresnel = openpbr_metal_average_fresnel(material.baseWeight * material.baseColor, material.specularColor);\n prepared.metalMsScale = material.specularWeight * averageFresnel * averageFresnel;\n float viewLoss = openpbr_metal_energy_complement(prepared.specularAlpha, viewCosine);\n prepared.metalMsFactor = viewLoss / max(openpbr_metal_average_energy_complement(prepared.specularAlpha), 1e-12);\n float 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 = vec3(0.0);\n prepared.dielectricMsTransmissionScale = vec3(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 float nonOpaqueWeight = prepared.weights[OPENPBR_LOBE_TRANSMISSION] + prepared.weights[OPENPBR_LOBE_SUBSURFACE];\n prepared.dielectricMsReflectionScale = (material.inside ? vec3(1.0) : material.specularColor) * nonOpaqueWeight;\n prepared.dielectricMsTransmissionScale = transmissionTint * prepared.weights[OPENPBR_LOBE_TRANSMISSION]\n + vec3(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 vec3 averageScale = mix(prepared.dielectricMsTransmissionScale, prepared.dielectricMsReflectionScale, prepared.dielectricMsReflectionRatio);\n float 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 && material.thinFilmWeight > 0.0) {\n float 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 float 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\nfloat openpbr_substrate_fresnel(OpenPbrPreparedBsdf prepared, float cosine, bool reflection) {\n if (prepared.material.coatWeight <= 0.0) {\n return openpbr_dielectric_fresnel(cosine, prepared.material.relativeIor);\n }\n float transFresnel = openpbr_dielectric_fresnel(cosine, prepared.transmissionReflectionIor);\n if (!reflection) { return transFresnel; }\n float opaqueFresnel = openpbr_dielectric_fresnel(cosine, 1.0 / prepared.specularEtaTOverEtaI);\n float nonOpaque = prepared.material.transmissionWeight + (1.0 - prepared.material.transmissionWeight) * prepared.material.subsurfaceWeight;\n return mix(opaqueFresnel, transFresnel, nonOpaque);\n}\n\nvec3 openpbr_eval_prepared(\n OpenPbrPreparedBsdf prepared,\n vec3 lightDirection,\n out float pdf\n) {\n OpenPbrResolvedMaterial material = prepared.material;\n vec3 viewLocal = prepared.viewLocal;\n vec3 lightLocal = openpbr_to_local(prepared.basis, lightDirection);\n bool reflection = lightLocal.z * viewLocal.z > 0.0;\n#ifdef USE_COAT_NORMAL_FRAMES\n bool independentCoat = material.coatNormalEnabled;\n bool geometricReflection = dot(material.geometricNormal, lightDirection) > 0.0;\n float coatLightCosine = dot(prepared.coatBasis.normal, lightDirection);\n bool coatReflection = geometricReflection && prepared.coatViewLocal.z > 0.0 && coatLightCosine > 0.0;\n#endif\n\n vec3 halfVector;\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 vec3 response = vec3(0.0);\n pdf = 0.0;\n float lobePdf = 0.0;\n float viewHalf = abs(dot(viewLocal, halfVector));\n float diffuseSpecularEnergyCompensation =\n prepared.diffuseSpecularEnergyCompensation\n * openpbr_opaque_energy_complement(\n prepared.specularEtaTOverEtaI,\n prepared.specularAlpha,\n abs(lightLocal.z)\n );\n\n#ifdef USE_COAT_NORMAL_FRAMES\n if (!independentCoat || (lightLocal.z != 0.0 && reflection == geometricReflection)) {\n#endif\n if (reflection) {\n if (prepared.weights[OPENPBR_LOBE_DIFFUSE] > 0.0) {\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 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) {\n vec3 dielectricFresnel = vec3(openpbr_substrate_fresnel(prepared, viewHalf, true));\n if (material.thinFilmWeight > 0.0) {\n dielectricFresnel = openpbr_apply_dielectric_thin_film(\n dielectricFresnel,\n viewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n prepared.thinFilmBaseIor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n if (material.thinWalled) {\n dielectricFresnel = openpbr_thin_wall_reflection_coefficient(\n dielectricFresnel,\n material.transmissionWeight\n );\n }\n vec3 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) {\n vec3 fresnel = openpbr_metal_fresnel(\n viewHalf,\n material.baseWeight * material.baseColor,\n material.specularColor\n );\n if (material.thinFilmWeight > 0.0) {\n fresnel = openpbr_apply_metal_thin_film(\n fresnel,\n viewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n material.baseWeight * material.baseColor,\n material.specularColor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n fresnel *= material.specularWeight;\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 float 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 vec3 fresnel = vec3(openpbr_substrate_fresnel(prepared, viewHalf, false));\n if (material.thinFilmWeight > 0.0) {\n fresnel = openpbr_apply_dielectric_thin_film(\n fresnel,\n viewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n prepared.thinFilmBaseIor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n if (prepared.weights[OPENPBR_LOBE_TRANSMISSION] > 0.0) {\n if (material.thinWalled) {\n vec3 mirroredLight = vec3(lightLocal.xy, -lightLocal.z);\n vec3 thinWallHalf = normalize(mirroredLight + viewLocal);\n if (thinWallHalf.z < 0.0) {\n thinWallHalf = -thinWallHalf;\n }\n float thinWallViewHalf = abs(dot(viewLocal, thinWallHalf));\n vec3 thinWallFresnel = vec3(openpbr_substrate_fresnel(prepared, thinWallViewHalf, false));\n if (material.thinFilmWeight > 0.0) {\n thinWallFresnel = openpbr_apply_dielectric_thin_film(\n thinWallFresnel,\n thinWallViewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n prepared.thinFilmBaseIor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n vec3 windowFresnel = openpbr_thin_wall_reflection_coefficient(\n thinWallFresnel,\n 1.0\n );\n vec3 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 * (vec3(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) {\n if (material.thinWalled) {\n vec3 flippedLight = vec3(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) {\n float cosine = abs(lightLocal.z);\n if (cosine > 0.0) {\n float ior = (reflection ? prepared.refractionEtaTOverEtaI : 1.0 / prepared.refractionEtaTOverEtaI);\n float ratio = (reflection ? prepared.dielectricMsReflectionRatio : 1.0 - prepared.dielectricMsReflectionRatio);\n float factors = prepared.dielectricMsViewLoss * openpbr_ideal_dielectric_loss(ior, prepared.specularAlpha, cosine)\n / max(openpbr_ideal_dielectric_average_loss(ior, prepared.specularAlpha), 1e-12);\n float factor = min(factors * ratio, 1.0 / cosine);\n vec3 scale = (reflection ? prepared.dielectricMsReflectionScale : prepared.dielectricMsTransmissionScale);\n response += scale * factor * INVPI;\n pdf += prepared.probabilities[OPENPBR_LOBE_TRANSMISSION] * prepared.dielectricMsProbability * ratio * 0.5 * INVPI;\n }\n }\n\n#ifdef USE_COAT_NORMAL_FRAMES\n } // base hemisphere support\n#endif\n if (material.coatWeight > 0.0) {\n response *= openpbr_coat_base_scale(prepared,\n#ifdef USE_COAT_NORMAL_FRAMES\n independentCoat ? coatLightCosine : lightLocal.z\n#else\n lightLocal.z\n#endif\n );\n }\n#ifdef USE_COAT_NORMAL_FRAMES\n if (independentCoat) { response *= abs(lightLocal.z); }\n#endif\n\n if (\n#ifdef USE_COAT_NORMAL_FRAMES\n (independentCoat ? coatReflection : reflection)\n#else\n reflection\n#endif\n && prepared.weights[OPENPBR_LOBE_COAT] > 0.0) {\n vec3 coatView = viewLocal;\n vec3 coatLight = lightLocal;\n vec3 coatHalf = halfVector;\n float coatViewHalf = viewHalf;\n if (prepared.directionFrames) {\n coatView = openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal));\n#ifdef USE_COAT_NORMAL_FRAMES\n if (independentCoat) { coatView = prepared.coatViewLocal; }\n#endif\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 vec3 coatFresnel = vec3(openpbr_dielectric_fresnel(\n coatViewHalf,\n 1.0 / material.coatIor\n ));\n OpenPbrResolvedMaterial coatMaterial = material;\n vec2 coatAlpha = openpbr_roughness_to_alpha(material.coatRoughness, material.coatAnisotropy);\n coatMaterial.alphaX = coatAlpha.x;\n coatMaterial.alphaY = coatAlpha.y;\n response += prepared.weights[OPENPBR_LOBE_COAT]\n#ifdef USE_COAT_NORMAL_FRAMES\n * (independentCoat ? max(coatLightCosine, 0.0) : 1.0)\n#endif\n * 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 if (\n#ifdef USE_COAT_NORMAL_FRAMES\n (independentCoat ? geometricReflection : reflection)\n#else\n reflection\n#endif\n && prepared.weights[OPENPBR_LOBE_FUZZ] > 0.0) {\n float fuzzAlbedo = openpbr_fuzz_directional_albedo(\n abs(viewLocal.z),\n material.fuzzRoughness\n );\n response *= max(\n vec3(0.0),\n vec3(1.0 - prepared.weights[OPENPBR_LOBE_FUZZ] * fuzzAlbedo)\n );\n response += prepared.weights[OPENPBR_LOBE_FUZZ]\n#ifdef USE_COAT_NORMAL_FRAMES\n * (independentCoat ? max(lightLocal.z, 0.0) : 1.0)\n#endif\n * openpbr_eval_fuzz(\n material,\n viewLocal,\n lightLocal,\n lobePdf\n );\n pdf += prepared.probabilities[OPENPBR_LOBE_FUZZ] * lobePdf;\n }\n\n#ifdef USE_COAT_NORMAL_FRAMES\n if (independentCoat) { return response; }\n#endif\n return response * abs(lightLocal.z);\n}\n\nvec3 openpbr_eval(\n vec3 tangent,\n OpenPbrResolvedMaterial material,\n vec3 viewDirection,\n vec3 normal,\n vec3 lightDirection,\n out float pdf\n) {\n OpenPbrPreparedBsdf prepared = openpbr_prepare(\n tangent,\n material,\n viewDirection,\n normal\n );\n return openpbr_eval_prepared(prepared, lightDirection, pdf);\n}\n\nfloat openpbr_pdf(\n vec3 tangent,\n OpenPbrResolvedMaterial material,\n vec3 viewDirection,\n vec3 normal,\n vec3 lightDirection\n) {\n float pdf;\n openpbr_eval(\n tangent,\n material,\n viewDirection,\n normal,\n lightDirection,\n pdf\n );\n return pdf;\n}\n\nvec3 openpbr_sample_prepared(\n OpenPbrPreparedBsdf prepared,\n vec3 randomSamples,\n out vec3 lightDirection,\n out float pdf,\n out bool isTransmissionEvent\n) {\n OpenPbrResolvedMaterial material = prepared.material;\n vec3 viewLocal = prepared.viewLocal;\n float randomX = randomSamples.x;\n float randomY = randomSamples.y;\n float lobeSample = randomSamples.z;\n pdf = 0.0;\n isTransmissionEvent = false;\n lightDirection = prepared.basis.normal;\n\n // At eta=1 the solid-refraction half vector is the zero vector. Treat the\n // physically null interface as straight, unit-throughput transmission.\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(prepared.basis, -viewLocal);\n pdf = 1.0;\n isTransmissionEvent = true;\n return (openpbr_transmission_tint(prepared.material) * openpbr_coat_base_scale(prepared,\n#ifdef USE_COAT_NORMAL_FRAMES\n prepared.material.coatNormalEnabled ? dot(prepared.coatBasis.normal, lightDirection) : -prepared.viewLocal.z\n#else\n -prepared.viewLocal.z\n#endif\n ));\n }\n\n float cdf[OPENPBR_LOBE_COUNT];\n cdf[OPENPBR_LOBE_FUZZ] = prepared.probabilities[OPENPBR_LOBE_FUZZ];\n for (int lobe = 1; lobe < OPENPBR_LOBE_COUNT; lobe++) {\n cdf[lobe] = cdf[lobe - 1] + prepared.probabilities[lobe];\n }\n if (cdf[OPENPBR_LOBE_SUBSURFACE] <= 0.0) {\n return vec3(0.0);\n }\n\n vec3 lightLocal;\n if (lobeSample < cdf[OPENPBR_LOBE_FUZZ] || (\n lobeSample >= cdf[OPENPBR_LOBE_TRANSMISSION] && lobeSample < cdf[OPENPBR_LOBE_DIFFUSE]\n )) {\n lightLocal = sampling_cosine_hemisphere(vec2(randomX, randomY));\n }\n else if (lobeSample < cdf[OPENPBR_LOBE_COAT]) {\n vec3 coatView = viewLocal;\n if (prepared.directionFrames) { coatView = openpbr_to_local(prepared.coatBasis, openpbr_to_world(prepared.basis, viewLocal)); }\n#ifdef USE_COAT_NORMAL_FRAMES\n if (material.coatNormalEnabled) { coatView = prepared.coatViewLocal; }\n#endif\n vec3 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 vec2(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n lightLocal = normalize(reflect(-coatView, halfVector));\n#ifdef USE_COAT_NORMAL_FRAMES\n if (material.coatNormalEnabled) {\n lightDirection = openpbr_to_world(prepared.coatBasis, lightLocal);\n if (lightLocal.z <= 0.0 || dot(material.geometricNormal, lightDirection) <= 0.0) { return vec3(0.0); }\n return openpbr_eval_prepared(prepared, lightDirection, pdf);\n }\n#endif\n if (prepared.directionFrames) { lightLocal = openpbr_to_local(prepared.basis, openpbr_to_world(prepared.coatBasis, lightLocal)); }\n }\n else if (lobeSample < cdf[OPENPBR_LOBE_SPECULAR]) {\n bool isMetal = lobeSample < cdf[OPENPBR_LOBE_METAL];\n float metalDraw = (lobeSample - cdf[OPENPBR_LOBE_COAT]) / max(prepared.probabilities[OPENPBR_LOBE_METAL], 1e-12);\n if (isMetal && metalDraw < prepared.metalMsProbability) {\n float z = randomX;\n float radial = sqrt(max(0.0, 1.0 - z * z));\n float azimuth = TWOPI * randomY;\n lightLocal = vec3(radial * cos(azimuth), radial * sin(azimuth), z);\n }\n else {\n vec3 halfVector = sampling_ggx_visible_normal(\n viewLocal,\n vec2(material.alphaX, material.alphaY),\n vec2(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 isTransmissionEvent = true;\n // Split the existing BSDF_LOBE interval. BSDF_DIRECTION retains its\n // two dimensions; rand.x selects and remaps the reference hemisphere.\n float msDraw = (lobeSample - cdf[OPENPBR_LOBE_SPECULAR]) / max(prepared.probabilities[OPENPBR_LOBE_TRANSMISSION], 1e-12);\n if (prepared.dielectricMsProbability > 0.0 && msDraw < prepared.dielectricMsProbability) {\n float ratio = prepared.dielectricMsReflectionRatio;\n bool reflectMs = randomX < ratio;\n float z;\n if (reflectMs) { z = min(randomX / ratio, 0.99999994); }\n else { z = -min((randomX - ratio) / (1.0 - ratio), 0.99999994); }\n float radial = sqrt(max(0.0, 1.0 - z * z));\n float azimuth = TWOPI * randomY;\n lightLocal = vec3(radial * cos(azimuth), radial * sin(azimuth), z);\n isTransmissionEvent = !reflectMs;\n }\n else {\n vec3 halfVector = sampling_ggx_visible_normal(\n viewLocal,\n vec2(material.alphaX, material.alphaY),\n vec2(randomX, randomY)\n );\n if (halfVector.z < 0.0) {\n halfVector = -halfVector;\n }\n float viewHalf = abs(dot(viewLocal, halfVector));\n vec3 fresnel = vec3(openpbr_dielectric_fresnel(\n viewHalf,\n prepared.material.relativeIor\n ));\n if (material.thinFilmWeight > 0.0) {\n fresnel = openpbr_apply_dielectric_thin_film(\n fresnel,\n viewHalf,\n material.thinFilmThickness,\n material.thinFilmIor,\n prepared.thinFilmBaseIor,\n material.thinFilmWeight,\n prepared.thinFilmExteriorIor\n );\n }\n if (material.thinWalled) {\n vec3 reflectedLocal = normalize(reflect(-viewLocal, halfVector));\n lightLocal = vec3(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 vec3(0.0);\n }\n lightLocal = normalize(lightLocal);\n }\n }\n }\n else {\n isTransmissionEvent = true;\n if (material.thinWalled) {\n float hemisphereSample = randomX;\n bool transmit = hemisphereSample >= 0.5;\n randomX = transmit\n ? (hemisphereSample - 0.5) * 2.0\n : hemisphereSample * 2.0;\n lightLocal = sampling_cosine_hemisphere(vec2(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(vec2(randomX, randomY));\n lightLocal.z = -lightLocal.z;\n }\n }\n\n lightDirection = openpbr_to_world(prepared.basis, lightLocal);\n // Reject VNDF draws on the wrong side before aggregate BSDF evaluation.\n if (lightLocal.z == 0.0 || (lightLocal.z < 0.0) != isTransmissionEvent) {\n return vec3(0.0);\n }\n#ifdef USE_COAT_NORMAL_FRAMES\n if (material.coatNormalEnabled &&\n ((dot(material.geometricNormal, lightDirection) <= 0.0) != isTransmissionEvent)) { return vec3(0.0); }\n#endif\n return openpbr_eval_prepared(prepared, lightDirection, pdf);\n}\n\nvec3 openpbr_sample(\n vec3 tangent,\n OpenPbrResolvedMaterial material,\n vec3 randomSamples,\n vec3 viewDirection,\n vec3 normal,\n out vec3 lightDirection,\n out float pdf,\n out bool isTransmissionEvent\n) {\n OpenPbrPreparedBsdf prepared = openpbr_prepare(tangent, material, viewDirection, normal);\n return openpbr_sample_prepared(prepared, randomSamples, lightDirection, pdf, isTransmissionEvent);\n}`;\n"]}
|
|
@@ -40,7 +40,7 @@ struct ad {
|
|
|
40
40
|
bool hit;
|
|
41
41
|
vec3 position;
|
|
42
42
|
vec3 normal;
|
|
43
|
-
#ifdef
|
|
43
|
+
#ifdef USE_COAT_NORMAL_FRAMES
|
|
44
44
|
vec3 coatNormal;
|
|
45
45
|
#endif
|
|
46
46
|
vec3 vertexNormal;
|
|
@@ -124,7 +124,7 @@ vec3 directionTangent;
|
|
|
124
124
|
vec3 directionBitangent;
|
|
125
125
|
vec3 geometryTangent;
|
|
126
126
|
vec3 geometryBitangent;
|
|
127
|
-
#ifdef
|
|
127
|
+
#ifdef USE_COAT_NORMAL_FRAMES
|
|
128
128
|
vec3 coatNormal;
|
|
129
129
|
bool coatNormalEnabled;
|
|
130
130
|
vec3 geometricNormal;
|
|
@@ -164,7 +164,7 @@ ray.tMax = RAY_MAX_DISTANCE;
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
|
|
167
|
-
ivec2
|
|
167
|
+
ivec2 gR(int i, int jz) {
|
|
168
168
|
ivec2 u;
|
|
169
169
|
u.y = i >> jz;
|
|
170
170
|
u.x = i - (u.y << jz);
|
|
@@ -172,11 +172,11 @@ return u;
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
vec4 kG(sampler2D s, int i, int jz) {
|
|
175
|
-
return texelFetch(s,
|
|
175
|
+
return texelFetch(s, gR(i, jz), 0);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
ivec4 kG(highp isampler2D s, int i, int jz) {
|
|
179
|
-
return texelFetch(s,
|
|
179
|
+
return texelFetch(s, gR(i, jz), 0);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
struct Path {
|
|
@@ -213,12 +213,12 @@ lightsTex,
|
|
|
213
213
|
ivec2(firstTexel + 1, 0),
|
|
214
214
|
0
|
|
215
215
|
);
|
|
216
|
-
vec4
|
|
216
|
+
vec4 nP = texelFetch(
|
|
217
217
|
lightsTex,
|
|
218
218
|
ivec2(firstTexel + 2, 0),
|
|
219
219
|
0
|
|
220
220
|
);
|
|
221
|
-
vec4
|
|
221
|
+
vec4 lM = texelFetch(
|
|
222
222
|
lightsTex,
|
|
223
223
|
ivec2(firstTexel + 3, 0),
|
|
224
224
|
0
|
|
@@ -231,13 +231,13 @@ ivec2(firstTexel + 4, 0),
|
|
|
231
231
|
return Light(
|
|
232
232
|
pO.xyz,
|
|
233
233
|
po.xyz,
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
nP.xyz,
|
|
235
|
+
lM.xyz,
|
|
236
236
|
shape.x,
|
|
237
237
|
shape.y,
|
|
238
238
|
shape.z,
|
|
239
|
-
int(
|
|
240
|
-
|
|
239
|
+
int(lM.w),
|
|
240
|
+
nP.w,
|
|
241
241
|
pO.w,
|
|
242
242
|
po.w,
|
|
243
243
|
texelFetch(lightsTex, ivec2(firstTexel + 5, 0), 0).xyz
|
|
@@ -256,9 +256,9 @@ return packed[ls % 4];
|
|
|
256
256
|
|
|
257
257
|
float kR(int dV, vec2 uv) {
|
|
258
258
|
if (dV < 0) return 1.0;
|
|
259
|
-
vec2
|
|
260
|
-
ivec2 p0 = ivec2(floor(
|
|
261
|
-
vec2 f = fract(
|
|
259
|
+
vec2 fQ = fract(uv) * 128.0 - 0.5;
|
|
260
|
+
ivec2 p0 = ivec2(floor(fQ));
|
|
261
|
+
vec2 f = fract(fQ);
|
|
262
262
|
int xa = p0.x < 0 ? 127 : p0.x;
|
|
263
263
|
int xb = p0.x >= 127 ? 0 : p0.x + 1;
|
|
264
264
|
int ya = p0.y < 0 ? 127 : p0.y;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rayTraceCore.glsl.js","sourceRoot":"","sources":["../../../../src/renderer/glsl/chunks/rayTraceCore.glsl.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kFAAkF;AAElF,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,eAAe,iBAAiB,CAAC;IAC/B,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;QACtC,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;QAC3C,iBAAiB,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3C,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QACjC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;QAClC,oBAAoB,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;KACjD;IACD,OAAO,EAAE;QACP,QAAQ,EAAE,CAAC;QACX,KAAK,EAAE,aAAa;QACpB,cAAc,EAAE,qBAAqB;QACrC,UAAU,EAAE,mBAAmB;KAChC;IACD,IAAI,EAAE,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuQX;CACA,CAAC,CAAC","sourcesContent":["// Portions adapted from ray-tracing-renderer@0.10.15\n// (https://github.com/hoverinc/ray-tracing-renderer).\n// Copyright (c) 2019 HOVER, MIT license; see LICENSES/RayTracingRenderer-MIT.txt.\n\nimport { createShaderMixin, glsl, Shader } from '../../shader.js';\nimport { ShadowCatcherMaterial, UnlitMaterial } from '../../../constants.js';\nimport { CONSTANT_SPOT_LIGHT } from '../../lightsUtil.js';\n\nexport default createShaderMixin({\n uniforms: {\n lightsTex: Shader.uniform('sampler2D'),\n iesProfileData: Shader.uniform('sampler2D'),\n iesProfileColumns: Shader.uniform('int', 1),\n pixelSize: Shader.uniform('vec2'),\n jitter: Shader.uniform('vec2'),\n sampleCount: Shader.uniform('int'),\n envLightSelectionPmf: Shader.uniform('float', 0)\n },\n defines: {\n STANDARD: 0,\n UNLIT: UnlitMaterial,\n SHADOW_CATCHER: ShadowCatcherMaterial,\n SPOT_LIGHT: CONSTANT_SPOT_LIGHT\n },\n main: glsl`\n// https://www.w3.org/WAI/GL/wiki/Relative_luminance\nconst vec3 luminance = vec3(0.2126, 0.7152, 0.0722);\nconst int bounceSamples = 8;\n\n#define RAY_MAX_DISTANCE 9999.0\n\n#define NO_UNROLL(X) (X + min(0, sampleCount))\n#define NO_UNROLLU(X) (X + uint(min(0, sampleCount)))\n\nstruct Ray {\n vec3 o;\n vec3 d;\n float tMax;\n};\n\nstruct SurfaceInteraction {\n bool hit;\n vec3 position;\n vec3 normal; // material shading normal\n#ifdef
|
|
1
|
+
{"version":3,"file":"rayTraceCore.glsl.js","sourceRoot":"","sources":["../../../../src/renderer/glsl/chunks/rayTraceCore.glsl.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kFAAkF;AAElF,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,eAAe,iBAAiB,CAAC;IAC/B,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;QACtC,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;QAC3C,iBAAiB,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3C,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QACjC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;QAClC,oBAAoB,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;KACjD;IACD,OAAO,EAAE;QACP,QAAQ,EAAE,CAAC;QACX,KAAK,EAAE,aAAa;QACpB,cAAc,EAAE,qBAAqB;QACrC,UAAU,EAAE,mBAAmB;KAChC;IACD,IAAI,EAAE,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuQX;CACA,CAAC,CAAC","sourcesContent":["// Portions adapted from ray-tracing-renderer@0.10.15\n// (https://github.com/hoverinc/ray-tracing-renderer).\n// Copyright (c) 2019 HOVER, MIT license; see LICENSES/RayTracingRenderer-MIT.txt.\n\nimport { createShaderMixin, glsl, Shader } from '../../shader.js';\nimport { ShadowCatcherMaterial, UnlitMaterial } from '../../../constants.js';\nimport { CONSTANT_SPOT_LIGHT } from '../../lightsUtil.js';\n\nexport default createShaderMixin({\n uniforms: {\n lightsTex: Shader.uniform('sampler2D'),\n iesProfileData: Shader.uniform('sampler2D'),\n iesProfileColumns: Shader.uniform('int', 1),\n pixelSize: Shader.uniform('vec2'),\n jitter: Shader.uniform('vec2'),\n sampleCount: Shader.uniform('int'),\n envLightSelectionPmf: Shader.uniform('float', 0)\n },\n defines: {\n STANDARD: 0,\n UNLIT: UnlitMaterial,\n SHADOW_CATCHER: ShadowCatcherMaterial,\n SPOT_LIGHT: CONSTANT_SPOT_LIGHT\n },\n main: glsl`\n// https://www.w3.org/WAI/GL/wiki/Relative_luminance\nconst vec3 luminance = vec3(0.2126, 0.7152, 0.0722);\nconst int bounceSamples = 8;\n\n#define RAY_MAX_DISTANCE 9999.0\n\n#define NO_UNROLL(X) (X + min(0, sampleCount))\n#define NO_UNROLLU(X) (X + uint(min(0, sampleCount)))\n\nstruct Ray {\n vec3 o;\n vec3 d;\n float tMax;\n};\n\nstruct SurfaceInteraction {\n bool hit;\n vec3 position;\n vec3 normal; // material shading normal\n#ifdef USE_COAT_NORMAL_FRAMES\n vec3 coatNormal;\n#endif\n vec3 vertexNormal; // interpolated mesh normal before normal mapping\n vec3 shadowOffset; // smooth-surface offset used by direct-light shadow rays\n \n vec2 uv;\n vec2 uv1;\n vec3 vertexColor;\n\n#ifdef USE_TANGENT\n vec3 tangent;\n#endif\n vec3 directionTangent;\n vec3 directionBitangent;\n vec3 geometryTangent;\n vec3 geometryBitangent;\n vec3 faceNormal; // normal of the triangle\n\n bool isTransparency;\n // For perfect specular or transmission surface\n // We use the albedo and normal of first diffuse or glossy surface hit instead for denoising\n // See albedo section in https://www.openimagedenoise.org/documentation.html\n bool albedoSampled;\n\n int triangleIndex;\n int instanceIndex;\n int materialIndex;\n\n int mediumType;\n vec3 mediumExtinction;\n vec3 mediumScattering;\n float mediumAnisotropy;\n};\n\n// Texture-resolved OpenPBR inputs used by the surface integrator. The storage\n// buffer keeps its compact legacy layout; getMaterial() is the compatibility\n// boundary that maps it to these canonical names.\nstruct OpenPbrResolvedMaterial {\n int materialType;\n\n float specularRoughness;\n float baseMetalness;\n vec3 baseColor;\n float specularIor;\n vec3 emission;\n float transmissionWeight;\n bool thinWalled;\n float specularAnisotropy;\n float alpha;\n\n float transmissionDispersionScale;\n float dispersionAbbeNumber;\n float thinFilmThickness;\n float thinFilmIor;\n float thinFilmWeight;\n\n // Medium\n int mediumType;\n vec3 mediumExtinction;\n vec3 mediumScattering;\n float mediumAnisotropy;\n\n float baseWeight;\n float diffuseRoughness;\n float specularWeight;\n vec3 specularColor;\n float subsurfaceWeight;\n vec3 subsurfaceColor;\n vec3 transmissionColor;\n float fuzzWeight;\n vec3 fuzzColor;\n float fuzzRoughness;\n float coatWeight;\n vec3 coatColor;\n float coatRoughness;\n float coatAnisotropy;\n vec2 anisotropyDirection;\n bool anisotropyDirectionEnabled;\n float coatAnisotropyRotation;\n vec3 directionTangent;\n vec3 directionBitangent;\n vec3 geometryTangent;\n vec3 geometryBitangent;\n#ifdef USE_COAT_NORMAL_FRAMES\n vec3 coatNormal;\n bool coatNormalEnabled;\n vec3 geometricNormal;\n#endif\n float coatIor;\n float coatDarkening;\n\n float bevel;\n\n // Calculated\n float relativeIor;\n bool inside;\n float alphaX;\n float alphaY;\n};\n\n\nstruct Light {\n vec3 position;\n vec3 emission;\n vec3 u;\n vec3 v;\n float radius;\n float area;\n float type;\n int textureIndex;\n float spread;\n float selectionPmf;\n float selectionCdf;\n vec3 iesReference;\n};\n\nvoid initRay(inout Ray ray, vec3 origin, vec3 direction) {\n ray.o = origin;\n ray.d = direction;\n ray.tMax = RAY_MAX_DISTANCE;\n}\n\n// given the index from a 1D array, retrieve corresponding position from packed 2D texture\nivec2 unpackTexel(int i, int columnsLog2) {\n ivec2 u;\n u.y = i >> columnsLog2; // equivalent to (i / 2^columnsLog2)\n u.x = i - (u.y << columnsLog2); // equivalent to (i % 2^columnsLog2)\n return u;\n}\n\nvec4 fetchData(sampler2D s, int i, int columnsLog2) {\n return texelFetch(s, unpackTexel(i, columnsLog2), 0);\n}\n\nivec4 fetchData(highp isampler2D s, int i, int columnsLog2) {\n return texelFetch(s, unpackTexel(i, columnsLog2), 0);\n}\n\nstruct Path {\n Ray ray;\n vec3 li;\n float alpha;\n vec3 beta;\n bool fromEye;\n bool abort;\n float scatteringPdf;\n bool scatteringIsDelta;\n bool isNextRefraction;\n};\n\nstruct LightSampleRec {\n vec3 normal;\n vec3 emission;\n vec3 direction;\n float dist;\n float pdf;\n bool isDelta;\n};\n\n\nLight fetchLight(int i) {\n int firstTexel = i * 6;\n vec4 positionAndPmf = texelFetch(\n lightsTex,\n ivec2(firstTexel, 0),\n 0\n );\n vec4 emissionAndCdf = texelFetch(\n lightsTex,\n ivec2(firstTexel + 1, 0),\n 0\n );\n vec4 tangentAndSpread = texelFetch(\n lightsTex,\n ivec2(firstTexel + 2, 0),\n 0\n );\n vec4 bitangentAndTexture = texelFetch(\n lightsTex,\n ivec2(firstTexel + 3, 0),\n 0\n );\n vec4 shape = texelFetch(\n lightsTex,\n ivec2(firstTexel + 4, 0),\n 0\n );\n return Light(\n positionAndPmf.xyz,\n emissionAndCdf.xyz,\n tangentAndSpread.xyz,\n bitangentAndTexture.xyz,\n shape.x,\n shape.y,\n shape.z,\n int(bitangentAndTexture.w),\n tangentAndSpread.w,\n positionAndPmf.w,\n emissionAndCdf.w,\n texelFetch(lightsTex, ivec2(firstTexel + 5, 0), 0).xyz\n );\n}\n\nfloat iesProfileScalar(int baseTexel, int scalarIndex) {\n int texelIndex = baseTexel + scalarIndex / 4;\n vec4 packed = texelFetch(\n iesProfileData,\n ivec2(texelIndex % iesProfileColumns, texelIndex / iesProfileColumns),\n 0\n );\n return packed[scalarIndex % 4];\n}\n\nfloat sampleIESProfile(int baseTexel, vec2 uv) {\n if (baseTexel < 0) return 1.0;\n vec2 pixel = fract(uv) * 128.0 - 0.5;\n ivec2 p0 = ivec2(floor(pixel));\n vec2 f = fract(pixel);\n int xa = p0.x < 0 ? 127 : p0.x;\n int xb = p0.x >= 127 ? 0 : p0.x + 1;\n int ya = p0.y < 0 ? 127 : p0.y;\n int yb = p0.y >= 127 ? 0 : p0.y + 1;\n float a = iesProfileScalar(baseTexel, ya * 128 + xa);\n float b = iesProfileScalar(baseTexel, ya * 128 + xb);\n float c = iesProfileScalar(baseTexel, yb * 128 + xa);\n float d = iesProfileScalar(baseTexel, yb * 128 + xb);\n return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n}\n\nfloat evaluateSpotEmissionMultiplier(Light light, vec3 lightToSurface) {\n vec3 axis = normalize(light.u);\n float cosTheta = dot(axis, lightToSurface);\n float cone = light.v.y > light.v.x + 1e-6\n ? smoothstep(light.v.x, light.v.y, cosTheta)\n : step(light.v.x, cosTheta);\n if (cone <= 0.0 || light.textureIndex < 0) return cone;\n vec3 xAxis = light.iesReference;\n vec3 yAxis = cross(axis, xAxis);\n float vertical = acos(clamp(cosTheta, -1.0, 1.0));\n float horizontal = atan(dot(lightToSurface, yAxis), dot(lightToSurface, xAxis));\n return cone * sampleIESProfile(light.textureIndex, vec2(vertical / PI, horizontal / TWOPI + 0.5));\n}\n`\n});\n"]}
|
|
@@ -7,8 +7,8 @@ in vec3 shadingPoint,
|
|
|
7
7
|
in vec2 samplePoint,
|
|
8
8
|
inout LightSampleRec result
|
|
9
9
|
) {
|
|
10
|
-
vec3
|
|
11
|
-
float aj = length(
|
|
10
|
+
vec3 nR = light.position - shadingPoint;
|
|
11
|
+
float aj = length(nR);
|
|
12
12
|
if (light.radius <= 0.0) {
|
|
13
13
|
if (aj <= 1e-6) {
|
|
14
14
|
result.direction = vec3(0.0, 0.0, 1.0);
|
|
@@ -19,7 +19,7 @@ result.emission = vec3(0.0);
|
|
|
19
19
|
result.isDelta = true;
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
result.direction =
|
|
22
|
+
result.direction = nR / aj;
|
|
23
23
|
result.normal = -result.direction;
|
|
24
24
|
result.dist = aj;
|
|
25
25
|
result.pdf = 1.0;
|
|
@@ -39,7 +39,7 @@ direction = vec3(fl * cos(br), fl * sin(br), z);
|
|
|
39
39
|
result.pdf = INV4PI;
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
|
-
vec3 axis =
|
|
42
|
+
vec3 axis = nR / aj;
|
|
43
43
|
float rT = min(
|
|
44
44
|
1.0,
|
|
45
45
|
radius * radius / (aj * aj)
|
|
@@ -57,9 +57,9 @@ direction = axis * au
|
|
|
57
57
|
result.pdf = 1.0 / max(TWOPI * (1.0 - rW), 1e-8);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
float jf = dot(direction,
|
|
60
|
+
float jf = dot(direction, nR);
|
|
61
61
|
float ej = jf * jf
|
|
62
|
-
- dot(
|
|
62
|
+
- dot(nR, nR) + radius * radius;
|
|
63
63
|
float wK = sqrt(max(0.0, ej));
|
|
64
64
|
float cN = jf - wK;
|
|
65
65
|
float cC = jf + wK;
|
|
@@ -101,8 +101,8 @@ vec3 lowerLeft = light.position - 0.5 * (light.u + light.v);
|
|
|
101
101
|
float spread = clamp(light.spread, 0.0, 1.0);
|
|
102
102
|
|
|
103
103
|
if (spread <= 0.001) {
|
|
104
|
-
float
|
|
105
|
-
vec3 surfacePoint = shadingPoint - normal *
|
|
104
|
+
float gg = dot(normal, shadingPoint - light.position);
|
|
105
|
+
vec3 surfacePoint = shadingPoint - normal * gg;
|
|
106
106
|
vec3 ib = surfacePoint - lowerLeft;
|
|
107
107
|
float width = length(light.u);
|
|
108
108
|
float height = length(light.v);
|
|
@@ -110,14 +110,14 @@ vec2 uv = vec2(
|
|
|
110
110
|
dot(ib, light.u / width) / width,
|
|
111
111
|
dot(ib, light.v / height) / height
|
|
112
112
|
);
|
|
113
|
-
if (
|
|
113
|
+
if (gg <= 0.0 || any(lessThan(uv, vec2(0.0)))
|
|
114
114
|
|| any(greaterThan(uv, vec2(1.0)))) {
|
|
115
115
|
result.emission = vec3(0.0);
|
|
116
116
|
result.pdf = 0.0;
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
result.direction = -normal;
|
|
120
|
-
result.dist =
|
|
120
|
+
result.dist = gg;
|
|
121
121
|
result.normal = normal;
|
|
122
122
|
result.emission = light.emission;
|
|
123
123
|
result.pdf = 1.0;
|
|
@@ -134,19 +134,19 @@ float hq = dot(offset, offset);
|
|
|
134
134
|
result.dist = sqrt(hq);
|
|
135
135
|
result.direction = offset / result.dist;
|
|
136
136
|
result.normal = normal;
|
|
137
|
-
float
|
|
138
|
-
float
|
|
139
|
-
if (
|
|
137
|
+
float ga = dot(-result.direction, normal);
|
|
138
|
+
float oN = cos(0.5 * PI * spread);
|
|
139
|
+
if (ga <= 0.0 || ga < oN) {
|
|
140
140
|
result.emission = vec3(0.0);
|
|
141
141
|
result.pdf = 0.0;
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
144
|
result.emission = light.emission;
|
|
145
|
-
result.pdf = hq / max(light.area *
|
|
145
|
+
result.pdf = hq / max(light.area * ga, 1e-8);
|
|
146
146
|
da(light, surfacePoint, lowerLeft, result);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
void
|
|
149
|
+
void gd(
|
|
150
150
|
in Light light,
|
|
151
151
|
in vec3 shadingPoint,
|
|
152
152
|
inout LightSampleRec result
|
|
@@ -200,15 +200,15 @@ return;
|
|
|
200
200
|
#endif
|
|
201
201
|
#ifdef HAS_DIRECTIONAL_LIGHTS
|
|
202
202
|
if (dP == DIRECTIONAL_LIGHT) {
|
|
203
|
-
|
|
203
|
+
gd(light, shadingPoint, result);
|
|
204
204
|
}
|
|
205
205
|
#endif
|
|
206
206
|
}
|
|
207
207
|
#endif
|
|
208
208
|
|
|
209
|
-
void
|
|
209
|
+
void mG(in vec2 samplePoint, inout LightSampleRec result) {
|
|
210
210
|
vec2 uv;
|
|
211
|
-
result.direction =
|
|
211
|
+
result.direction = oL(samplePoint, uv, result.pdf);
|
|
212
212
|
result.emission = textureLinear(envMap, uv).rgb;
|
|
213
213
|
result.dist = INF;
|
|
214
214
|
}
|
|
@@ -225,7 +225,7 @@ bool kA = false;
|
|
|
225
225
|
#ifdef USE_ENVMAP
|
|
226
226
|
kA = uc < envLightSelectionPmf;
|
|
227
227
|
if (kA) {
|
|
228
|
-
|
|
228
|
+
mG(randomSampleValue.xy, result);
|
|
229
229
|
result.pdf *= envLightSelectionPmf;
|
|
230
230
|
}
|
|
231
231
|
#endif
|