Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef IRIDESCENCE_GLSLLIB
#define IRIDESCENCE_GLSLLIB 1
#include "principledMaterialFresnel.glsllib" // qt_schlick
#ifndef M_PI
#define M_PI 3.14159265359
#endif
// Thin-film model of KHR_materials_iridescence (Belcour and Barla 2017)
vec3 qt_iridescenceFresnel0ToIor(vec3 f0)
{
vec3 sqrtF0 = sqrt(f0);
return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0);
}
float qt_iridescenceIorToFresnel0(float transmittedIor, float incidentIor)
{
float r = (transmittedIor - incidentIor) / (transmittedIor + incidentIor);
return r * r;
}
vec3 qt_iridescenceIorToFresnel0(vec3 transmittedIor, float incidentIor)
{
vec3 r = (transmittedIor - vec3(incidentIor)) / (transmittedIor + vec3(incidentIor));
return r * r;
}
// Optical path difference to color, using the extension's fit of the CIE XYZ curves
vec3 qt_iridescenceEvalSensitivity(float opd, vec3 shift)
{
float phase = 2.0 * M_PI * opd * 1.0e-9;
vec3 val = vec3(5.4856e-13, 4.4201e-13, 5.2481e-13);
vec3 pos = vec3(1.6810e+06, 1.7953e+06, 2.2084e+06);
vec3 var = vec3(4.3278e+09, 9.3046e+09, 6.6121e+09);
vec3 xyz = val * sqrt(2.0 * M_PI * var) * cos(pos * phase + shift) * exp(-(phase * phase) * var);
xyz.x += 9.7470e-14 * sqrt(2.0 * M_PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0])
* exp(-4.5282e+09 * (phase * phase));
xyz /= 1.0685e-7;
// Column major
const mat3 qt_xyzToRec709 = mat3( 3.2404542, -0.9692660, 0.0556434,
-1.5371385, 1.8760108, -0.2040259,
-0.4985314, 0.0415560, 1.0572252);
return qt_xyzToRec709 * xyz;
}
// Thickness is in nanometers
vec3 qt_evalIridescence(float outsideIor, float iridescenceIor, float cosTheta1,
float iridescenceThickness, vec3 baseF0)
{
float r0 = qt_iridescenceIorToFresnel0(iridescenceIor, outsideIor);
float r12 = r0 + (1.0 - r0) * qt_schlick(cosTheta1);
float t121 = 1.0 - r12;
// Total internal reflection
float sinTheta2Sq = (outsideIor / iridescenceIor) * (outsideIor / iridescenceIor)
* (1.0 - cosTheta1 * cosTheta1);
float cosTheta2Sq = 1.0 - sinTheta2Sq;
if (cosTheta2Sq < 0.0)
return vec3(1.0);
float cosTheta2 = sqrt(cosTheta2Sq);
// The conversion is singular at F0 = 1
vec3 baseIor = qt_iridescenceFresnel0ToIor(clamp(baseF0, 0.0, 0.9999));
vec3 r1 = qt_iridescenceIorToFresnel0(baseIor, iridescenceIor);
vec3 r23 = r1 + (vec3(1.0) - r1) * qt_schlick(cosTheta2);
float opd = 2.0 * iridescenceIor * iridescenceThickness * cosTheta2;
float phi12 = iridescenceIor < outsideIor ? M_PI : 0.0;
float phi21 = M_PI - phi12;
vec3 phi23 = vec3(baseIor[0] < iridescenceIor ? M_PI : 0.0,
baseIor[1] < iridescenceIor ? M_PI : 0.0,
baseIor[2] < iridescenceIor ? M_PI : 0.0);
vec3 phi = vec3(phi21) + phi23;
// Two terms of the series are enough, as r123 < 1
vec3 r123 = clamp(vec3(r12) * r23, 1e-5, 0.9999);
vec3 r123Sqrt = sqrt(r123);
vec3 rs = (t121 * t121) * r23 / (vec3(1.0) - r123);
vec3 c0 = vec3(r12) + rs;
vec3 i = c0;
vec3 cm = rs - vec3(t121);
for (int m = 1; m <= 2; ++m) {
cm *= r123Sqrt;
vec3 sm = 2.0 * qt_iridescenceEvalSensitivity(float(m) * opd, float(m) * phi);
i += cm * sm;
}
return max(i, vec3(0.0));
}
#endif // IRIDESCENCE_GLSLLIB