double Path::G(int i, int j, ManifoldPerturbation* offsetMutator) const { // No edges => error. if (i >= j) { SLog(EError, "Path::G() called with i >= j"); return 1.0; } // One edge => traditional G( x(i) <-> x(j) ) if (j == i + 1) { double cosI = absDot(edge(i)->d, vertex(i)->getShadingNormal()); double cosJ = absDot(edge(i)->d, vertex(j)->getShadingNormal()); double len = edge(i)->length; return cosI * cosJ / (len * len); } // Two or more edges => generalized G double res = offsetMutator->getSpecularManifold()->G(*this, i, j); // Check errors. if (res <= 0.0 || !std::isfinite(res)){ SLog(EWarn, "Invalid Path::G(%d,%d)=%g (k=%d)", i, j, res, length()); } return res; }
Spectrum PathEdge::evalCached(const PathVertex *pred, const PathVertex *succ, unsigned int what) const { /* Extract the requested information based on what is currently cached in the vertex. The actual computation that has to happen here is pretty awful, but it works. It might be worth to change the caching scheme to make this function simpler in a future revision */ Spectrum result(1.0f); if (length == 0) { if (what & EValueImp) result *= pred->weight[EImportance] * pred->pdf[EImportance]; if (what & EValueRad) result *= succ->weight[ERadiance] * succ->pdf[ERadiance]; } else { if (what & EValueImp) { Float tmp = pred->pdf[EImportance]; if (pred->isConnectable()) { tmp *= length * length; if (succ->isOnSurface()) tmp /= dot(succ->getGeometricNormal(), d); if (pred->isOnSurface() && !(what & ECosineImp)) tmp /= dot(pred->getShadingNormal(), d); } result *= pred->weight[EImportance] * std::abs(tmp); } else if ((what & ECosineImp) && pred->isOnSurface() && pred->isConnectable()) { result *= absDot(pred->getShadingNormal(), d); } if (what & EValueRad) { Float tmp = succ->pdf[ERadiance]; if (succ->isConnectable()) { tmp *= length * length; if (pred->isOnSurface()) tmp /= dot(pred->getGeometricNormal(), d); if (succ->isOnSurface() && !(what & ECosineRad)) tmp /= dot(succ->getShadingNormal(), d); } result *= succ->weight[ERadiance] * std::abs(tmp); } else if ((what & ECosineRad) && succ->isOnSurface() && succ->isConnectable()) { result *= absDot(succ->getShadingNormal(), d); } if (what & EInverseSquareFalloff) result /= length * length; if (what & ETransmittance) result *= weight[EImportance] * pdf[EImportance]; } return result; }
Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const { if (measure != ESolidAngle || Frame::cosTheta(bRec.wi) < 0 || Frame::cosTheta(bRec.wo) < 0 || ((bRec.component != -1 && bRec.component != 0) || !(bRec.typeMask & EGlossyReflection))) return 0.0f; /* Calculate the reflection half-vector */ Vector H = normalize(bRec.wo+bRec.wi); /* Construct the microfacet distribution matching the roughness values at the current surface position. */ MicrofacetDistribution distr( m_type, m_alphaU->eval(bRec.its).average(), m_alphaV->eval(bRec.its).average(), m_sampleVisible ); if (m_sampleVisible) return distr.eval(H) * distr.smithG1(bRec.wi, H) / (4.0f * Frame::cosTheta(bRec.wi)); else return distr.pdf(bRec.wi, H) / (4 * absDot(bRec.wo, H)); }
Spectrum eval(const BSDFSamplingRecord &bRec, EMeasure measure) const { bool hasNested = (bRec.typeMask & m_nested->getType() & BSDF::EAll) && (bRec.component == -1 || bRec.component < (int) m_components.size()-1); bool hasSpecular = (bRec.typeMask & EGlossyReflection) && (bRec.component == -1 || bRec.component == (int) m_components.size()-1) && measure == ESolidAngle; /* Evaluate the roughness texture */ Float alpha = m_alpha->eval(bRec.its).average(); Float alphaT = m_distribution.transformRoughness(alpha); Spectrum result(0.0f); if (hasSpecular && Frame::cosTheta(bRec.wo) * Frame::cosTheta(bRec.wi) > 0) { /* Calculate the reflection half-vector */ const Vector H = normalize(bRec.wo+bRec.wi) * signum(Frame::cosTheta(bRec.wo)); /* Evaluate the microsurface normal distribution */ const Float D = m_distribution.eval(H, alphaT); /* Fresnel term */ const Float F = fresnelDielectricExt(absDot(bRec.wi, H), m_eta); /* Smith's shadow-masking function */ const Float G = m_distribution.G(bRec.wi, bRec.wo, H, alphaT); /* Calculate the specular reflection component */ Float value = F * D * G / (4.0f * std::abs(Frame::cosTheta(bRec.wi))); result += m_specularReflectance->eval(bRec.its) * value; } if (hasNested) { BSDFSamplingRecord bRecInt(bRec); bRecInt.wi = refractTo(EInterior, bRec.wi); bRecInt.wo = refractTo(EInterior, bRec.wo); Spectrum nestedResult = m_nested->eval(bRecInt, measure) * m_roughTransmittance->eval(std::abs(Frame::cosTheta(bRec.wi)), alpha) * m_roughTransmittance->eval(std::abs(Frame::cosTheta(bRec.wo)), alpha); Spectrum sigmaA = m_sigmaA->eval(bRec.its) * m_thickness; if (!sigmaA.isZero()) nestedResult *= (-sigmaA * (1/std::abs(Frame::cosTheta(bRecInt.wi)) + 1/std::abs(Frame::cosTheta(bRecInt.wo)))).exp(); if (measure == ESolidAngle) { /* Solid angle compression & irradiance conversion factors */ nestedResult *= m_invEta * m_invEta * Frame::cosTheta(bRec.wi) * Frame::cosTheta(bRec.wo) / (Frame::cosTheta(bRecInt.wi) * Frame::cosTheta(bRecInt.wo)); } result += nestedResult; } return result; }
Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const { bool hasNested = (bRec.typeMask & m_nested->getType() & BSDF::EAll) && (bRec.component == -1 || bRec.component < (int) m_components.size()-1); bool hasSpecular = (bRec.typeMask & EGlossyReflection) && (bRec.component == -1 || bRec.component == (int) m_components.size()-1) && measure == ESolidAngle; /* Calculate the reflection half-vector */ const Vector H = normalize(bRec.wo+bRec.wi) * signum(Frame::cosTheta(bRec.wo)); /* Evaluate the roughness texture */ Float alpha = m_alpha->eval(bRec.its).average(); Float alphaT = m_distribution.transformRoughness(alpha); Float probNested, probSpecular; if (hasSpecular && hasNested) { /* Find the probability of sampling the specular component */ probSpecular = 1-m_roughTransmittance->eval( std::abs(Frame::cosTheta(bRec.wi)), alpha); /* Reallocate samples */ probSpecular = (probSpecular*m_specularSamplingWeight) / (probSpecular*m_specularSamplingWeight + (1-probSpecular) * (1-m_specularSamplingWeight)); probNested = 1 - probSpecular; } else { probNested = probSpecular = 1.0f; } Float result = 0.0f; if (hasSpecular && Frame::cosTheta(bRec.wo) * Frame::cosTheta(bRec.wi) > 0) { /* Jacobian of the half-direction mapping */ const Float dwh_dwo = 1.0f / (4.0f * absDot(bRec.wo, H)); /* Evaluate the microsurface normal distribution */ const Float prob = m_distribution.pdf(H, alphaT); result = prob * dwh_dwo * probSpecular; } if (hasNested) { BSDFSamplingRecord bRecInt(bRec); bRecInt.wi = refractTo(EInterior, bRec.wi); bRecInt.wo = refractTo(EInterior, bRec.wo); Float prob = m_nested->pdf(bRecInt, measure); if (measure == ESolidAngle) { prob *= m_invEta * m_invEta * Frame::cosTheta(bRec.wo) / Frame::cosTheta(bRecInt.wo); } result += prob * probNested; } return result; }
Float Shape::pdfDirect(const DirectSamplingRecord &dRec) const { Float pdfPos = pdfPosition(dRec); if (dRec.measure == ESolidAngle) return pdfPos * (dRec.dist * dRec.dist) / absDot(dRec.d, dRec.n); else if (dRec.measure == EArea) return pdfPos; else return 0.0f; }
Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const { if (measure != ESolidAngle || Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0 || ((bRec.component != -1 && bRec.component != 0) || !(bRec.typeMask & EGlossyReflection))) return 0.0f; /* Calculate the reflection half-vector */ Vector H = normalize(bRec.wo+bRec.wi); return PDF(H) / (4 * absDot(bRec.wo, H)); }
void Shape::sampleDirect(DirectSamplingRecord &dRec, const Point2 &sample) const { /* Piggyback on sampleArea() */ samplePosition(dRec, sample); dRec.d = dRec.p - dRec.ref; Float distSquared = dRec.d.lengthSquared(); dRec.dist = std::sqrt(distSquared); dRec.d /= dRec.dist; Float dp = absDot(dRec.d, dRec.n); dRec.pdf *= dp != 0 ? (distSquared / dp) : 0.0f; dRec.measure = ESolidAngle; }
Float pdfDirect(const DirectSamplingRecord &dRec) const { Float pdfSA; if (!dRec.refN.isZero()) pdfSA = INV_PI * std::max((Float) 0.0f, dot(dRec.d, dRec.refN)); else pdfSA = Warp::squareToUniformSpherePdf(); if (dRec.measure == ESolidAngle) return pdfSA; else if (dRec.measure == EArea) return pdfSA * absDot(dRec.d, dRec.n) / (dRec.dist * dRec.dist); else return 0.0f; }
Spectrum Lo(const Scene *scene, Sampler *sampler, const Intersection &its, const Vector &d, int depth) const { if (!m_ready || m_ssFactor.isZero()) return Spectrum(0.0f); IsotropicDipoleQuery query(m_zr, m_zv, m_sigmaTr, m_Fdt, its.p); const Normal &n = its.shFrame.n; m_octree->execute(query); if (m_eta == 1.0f) { return query.getResult() * m_ssFactor * INV_PI; } else { Float Ft = 1.0f - fresnel(absDot(n, d)); return query.getResult() * m_ssFactor * INV_PI * (Ft / m_Fdr); } }
void PathTracingRenderer::Job::kernel(uint32_t threadID) { ArenaAllocator &mem = mems[threadID]; IndependentLightPathSampler &pathSampler = *pathSamplers[threadID]; for (int ly = 0; ly < numPixelY; ++ly) { for (int lx = 0; lx < numPixelX; ++lx) { float time = pathSampler.getTimeSample(timeStart, timeEnd); PixelPosition p = pathSampler.getPixelPositionSample(basePixelX + lx, basePixelY + ly); float selectWLPDF; WavelengthSamples wls = WavelengthSamples::createWithEqualOffsets(pathSampler.getWavelengthSample(), pathSampler.getWLSelectionSample(), &selectWLPDF); LensPosQuery lensQuery(time, wls); LensPosQueryResult lensResult; SampledSpectrum We0 = camera->sample(lensQuery, pathSampler.getLensPosSample(), &lensResult); IDFSample WeSample(p.x / imageWidth, p.y / imageHeight); IDFQueryResult WeResult; IDF* idf = camera->createIDF(lensResult.surfPt, wls, mem); SampledSpectrum We1 = idf->sample(WeSample, &WeResult); Ray ray(lensResult.surfPt.p, lensResult.surfPt.shadingFrame.fromLocal(WeResult.dirLocal), time); SampledSpectrum C = contribution(*scene, wls, ray, pathSampler, mem); SLRAssert(C.hasNaN() == false && C.hasInf() == false && C.hasMinus() == false, "Unexpected value detected: %s\n" "pix: (%f, %f)", C.toString().c_str(), px, py); SampledSpectrum weight = (We0 * We1) * (absDot(ray.dir, lensResult.surfPt.gNormal) / (lensResult.areaPDF * WeResult.dirPDF * selectWLPDF)); SLRAssert(weight.hasNaN() == false && weight.hasInf() == false && weight.hasMinus() == false, "Unexpected value detected: %s\n" "pix: (%f, %f)", weight.toString().c_str(), px, py); sensor->add(p.x, p.y, wls, weight * C); mem.reset(); } } }
Float Path::getGeometryTerm(int i) const{ Float cosI = absDot(edge(i)->d, vertex(i)->getShadingNormal()); float cosJ = absDot(edge(i)->d, vertex(i + 1)->getShadingNormal()); Float len = edge(i)->length; return cosI * cosJ / (len * len); }
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; }
Spectrum Path::computeLi( const vec2f &pFilm, const SceneData &i_scene, const Camera &i_camera, const Sampler &i_sampler, int i_depth ) { Camera::CameraSample cameraSample; cameraSample.pixelSample = pFilm + i_sampler.sample2D(); cameraSample.lensSample = i_sampler.sample2D(); Ray ray; float weight = i_camera.generateRay( cameraSample, &ray ); // Radiance Spectrum L = Spectrum( 0.0f ); // Importance Spectrum beta = Spectrum( 1.0 ); for ( int bounces = 0;; bounces++ ) { RayIntersection intersection; bool foundIntersection = i_scene.intersect( ray, intersection ); float maxDepth = 5; if ( !foundIntersection || bounces >= maxDepth ) { break; } Spectrum Le = intersection.Le(); if ( Le.isBlack() ) { // Direct illumination sampling Spectrum Ld = beta * uniformSampleOneLight( intersection, i_sampler, i_scene ); L += Ld; // BSDF Sampling const shading::BSDF &bsdf = intersection.getBSDF(); const vec3f &surfacePosition = intersection.getSurfacePoint().pos; vec3f woLocal = -intersection.worldToSurface( -intersection.dir ); vec3f wiLocal; float pdf = 1.0; const vec2f &bsdfSample = i_sampler.sample2D(); Spectrum f = bsdf.sampleF( woLocal, &wiLocal, bsdfSample, &pdf ); // No point of continuing if no color if ( f.isBlack() || pdf == 0.0 ) { break; } // Transform to world space vec3f wiWorld = intersection.surfaceToWorld( wiLocal ); // Generate new ray ray = Ray( surfacePosition, wiWorld ); beta *= ( f * absDot( wiWorld, intersection.getSurfacePoint().normal ) ) / pdf; // Russian Roulette if ( beta.y() < 1.0f && bounces > 3 ) { float q = std::max( .05f, 1 - beta.maxComponentValue() ); if ( i_sampler.sample1D() > q ) { beta /= ( 1.0 - q ); } else { break; } } } else { L += beta * intersection.Le() / 50.0; break; } } return weight * L; }
Spectrum Path::estimateDirectLighting( const RayIntersection i_intersection, RenderablePtr i_light, const Sampler &i_sampler, const SceneData &i_scene ) { // Direct contribution Spectrum Ld = Spectrum::black(); float lightPdf = 0.0f; float surfacePdf = 0.0f; vec3f wiWorld; const vec3f &woWorld = -i_intersection.dir; const vec3f &woLocal = i_intersection.worldToSurface( woWorld ); const vec3f &normal = i_intersection.getSurfacePoint().normal; const shading::BSDF &bsdf = i_intersection.getBSDF(); /* ==================== */ /* Sample Light */ /* ==================== */ { VisibilityTester visibilityTester; const vec3f &uLight = i_sampler.sample3D(); Spectrum Li = i_light->sampleIncidenceRadiance( i_intersection, uLight, &wiWorld, &lightPdf, &visibilityTester ); // Convert to local (surface) coords const vec3f &wiLocal = i_intersection.worldToSurface( wiWorld ); // Sample light if ( !Li.isBlack() && lightPdf > 0.0f ) { const Spectrum &f = bsdf.computeF( woLocal, wiLocal ) * absDot( wiWorld, normal ); surfacePdf = bsdf.pdf( woLocal, wiLocal ); if ( !f.isBlack() ) { // Ray is in shadow if ( visibilityTester.isOccluded( i_scene ) ) { Li = Spectrum::black(); } if ( !Li.isBlack() ) { // Weight with MIS float misWeight = powerHeuristic( lightPdf, surfacePdf ); Ld += ( Li * f * misWeight ) / lightPdf; } } } } /* ==================== */ /* Sample BSDF */ /* ==================== */ { const vec2f &uSurface = i_sampler.sample2D(); vec3f wiLocal; const Spectrum &f = bsdf.sampleF( woLocal, &wiLocal, uSurface, &surfacePdf ) * absDot( wiWorld, normal ); if ( !f.isBlack() && surfacePdf > 0.0f ) { // TODO computer light PDF lightPdf = 0.0; // No contribution, return if ( lightPdf == 0.0 ) { return Ld; } // Convert to local (surface) coords vec3f wiWorld = i_intersection.surfaceToWorld( wiLocal ); const Ray bsdfRay = Ray( i_intersection.getSurfacePoint().pos, wiWorld ); RayIntersection lightIntersection; bool foundIntersection = i_scene.intersect( bsdfRay, lightIntersection ); Spectrum Li = Spectrum::black(); if ( foundIntersection ) { if ( lightIntersection.m_shapeId == i_light->getIdentifier() ) { Li = i_light->computeRadiance( lightIntersection.getSurfacePoint(), wiWorld ); } } if ( !Li.isBlack() ) { // Weight with MIS float misWeight = powerHeuristic( lightPdf, surfacePdf ); Ld += ( Li * f * misWeight ) / surfacePdf; } } } return Ld; }