bool GeometricPrimitive::Intersect(const Ray &r, SurfaceInteraction *si) const { Float tHit; if (!shape->Intersect(r, &tHit, si)) return false; r.tMax = tHit; si->primitive = this; Assert(Dot(si->n, si->shading.n) >= 0.); // Initialize _SurfaceInteraction::mediumInterface_ after _Shape_ // intersection if (mediumInterface.IsMediumTransition()) si->mediumInterface = mediumInterface; else si->mediumInterface = MediumInterface(r.medium); return true; }
// InfiniteAreaLight Method Definitions InfiniteAreaLight::InfiniteAreaLight(const Transform &LightToWorld, const Spectrum &L, int nSamples, const std::string &texmap) : Light((int)LightFlags::Infinite, LightToWorld, MediumInterface(), nSamples) { // Read texel data from _texmap_ and initialize _Lmap_ Point2i resolution; std::unique_ptr<RGBSpectrum[]> texels(nullptr); if (texmap != "") { texels = ReadImage(texmap, &resolution); if (texels) for (int i = 0; i < resolution.x * resolution.y; ++i) texels[i] *= L.ToRGBSpectrum(); } if (!texels) { resolution.x = resolution.y = 1; texels = std::unique_ptr<RGBSpectrum[]>(new RGBSpectrum[1]); texels[0] = L.ToRGBSpectrum(); } Lmap.reset(new MIPMap<RGBSpectrum>(resolution, texels.get())); // Initialize sampling PDFs for infinite area light // Compute scalar-valued image _img_ from environment map int width = 2 * Lmap->Width(), height = 2 * Lmap->Height(); std::unique_ptr<Float[]> img(new Float[width * height]); float fwidth = 0.5f / std::min(width, height); ParallelFor( [&](int64_t v) { Float vp = (v + .5f) / (Float)height; Float sinTheta = std::sin(Pi * (v + .5f) / height); for (int u = 0; u < width; ++u) { Float up = (u + .5f) / (Float)width; img[u + v * width] = Lmap->Lookup(Point2f(up, vp), fwidth).y(); img[u + v * width] *= sinTheta; } }, height, 32); // Compute sampling distributions for rows and columns of image distribution.reset(new Distribution2D(img.get(), width, height)); }
const Distribution1D *SpatialLightDistribution::Lookup(const Point3f &p) const { ProfilePhase _(Prof::LightDistribLookup); ++nLookups; // Compute integer voxel coordinates for the given point |p| with // respect to the overall voxel grid. Vector3f offset = scene.WorldBound().Offset(p); // offset in [0,1]. Point3i pi; for (int i = 0; i < 3; ++i) pi[i] = int(offset[i] * nVoxels[i]); // Create the per-thread cache of sampling distributions if needed. LocalBucketHash *localVoxelDistribution = localVoxelDistributions[ThreadIndex].get(); if (!localVoxelDistribution) { LOG(INFO) << "Created per-thread SpatialLightDistribution for thread" << ThreadIndex; localVoxelDistribution = new LocalBucketHash; localVoxelDistributions[ThreadIndex].reset(localVoxelDistribution); } else { // Otherwise see if we have a sampling distribution for the voxel // that |p| is in already available in the local cache. auto iter = localVoxelDistribution->find(pi); if (iter != localVoxelDistribution->end()) return iter->second; } // Now we need to either get the distribution from the shared hash // table (if another thread has already created it), or create it // ourselves and add it to the shared table. ProfilePhase __(Prof::LightDistribLookupL2); // First, compute a hash into the first-level hash table. size_t hash = std::hash<int>{}(pi[0] + nVoxels[0] * pi[1] + nVoxels[0] * nVoxels[1] * pi[2]); hash &= (nBuckets - 1); // Acquire the lock for the corresponding second-level hash table. std::lock_guard<std::mutex> lock(mutexes[hash]); // See if we can find it. auto iter = voxelDistribution[hash].find(pi); if (iter != voxelDistribution[hash].end()) { // Success. Add the pointer to the thread-specific hash table so // that we can look up this distribution more efficiently in the // future. (*localVoxelDistribution)[pi] = iter->second.get(); return iter->second.get(); } // We need to compute a new sampling distriibution for this voxel. Note // that we're holding the lock for the first-level hash table bucket // throughout the following; in general, we'd like to do the following // quickly so that other threads don't get held up waiting for that // lock (for this or other voxels that share it). ProfilePhase ___(Prof::LightDistribCreation); ++nCreated; ++nDistributions; // Compute the world-space bounding box of the voxel. Point3f p0(Float(pi[0]) / Float(nVoxels[0]), Float(pi[1]) / Float(nVoxels[1]), Float(pi[2]) / Float(nVoxels[2])); Point3f p1(Float(pi[0] + 1) / Float(nVoxels[0]), Float(pi[1] + 1) / Float(nVoxels[1]), Float(pi[2] + 1) / Float(nVoxels[2])); Bounds3f voxelBounds(scene.WorldBound().Lerp(p0), scene.WorldBound().Lerp(p1)); // Compute the sampling distribution. Sample a number of points inside // voxelBounds using a 3D Halton sequence; at each one, sample each // light source and compute a weight based on Li/pdf for the light's // sample (ignoring visibility between the point in the voxel and the // point on the light source) as an approximation to how much the light // is likely to contribute to illumination in the voxel. int nSamples = 128; std::vector<Float> lightContrib(scene.lights.size(), Float(0)); for (int i = 0; i < nSamples; ++i) { Point3f po = voxelBounds.Lerp(Point3f( RadicalInverse(0, i), RadicalInverse(1, i), RadicalInverse(2, i))); Interaction intr(po, Normal3f(), Vector3f(), Vector3f(1, 0, 0), 0 /* time */, MediumInterface()); // Use the next two Halton dimensions to sample a point on the // light source. Point2f u(RadicalInverse(3, i), RadicalInverse(4, i)); for (size_t j = 0; j < scene.lights.size(); ++j) { Float pdf; Vector3f wi; VisibilityTester vis; Spectrum Li = scene.lights[j]->Sample_Li(intr, u, &wi, &pdf, &vis); if (pdf > 0) { // TODO: look at tracing shadow rays / computing beam // transmittance. Probably shouldn't give those full weight // but instead e.g. have an occluded shadow ray scale down // the contribution by 10 or something. lightContrib[j] += Li.y() / pdf; } } } // We don't want to leave any lights with a zero probability; it's // possible that a light contributes to points in the voxel even though // we didn't find such a point when sampling above. Therefore, compute // a minimum (small) weight and ensure that all lights are given at // least the corresponding probability. Float sumContrib = std::accumulate(lightContrib.begin(), lightContrib.end(), Float(0)); Float avgContrib = sumContrib / (nSamples * lightContrib.size()); Float minContrib = (avgContrib > 0) ? .001 * avgContrib : 1; for (size_t i = 0; i < lightContrib.size(); ++i) { VLOG(2) << "Voxel pi = " << pi << ", light " << i << " contrib = " << lightContrib[i]; lightContrib[i] = std::max(lightContrib[i], minContrib); } LOG(INFO) << "Initialized light distribution in voxel pi= " << pi << ", avgContrib = " << avgContrib; // Compute a sampling distribution from the accumulated // contributions. std::unique_ptr<Distribution1D> distrib( new Distribution1D(&lightContrib[0], lightContrib.size())); // Store a pointer to it in the per-thread cache for the future. (*localVoxelDistribution)[pi] = distrib.get(); // Store the canonical unique_ptr for it in the global hash table so // other threads can use it. voxelDistribution[hash][pi] = std::move(distrib); return (*localVoxelDistribution)[pi]; }
Distribution1D * SpatialLightDistribution::ComputeDistribution(Point3i pi) const { ProfilePhase _(Prof::LightDistribCreation); ++nCreated; ++nDistributions; // Compute the world-space bounding box of the voxel corresponding to // |pi|. Point3f p0(Float(pi[0]) / Float(nVoxels[0]), Float(pi[1]) / Float(nVoxels[1]), Float(pi[2]) / Float(nVoxels[2])); Point3f p1(Float(pi[0] + 1) / Float(nVoxels[0]), Float(pi[1] + 1) / Float(nVoxels[1]), Float(pi[2] + 1) / Float(nVoxels[2])); Bounds3f voxelBounds(scene.WorldBound().Lerp(p0), scene.WorldBound().Lerp(p1)); // Compute the sampling distribution. Sample a number of points inside // voxelBounds using a 3D Halton sequence; at each one, sample each // light source and compute a weight based on Li/pdf for the light's // sample (ignoring visibility between the point in the voxel and the // point on the light source) as an approximation to how much the light // is likely to contribute to illumination in the voxel. int nSamples = 128; std::vector<Float> lightContrib(scene.lights.size(), Float(0)); for (int i = 0; i < nSamples; ++i) { Point3f po = voxelBounds.Lerp(Point3f( RadicalInverse(0, i), RadicalInverse(1, i), RadicalInverse(2, i))); Interaction intr(po, Normal3f(), Vector3f(), Vector3f(1, 0, 0), 0 /* time */, MediumInterface()); // Use the next two Halton dimensions to sample a point on the // light source. Point2f u(RadicalInverse(3, i), RadicalInverse(4, i)); for (size_t j = 0; j < scene.lights.size(); ++j) { Float pdf; Vector3f wi; VisibilityTester vis; Spectrum Li = scene.lights[j]->Sample_Li(intr, u, &wi, &pdf, &vis); if (pdf > 0) { // TODO: look at tracing shadow rays / computing beam // transmittance. Probably shouldn't give those full weight // but instead e.g. have an occluded shadow ray scale down // the contribution by 10 or something. lightContrib[j] += Li.y() / pdf; } } } // We don't want to leave any lights with a zero probability; it's // possible that a light contributes to points in the voxel even though // we didn't find such a point when sampling above. Therefore, compute // a minimum (small) weight and ensure that all lights are given at // least the corresponding probability. Float sumContrib = std::accumulate(lightContrib.begin(), lightContrib.end(), Float(0)); Float avgContrib = sumContrib / (nSamples * lightContrib.size()); Float minContrib = (avgContrib > 0) ? .001 * avgContrib : 1; for (size_t i = 0; i < lightContrib.size(); ++i) { VLOG(2) << "Voxel pi = " << pi << ", light " << i << " contrib = " << lightContrib[i]; lightContrib[i] = std::max(lightContrib[i], minContrib); } LOG(INFO) << "Initialized light distribution in voxel pi= " << pi << ", avgContrib = " << avgContrib; // Compute a sampling distribution from the accumulated contributions. return new Distribution1D(&lightContrib[0], lightContrib.size()); }