bool BidirPathTracing::sampleScattering(BSDF& bsdf , const Vector3& hitPos , BidirPathState& pathState) { Real bsdfDirPdf , cosWo; int sampledBSDFType; Color3 bsdfFactor = bsdf.sample(scene , rng.randVector3() , pathState.dir , bsdfDirPdf , cosWo , &sampledBSDFType); if (bsdfFactor.isBlack()) return 0; Real bsdfRevPdf = bsdfDirPdf; if ((sampledBSDFType & BSDF_SPECULAR) == 0) bsdfRevPdf = bsdf.pdf(scene , pathState.dir , 1); Real contProb = bsdf.continueProb; if (rng.randFloat() > contProb) return 0; bsdfDirPdf *= contProb; bsdfRevPdf *= contProb; // Partial sub-path MIS quantities // the evaluation is completed when the actual hit point is known! // i.e. after tracing the ray, out of the procedure if (sampledBSDFType & BSDF_SPECULAR) { pathState.specularVertexNum++; pathState.dVCM = 0.f; pathState.dVC *= mis(cosWo); } else { pathState.specularPath &= 0; pathState.dVC = mis(1.f / bsdfDirPdf) * (pathState.dVCM + pathState.dVC * mis(bsdfRevPdf)); pathState.dVCM = mis(1.f / bsdfDirPdf); } pathState.origin = hitPos; pathState.throughput = (pathState.throughput | bsdfFactor) * (cosWo / bsdfDirPdf); return 1; }
void compute_ibl_bsdf_sampling( SamplingContext& sampling_context, const ShadingContext& shading_context, const EnvironmentEDF& environment_edf, const ShadingPoint& shading_point, const Vector3d& outgoing, const BSDF& bsdf, const void* bsdf_data, const int bsdf_sampling_modes, const size_t bsdf_sample_count, const size_t env_sample_count, Spectrum& radiance) { assert(is_normalized(outgoing)); const Vector3d& geometric_normal = shading_point.get_geometric_normal(); const Basis3d& shading_basis = shading_point.get_shading_basis(); radiance.set(0.0f); for (size_t i = 0; i < bsdf_sample_count; ++i) { // Sample the BSDF. // todo: rendering will be incorrect if the BSDF value returned by the sample() method // includes the contribution of a specular component since these are explicitly rejected // afterward. We need a mechanism to indicate that we want the contribution of some of // the components only. Vector3d incoming; Spectrum bsdf_value; double bsdf_prob; const BSDF::Mode bsdf_mode = bsdf.sample( sampling_context, bsdf_data, false, // not adjoint true, // multiply by |cos(incoming, normal)| geometric_normal, shading_basis, outgoing, incoming, bsdf_value, bsdf_prob); // Filter scattering modes. if (!(bsdf_sampling_modes & bsdf_mode)) return; // Discard occluded samples. const double transmission = shading_context.get_tracer().trace( shading_point, incoming, ShadingRay::ShadowRay); if (transmission == 0.0) continue; // Evaluate the environment's EDF. InputEvaluator input_evaluator(shading_context.get_texture_cache()); Spectrum env_value; double env_prob; environment_edf.evaluate( input_evaluator, incoming, env_value, env_prob); // Apply all weights, including MIS weight. if (bsdf_mode == BSDF::Specular) env_value *= static_cast<float>(transmission); else { const double mis_weight = mis_power2( bsdf_sample_count * bsdf_prob, env_sample_count * env_prob); env_value *= static_cast<float>(transmission / bsdf_prob * mis_weight); } // Add the contribution of this sample to the illumination. env_value *= bsdf_value; radiance += env_value; } if (bsdf_sample_count > 1) radiance /= static_cast<float>(bsdf_sample_count); }
bool extend(BDPTPathVertex& next, const Scene& scene, uint32_t pathCount, bool sampleAdjoint, // Number of paths sampled with the strategies s/t = m_nDepth + 1, t/s = * RandomGenerator&& rng, MisFunctor&& mis) { if(!m_Intersection) { return false; // Can't sample from infinity } Sample3f woSample; float cosThetaOutDir; uint32_t sampledEvent; auto fs = m_BSDF.sample(Vec3f(getFloat(rng), getFloat2(rng)), woSample, cosThetaOutDir, &sampledEvent, sampleAdjoint); if(woSample.pdf == 0.f || fs == zero<Vec3f>()) { return false; } float reversePdf = m_BSDF.pdf(woSample.value, true); auto nextI = scene.intersect(Ray(m_Intersection, woSample.value)); if(!nextI) { next.m_Intersection = nextI; next.m_BSDF.init(-woSample.value, scene); next.m_fPdfWrtArea = woSample.pdf; next.m_fPathPdf = m_fPathPdf * woSample.pdf; next.m_Power = m_Power * abs(cosThetaOutDir) * fs / woSample.pdf; next.m_nDepth = m_nDepth + 1; auto previousPointToIncidentDirectionJacobian = abs(cosThetaOutDir); // No division by squared distance since the point is at infinity if((sampledEvent & ScatteringEvent::Specular) && m_BSDF.isDelta()) { next.m_fdVC = mis(previousPointToIncidentDirectionJacobian) * m_fdVC; next.m_fdVCM = 0.f; } else { // We set dVC first in case next == *this, so that the dVCM is unchanged until next line next.m_fdVC = mis(previousPointToIncidentDirectionJacobian / next.m_fPdfWrtArea) * (m_fdVCM + mis(reversePdf) * m_fdVC); next.m_fdVCM = mis(pathCount / next.m_fPdfWrtArea); } } else { auto incidentDirection = -woSample.value; auto sqrLength = sqr(nextI.distance); if(sqrLength == 0.f) { return false; } auto pdfWrtArea = woSample.pdf * abs(dot(nextI.Ns, incidentDirection)) / sqrLength; if(pdfWrtArea == 0.f) { return false; } next.m_Intersection = nextI; next.m_BSDF.init(incidentDirection, nextI, scene); next.m_fPdfWrtArea = pdfWrtArea; next.m_fPathPdf = m_fPathPdf * pdfWrtArea; next.m_Power = m_Power * abs(cosThetaOutDir) * fs / woSample.pdf; next.m_nDepth = m_nDepth + 1; auto previousPointToIncidentDirectionJacobian = abs(cosThetaOutDir) / sqrLength; if((sampledEvent & ScatteringEvent::Specular) && m_BSDF.isDelta()) { next.m_fdVC = mis(previousPointToIncidentDirectionJacobian) * m_fdVC; next.m_fdVCM = 0.f; } else { // We set dVC first in case next == *this, so that the dVCM is unchanged until next line next.m_fdVC = mis(previousPointToIncidentDirectionJacobian / next.m_fPdfWrtArea) * (m_fdVCM + mis(reversePdf) * m_fdVC); next.m_fdVCM = mis(pathCount / next.m_fPdfWrtArea); } } return true; }
SampledSpectrum PathTracingRenderer::Job::contribution(const Scene &scene, const WavelengthSamples &initWLs, const Ray &initRay, IndependentLightPathSampler &pathSampler, ArenaAllocator &mem) const { WavelengthSamples wls = initWLs; Ray ray = initRay; SurfacePoint surfPt; SampledSpectrum alpha = SampledSpectrum::One; float initY = alpha.importance(wls.selectedLambda); SampledSpectrumSum sp(SampledSpectrum::Zero); uint32_t pathLength = 0; Intersection isect; if (!scene.intersect(ray, &isect)) return SampledSpectrum::Zero; isect.getSurfacePoint(&surfPt); Vector3D dirOut_sn = surfPt.shadingFrame.toLocal(-ray.dir); if (surfPt.isEmitting()) { EDF* edf = surfPt.createEDF(wls, mem); SampledSpectrum Le = surfPt.emittance(wls) * edf->evaluate(EDFQuery(), dirOut_sn); sp += alpha * Le; } if (surfPt.atInfinity) return sp; while (true) { ++pathLength; if (pathLength >= 100) break; Normal3D gNorm_sn = surfPt.shadingFrame.toLocal(surfPt.gNormal); BSDF* bsdf = surfPt.createBSDF(wls, mem); BSDFQuery fsQuery(dirOut_sn, gNorm_sn, wls.selectedLambda); // Next Event Estimation (explicit light sampling) if (bsdf->hasNonDelta()) { float lightProb; Light light; scene.selectLight(pathSampler.getLightSelectionSample(), &light, &lightProb); SLRAssert(!std::isnan(lightProb) && !std::isinf(lightProb), "lightProb: unexpected value detected: %f", lightProb); LightPosQuery lpQuery(ray.time, wls); LightPosQueryResult lpResult; SampledSpectrum M = light.sample(lpQuery, pathSampler.getLightPosSample(), &lpResult); SLRAssert(!std::isnan(lpResult.areaPDF)/* && !std::isinf(xpResult.areaPDF)*/, "areaPDF: unexpected value detected: %f", lpResult.areaPDF); if (scene.testVisibility(surfPt, lpResult.surfPt, ray.time)) { float dist2; Vector3D shadowDir = lpResult.surfPt.getDirectionFrom(surfPt.p, &dist2); Vector3D shadowDir_l = lpResult.surfPt.shadingFrame.toLocal(-shadowDir); Vector3D shadowDir_sn = surfPt.shadingFrame.toLocal(shadowDir); EDF* edf = lpResult.surfPt.createEDF(wls, mem); SampledSpectrum Le = M * edf->evaluate(EDFQuery(), shadowDir_l); float lightPDF = lightProb * lpResult.areaPDF; SLRAssert(!Le.hasNaN() && !Le.hasInf(), "Le: unexpected value detected: %s", Le.toString().c_str()); SampledSpectrum fs = bsdf->evaluate(fsQuery, shadowDir_sn); float cosLight = absDot(-shadowDir, lpResult.surfPt.gNormal); float bsdfPDF = bsdf->evaluatePDF(fsQuery, shadowDir_sn) * cosLight / dist2; float MISWeight = 1.0f; if (!lpResult.posType.isDelta() && !std::isinf(lpResult.areaPDF)) MISWeight = (lightPDF * lightPDF) / (lightPDF * lightPDF + bsdfPDF * bsdfPDF); SLRAssert(MISWeight <= 1.0f, "Invalid MIS weight: %g", MISWeight); float G = absDot(shadowDir_sn, gNorm_sn) * cosLight / dist2; sp += alpha * Le * fs * (G * MISWeight / lightPDF); SLRAssert(!std::isnan(G) && !std::isinf(G), "G: unexpected value detected: %f", G); } } // get a next direction by sampling BSDF. BSDFQueryResult fsResult; SampledSpectrum fs = bsdf->sample(fsQuery, pathSampler.getBSDFSample(), &fsResult); if (fs == SampledSpectrum::Zero || fsResult.dirPDF == 0.0f) break; if (fsResult.dirType.isDispersive()) { fsResult.dirPDF /= WavelengthSamples::NumComponents; wls.flags |= WavelengthSamples::LambdaIsSelected; } alpha *= fs * absDot(fsResult.dir_sn, gNorm_sn) / fsResult.dirPDF; SLRAssert(!alpha.hasInf() && !alpha.hasNaN(), "alpha: %s\nlength: %u, cos: %g, dirPDF: %g", alpha.toString().c_str(), pathLength, absDot(fsResult.dir_sn, gNorm_sn), fsResult.dirPDF); Vector3D dirIn = surfPt.shadingFrame.fromLocal(fsResult.dir_sn); ray = Ray(surfPt.p, dirIn, ray.time, Ray::Epsilon); // find a next intersection point. isect = Intersection(); if (!scene.intersect(ray, &isect)) break; isect.getSurfacePoint(&surfPt); dirOut_sn = surfPt.shadingFrame.toLocal(-ray.dir); // implicit light sampling if (surfPt.isEmitting()) { float bsdfPDF = fsResult.dirPDF; EDF* edf = surfPt.createEDF(wls, mem); SampledSpectrum Le = surfPt.emittance(wls) * edf->evaluate(EDFQuery(), dirOut_sn); float lightProb = scene.evaluateProb(Light(isect.obj)); float dist2 = surfPt.getSquaredDistance(ray.org); float lightPDF = lightProb * surfPt.evaluateAreaPDF() * dist2 / absDot(ray.dir, surfPt.gNormal); SLRAssert(!Le.hasNaN() && !Le.hasInf(), "Le: unexpected value detected: %s", Le.toString().c_str()); SLRAssert(!std::isnan(lightPDF)/* && !std::isinf(lightPDF)*/, "lightPDF: unexpected value detected: %f", lightPDF); float MISWeight = 1.0f; if (!fsResult.dirType.isDelta()) MISWeight = (bsdfPDF * bsdfPDF) / (lightPDF * lightPDF + bsdfPDF * bsdfPDF); SLRAssert(MISWeight <= 1.0f, "Invalid MIS weight: %g", MISWeight); sp += alpha * Le * MISWeight; } if (surfPt.atInfinity) break; // Russian roulette float continueProb = std::min(alpha.importance(wls.selectedLambda) / initY, 1.0f); if (pathSampler.getPathTerminationSample() < continueProb) alpha /= continueProb; else break; } return sp; }
Color3 BidirPathTracing::getDirectIllumination(BidirPathState& cameraState , const Vector3& hitPos , BSDF& bsdf) { Color3 res(0); Real weight = 0.f; int lightCount = scene.lights.size(); Real lightPickProb = 1.f / lightCount; int lightId = (int)(rng.randFloat() * lightCount); AbstractLight *light = scene.lights[lightId]; Vector3 dirToLight; Real dist , directPdf , emissionPdf , cosAtLight , cosAtSurface; int sampledBSDFType; Color3 illu = light->illuminance(scene.sceneSphere , hitPos , rng.randVector3() , dirToLight , dist , directPdf , &emissionPdf , &cosAtLight); Real bsdfDirPdf , bsdfRevPdf , cosToLight; if (!illu.isBlack() && directPdf > 0) { Color3 bsdfFactor = bsdf.f(scene , dirToLight , cosToLight , &bsdfDirPdf , &bsdfRevPdf); Color3 tmp; if (!bsdfFactor.isBlack()) { Real contProb = bsdf.continueProb; bsdfDirPdf *= light->isDelta() ? 0.f : contProb; bsdfRevPdf *= contProb; tmp = (illu | bsdfFactor) * cosToLight / (directPdf * lightPickProb); if (!tmp.isBlack() && !scene.occluded(hitPos , dirToLight , hitPos + dirToLight * dist)) { Real wLight = mis(bsdfDirPdf) / mis(directPdf * lightPickProb); Real wCamera = mis(emissionPdf * cosToLight / (directPdf * cosAtLight)) * (cameraState.dVCM + mis(bsdfRevPdf) * cameraState.dVC); weight = 1.f / (wLight + 1.f + wCamera); //fprintf(fp , "weight = %.6f\n" , weight); if (light->isDelta()) { res = res + tmp; } else { Real _weight = mis(directPdf) / (mis(directPdf) + mis(bsdfDirPdf)); res = res + tmp * _weight; } } } } if (!light->isDelta()) { Color3 bsdfFactor = bsdf.sample(scene , rng.randVector3() , dirToLight , directPdf , cosAtSurface , &sampledBSDFType); if (!bsdfFactor.isBlack() && directPdf > 0) { Real weight = 1.f; Real lightPdf; if (!(sampledBSDFType & BSDF_SPECULAR)) { illu = light->getRadiance(scene.sceneSphere , dirToLight , hitPos , &lightPdf , &emissionPdf); if (cmp(lightPdf) == 0) return res; weight = mis(directPdf) / (mis(directPdf) + mis(lightPdf)); } Intersection lightInter; Color3 tmp(0.f); Ray ray(hitPos + dirToLight * EPS , dirToLight); if (scene.intersect(ray , lightInter) != NULL) { if (lightInter.matId < 0) { if (light != scene.lights[-lightInter.matId - 1]) { illu = Color3(0.f); } } else { illu = Color3(0.f); } } else { illu = Color3(0.f); if (scene.background != NULL) { illu = getLightRadiance(scene.background , cameraState , Vector3(0) , ray.dir); } } if (!illu.isBlack()) { tmp = (illu | bsdfFactor) * cosAtSurface / (directPdf); res = res + tmp * weight; } } } return res * weight; }
void compute_ibl_bsdf_sampling( SamplingContext& sampling_context, const ShadingContext& shading_context, const EnvironmentEDF& environment_edf, const ShadingPoint& shading_point, const Dual3d& outgoing, const BSDF& bsdf, const void* bsdf_data, const int bsdf_sampling_modes, const size_t bsdf_sample_count, const size_t env_sample_count, Spectrum& radiance) { assert(is_normalized(outgoing.get_value())); radiance.set(0.0f); for (size_t i = 0; i < bsdf_sample_count; ++i) { // Sample the BSDF. // todo: rendering will be incorrect if the BSDF value returned by the sample() method // includes the contribution of a specular component since these are explicitly rejected // afterward. We need a mechanism to indicate that we want the contribution of some of // the components only. BSDFSample sample(shading_point, outgoing); bsdf.sample( sampling_context, bsdf_data, false, // not adjoint true, // multiply by |cos(incoming, normal)| sample); // Filter scattering modes. if (!(bsdf_sampling_modes & sample.m_mode)) continue; // Discard occluded samples. const float transmission = shading_context.get_tracer().trace( shading_point, Vector3d(sample.m_incoming.get_value()), VisibilityFlags::ShadowRay); if (transmission == 0.0f) continue; // Evaluate the environment's EDF. InputEvaluator input_evaluator(shading_context.get_texture_cache()); Spectrum env_value; float env_prob; environment_edf.evaluate( shading_context, input_evaluator, sample.m_incoming.get_value(), env_value, env_prob); // Apply all weights, including MIS weight. if (sample.m_mode == ScatteringMode::Specular) env_value *= transmission; else { const float mis_weight = mis_power2( bsdf_sample_count * sample.m_probability, env_sample_count * env_prob); env_value *= transmission / sample.m_probability * mis_weight; } // Add the contribution of this sample to the illumination. env_value *= sample.m_value; radiance += env_value; } if (bsdf_sample_count > 1) radiance /= static_cast<float>(bsdf_sample_count); }