// Computes total reflectance including spectral interference from a thin film // that is `thickness' nanometers thick and has refraction index eta // See http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/thin-film-interference-for-computer-graphics-r2962 static inline Vec3f thinFilmReflectanceInterference(float eta, float cosThetaI, float thickness, float &cosThetaT) { const Vec3f invLambdas = 1.0f/Vec3f(650.0f, 510.0f, 475.0f); float cosThetaISq = cosThetaI*cosThetaI; float sinThetaISq = 1.0f - cosThetaISq; float invEta = 1.0f/eta; float sinThetaTSq = eta*eta*sinThetaISq; if (sinThetaTSq > 1.0f) { cosThetaT = 0.0f; return Vec3f(1.0f); } cosThetaT = std::sqrt(1.0f - sinThetaTSq); float Ts = 4.0f*eta*cosThetaI*cosThetaT/sqr(eta*cosThetaI + cosThetaT); float Tp = 4.0f*eta*cosThetaI*cosThetaT/sqr(eta*cosThetaT + cosThetaI); float Rs = 1.0f - Ts; float Rp = 1.0f - Tp; Vec3f phi = (thickness*cosThetaT*FOUR_PI*invEta)*invLambdas; Vec3f cosPhi(std::cos(phi.x()), std::cos(phi.y()), std::cos(phi.z())); Vec3f tS = sqr(Ts)/((sqr(Rs) + 1.0f) - 2.0f*Rs*cosPhi); Vec3f tP = sqr(Tp)/((sqr(Rp) + 1.0f) - 2.0f*Rp*cosPhi); return 1.0f - (tS + tP)*0.5f; }
void microfacetNoExpFourierSeries(Float mu_o, Float mu_i, std::complex<Float> eta_, Float alpha, size_t n, Float phiMax, std::vector<Float> &result) { bool reflect = -mu_i * mu_o > 0; Float sinMu2 = math::safe_sqrt((1 - mu_i * mu_i) * (1 - mu_o * mu_o)), phiCritical = 0.0f; bool conductor = (eta_.imag() != 0.0f); std::complex<Float> eta = (-mu_i > 0 || conductor) ? eta_ : std::complex<Float>(1) / eta_; if (reflect) { if (!conductor) phiCritical = math::safe_acos((2*eta.real()*eta.real()-mu_i*mu_o-1)/sinMu2); } else if (!reflect) { if (conductor) throw std::runtime_error("lowfreqFourierSeries(): encountered refraction case for a conductor"); Float etaDenser = (eta.real() > 1 ? eta.real() : 1 / eta.real()); phiCritical = math::safe_acos((1 - etaDenser * mu_i * mu_o) / (etaDenser * sinMu2)); } if (!conductor && phiCritical > math::Epsilon && phiCritical < math::Pi - math::Epsilon && phiCritical < phiMax - math::Epsilon) { /* Uh oh, some high frequency content leaked in the generally low frequency part. Increase the number of coefficients so that we can capture it. Fortunately, this happens very rarely. */ n = std::max(n, (size_t) 100); } VectorX coeffs(n); coeffs.setZero(); std::function<Float(Float)> integrand = std::bind( µfacetNoExp, mu_o, mu_i, eta_, alpha, std::placeholders::_1); const int nEvals = 200; if (reflect) { if (phiCritical > math::Epsilon && phiCritical < phiMax-math::Epsilon) { filonIntegrate(integrand, coeffs.data(), n, nEvals, 0, phiCritical); filonIntegrate(integrand, coeffs.data(), n, nEvals, phiCritical, phiMax); } else { filonIntegrate(integrand, coeffs.data(), n, nEvals, 0, phiMax); } } else { filonIntegrate(integrand, coeffs.data(), n, nEvals, 0, std::min(phiCritical, phiMax)); } if (phiMax < math::Pi - math::Epsilon) { /* Precompute some sines and cosines */ VectorX cosPhi(n), sinPhi(n); for (int i=0; i<n; ++i) { sinPhi[i] = std::sin(i*phiMax); cosPhi[i] = std::cos(i*phiMax); } /* The fit only occurs on a subset [0, phiMax], where the Fourier Fourier basis functions are not orthogonal anymore! The following then does a change of basis to proper Fourier coefficients. */ MatrixX A(n, n); for (int i=0; i<n; ++i) { for (int j=0; j<=i; ++j) { if (i != j) { A(i, j) = A(j, i) = (i * cosPhi[j] * sinPhi[i] - j * cosPhi[i] * sinPhi[j]) / (i * i - j * j); } else if (i != 0) { A(i, i) = (std::sin(2 * i * phiMax) + 2 * i * phiMax) / (4 * i); } else { A(i, i) = phiMax; } } } auto svd = A.bdcSvd(Eigen::ComputeFullU | Eigen::ComputeFullV); const MatrixX &U = svd.matrixU(); const MatrixX &V = svd.matrixV(); const VectorX &sigma = svd.singularValues(); if (sigma[0] == 0) { result.clear(); result.push_back(0); return; } VectorX temp = VectorX::Zero(n); coeffs[0] *= math::Pi; coeffs.tail(n-1) *= 0.5 * math::Pi; for (int i=0; i<n; ++i) { if (sigma[i] < 1e-9f * sigma[0]) break; temp += V.col(i) * U.col(i).dot(coeffs) / sigma[i]; } coeffs = temp; } result.resize(coeffs.size()); memcpy(result.data(), coeffs.data(), sizeof(Float) * coeffs.size()); }