Spectrum DiffuseAreaLight::Sample_Le(const Point2f &u1, const Point2f &u2, Float time, Ray *ray, Normal3f *nLight, Float *pdfPos, Float *pdfDir) const { ProfilePhase _(Prof::LightSample); // Sample a point on the area light's _Shape_, _pShape_ Interaction pShape = shape->Sample(u1, pdfPos); pShape.mediumInterface = mediumInterface; *nLight = pShape.n; // Sample a cosine-weighted outgoing direction _w_ for area light Vector3f w; if (twoSided) { Point2f u = u2; // Choose a side to sample and then remap u[0] to [0,1] before // applying cosine-weighted hemisphere sampling for the chosen side. if (u[0] < .5) { u[0] = std::min(u[0] * 2, OneMinusEpsilon); w = CosineSampleHemisphere(u); } else { u[0] = std::min((u[0] - .5f) * 2, OneMinusEpsilon); w = CosineSampleHemisphere(u); w.z *= -1; } *pdfDir = 0.5f * CosineHemispherePdf(std::abs(w.z)); } else { w = CosineSampleHemisphere(u2); *pdfDir = CosineHemispherePdf(w.z); } Vector3f v1, v2, n(pShape.n); CoordinateSystem(n, &v1, &v2); w = w.x * v1 + w.y * v2 + w.z * n; *ray = pShape.SpawnRay(w); return L(pShape, w); }
//================================================================================= TVector3 L_out (const SRayHitInfo& X, const TVector3& outDir, size_t bouncesLeft) { // if no bounces left, return the ray miss color if (bouncesLeft == 0) return c_rayMissColor; // start with emissive lighting const SMaterial* material = X.m_material; TVector3 ret = material->m_emissive; // add in random recursive samples for global illumination { #if COSINE_WEIGHTED_HEMISPHERE_SAMPLES() TVector3 newRayDir = CosineSampleHemisphere(X.m_surfaceNormal); SRayHitInfo info; if (ClosestIntersection(X.m_intersectionPoint + newRayDir * c_rayBounceEpsilon, newRayDir, info)) ret += L_out(info, -newRayDir, bouncesLeft - 1) * material->m_diffuse; else ret += c_rayMissColor * material->m_diffuse; #else TVector3 newRayDir = UniformSampleHemisphere(X.m_surfaceNormal); SRayHitInfo info; if (ClosestIntersection(X.m_intersectionPoint + newRayDir * c_rayBounceEpsilon, newRayDir, info)) ret += Dot(newRayDir, X.m_surfaceNormal) * 2.0f * L_out(info, -newRayDir, bouncesLeft - 1) * material->m_diffuse; else ret += Dot(newRayDir, X.m_surfaceNormal) * 2.0f * c_rayMissColor * material->m_diffuse; #endif } return ret; }
// commented, dpl 10 august 2005 Spectrum Lafortune::Sample_f(const Vector &wo, Vector *wi, float u1, float u2, float *pdf) const { u_int comp = RandomUInt() % (nLobes+1); if (comp == nLobes) { // Cosine-sample the hemisphere, flipping the direction if necessary *wi = CosineSampleHemisphere(u1, u2); if (wo.z < 0.) wi->z *= -1.f; } else { // Sample lobe _comp_ for Lafortune BRDF float xlum = x[comp].y(); float ylum = y[comp].y(); float zlum = z[comp].y(); float costheta = powf(u1, 1.f / (.8f * exponent[comp].y() + 1)); float sintheta = sqrtf(max(0.f, 1.f - costheta*costheta)); float phi = u2 * 2.f * M_PI; Vector lobeCenter = Normalize(Vector(xlum * wo.x, ylum * wo.y, zlum * wo.z)); Vector lobeX, lobeY; CoordinateSystem(lobeCenter, &lobeX, &lobeY); *wi = SphericalDirection(sintheta, costheta, phi, lobeX, lobeY, lobeCenter); } if (!SameHemisphere(wo, *wi)) return Spectrum(0.f); *pdf = Pdf(wo, *wi); return f(wo, *wi); }
Spectrum BxDF::Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &u, Float *pdf, BxDFType *sampledType) const { // Cosine-sample the hemisphere, flipping the direction if necessary *wi = CosineSampleHemisphere(u); if (wo.z < 0) wi->z *= -1; *pdf = Pdf(wo, *wi); return f(wo, *wi); }
Spectrum BxDF::Sample_f(const Vector &wo, Vector *wi, float u1, float u2, float *pdf) const { // Cosine-sample the hemisphere, flipping the direction if necessary *wi = CosineSampleHemisphere(u1, u2); if (wo.z < 0.) wi->z *= -1.f; *pdf = Pdf(wo, *wi); return f(wo, *wi); }
Spectrum LambertianTransmission::Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &u, Float *pdf, BxDFType *sampledType) const { *wi = CosineSampleHemisphere(u); if (wo.z > 0) wi->z *= -1; *pdf = Pdf(wo, *wi); return f(wo, *wi); }
Spectrum IrradianceCacheIntegrator::indirectLo(const Point &p, const Normal &ng, float pixelSpacing, const Vector &wo, float rayEpsilon, BSDF *bsdf, BxDFType flags, RNG &rng, const Scene *scene, const Renderer *renderer, MemoryArena &arena) const { if (bsdf->NumComponents(flags) == 0) return Spectrum(0.); Spectrum E; Vector wi; // Get irradiance _E_ and average incident direction _wi_ at point _p_ if (!interpolateE(scene, p, ng, &E, &wi)) { // Compute irradiance at current point PBRT_IRRADIANCE_CACHE_STARTED_COMPUTING_IRRADIANCE(const_cast<Point *>(&p), const_cast<Normal *>(&ng)); uint32_t scramble[2] = { rng.RandomUInt(), rng.RandomUInt() }; float minHitDistance = INFINITY; Vector wAvg(0,0,0); Spectrum LiSum = 0.f; for (int i = 0; i < nSamples; ++i) { // Sample direction for irradiance estimate ray float u[2]; Sample02(i, scramble, u); Vector w = CosineSampleHemisphere(u[0], u[1]); RayDifferential r(p, bsdf->LocalToWorld(w), rayEpsilon); r.d = Faceforward(r.d, ng); // Trace ray to sample radiance for irradiance estimate PBRT_IRRADIANCE_CACHE_STARTED_RAY(&r); Spectrum L = pathL(r, scene, renderer, rng, arena); LiSum += L; wAvg += r.d * L.y(); minHitDistance = min(minHitDistance, r.maxt); PBRT_IRRADIANCE_CACHE_FINISHED_RAY(&r, r.maxt, &L); } E = (M_PI / float(nSamples)) * LiSum; PBRT_IRRADIANCE_CACHE_FINISHED_COMPUTING_IRRADIANCE(const_cast<Point *>(&p), const_cast<Normal *>(&ng)); // Add computed irradiance value to cache // Compute irradiance sample's contribution extent and bounding box float maxDist = maxSamplePixelSpacing * pixelSpacing; float minDist = minSamplePixelSpacing * pixelSpacing; float contribExtent = Clamp(minHitDistance / 2.f, minDist, maxDist); BBox sampleExtent(p); sampleExtent.Expand(contribExtent); PBRT_IRRADIANCE_CACHE_ADDED_NEW_SAMPLE(const_cast<Point *>(&p), const_cast<Normal *>(&ng), contribExtent, &E, &wAvg, pixelSpacing); // Allocate _IrradianceSample_, get write lock, add to octree IrradianceSample *sample = new IrradianceSample(E, p, ng, wAvg, contribExtent); RWMutexLock lock(*mutex, WRITE); octree->Add(sample, sampleExtent); wi = wAvg; } // Compute reflected radiance due to irradiance and BSDF if (wi.LengthSquared() == 0.f) return Spectrum(0.); return bsdf->f(wo, Normalize(wi), flags) * E; }
void Gen_CosHemisphere(BSDF* bsdf, const Vector3f& wo, Vector3f* wi, Float* pdf, Spectrum* f) { float u1 = rng.UniformFloat(); float u2 = rng.UniformFloat(); Vector3f wiL = CosineSampleHemisphere(Point2f(u1, u2)); *wi = bsdf->LocalToWorld(wiL); float cosTheta = wiL.z; *pdf = CosineHemispherePdf(cosTheta); *f = bsdf->f(wo, *wi); }
void Gen_CosHemisphere(BSDF* bsdf, const Vector & wo, Vector* wi, float* pdf, Spectrum* f) { float u1 = rng.RandomFloat(); float u2 = rng.RandomFloat(); Vector wiL = CosineSampleHemisphere(u1, u2); *wi = bsdf->LocalToWorld(wiL); float cosTheta = wiL.z; *pdf = CosineHemispherePdf(cosTheta, u2); *f = bsdf->f(wo, *wi); }
Spectrum Heitz::Sample_f(const Vector &wo, Vector *wi, float u1, float u2, float *pdf) const { // TODO: for test if (mUseUniformSampling) { return BxDF::Sample_f(wo, wi, u1, u2, pdf); } else { // TODO: what percentage between diffuse and specular? Currently use 50% if (u1 < 0.5f) { // Stretch u1 back to [0, 1) u1 *= 2; if (wo.z < 0.f) { // Reverse side mDistribution.Sample_f(-wo, wi, u1, u2, pdf); *wi = -(*wi); } else { mDistribution.Sample_f(wo, wi, u1, u2, pdf); } // Note: wo and wi are not guaranteed to be on the +Z side, nor are // they to be on the same side if (*pdf == 0.f) { // The sample is invalid - note it still needs to be considered // in Monte Carlo because invalid sample takes part of the PDF return Spectrum(0.f); } } else { // Stretch u1 back to [0, 1) u1 = 2 * (u1 - 0.5f); // Sampling wi at the same side as wo *wi = CosineSampleHemisphere(u1, u2); if (wo.z < 0.f) { *wi = -(*wi); } } // Compute PDF by calling Pdf(). This implementation is simpler and // less prone to error, but it's also less efficient *pdf = Pdf(wo, *wi); return f(wo, *wi); } }
// commented, dpl 10 august 2005 Spectrum FresnelBlend::Sample_f(const Vector &wo, Vector *wi, float u1, float u2, float *pdf) const { if (u1 < .5) { u1 = 2.f * u1; // Cosine-sample the hemisphere, flipping the direction if necessary *wi = CosineSampleHemisphere(u1, u2); if (wo.z < 0.) wi->z *= -1.f; } else { u1 = 2.f * (u1 - .5f); distribution->Sample_f(wo, wi, u1, u2, pdf); if (!SameHemisphere(wo, *wi)) return Spectrum(0.f); } *pdf = Pdf(wo, *wi); return f(wo, *wi); }
Spectrum DiffuseAreaLight::Sample_Le(const Point2f &u1, const Point2f &u2, Float time, Ray *ray, Normal3f *nLight, Float *pdfPos, Float *pdfDir) const { Interaction pShape = shape->Sample(u1); pShape.mediumInterface = mediumInterface; Vector3f w = CosineSampleHemisphere(u2); *pdfDir = CosineHemispherePdf(w.z); // Transform cosine-weighted direction to normal's coordinate system Vector3f v1, v2, n(pShape.n); CoordinateSystem(n, &v1, &v2); w = w.x * v1 + w.y * v2 + w.z * n; *ray = pShape.SpawnRay(w); *nLight = pShape.n; *pdfPos = shape->Pdf(pShape); return L(pShape, w); }
Spectrum DiffuseAreaLight::Sample_Le(const Point2f &u1, const Point2f &u2, Float time, Ray *ray, Normal3f *nLight, Float *pdfPos, Float *pdfDir) const { // Sample a point on the area light's _Shape_, _pShape_ Interaction pShape = shape->Sample(u1); pShape.mediumInterface = mediumInterface; *pdfPos = shape->Pdf(pShape); *nLight = pShape.n; // Sample a cosine-weighted outgoing direction _w_ for area light Vector3f w = CosineSampleHemisphere(u2); *pdfDir = CosineHemispherePdf(w.z); Vector3f v1, v2, n(pShape.n); CoordinateSystem(n, &v1, &v2); w = w.x * v1 + w.y * v2 + w.z * n; *ray = pShape.SpawnRay(w); return L(pShape, w); }
Spectrum FresnelBlend::Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &uOrig, Float *pdf, BxDFType *sampledType) const { Point2f u = uOrig; if (u[0] < .5) { u[0] = 2 * u[0]; // Cosine-sample the hemisphere, flipping the direction if necessary *wi = CosineSampleHemisphere(u); if (wo.z < 0) wi->z *= -1; } else { u[0] = 2 * (u[0] - .5f); // Sample microfacet orientation $\wh$ and reflected direction $\wi$ Vector3f wh = distribution->Sample_wh(wo, u); *wi = Reflect(wo, wh); if (!SameHemisphere(wo, *wi)) return Spectrum(0.f); } *pdf = Pdf(wo, *wi); return f(wo, *wi); }
//--------------------------------------------------------------------- Vec3 Scene::ComputeFirstBounceIllumination(const Ray& ray, KdTreeStats& kdTreeStats, ShadingStats& shadingStats) const { const int nSamples = 128; const Object* obj = m_kdtree.Intersect(ray, kdTreeStats); if (!obj) { return Vec3(0.f); } Vec3 p = ray(ray.tmax); Vec3 n = (p - obj->center).GetNormalized(); Vec3 t1, t2; BuildCoordSystem(n, t1, t2); Vec3 radiance = Vec3(0.f); for (int i = 0; i < nSamples; i++) { float u1 = genrand_float(); float u2 = genrand_float(); Vec3 localDir = CosineSampleHemisphere(u1, u2); Vec3 wi = localDir.x * t1 + localDir.y * t2 + localDir.z * n; Ray ray2(p, wi); radiance += ComputeDirectIllumination(ray2, kdTreeStats, shadingStats); } radiance *= obj->albedo / nSamples; return radiance; }
// BxDF Method Definitions bool BxDF::SampleF(const SpectrumWavelengths &sw, const Vector &wo, Vector *wi, float u1, float u2, SWCSpectrum *const f, float *pdf, float *pdfBack, bool reverse) const { // Cosine-sample the hemisphere, flipping the direction if necessary *wi = CosineSampleHemisphere(u1, u2); if (wo.z < 0.f) wi->z = -(wi->z); // wi may be in the tangent plane, which will // fail the SameHemisphere test in Pdf() if (!SameHemisphere(wo, *wi)) return false; *pdf = Pdf(sw, wo, *wi); if (pdfBack) *pdfBack = Pdf(sw, *wi, wo); *f = SWCSpectrum(0.f); if (reverse) F(sw, *wi, wo, f); else F(sw, wo, *wi, f); *f /= *pdf; return true; }
Spectrum IrradianceCache::IndirectLo(const Point &p, const Normal &n, const Vector &wo, BSDF *bsdf, BxDFType flags, const Sample *sample, const Scene *scene) const { if (bsdf->NumComponents(flags) == 0) return Spectrum(0.); Spectrum E; if (!InterpolateIrradiance(scene, p, n, &E)) { // Compute irradiance at current point u_int scramble[2] = { RandomUInt(), RandomUInt() }; float sumInvDists = 0.; for (int i = 0; i < nSamples; ++i) { // Trace ray to sample radiance for irradiance estimate // Update irradiance statistics for rays traced static StatsCounter nIrradiancePaths("Irradiance Cache", "Paths followed for irradiance estimates"); ++nIrradiancePaths; float u[2]; Sample02(i, scramble, u); Vector w = CosineSampleHemisphere(u[0], u[1]); RayDifferential r(p, bsdf->LocalToWorld(w)); if (Dot(r.d, n) < 0) r.d = -r.d; Spectrum L(0.); // Do path tracing to compute radiance along ray for estimate { // Declare common path integration variables Spectrum pathThroughput = 1.; RayDifferential ray(r); bool specularBounce = false; for (int pathLength = 0; ; ++pathLength) { // Find next vertex of path Intersection isect; if (!scene->Intersect(ray, &isect)) break; if (pathLength == 0) r.maxt = ray.maxt; pathThroughput *= scene->Transmittance(ray); // Possibly add emitted light at path vertex if (specularBounce) L += pathThroughput * isect.Le(-ray.d); // Evaluate BSDF at hit point BSDF *bsdf = isect.GetBSDF(ray); // Sample illumination from lights to find path contribution const Point &p = bsdf->dgShading.p; const Normal &n = bsdf->dgShading.nn; Vector wo = -ray.d; L += pathThroughput * UniformSampleOneLight(scene, p, n, wo, bsdf, sample); if (pathLength+1 == maxIndirectDepth) break; // Sample BSDF to get new path direction // Get random numbers for sampling new direction, \mono{bs1}, \mono{bs2}, and \mono{bcs} float bs1 = RandomFloat(), bs2 = RandomFloat(), bcs = RandomFloat(); Vector wi; float pdf; BxDFType flags; Spectrum f = bsdf->Sample_f(wo, &wi, bs1, bs2, bcs, &pdf, BSDF_ALL, &flags); if (f.Black() || pdf == 0.) break; specularBounce = (flags & BSDF_SPECULAR) != 0; pathThroughput *= f * AbsDot(wi, n) / pdf; ray = RayDifferential(p, wi); // Possibly terminate the path if (pathLength > 3) { float continueProbability = .5f; if (RandomFloat() > continueProbability) break; pathThroughput /= continueProbability; } } } E += L; float dist = r.maxt * r.d.Length(); sumInvDists += 1.f / dist; } E *= M_PI / float(nSamples); // Add computed irradiance value to cache // Update statistics for new irradiance sample static StatsCounter nSamplesComputed("Irradiance Cache", "Irradiance estimates computed"); ++nSamplesComputed; // Compute bounding box of irradiance sample's contribution region static float minMaxDist = .001f * powf(scene->WorldBound().Volume(), 1.f/3.f); static float maxMaxDist = .125f * powf(scene->WorldBound().Volume(), 1.f/3.f); float maxDist = nSamples / sumInvDists; if (minMaxDist > 0.f) maxDist = Clamp(maxDist, minMaxDist, maxMaxDist); maxDist *= maxError; BBox sampleExtent(p); sampleExtent.Expand(maxDist); octree->Add(IrradianceSample(E, p, n, maxDist), sampleExtent); } return .5f * bsdf->rho(wo, flags) * E; }
void TestBSDF(void (*createBSDF)(BSDF*, MemoryArena&), const char* description) { MemoryArena arena; Options opt; pbrtInit(opt); const int thetaRes = CHI2_THETA_RES; const int phiRes = CHI2_PHI_RES; const int sampleCount = CHI2_SAMPLECOUNT; Float* frequencies = new Float[thetaRes * phiRes]; Float* expFrequencies = new Float[thetaRes * phiRes]; RNG rng; int index = 0; std::cout.precision(3); // Create BSDF, which requires creating a Shape, casting a Ray that // hits the shape to get a SurfaceInteraction object. BSDF* bsdf = nullptr; Transform t = RotateX(-90); Transform tInv = Inverse(t); { bool reverseOrientation = false; ParamSet p; std::shared_ptr<Shape> disk( new Disk(&t, &tInv, reverseOrientation, 0., 1., 0, 360.)); Point3f origin(0.1, 1, 0); // offset slightly so we don't hit center of disk Vector3f direction(0, -1, 0); Float tHit; Ray r(origin, direction); SurfaceInteraction isect; disk->Intersect(r, &tHit, &isect); bsdf = ARENA_ALLOC(arena, BSDF)(isect); createBSDF(bsdf, arena); } for (int k = 0; k < CHI2_RUNS; ++k) { /* Randomly pick an outgoing direction on the hemisphere */ Point2f sample {rng.UniformFloat(), rng.UniformFloat()}; Vector3f woL = CosineSampleHemisphere(sample); Vector3f wo = bsdf->LocalToWorld(woL); FrequencyTable(bsdf, wo, rng, sampleCount, thetaRes, phiRes, frequencies); IntegrateFrequencyTable(bsdf, wo, sampleCount, thetaRes, phiRes, expFrequencies); std::string filename = StringPrintf("/tmp/chi2test_%s_%03i.m", description, ++index); DumpTables(frequencies, expFrequencies, thetaRes, phiRes, filename.c_str()); auto result = Chi2Test(frequencies, expFrequencies, thetaRes, phiRes, sampleCount, CHI2_MINFREQ, CHI2_SLEVEL, CHI2_RUNS); EXPECT_TRUE(result.first) << result.second << ", iteration " << k; } delete[] frequencies; delete[] expFrequencies; pbrtCleanup(); }