void WriteImage(const std::string &name, const Float *rgb, const Bounds2i &outputBounds, const Point2i &totalResolution, Float gamma) { if (name.size() >= 5) { size_t suffixOffset = name.size() - 4; if (!strcmp(name.c_str() + suffixOffset, ".exr") || !strcmp(name.c_str() + suffixOffset, ".EXR")) { WriteImageEXR(name, rgb, outputBounds.pMax.x - outputBounds.pMin.x, outputBounds.pMax.y - outputBounds.pMin.y, totalResolution.x, totalResolution.y, outputBounds.pMin.x, outputBounds.pMin.y); return; } if (!strcmp(name.c_str() + suffixOffset, ".pfm") || !strcmp(name.c_str() + suffixOffset, ".PFM")) { Vector2i resolution = outputBounds.Diagonal(); WriteImagePFM(name, rgb, resolution.x, resolution.y); return; } if (!strcmp(name.c_str() + suffixOffset, ".tga") || !strcmp(name.c_str() + suffixOffset, ".TGA") || !strcmp(name.c_str() + suffixOffset, ".png") || !strcmp(name.c_str() + suffixOffset, ".PNG")) { // 8-bit formats; apply gamma Vector2i resolution = outputBounds.Diagonal(); std::unique_ptr<uint8_t[]> rgb8( new uint8_t[3 * resolution.x * resolution.y]); uint8_t *dst = rgb8.get(); for (int y = 0; y < resolution.y; ++y) { for (int x = 0; x < resolution.x; ++x) { #define TO_BYTE(v) (uint8_t(Clamp(255.f * powf((v), 1.f / gamma), 0.f, 255.f))) dst[0] = TO_BYTE(rgb[3 * (y * resolution.x + x) + 0]); dst[1] = TO_BYTE(rgb[3 * (y * resolution.x + x) + 1]); dst[2] = TO_BYTE(rgb[3 * (y * resolution.x + x) + 2]); #undef TO_BYTE dst += 3; } } if (!strcmp(name.c_str() + suffixOffset, ".tga") || !strcmp(name.c_str() + suffixOffset, ".TGA")) WriteImageTGA(name, rgb8.get(), outputBounds.pMax.x - outputBounds.pMin.x, outputBounds.pMax.y - outputBounds.pMin.y, totalResolution.x, totalResolution.y, outputBounds.pMin.x, outputBounds.pMin.y); else if (stbi_write_png(name.c_str(), resolution.x, resolution.y, 3, rgb8.get(), 3 * resolution.x) == 0) Error("Error writing PNG \"%s\"", name.c_str()); return; } } Error("Can't determine image file type from suffix of filename \"%s\"", name.c_str()); }
void WriteImage(const std::string &name, const Float *rgb, const Bounds2i &outputBounds, const Point2i &totalResolution) { Vector2i resolution = outputBounds.Diagonal(); if (HasExtension(name, ".exr")) { WriteImageEXR(name, rgb, resolution.x, resolution.y, totalResolution.x, totalResolution.y, outputBounds.pMin.x, outputBounds.pMin.y); } else if (HasExtension(name, ".pfm")) { WriteImagePFM(name, rgb, resolution.x, resolution.y); } else if (HasExtension(name, ".tga") || HasExtension(name, ".png")) { // 8-bit formats; apply gamma Vector2i resolution = outputBounds.Diagonal(); std::unique_ptr<uint8_t[]> rgb8( new uint8_t[3 * resolution.x * resolution.y]); uint8_t *dst = rgb8.get(); for (int y = 0; y < resolution.y; ++y) { for (int x = 0; x < resolution.x; ++x) { #define TO_BYTE(v) (uint8_t) Clamp(255.f * GammaCorrect(v) + 0.5f, 0.f, 255.f) dst[0] = TO_BYTE(rgb[3 * (y * resolution.x + x) + 0]); dst[1] = TO_BYTE(rgb[3 * (y * resolution.x + x) + 1]); dst[2] = TO_BYTE(rgb[3 * (y * resolution.x + x) + 2]); #undef TO_BYTE dst += 3; } } if (HasExtension(name, ".tga")) WriteImageTGA(name, rgb8.get(), resolution.x, resolution.y, totalResolution.x, totalResolution.y, outputBounds.pMin.x, outputBounds.pMin.y); else { unsigned int error = lodepng_encode24_file( name.c_str(), rgb8.get(), resolution.x, resolution.y); if (error != 0) Error("Error writing PNG \"%s\": %s", name.c_str(), lodepng_error_text(error)); } } else { Error("Can't determine image file type from suffix of filename \"%s\"", name.c_str()); } }
// MLT Method Definitions Spectrum MLTIntegrator::L(const Scene &scene, MemoryArena &arena, MLTSampler &sampler, int depth, Point2f *samplePos) { sampler.SetStream(3, 0); // Determine the number of available strategies and pick a specific one int s, t, nStrategies; if (depth == 0) { nStrategies = 1; s = 0; t = 2; } else { nStrategies = depth + 2; s = std::min((int)(sampler.Get1D() * nStrategies), nStrategies - 1); t = nStrategies - s; } // Generate a camera subpath with exactly _t_ vertices Vertex *cameraSubpath = (Vertex *)arena.Alloc<Vertex>(t); Bounds2i sampleBounds = camera->film->GetSampleBounds(); Vector2i diag = sampleBounds.Diagonal(); *samplePos = Point2f(sampleBounds.pMin.x + diag.x * sampler.Get1D(), sampleBounds.pMin.y + diag.y * sampler.Get1D()); if (GenerateCameraSubpath(scene, sampler, arena, t, *camera, *samplePos, cameraSubpath) != t) return Spectrum(0.f); // Generate a light subpath with exactly _s_ vertices sampler.SetStream(3, 1); Vertex *lightSubpath = (Vertex *)arena.Alloc<Vertex>(s); if (GenerateLightSubpath(scene, sampler, arena, s, cameraSubpath[0].time(), *lightDistr, lightSubpath) != s) return Spectrum(0.f); // Execute connection strategy and return the radiance estimate sampler.SetStream(3, 2); Spectrum L = ConnectBDPT(scene, lightSubpath, cameraSubpath, s, t, *lightDistr, *camera, sampler, samplePos); arena.Reset(); return L * nStrategies; }
void BDPTIntegrator::Render(const Scene &scene) { std::unique_ptr<LightDistribution> lightDistribution = CreateLightSampleDistribution(lightSampleStrategy, scene); // Compute a reverse mapping from light pointers to offsets into the // scene lights vector (and, equivalently, offsets into // lightDistr). Added after book text was finalized; this is critical // to reasonable performance with 100s+ of light sources. std::unordered_map<const Light *, size_t> lightToIndex; for (size_t i = 0; i < scene.lights.size(); ++i) lightToIndex[scene.lights[i].get()] = i; // Partition the image into tiles Film *film = camera->film; const Bounds2i sampleBounds = film->GetSampleBounds(); const Vector2i sampleExtent = sampleBounds.Diagonal(); const int tileSize = 16; const int nXTiles = (sampleExtent.x + tileSize - 1) / tileSize; const int nYTiles = (sampleExtent.y + tileSize - 1) / tileSize; ProgressReporter reporter(nXTiles * nYTiles, "Rendering"); // Allocate buffers for debug visualization const int bufferCount = (1 + maxDepth) * (6 + maxDepth) / 2; std::vector<std::unique_ptr<Film>> weightFilms(bufferCount); if (visualizeStrategies || visualizeWeights) { for (int depth = 0; depth <= maxDepth; ++depth) { for (int s = 0; s <= depth + 2; ++s) { int t = depth + 2 - s; if (t == 0 || (s == 1 && t == 1)) continue; std::string filename = StringPrintf("bdpt_d%02i_s%02i_t%02i.exr", depth, s, t); weightFilms[BufferIndex(s, t)] = std::unique_ptr<Film>(new Film( film->fullResolution, Bounds2f(Point2f(0, 0), Point2f(1, 1)), std::unique_ptr<Filter>(CreateBoxFilter(ParamSet())), film->diagonal * 1000, filename, 1.f)); } } } // Render and write the output image to disk if (scene.lights.size() > 0) { ParallelFor2D([&](const Point2i tile) { // Render a single tile using BDPT MemoryArena arena; int seed = tile.y * nXTiles + tile.x; std::unique_ptr<Sampler> tileSampler = sampler->Clone(seed); int x0 = sampleBounds.pMin.x + tile.x * tileSize; int x1 = std::min(x0 + tileSize, sampleBounds.pMax.x); int y0 = sampleBounds.pMin.y + tile.y * tileSize; int y1 = std::min(y0 + tileSize, sampleBounds.pMax.y); Bounds2i tileBounds(Point2i(x0, y0), Point2i(x1, y1)); std::unique_ptr<FilmTile> filmTile = camera->film->GetFilmTile(tileBounds); for (Point2i pPixel : tileBounds) { tileSampler->StartPixel(pPixel); if (!InsideExclusive(pPixel, pixelBounds)) continue; do { // Generate a single sample using BDPT Point2f pFilm = (Point2f)pPixel + tileSampler->Get2D(); // Trace the camera subpath Vertex *cameraVertices = arena.Alloc<Vertex>(maxDepth + 2); Vertex *lightVertices = arena.Alloc<Vertex>(maxDepth + 1); int nCamera = GenerateCameraSubpath( scene, *tileSampler, arena, maxDepth + 2, *camera, pFilm, cameraVertices); // Get a distribution for sampling the light at the // start of the light subpath. Because the light path // follows multiple bounces, basing the sampling // distribution on any of the vertices of the camera // path is unlikely to be a good strategy. We use the // PowerLightDistribution by default here, which // doesn't use the point passed to it. const Distribution1D *lightDistr = lightDistribution->Lookup(cameraVertices[0].p()); // Now trace the light subpath int nLight = GenerateLightSubpath( scene, *tileSampler, arena, maxDepth + 1, cameraVertices[0].time(), *lightDistr, lightToIndex, lightVertices); // Execute all BDPT connection strategies Spectrum L(0.f); for (int t = 1; t <= nCamera; ++t) { for (int s = 0; s <= nLight; ++s) { int depth = t + s - 2; if ((s == 1 && t == 1) || depth < 0 || depth > maxDepth) continue; // Execute the $(s, t)$ connection strategy and // update _L_ Point2f pFilmNew = pFilm; Float misWeight = 0.f; Spectrum Lpath = ConnectBDPT( scene, lightVertices, cameraVertices, s, t, *lightDistr, lightToIndex, *camera, *tileSampler, &pFilmNew, &misWeight); VLOG(2) << "Connect bdpt s: " << s <<", t: " << t << ", Lpath: " << Lpath << ", misWeight: " << misWeight; if (visualizeStrategies || visualizeWeights) { Spectrum value; if (visualizeStrategies) value = misWeight == 0 ? 0 : Lpath / misWeight; if (visualizeWeights) value = Lpath; weightFilms[BufferIndex(s, t)]->AddSplat( pFilmNew, value); } if (t != 1) L += Lpath; else film->AddSplat(pFilmNew, Lpath); } } VLOG(2) << "Add film sample pFilm: " << pFilm << ", L: " << L << ", (y: " << L.y() << ")"; filmTile->AddSample(pFilm, L); arena.Reset(); } while (tileSampler->StartNextSample()); } film->MergeFilmTile(std::move(filmTile)); reporter.Update(); }, Point2i(nXTiles, nYTiles)); reporter.Done(); } film->WriteImage(1.0f / sampler->samplesPerPixel); // Write buffers for debug visualization if (visualizeStrategies || visualizeWeights) { const Float invSampleCount = 1.0f / sampler->samplesPerPixel; for (size_t i = 0; i < weightFilms.size(); ++i) if (weightFilms[i]) weightFilms[i]->WriteImage(invSampleCount); } }
// SamplerIntegrator Method Definitions void SamplerIntegrator::Render(const Scene &scene) { ProfilePhase p(Prof::IntegratorRender); Preprocess(scene, *sampler); // Render image tiles in parallel // Compute number of tiles, _nTiles_, to use for parallel rendering Bounds2i sampleBounds = camera->film->GetSampleBounds(); Vector2i sampleExtent = sampleBounds.Diagonal(); const int tileSize = 16; Point2i nTiles((sampleExtent.x + tileSize - 1) / tileSize, (sampleExtent.y + tileSize - 1) / tileSize); ProgressReporter reporter(nTiles.x * nTiles.y, "Rendering"); { StatTimer timer(&renderingTime); ParallelFor2D([&](Point2i tile) { // Render section of image corresponding to _tile_ // Allocate _MemoryArena_ for tile MemoryArena arena; // Get sampler instance for tile int seed = tile.y * nTiles.x + tile.x; std::unique_ptr<Sampler> tileSampler = sampler->Clone(seed); // Compute sample bounds for tile int x0 = sampleBounds.pMin.x + tile.x * tileSize; int x1 = std::min(x0 + tileSize, sampleBounds.pMax.x); int y0 = sampleBounds.pMin.y + tile.y * tileSize; int y1 = std::min(y0 + tileSize, sampleBounds.pMax.y); Bounds2i tileBounds(Point2i(x0, y0), Point2i(x1, y1)); // Get _FilmTile_ for tile std::unique_ptr<FilmTile> filmTile = camera->film->GetFilmTile(tileBounds); // Loop over pixels in tile to render them for (Point2i pixel : tileBounds) { { ProfilePhase pp(Prof::StartPixel); tileSampler->StartPixel(pixel); } do { // Initialize _CameraSample_ for current sample CameraSample cameraSample = tileSampler->GetCameraSample(pixel); // Generate camera ray for current sample RayDifferential ray; Float rayWeight = camera->GenerateRayDifferential(cameraSample, &ray); ray.ScaleDifferentials( 1 / std::sqrt((Float)tileSampler->samplesPerPixel)); ++nCameraRays; // Evaluate radiance along camera ray Spectrum L(0.f); if (rayWeight > 0) L = Li(ray, scene, *tileSampler, arena); // Issue warning if unexpected radiance value returned if (L.HasNaNs()) { Error( "Not-a-number radiance value returned " "for image sample. Setting to black."); L = Spectrum(0.f); } else if (L.y() < -1e-5) { Error( "Negative luminance value, %f, returned " "for image sample. Setting to black.", L.y()); L = Spectrum(0.f); } else if (std::isinf(L.y())) { Error( "Infinite luminance value returned " "for image sample. Setting to black."); L = Spectrum(0.f); } // Add camera ray's contribution to image filmTile->AddSample(cameraSample.pFilm, L, rayWeight); // Free _MemoryArena_ memory from computing image sample // value arena.Reset(); } while (tileSampler->StartNextSample()); } // Merge image tile into _Film_ camera->film->MergeFilmTile(std::move(filmTile)); reporter.Update(); }, nTiles); reporter.Done(); } // Save final image after rendering camera->film->WriteImage(); }
void BDPTIntegrator::Render(const Scene &scene) { ProfilePhase p(Prof::IntegratorRender); // Compute _lightDistr_ for sampling lights proportional to power std::unique_ptr<Distribution1D> lightDistr = ComputeLightPowerDistribution(scene); // Partition the image into tiles Film *film = camera->film; const Bounds2i sampleBounds = film->GetSampleBounds(); const Vector2i sampleExtent = sampleBounds.Diagonal(); const int tileSize = 16; const int nXTiles = (sampleExtent.x + tileSize - 1) / tileSize; const int nYTiles = (sampleExtent.y + tileSize - 1) / tileSize; ProgressReporter reporter(nXTiles * nYTiles, "Rendering"); // Allocate buffers for debug visualization const int bufferCount = (1 + maxDepth) * (6 + maxDepth) / 2; std::vector<std::unique_ptr<Film>> weightFilms(bufferCount); if (visualizeStrategies || visualizeWeights) { for (int depth = 0; depth <= maxDepth; ++depth) { for (int s = 0; s <= depth + 2; ++s) { int t = depth + 2 - s; if (t == 0 || (s == 1 && t == 1)) continue; std::string filename = StringPrintf("bdpt_d%02i_s%02i_t%02i.exr", depth, s, t); weightFilms[BufferIndex(s, t)] = std::unique_ptr<Film>(new Film( film->fullResolution, Bounds2f(Point2f(0, 0), Point2f(1, 1)), std::unique_ptr<Filter>(CreateBoxFilter(ParamSet())), film->diagonal * 1000, filename, 1.f)); } } } // Render and write the output image to disk if (scene.lights.size() > 0) { StatTimer timer(&renderingTime); ParallelFor2D([&](const Point2i tile) { // Render a single tile using BDPT MemoryArena arena; int seed = tile.y * nXTiles + tile.x; std::unique_ptr<Sampler> tileSampler = sampler->Clone(seed); int x0 = sampleBounds.pMin.x + tile.x * tileSize; int x1 = std::min(x0 + tileSize, sampleBounds.pMax.x); int y0 = sampleBounds.pMin.y + tile.y * tileSize; int y1 = std::min(y0 + tileSize, sampleBounds.pMax.y); Bounds2i tileBounds(Point2i(x0, y0), Point2i(x1, y1)); std::unique_ptr<FilmTile> filmTile = camera->film->GetFilmTile(tileBounds); for (Point2i pPixel : tileBounds) { tileSampler->StartPixel(pPixel); if (!InsideExclusive(pPixel, pixelBounds)) continue; do { // Generate a single sample using BDPT Point2f pFilm = (Point2f)pPixel + tileSampler->Get2D(); // Trace the camera and light subpaths Vertex *cameraVertices = arena.Alloc<Vertex>(maxDepth + 2); Vertex *lightVertices = arena.Alloc<Vertex>(maxDepth + 1); int nCamera = GenerateCameraSubpath( scene, *tileSampler, arena, maxDepth + 2, *camera, pFilm, cameraVertices); int nLight = GenerateLightSubpath( scene, *tileSampler, arena, maxDepth + 1, cameraVertices[0].time(), *lightDistr, lightVertices); // Execute all BDPT connection strategies Spectrum L(0.f); for (int t = 1; t <= nCamera; ++t) { for (int s = 0; s <= nLight; ++s) { int depth = t + s - 2; if ((s == 1 && t == 1) || depth < 0 || depth > maxDepth) continue; // Execute the $(s, t)$ connection strategy and // update _L_ Point2f pFilmNew = pFilm; Float misWeight = 0.f; Spectrum Lpath = ConnectBDPT( scene, lightVertices, cameraVertices, s, t, *lightDistr, *camera, *tileSampler, &pFilmNew, &misWeight); if (visualizeStrategies || visualizeWeights) { Spectrum value; if (visualizeStrategies) value = misWeight == 0 ? 0 : Lpath / misWeight; if (visualizeWeights) value = Lpath; weightFilms[BufferIndex(s, t)]->AddSplat( pFilmNew, value); } if (t != 1) L += Lpath; else film->AddSplat(pFilmNew, Lpath); } } filmTile->AddSample(pFilm, L); arena.Reset(); } while (tileSampler->StartNextSample()); } film->MergeFilmTile(std::move(filmTile)); reporter.Update(); }, Point2i(nXTiles, nYTiles)); reporter.Done(); } film->WriteImage(1.0f / sampler->samplesPerPixel); // Write buffers for debug visualization if (visualizeStrategies || visualizeWeights) { const Float invSampleCount = 1.0f / sampler->samplesPerPixel; for (size_t i = 0; i < weightFilms.size(); ++i) if (weightFilms[i]) weightFilms[i]->WriteImage(invSampleCount); } }
void BDPTIntegrator::Render(const Scene &scene) { // Compute _lightDistr_ for sampling lights proportional to power std::unique_ptr<Distribution1D> lightDistr(ComputeLightSamplingCDF(scene)); // Partition the image into buckets Film *film = camera->film; const Bounds2i sampleBounds = film->GetSampleBounds(); const Vector2i sampleExtent = sampleBounds.Diagonal(); const int bucketSize = 16; const int nXBuckets = (sampleExtent.x + bucketSize - 1) / bucketSize; const int nYBuckets = (sampleExtent.y + bucketSize - 1) / bucketSize; ProgressReporter reporter(nXBuckets * nYBuckets, "Rendering"); // Allocate buffers for debug visualization const int bufferCount = (1 + maxdepth) * (6 + maxdepth) / 2; std::vector<std::unique_ptr<Film>> films(bufferCount); if (visualize_strategies || visualize_weights) { for (int depth = 0; depth <= maxdepth; ++depth) { for (int s = 0; s <= depth + 2; ++s) { int t = depth + 2 - s; if (t == 0 || (s == 1 && t == 1)) continue; char filename[32]; snprintf(filename, sizeof(filename), "bdpt_d%02i_s%02i_t%02i.exr", depth, s, t); films[BufferIndex(s, t)] = std::unique_ptr<Film>(new Film( film->fullResolution, Bounds2f(Point2f(0, 0), Point2f(1, 1)), CreateBoxFilter(ParamSet()), film->diagonal * 1000, // XXX what does this parameter // mean? Why the multiplication? filename, 1.f, 2.2f)); } } } // Render and write the output image to disk { StatTimer timer(&renderingTime); ParallelFor([&](const Point2i bucket) { // Render a single bucket using BDPT MemoryArena arena; int seed = bucket.y * nXBuckets + bucket.x; std::unique_ptr<Sampler> bucketSampler = sampler->Clone(seed); int x0 = sampleBounds.pMin.x + bucket.x * bucketSize; int x1 = std::min(x0 + bucketSize, sampleBounds.pMax.x); int y0 = sampleBounds.pMin.y + bucket.y * bucketSize; int y1 = std::min(y0 + bucketSize, sampleBounds.pMax.y); Bounds2i bucketBounds(Point2i(x0, y0), Point2i(x1, y1)); std::unique_ptr<FilmTile> filmTile = camera->film->GetFilmTile(bucketBounds); for (Point2i pixel : bucketBounds) { bucketSampler->StartPixel(pixel); do { // Generate a single sample using BDPT Point2f rasterPos((Float)pixel.x, (Float)pixel.y); rasterPos += bucketSampler->Get2D(); // Trace the light and camera subpaths Vertex *cameraSubpath = (Vertex *)arena.Alloc<Vertex>(maxdepth + 2); Vertex *lightSubpath = (Vertex *)arena.Alloc<Vertex>(maxdepth + 1); int nCamera = GenerateCameraSubpath( scene, *bucketSampler, arena, maxdepth + 2, *camera, rasterPos, cameraSubpath); int nLight = GenerateLightSubpath( scene, *bucketSampler, arena, maxdepth + 1, cameraSubpath[0].GetTime(), *lightDistr, lightSubpath); // Execute all connection strategies Spectrum pixelWeight(0.f); for (int t = 1; t <= nCamera; ++t) { for (int s = 0; s <= nLight; ++s) { int depth = t + s - 2; if ((s == 1 && t == 1) || depth < 0 || depth > maxdepth) continue; // Execute the $(s, t)$ connection strategy Point2f finalRasterPos = rasterPos; Float misWeight = 0.f; Spectrum weight = ConnectBDPT( scene, lightSubpath, cameraSubpath, s, t, *lightDistr, *camera, *bucketSampler, &finalRasterPos, &misWeight); if (visualize_strategies || visualize_weights) { Spectrum value(1.0f); if (visualize_strategies) value *= weight; if (visualize_weights) value *= misWeight; films[BufferIndex(s, t)]->Splat(finalRasterPos, value); } if (t != 1) pixelWeight += weight * misWeight; else film->Splat(finalRasterPos, weight * misWeight); } } filmTile->AddSample(rasterPos, pixelWeight, 1.0f); arena.Reset(); } while (bucketSampler->StartNextSample()); } film->MergeFilmTile(std::move(filmTile)); reporter.Update(); }, Point2i(nXBuckets, nYBuckets)); reporter.Done(); } film->WriteImage(1.0f / sampler->samplesPerPixel); // Write buffers for debug visualization if (visualize_strategies || visualize_weights) { const Float invSampleCount = 1.0f / sampler->samplesPerPixel; for (size_t i = 0; i < films.size(); ++i) { if (films[i]) films[i]->WriteImage(invSampleCount); } } }