/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "CubemapSH.h" #include #include "Cubemap.h" #include "CubemapUtils.h" #include "utilities.h" using namespace math; // ----------------------------------------------------------------------------------------------- // A few useful utilities // ----------------------------------------------------------------------------------------------- /* * returns n! / d! */ static double factorial(size_t n, size_t d = 1) { d = std::max(size_t(1), d); n = std::max(size_t(1), n); double r = 1.0; if (n == d) { // intentionally left blank } else if (n > d) { for ( ; n>d ; n--) { r *= n; } } else { for ( ; d>n ; d--) { r *= d; } r = 1.0 / r; } return r; } // ----------------------------------------------------------------------------------------------- /* * SH scaling factors: * returns sqrt((2*l + 1) / 4*pi) * sqrt( (l-|m|)! / (l+|m|)! ) */ double CubemapSH::Kml(ssize_t m, size_t l) { m = std::abs(m); const double K = (2*l + 1) * factorial(size_t(l-m), size_t(l+m)); return std::sqrt(K) * (M_2_SQRTPI * 0.25); } // < cos(theta) > SH coefficients pre-multiplied by 1 / K(0,l) double CubemapSH::computeTruncatedCosSh(size_t l) { if (l == 0) { return M_PI; } else if (l == 1) { return 2 * M_PI / 3; } else if (l & 1) { return 0; } const size_t l_2 = l / 2; double A0 = ((l_2 & 1) ? 1.0 : -1.0) / ((l + 2) * (l - 1)); double A1 = factorial(l, l_2) / (factorial(l_2) * (1 << l)); return 2 * M_PI * A0 * A1; } /* * Calculates non-normalized SH bases, i.e.: * m > 0, cos(m*phi) * P(m,l) * m < 0, sin(|m|*phi) * P(|m|,l) * m = 0, P(0,l) */ void CubemapSH::computeShBasis( double* UTILS_RESTRICT SHb, size_t numBands, const double3& s) { #if 0 // Reference implementation double phi = atan2(s.x, s.y); for (size_t l = 0; l < numBands; l++) { SHb[SHindex(0, l)] = Legendre(l, 0, s.z); for (size_t m = 1; m <= l; m++) { double p = Legendre(l, m, s.z); SHb[SHindex(-m, l)] = std::sin(m * phi) * p; SHb[SHindex( m, l)] = std::cos(m * phi) * p; } } #endif /* * TODO: all the Legendre computation below is identical for all faces, so it * might make sense to pre-compute it once. Also note that there is * a fair amount of symmetry within a face (which we could take advantage of * to reduce the pre-compute table). */ /* * Below, we compute the associated Legendre polynomials using recursion. * see: http://mathworld.wolfram.com/AssociatedLegendrePolynomial.html * * Note [0]: s.z == cos(theta) ==> we only need to compute P(s.z) * * Note [1]: We in fact compute P(s.z) / sin(theta)^|m|, by removing * the "sqrt(1 - s.z*s.z)" [i.e.: sin(theta)] factor from the recursion. * This is later corrected in the ( cos(m*phi), sin(m*phi) ) recursion. */ // s = (x, y, z) = (sin(theta)*cos(phi), sin(theta)*sin(phi), cos(theta)) // handle m=0 separately, since it produces only one coefficient double Pml_2 = 0; double Pml_1 = 1; SHb[0] = Pml_1; for (size_t l=1; l CubemapSH::computeSH(const Cubemap& cm, size_t numBands, bool irradiance) { const size_t numCoefs = numBands*numBands; std::unique_ptr SH(new double3[numCoefs]{}); struct State { State() = default; explicit State(size_t numCoefs) : numCoefs(numCoefs) { } State& operator=(State const & rhs) { SH.reset(new double3[rhs.numCoefs]{}); // NOLINT(modernize-make-unique) SHb.reset(new double[rhs.numCoefs]{}); // NOLINT(modernize-make-unique) return *this; } size_t numCoefs = 0; std::unique_ptr SH; std::unique_ptr SHb; } prototype(numCoefs); CubemapUtils::process(cm, [&](State& state, size_t y, Cubemap::Face f, Cubemap::Texel const* data, size_t dim) { for (size_t x=0 ; x& sh, size_t numBands) { const size_t numCoefs = numBands*numBands; std::vector K(numCoefs); // precompute the scaling factor K for (size_t l=0 ; l(cm, [&](CubemapUtils::EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) { std::vector SHb(numCoefs); for (size_t x=0 ; x CubemapSH::computeIrradianceSH3Bands(const Cubemap& cm) { const size_t numCoefs = 9; std::unique_ptr SH(new double3[numCoefs]{}); std::unique_ptr A(new double[numCoefs]{}); const double c0 = computeTruncatedCosSh(0); const double c1 = computeTruncatedCosSh(1); const double c2 = computeTruncatedCosSh(2); A[0] = sq(M_2_SQRTPI / 4) * c0; A[1] = sq(M_2_SQRTPI / 4) * 3 * c1; A[2] = sq(M_2_SQRTPI / 4) * 3 * c1; A[3] = sq(M_2_SQRTPI / 4) * 3 * c1; A[4] = sq(M_2_SQRTPI / 4) * 15 * c2; A[5] = sq(M_2_SQRTPI / 4) * 15 * c2; A[6] = sq(M_2_SQRTPI / 8) * 5 * c2; A[7] = sq(M_2_SQRTPI / 4) * 15 * c2; A[8] = sq(M_2_SQRTPI / 8) * 15 * c2; struct State { double3 SH[9] = { }; }; CubemapUtils::process(cm, [&](State& state, size_t y, Cubemap::Face f, Cubemap::Texel const* data, size_t dim) { for (size_t x=0 ; x& sh) { CubemapUtils::process(cm, [&](CubemapUtils::EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) { for (size_t x=0 ; x 0) { double somx2 = sqrt((1.0 - x) * (1.0 + x)); double fact = 1.0; for (int i = 1; i <= m; i++) { pmm *= (-fact) * somx2; fact += 2.0; } } if (l == m) return pmm; double pmmp1 = x * (2.0 * m + 1.0) * pmm; if (l == m + 1) return pmmp1; double pll = 0.0; for (ssize_t ll = m + 2; ll <= l; ++ll) { pll = ((2.0 * ll - 1.0) * x * pmmp1 - (ll + m - 1.0) * pmm) / (ll - m); pmm = pmmp1; pmmp1 = pll; } return pll; } // Only used for debugging double __UNUSED CubemapSH::TSH(int l, int m, const double3& d) { if (l==0 && m==0) { return 1 / (2*sqrt(M_PI)); } else if (l==1 && m==-1) { return -(sqrt(3)*d.y)/(2*sqrt(M_PI)); } else if (l==1 && m==0) { return (sqrt(3)*d.z)/(2*sqrt(M_PI)); } else if (l==1 && m==1) { return -(sqrt(3)*d.x)/(2*sqrt(M_PI)); } else if (l==2 && m==-2) { return (sqrt(15)*d.y*d.x)/(2*sqrt(M_PI)); } else if (l==2 && m==-1) { return -(sqrt(15)*d.y*d.z)/(2*sqrt(M_PI)); } else if (l==2 && m==0) { return (sqrt(5)*(3*d.z*d.z-1))/(4*sqrt(M_PI)); } else if (l==2 && m==1) { return -(sqrt(15)*d.z*d.x)/(2*sqrt(M_PI)); } else if (l==2 && m==2) { return (sqrt(15)*(d.x*d.x - d.y*d.y))/(4*sqrt(M_PI)); } return 0; } void __UNUSED CubemapSH::printShBase(std::ostream& out, int l, int m) { if (l<3 && std::abs(m) <= l) { const char* d = nullptr; double c = 0; if (l==0 && m==0) { c = M_2_SQRTPI * 0.25; d = " "; } else if (l==1 && m==-1) { c = -M_2_SQRTPI * sqrt(3) * 0.25; d = " * y; "; } else if (l==1 && m==0) { c = M_2_SQRTPI * sqrt(3) * 0.25; d = " * z; "; } else if (l==1 && m==1) { c = -M_2_SQRTPI * sqrt(3) * 0.25; d = " * x; "; } else if (l==2 && m==-2) { c = M_2_SQRTPI * sqrt(15) * 0.25; d = " * y*x; "; } else if (l==2 && m==-1) { c = -M_2_SQRTPI * sqrt(15) * 0.25; d = " * y*z; "; } else if (l==2 && m==0) { c = M_2_SQRTPI * sqrt(5) * 0.125; d = " * (3*z*z -1); "; } else if (l==2 && m==1) { c = -M_2_SQRTPI * sqrt(15) * 0.25; d = " * z*x; "; } else if (l==2 && m==2) { c = M_2_SQRTPI * sqrt(15) * 0.125; d = " * (x*x - y*y);"; } out << "SHb[" << SHindex(m, size_t(l)) << "] = "; out << std::fixed << std::setprecision(15) << std::setw(18) << c << d; out << " // L" << l << m; } }