void test02_shSampler() { /* Draw 100 samples from a SH expansion of a clamped cosine-shaped distribution and verify the returned probabilities */ int bands = 13, numSamples = 100, depth = 12; Vector v = normalize(Vector(1, 2, 3)); ref<Random> random = new Random(); SHVector clampedCos = SHVector(bands); clampedCos.project(ClampedCos(v), numSamples); //Float clampedCosError = clampedCos.l2Error(ClampedCos(v), numSamples); clampedCos.normalize(); //cout << "Projection error = " << clampedCosError << endl; //cout << "Precomputing mip-maps" << endl; ref<SHSampler> sampler = new SHSampler(bands, depth); //cout << "Done: "<< sampler->toString() << endl; Float accum = 0; int nsamples = 100, nInAvg = 0; for (int i=0; i<=nsamples; ++i) { Point2 sample(random->nextFloat(), random->nextFloat()); Float pdf1 = sampler->warp(clampedCos, sample); Float pdf2 = dot(v, sphericalDirection(sample.x, sample.y))/M_PI; Float relerr = std::abs(pdf1-pdf2)/pdf2; if (pdf2 > 0.01) { accum += relerr; ++nInAvg; assertTrue(relerr < 0.08); } } assertTrue(accum / nInAvg < 0.01); }
/// For testing purposes Float integrateOverOutgoing(const Vector &wi) { MediumSamplingRecord mRec; mRec.orientation = Vector(0,0,1); int res = 100; /* Nested composite Simpson's rule */ Float hExt = M_PI / res, hInt = (2*M_PI)/(res*2); Float result = 0; for (int i=0; i<=res; ++i) { Float theta = hExt*i; Float weightExt = (i & 1) ? 4.0f : 2.0f; if (i == 0 || i == res) weightExt = 1; for (int j=0; j<=res*2; ++j) { Float phi = hInt*j; Float weightInt = (j & 1) ? 4.0f : 2.0f; if (j == 0 || j == 2*res) weightInt = 1; Float value = f(mRec, wi, sphericalDirection(theta, phi))[0]*std::sin(theta) * weightExt*weightInt; result += value; } } return hExt*hInt/9; }
void MicrofacetDistribution_sample_wh(vec3 wh, const vec3 wo, const vec2 sample, const MicrofacetDistribution* distrib) { /* // Compute $\tan^2 \theta$ and $\phi$ for Beckmann distribution sample Float tan2Theta, phi; if (alphax == alphay) { Float logSample = std::log(u[0]); if (std::isinf(logSample)) logSample = 0; tan2Theta = -alphax * alphax * logSample; phi = u[1] * 2 * Pi; } else { // Compute _tan2Theta_ and _phi_ for anisotropic Beckmann // distribution Float logSample = std::log(u[0]); if (std::isinf(logSample)) logSample = 0; phi = std::atan(alphay / alphax * std::tan(2 * Pi * u[1] + 0.5f * Pi)); if (u[1] > 0.5f) phi += Pi; Float sinPhi = std::sin(phi), cosPhi = std::cos(phi); Float alphax2 = alphax * alphax, alphay2 = alphay * alphay; tan2Theta = -logSample / (cosPhi * cosPhi / alphax2 + sinPhi * sinPhi / alphay2); } // Map sampled Beckmann angles to normal direction _wh_ Float cosTheta = 1 / std::sqrt(1 + tan2Theta); Float sinTheta = std::sqrt(std::max((Float)0, 1 - cosTheta * cosTheta)); Vector3f wh = SphericalDirection(sinTheta, cosTheta, phi); if (!SameHemisphere(wo, wh)) wh = -wh; return wh; */ float tan_2_theta, phi; if(distrib->alphax == distrib->alphay) { float log_sample = logf(sample[0]); if(isinf(log_sample)) log_sample = 0.0f; tan_2_theta = -distrib->alphax * distrib->alphax * log_sample; phi = sample[1] * 2.0f * PI; }else { float log_sample = logf(sample[0]); if(isinf(log_sample)) log_sample = 0.0f; phi = atanf(distrib->alphay / distrib->alphax * tanf(2.0f * PI * sample[1] + 0.5f * PI)); if(sample[1] > 0.5f) phi += PI; float sin_phi = sinf(phi), cos_phi = cosf(phi); float alphax2 = distrib->alphax * distrib->alphax; float alphay2 = distrib->alphay * distrib->alphay; tan_2_theta = -log_sample / (cos_phi * cos_phi / alphax2 + sin_phi * sin_phi / alphay2); } float cos_theta = 1.0f / sqrtf(1.0f + tan_2_theta); float sin_theta = sqrtf(max(0.0f, 1.0f - cos_theta * cos_theta)); sphericalDirection(wh, sin_theta, cos_theta, phi); if(!sameHemisphere(wo, wh)) vec3_negate(wh, wh); }
bool CausticPerturbation::sampleMutation( Path &source, Path &proposal, MutationRecord &muRec) { int k = source.length(), m = k - 1, l = m - 1; if (k < 4 || !source.vertex(l)->isConnectable()) return false; --l; while (l >= 0 && !source.vertex(l)->isConnectable()) --l; if (l < 1) return false; muRec = MutationRecord(ECausticPerturbation, l, m, m-l, source.getPrefixSuffixWeight(l, m)); statsAccepted.incrementBase(); statsGenerated.incrementBase(); /* Heuristic perturbation size computation (Veach, p.354) */ Float lengthE = source.edge(m-1)->length; Float lengthL = 0; for (int i=l; i<m-1; ++i) lengthL += source.edge(i)->length; Float factor = lengthE/lengthL, theta1 = m_theta1 * factor, theta2 = m_theta2 * factor; Vector woSource = normalize(source.vertex(l+1)->getPosition() - source.vertex(l)->getPosition()); Float phi = m_sampler->next1D() * 2 * M_PI; Float theta = theta2 * math::fastexp(m_logRatio * m_sampler->next1D()); Vector wo = Frame(woSource).toWorld(sphericalDirection(theta, phi)); /* Allocate memory for the proposed path */ proposal.clear(); proposal.append(source, 0, l+1); proposal.append(m_pool.allocEdge()); for (int i=l+1; i<m; ++i) { proposal.append(m_pool.allocVertex()); proposal.append(m_pool.allocEdge()); } proposal.append(source, m, k+1); proposal.vertex(l) = proposal.vertex(l)->clone(m_pool); proposal.vertex(m) = proposal.vertex(m)->clone(m_pool); BDAssert(proposal.vertexCount() == source.vertexCount()); BDAssert(proposal.edgeCount() == source.edgeCount()); Float dist = source.edge(l)->length + perturbMediumDistance(m_sampler, source.vertex(l+1)); /* Sample a perturbation and propagate it through specular interactions */ if (!proposal.vertex(l)->perturbDirection(m_scene, proposal.vertex(l-1), proposal.edge(l-1), proposal.edge(l), proposal.vertex(l+1), wo, dist, source.vertex(l+1)->getType(), EImportance)) { proposal.release(l, m+1, m_pool); return false; } Vector woProposal = normalize(proposal.vertex(l+1)->getPosition() - source.vertex(l)->getPosition()); theta = unitAngle(woSource, woProposal); if (theta >= theta2 || theta <= theta1) { proposal.release(l, m+1, m_pool); return false; } /* If necessary, propagate the perturbation through a sequence of ideally specular interactions */ for (int i=l+1; i<m-1; ++i) { Float dist = source.edge(i)->length + perturbMediumDistance(m_sampler, source.vertex(i+1)); if (!proposal.vertex(i)->propagatePerturbation(m_scene, proposal.vertex(i-1), proposal.edge(i-1), proposal.edge(i), proposal.vertex(i+1), source.vertex(i)->getComponentType(), dist, source.vertex(i+1)->getType(), EImportance)) { proposal.release(l, m+1, m_pool); return false; } } if (!PathVertex::connect(m_scene, proposal.vertex(m-2), proposal.edge(m-2), proposal.vertex(m-1), proposal.edge(m-1), proposal.vertex(m), proposal.edge(m), proposal.vertex(m+1))) { proposal.release(l, m+1, m_pool); return false; } proposal.vertex(k-1)->updateSamplePosition( proposal.vertex(k-2)); ++statsGenerated; return true; }
inline Spectrum sample(BSDFSamplingRecord &bRec, Float &_pdf, const Point2 &_sample) const { Point2 sample(_sample); bool hasSpecular = (bRec.typeMask & EGlossyReflection) && (bRec.component == -1 || bRec.component == 0); bool hasDiffuse = (bRec.typeMask & EDiffuseReflection) && (bRec.component == -1 || bRec.component == 1); if (!hasSpecular && !hasDiffuse) return Spectrum(0.0f); bool choseSpecular = hasSpecular; if (hasDiffuse && hasSpecular) { if (sample.x <= m_specularSamplingWeight) { sample.x /= m_specularSamplingWeight; } else { sample.x = (sample.x - m_specularSamplingWeight) / (1-m_specularSamplingWeight); choseSpecular = false; } } if (choseSpecular) { Float alphaU = m_alphaU->eval(bRec.its).average(); Float alphaV = m_alphaV->eval(bRec.its).average(); Float phiH = std::atan(alphaV/alphaU * std::tan(2.0f * M_PI * sample.y)); if (sample.y > 0.5f) phiH += M_PI; Float cosPhiH = std::cos(phiH); Float sinPhiH = math::safe_sqrt(1.0f-cosPhiH*cosPhiH); Float thetaH = std::atan(math::safe_sqrt( -math::fastlog(sample.x) / ( (cosPhiH*cosPhiH) / (alphaU*alphaU) + (sinPhiH*sinPhiH) / (alphaV*alphaV) ))); Vector H = sphericalDirection(thetaH, phiH); bRec.wo = H * (2.0f * dot(bRec.wi, H)) - bRec.wi; bRec.sampledComponent = 1; bRec.sampledType = EGlossyReflection; if (Frame::cosTheta(bRec.wo) <= 0.0f) return Spectrum(0.0f); } else { bRec.wo = Warp::squareToCosineHemisphere(sample); bRec.sampledComponent = 0; bRec.sampledType = EDiffuseReflection; } bRec.eta = 1.0f; _pdf = pdf(bRec, ESolidAngle); if (_pdf == 0) return Spectrum(0.0f); else return eval(bRec, ESolidAngle) / _pdf; }
/// Invoke a series of t-tests on the provided input void activate() { int total = 0, passed = 0; pcg32 random; if (!m_bsdfs.empty()) { if (m_references.size() * m_bsdfs.size() != m_angles.size()) throw NoriException("Specified a different number of angles and reference values!"); if (!m_scenes.empty()) throw NoriException("Cannot test BSDFs and scenes at the same time!"); /* Test each registered BSDF */ int ctr = 0; for (auto bsdf : m_bsdfs) { for (size_t i=0; i<m_references.size(); ++i) { float angle = m_angles[i], reference = m_references[ctr++]; cout << "------------------------------------------------------" << endl; cout << "Testing (angle=" << angle << "): " << bsdf->toString() << endl; ++total; BSDFQueryRecord bRec(sphericalDirection(degToRad(angle), 0)); cout << "Drawing " << m_sampleCount << " samples .. " << endl; double mean=0, variance = 0; for (int k=0; k<m_sampleCount; ++k) { Point2f sample(random.nextFloat(), random.nextFloat()); double result = (double) bsdf->sample(bRec, sample).getLuminance(); /* Numerically robust online variance estimation using an algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */ double delta = result - mean; mean += delta / (double) (k+1); variance += delta * (result - mean); } variance /= m_sampleCount - 1; std::pair<bool, std::string> result = hypothesis::students_t_test(mean, variance, reference, m_sampleCount, m_significanceLevel, (int) m_references.size()); if (result.first) ++passed; cout << result.second << endl; } } } else { if (m_references.size() != m_scenes.size()) throw NoriException("Specified a different number of scenes and reference values!"); Sampler *sampler = static_cast<Sampler *>( NoriObjectFactory::createInstance("independent", PropertyList())); int ctr = 0; for (auto scene : m_scenes) { const Integrator *integrator = scene->getIntegrator(); const Camera *camera = scene->getCamera(); float reference = m_references[ctr++]; cout << "------------------------------------------------------" << endl; cout << "Testing scene: " << scene->toString() << endl; ++total; cout << "Generating " << m_sampleCount << " paths.. " << endl; double mean = 0, variance = 0; for (int k=0; k<m_sampleCount; ++k) { /* Sample a ray from the camera */ Ray3f ray; Point2f pixelSample = (sampler->next2D().array() * camera->getOutputSize().cast<float>().array()).matrix(); Color3f value = camera->sampleRay(ray, pixelSample, sampler->next2D()); /* Compute the incident radiance */ value *= integrator->Li(scene, sampler, ray); /* Numerically robust online variance estimation using an algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */ double result = (double) value.getLuminance(); double delta = result - mean; mean += delta / (double) (k+1); variance += delta * (result - mean); } variance /= m_sampleCount - 1; std::pair<bool, std::string> result = hypothesis::students_t_test(mean, variance, reference, m_sampleCount, m_significanceLevel, (int) m_references.size()); if (result.first) ++passed; cout << result.second << endl; } } cout << "Passed " << passed << "/" << total << " tests." << endl; }