GaussQuadratureTetrahedron::GaussQuadratureTetrahedron(size_t order) { GaussLegendre quadRule = GaussLegendre(order); GaussJacobi10 quadRule10 = GaussJacobi10(order); GaussJacobi20 quadRule20 = GaussJacobi20(order); m_order = std::min(quadRule.order(), std::min(quadRule10.order(), quadRule20.order())); m_numPoints = quadRule.size() * quadRule10.size() * quadRule20.size(); position_type* pvPoint = new position_type[m_numPoints]; weight_type* pvWeight = new weight_type[m_numPoints]; size_t cnt = 0; for(size_t i = 0; i < quadRule20.size(); i++) { for(size_t j = 0; j < quadRule10.size(); j++) { for(size_t k = 0; k < quadRule.size(); k++, cnt++) { pvPoint[cnt][0] = quadRule20.point(i)[0]; pvPoint[cnt][1] = (1.0 - quadRule20.point(i)[0] ) * quadRule10.point(j)[0]; pvPoint[cnt][2] = (1.0 - quadRule20.point(i)[0]) * (1.0 - quadRule10.point(j)[0]) * quadRule.point(k)[0]; pvWeight[cnt] = quadRule20.weight(i) * quadRule10.weight(j) * quadRule.weight(k); } } } m_pvPoint = pvPoint; m_pvWeight = pvWeight; };
/** * Integrate down the centre of this unknown * This will not take into account changes * in the jacobian across the extent of the basis * function */ static typename UnknownT::NumberT integrate( const UnknownT& unknown, const CoordinatesInterface<DIM, typename UnknownT::NumberT>& geom, const int direction) { GaussLegendre<UnknownT::ORDER, NumberT> quad; ValueT integral(0.0); for (auto interval1D : unknown.linearIntervals1D(direction)) { NumberT intervalMin = interval1D.minimum(0); NumberT intervalMax = interval1D.maximum(0); auto nodes = quad.nodes(intervalMin, intervalMax); auto weights = quad.weights(intervalMin, intervalMax); auto location = unknown.centres(); for (std::size_t j = 0; j < nodes.size(); ++j) { location[direction] = nodes[j]; integral += weights[j] * unknown.value(direction, nodes[j]) * geom.jacobian(direction, nodes[j]); } } return integral; }
static typename UnknownT::NumberT integrateComplex( const UnknownT& unknown, const CoordinatesInterface<DIM, typename UnknownT::NumberT>& geom, const std::vector<int>& directions ) { static const int QUAD_ORDER = UnknownT::ORDER + 1; static const int NUM = GaussLegendre<QUAD_ORDER, NumberT>::NUM; assert(DIM > 1); // integrate the the copy along direction GaussLegendre<QUAD_ORDER, NumberT> quad; std::array<int, DIM> indexSizes; indexSizes.fill(int(quad.size())); auto indices = MathsUtilities::allPermutations<DIM>(indexSizes); // The integral of the unknown in the directions // we to integrate in ValueT integral(0.0); for (auto interval : unknown.intervals()) { auto minima = interval.minima(); auto maxima = interval.maxima(); auto nodes = QuadratureOps<DIM, QUAD_ORDER, NumberT>:: nodes(quad, minima, maxima); auto weights = QuadratureOps<DIM, QUAD_ORDER, NumberT>:: weights(quad, minima, maxima); for (auto index : indices) { auto locationArray = ContainerUtilities:: select<DIM, NUM, NumberT>(nodes, index); auto allWeights = ContainerUtilities:: select<DIM, NUM, NumberT>(weights, index); Vector<DIM, NumberT> location(locationArray); assert(interval.isInside(location)); auto directionWeights = ContainerUtilities:: select<DIM, NumberT>(allWeights, directions); auto weight = ContainerUtilities:: product<NumberT>(directionWeights); integral += weight * unknown.value(directions, location) * geom.jacobian(Contravariant<DIM, NumberT>(location)); } } static_assert(NUM > 0, "NUM must be greater than zero"); return integral / NUM; }
static typename std::array<typename GaussLegendre<ORDER, T>::NodesAndWeights, DIM> nodesWeightsPairs( const Vector<DIM, T>& mins, const Vector<DIM, T>& maxs ) { GaussLegendre<ORDER, T> quad; std::array<GaussLegendre<ORDER, T>, DIM> allNodesAndWeights; for (int i = 0; i < DIM; ++i) { assert(maxs[i] > mins[i]); allNodesAndWeights[i] = quad.weights(mins[i], maxs[i]); } return allNodesAndWeights; }
GaussQuadratureQuadrilateral::GaussQuadratureQuadrilateral(size_t order) { GaussLegendre quadRule = GaussLegendre(order); m_order = std::min(quadRule.order(), quadRule.order()); m_numPoints = quadRule.size() * quadRule.size(); position_type* pvPoint = new position_type[m_numPoints]; weight_type* pvWeight = new weight_type[m_numPoints]; size_t cnt = 0; for(size_t i = 0; i < quadRule.size(); i ++) { for(size_t j = 0; j < quadRule.size(); j++, cnt++) { pvPoint[cnt][0] = quadRule.point(i)[0]; pvPoint[cnt][1] = quadRule.point(j)[0]; pvWeight[cnt] = quadRule.weight(i) * quadRule.weight(j); } } m_pvPoint = pvPoint; m_pvWeight = pvWeight; };
static std::array<std::array<T, ORDER+1>, DIM> weights(const GaussLegendre<ORDER, T>& quad, const Vector<DIM, T>& mins, const Vector<DIM, T>& maxs) { std::array<std::array<T, ORDER+1>, DIM> allWeights; for (uint i = 0; i < DIM; ++i) { assert(maxs[i] > mins[i]); allWeights[i] = quad.weights(mins[i], maxs[i]); } return allWeights; }
void HairBcsdf::precomputeAzimuthalDistributions() { const int Resolution = PrecomputedAzimuthalLobe::AzimuthalResolution; std::unique_ptr<Vec3f[]> valuesR (new Vec3f[Resolution*Resolution]); std::unique_ptr<Vec3f[]> valuesTT (new Vec3f[Resolution*Resolution]); std::unique_ptr<Vec3f[]> valuesTRT(new Vec3f[Resolution*Resolution]); // Ideally we could simply make this a constexpr, but MSVC does not support that yet (boo!) #define NumPoints 140 GaussLegendre<NumPoints> integrator; const auto points = integrator.points(); const auto weights = integrator.weights(); // Cache the gammaI across all integration points std::array<float, NumPoints> gammaIs; for (int i = 0; i < NumPoints; ++i) gammaIs[i] = std::asin(points[i]); // Precompute the Gaussian detector and sample it into three 1D tables. // This is the only part of the precomputation that is actually approximate. // 2048 samples are enough to support the lowest roughness that the BCSDF // can reliably simulate const int NumGaussianSamples = 2048; std::unique_ptr<float[]> Ds[3]; for (int p = 0; p < 3; ++p) { Ds[p].reset(new float[NumGaussianSamples]); for (int i = 0; i < NumGaussianSamples; ++i) Ds[p][i] = D(_betaR, i/(NumGaussianSamples - 1.0f)*TWO_PI); } // Simple wrapped linear interpolation of the precomputed table auto approxD = [&](int p, float phi) { float u = std::abs(phi*(INV_TWO_PI*(NumGaussianSamples - 1))); int x0 = int(u); int x1 = x0 + 1; u -= x0; return Ds[p][x0 % NumGaussianSamples]*(1.0f - u) + Ds[p][x1 % NumGaussianSamples]*u; }; // Here follows the actual precomputation of the azimuthal scattering functions // The scattering functions are parametrized with the azimuthal angle phi, // and the cosine of the half angle, cos(thetaD). // This parametrization makes the azimuthal function relatively smooth and allows using // really low resolutions for the table (64x64 in this case) without any visual // deviation from ground truth, even at the lowest supported roughness setting for (int y = 0; y < Resolution; ++y) { float cosHalfAngle = y/(Resolution - 1.0f); // Precompute reflection Fresnel factor and reduced absorption coefficient float iorPrime = std::sqrt(Eta*Eta - (1.0f - cosHalfAngle*cosHalfAngle))/cosHalfAngle; float cosThetaT = std::sqrt(1.0f - (1.0f - cosHalfAngle*cosHalfAngle)*sqr(1.0f/Eta)); Vec3f sigmaAPrime = _sigmaA/cosThetaT; // Precompute gammaT, f_t and internal absorption across all integration points std::array<float, NumPoints> fresnelTerms, gammaTs; std::array<Vec3f, NumPoints> absorptions; for (int i = 0; i < NumPoints; ++i) { gammaTs[i] = std::asin(clamp(points[i]/iorPrime, -1.0f, 1.0f)); fresnelTerms[i] = Fresnel::dielectricReflectance(1.0f/Eta, cosHalfAngle*std::cos(gammaIs[i])); absorptions[i] = std::exp(-sigmaAPrime*2.0f*std::cos(gammaTs[i])); } for (int phiI = 0; phiI < Resolution; ++phiI) { float phi = TWO_PI*phiI/(Resolution - 1.0f); float integralR = 0.0f; Vec3f integralTT(0.0f); Vec3f integralTRT(0.0f); // Here follows the integration across the fiber width, h. // Since we were able to precompute most of the factors that // are constant w.r.t phi for a given h, // we don't have to do much work here. for (int i = 0; i < integrator.numSamples(); ++i) { float fR = fresnelTerms[i]; Vec3f T = absorptions[i]; float AR = fR; Vec3f ATT = (1.0f - fR)*(1.0f - fR)*T; Vec3f ATRT = ATT*fR*T; integralR += weights[i]*approxD(0, phi - Phi(gammaIs[i], gammaTs[i], 0))*AR; integralTT += weights[i]*approxD(1, phi - Phi(gammaIs[i], gammaTs[i], 1))*ATT; integralTRT += weights[i]*approxD(2, phi - Phi(gammaIs[i], gammaTs[i], 2))*ATRT; } valuesR [phiI + y*Resolution] = Vec3f(0.5f*integralR); valuesTT [phiI + y*Resolution] = 0.5f*integralTT; valuesTRT[phiI + y*Resolution] = 0.5f*integralTRT; } } // Hand the values off to the helper class to construct sampling CDFs and so forth _nR .reset(new PrecomputedAzimuthalLobe(std::move(valuesR))); _nTT .reset(new PrecomputedAzimuthalLobe(std::move(valuesTT))); _nTRT.reset(new PrecomputedAzimuthalLobe(std::move(valuesTRT))); }