nori/src/main.cpp

259 lines
8.4 KiB
C++

/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <nori/parser.h>
#include <nori/scene.h>
#include <nori/camera.h>
#include <nori/block.h>
#include <nori/timer.h>
#include <nori/bitmap.h>
#include <nori/sampler.h>
#include <nori/integrator.h>
#include <nori/gui.h>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <tbb/task_scheduler_init.h>
#include <filesystem/resolver.h>
#include <thread>
using namespace nori;
static int threadCount = -1;
static bool gui = true;
static void renderBlock(const Scene *scene, Sampler *sampler, ImageBlock &block) {
const Camera *camera = scene->getCamera();
const Integrator *integrator = scene->getIntegrator();
Point2i offset = block.getOffset();
Vector2i size = block.getSize();
/* Clear the block contents */
block.clear();
/* For each pixel and pixel sample sample */
for (int y=0; y<size.y(); ++y) {
for (int x=0; x<size.x(); ++x) {
for (uint32_t i=0; i<sampler->getSampleCount(); ++i) {
Point2f pixelSample = Point2f((float) (x + offset.x()), (float) (y + offset.y())) + sampler->next2D();
Point2f apertureSample = sampler->next2D();
/* Sample a ray from the camera */
Ray3f ray;
Color3f value = camera->sampleRay(ray, pixelSample, apertureSample);
/* Compute the incident radiance */
value *= integrator->Li(scene, sampler, ray);
/* Store in the image block */
block.put(pixelSample, value);
}
}
}
}
static void render(Scene *scene, const std::string &filename) {
const Camera *camera = scene->getCamera();
Vector2i outputSize = camera->getOutputSize();
scene->getIntegrator()->preprocess(scene);
/* Create a block generator (i.e. a work scheduler) */
BlockGenerator blockGenerator(outputSize, NORI_BLOCK_SIZE);
/* Allocate memory for the entire output image and clear it */
ImageBlock result(outputSize, camera->getReconstructionFilter());
result.clear();
/* Create a window that visualizes the partially rendered result */
NoriScreen *screen = nullptr;
if (gui) {
nanogui::init();
screen = new NoriScreen(result);
}
/* Do the following in parallel and asynchronously */
std::thread render_thread([&] {
tbb::task_scheduler_init init(threadCount);
cout << "Rendering .. ";
cout.flush();
Timer timer;
tbb::blocked_range<int> range(0, blockGenerator.getBlockCount());
auto map = [&](const tbb::blocked_range<int> &range) {
/* Allocate memory for a small image block to be rendered
by the current thread */
ImageBlock block(Vector2i(NORI_BLOCK_SIZE),
camera->getReconstructionFilter());
/* Create a clone of the sampler for the current thread */
std::unique_ptr<Sampler> sampler(scene->getSampler()->clone());
for (int i=range.begin(); i<range.end(); ++i) {
/* Request an image block from the block generator */
blockGenerator.next(block);
/* Inform the sampler about the block to be rendered */
sampler->prepare(block);
/* Render all contained pixels */
renderBlock(scene, sampler.get(), block);
/* The image block has been processed. Now add it to
the "big" block that represents the entire image */
result.put(block);
}
};
/// Default: parallel rendering
tbb::parallel_for(range, map);
/// (equivalent to the following single-threaded call)
// map(range);
cout << "done. (took " << timer.elapsedString() << ")" << endl;
});
/* Enter the application main loop */
if (gui)
nanogui::mainloop(50.f);
/* Shut down the user interface */
render_thread.join();
if (gui) {
delete screen;
nanogui::shutdown();
}
/* Now turn the rendered image block into
a properly normalized bitmap */
std::unique_ptr<Bitmap> bitmap(result.toBitmap());
/* Determine the filename of the output bitmap */
std::string outputName = filename;
size_t lastdot = outputName.find_last_of(".");
if (lastdot != std::string::npos)
outputName.erase(lastdot, std::string::npos);
/* Save using the OpenEXR format */
bitmap->saveEXR(outputName);
/* Save tonemapped (sRGB) output using the PNG format */
bitmap->savePNG(outputName);
}
int main(int argc, char **argv) {
if (argc < 2) {
cerr << "Syntax: " << argv[0] << " <scene.xml> [--no-gui] [--threads N]" << endl;
return -1;
}
std::string sceneName = "";
std::string exrName = "";
for (int i = 1; i < argc; ++i) {
std::string token(argv[i]);
if (token == "-t" || token == "--threads") {
if (i+1 >= argc) {
cerr << "\"--threads\" argument expects a positive integer following it." << endl;
return -1;
}
threadCount = atoi(argv[i+1]);
i++;
if (threadCount <= 0) {
cerr << "\"--threads\" argument expects a positive integer following it." << endl;
return -1;
}
continue;
}
else if (token == "--no-gui") {
gui = false;
continue;
}
filesystem::path path(argv[i]);
try {
if (path.extension() == "xml") {
sceneName = argv[i];
/* Add the parent directory of the scene file to the
file resolver. That way, the XML file can reference
resources (OBJ files, textures) using relative paths */
getFileResolver()->prepend(path.parent_path());
} else if (path.extension() == "exr") {
/* Alternatively, provide a basic OpenEXR image viewer */
exrName = argv[i];
} else {
cerr << "Fatal error: unknown file \"" << argv[i]
<< "\", expected an extension of type .xml or .exr" << endl;
}
} catch (const std::exception &e) {
cerr << "Fatal error: " << e.what() << endl;
return -1;
}
}
if (exrName !="" && sceneName !="") {
cerr << "Both .xml and .exr files were provided. Please only provide one of them." << endl;
return -1;
}
else if (exrName == "" && sceneName == "") {
cerr << "Please provide the path to a .xml (or .exr) file." << endl;
return -1;
}
else if (exrName != "") {
if (!gui) {
cerr << "Flag --no-gui was set. Please remove it to display the EXR file." << endl;
return -1;
}
try {
Bitmap bitmap(exrName);
ImageBlock block(Vector2i((int) bitmap.cols(), (int) bitmap.rows()), nullptr);
block.fromBitmap(bitmap);
nanogui::init();
NoriScreen *screen = new NoriScreen(block);
nanogui::mainloop(50.f);
delete screen;
nanogui::shutdown();
} catch (const std::exception &e) {
cerr << e.what() << endl;
return -1;
}
}
else { // sceneName != ""
if (threadCount < 0) {
threadCount = tbb::task_scheduler_init::automatic;
}
try {
std::unique_ptr<NoriObject> root(loadFromXML(sceneName));
/* When the XML root object is a scene, start rendering it .. */
if (root->getClassType() == NoriObject::EScene)
render(static_cast<Scene *>(root.get()), sceneName);
} catch (const std::exception &e) {
cerr << e.what() << endl;
return -1;
}
}
return 0;
}