The base code of Nori is provided as a git repository. git is the most widespread distributed version control system in use today—it is a good idea to familiarize yourself with this system if you've never used it before.
Traditionally, a version control system (VCS) allows a team of people to collaboratively edit a repository of files. Multiple people might happen to work on the same file, and the job of the VCS is to track the (potentially conflicting) modifications and merge them into an unambiguous repository state that is usable to the rest of the team. These systems (e.g. CVS, SVN) store the authoritative repository state in a central repository server. Any changes must be committed to this server so that other team members can access them.
A distributed version control system (DVCS) is conceptually similar: multiple people can collaborate, and there is usually a central repository server. The main difference is that there is nothing special about this server: anyone with sufficient permissions can copy the entire repository state including history data using an operation referred to as cloning. From that point on, the local copy acts as a full-fledged repository server that works without a network connection to the central server. Users can inspect the history of files and even commit new changes, which are stored in the local copy. To allow another user to see these changes, they can either be pushed to that user's DVCS server, or they can be pushed to a central server and then pulled by the recipient. There are many providers (such as github or Bitbucket) which offer free repository hosting for open source projects. Because there is nothing special about the central server, it's easy to switch providers, and anyone can start their own version of an existing repository (this is referred to as forking).
Recommended learning resources for git are: try git, git-the simple guide, the git immersion tutorial, and the extensive free Pro Git ebook. The remainder of this section describes the steps needed to clone and compile the Nori base code.
You will receive your own private GitHub Classroom repository to work on your programming assignments. Please follow our instructions to get everything set up.
Begin by installing the CMake build system on your system. On Mac OS X, you will also need to install a reasonably up-to-date version of XCode along with the command line tools. On Linux, any reasonably recent version of GCC or Clang will work. Navigate to the Nori folder, create a build directory and start cmake-gui, like so:
$ cd path-to-nori $ mkdir build $ cd build $ cmake-gui ..After the Makefiles are generated, simply run make to compile all dependencies and Nori itself.
$ make -jThis can take quite a while; the above command compiles with as many processors as available. Note that you will probably see many warning messages while the dependencies are compiled—you can ignore them.
If you're working on Linux, you might have to install a few dependencies separately via your system's package manager. On Kubuntu for example, the following line is necessary:
$ apt-get install zlib1g-dev xorg-dev libglu1-mesa-dev
Begin by installing Visual Studio 2026 and a reasonably recent (≥ 4.2) version of CMake. Start CMake and navigate to the location where you cloned the Nori repository.
After setting up the project, click the Configure and Generate button. This will create a file called nori.slnx—double-click it to open Visual Studio.
The Build->Build Solution menu item will automatically compile all dependency libraries and Nori itself; the resulting executable is written to the Release or Debug subfolder of your chosen build directory. Note that you will probably see many warning messages while the dependencies are compiled—you can ignore them.| Directory | Description |
|---|---|
| src | A directory containing the main C++ source code |
| include/nori | A directory containing header files with declarations |
| ext | External dependency libraries (see the table right) |
| scenes | Example scenes and test datasets to validate your implementation |
| CMakeLists.txt | A CMake build file which specifies how to compile and link Nori |
| CMakeConfig.txt | A low-level CMake build file which specifies how to compile and link several dependency libraries upon which Nori depends. You probably won't have to change anything here. |
| Directory | Description |
|---|---|
| ext/openexr | A high dynamic range image format library |
| ext/pcg32 | A tiny self-contained pseudorandom number generator |
| ext/filesystem | A tiny self-contained library for manipulating paths on various platforms |
| ext/pugixml | A light-weight XML parsing library |
| ext/tbb | Intel's Boost Thread Building Blocks for multi-threading |
| ext/tinyformat | Type-safe C++11 version of printf and sprintf |
| ext/hypothesis | Functions for statistical hypothesis tests |
| ext/nanogui | A minimalistic GUI library for OpenGL |
| ext/nanogui/ext/eigen | A linear algebra library used by nanogui and Nori. |
| ext/zlib | A compression library used by OpenEXR |
When developing any kind of graphics-related software, it's important to be familiar with the core mathematics support library that is responsible for basic linear algebra types, such as vectors, points, normals, and linear transformations. Nori uses Eigen 3 for this purpose. We don't expect you to understand the inner workings of this library but recommend that you at least take a look at the helpful tutorial provided on the Eigen web page.
Nori provides a set of linear algebra types that are derived from Eigen's matrix/vector class (see e.g. the header file include/nori/vector.h). This is necessary because we will be handling various quantities that require different treatment when undergoing homogeneous coordinate transformations, and in particular we must distinguish between positions, vectors, and normals. The main subset of types that you will most likely use are:
The pugixml library implements a tiny XML parser that we use to load Nori scenes. The format of these scenes is described below. The XML parser is fully implemented for your convenience, but you may have to change it if you wish to extend the file format for your final project.
PCG is a family of tiny pseudorandom number generators with good performance that was recently proposed by Melissa O'Neill. The full implementation of pcg32 (one member of this family) is provided in a single header file in ext/pcg32/pcg32.h. You will be using this class as a source of pseudo-randomness starting with the second programming assignment on sample generation.
With each programming assignment, we will provide statistical hypothesis tests that you can use to verify that your algorithms are implemented correctly. You can think of them as unit tests with a little extra twist: suppose that the correct result of a certain computation in a is given by a constant \(c\). A normal unit test would check that the actual computed \(c'\) satisfies \(|c-c'|<\varepsilon\) for some small constant \(\varepsilon\) to allow for rounding errors etc. However, rendering algorithms usually employ randomness (they are Monte Carlo algorithms), and in practice the computed answer \(c'\) can be quite different from \(c\), which makes it tricky to choose a suitable constant \(\varepsilon\).
A statistical hypothesis test, on the other hand, analyzes the computed value and an estimate of its variance and tries to assess how likely it is that the difference \(|c-c'|\) is due to random noise or an actual implementation bug. When it is extremely unlikely (usually \(p<0.001\)) that the error could be attributed to noise, the test reports a failure.
OpenEXR is a standardized file format for storing high dynamic range images. It was originally developed by Industrial Light and Magic and is now widely used in the movie industry and for rendering in general. The directory ext/openexr contains the open source reference implementation of this standard. You will probably not be using this library directly but through Nori's Bitmap class implemented in src/bitmap.cpp and include/nori/bitmap.h to load and write OpenEXR files.
The NanoGUI library creates an OpenGL window and provides a small set of user interface elements (buttons, sliders, etc.). We use it to show the preview of the image being rendered. This library could be useful if your final project involves some kind of user interaction.
The tbb directory contains Intel's Thread Building Blocks. This is a library for parallelizing various kinds of programs similar in spirit to OpenMP and Grand Central Dispatch on Mac OS. You will see in the course that renderings often require significant amounts of computation, but this computation is easy to parallelize. We use TBB because it is more portable and flexible than the aforementioned platform-specific solutions. The basic rendering loop in Nori (in src/main.cpp) is already parallelized, so you will probably not have to read up on this library unless you plan to parallelize a custom algorithm for your final project.
$ ./nori path/to/scene.xmlNori also takes an optional argument --threads (or -t) that specifies the number of threads to use to render the scene:
$ ./nori path/to/scene.xml --threads 4If the argument is not specified, TBB automatically chooses the number of threads.
Take a moment to browse through the header files in include/nori. You will generally find all important interfaces and their documentation in this place. Most headers files also have a corresponding .cpp implementation file in the src directory. The most important class is called NoriObject—it is the base class of everything that can be constructed using the XML scene description language. Other interfaces (e.g. Camera) derive from this class and expose additional more specific functionality (e.g. to generate an outgoing ray from a camera).
Nori uses a very simple XML-based scene description language, which can be interpreted as a kind of building plan: the parser creates the scene step by step as it reads the scene file from top to bottom. The XML tags in this document are interpreted as requests to construct certain C++ objects including information on how to put them together.
Each XML tag is either an object or a property. Objects correspond to C++ instances that will be allocated on the heap. Properties are small bits of information that are passed to an object at the time of its instantiation. For instance, the following snippet creates red diffuse BSDF:
<bsdf type="diffuse">
<color name="albedo" value="0.5, 0, 0"/>
</bsdf>
Here, the <bsdf> tag will cause the creation of an object of type BSDF, and the type attribute specifies what specific subclass of BSDF should be used. The <color> tag creates a property of name albedo that will be passed to its constructor. If you open up the C++ source file src/diffuse.cpp, you will see that there is a constructor, which looks for this specific property:
Diffuse(const PropertyList &propList) {
m_albedo = propList.getColor("albedo", Color3f(0.5f));
}
The piece of code that associates the "diffuse" XML identifier with the Diffuse class in the C++ code is a macro found at the bottom of the file:
NORI_REGISTER_CLASS(Diffuse, "diffuse");
Certain objects can be nested hierarchically. For example, the following XML snippet creates a mesh that loads its contents from an external OBJ file and assigns a red diffuse BRDF to it.
<mesh type="obj">
<string type="filename" value="bunny.obj"/>
<bsdf type="diffuse">
<color name="albedo" value="0.5, 0, 0"/>
</bsdf>
</mesh>
Implementation-wise, this kind of nesting will cause a method named addChild() to be invoked within the parent object. In this specific example, this means that Mesh::addChild() is called, which roughly looks as follows:
void Mesh::addChild(NoriObject *obj) {
switch (obj->getClassType()) {
case EBSDF:
if (m_bsdf)
throw NoriException(
"Mesh: multiple BSDFs are not allowed!");
/// Store pointer to BSDF in local instance
m_bsdf = static_cast<BSDF *>(obj);
break;
// ..(omitted)..
}
This function verifies that the nested object is a BSDF, and that no BSDF was specified
before; otherwise, it throws an exception of type NoriException.
The following different types of properties can currently be passed to objects within the XML description language:
<!-- Basic parameter types --> <string name="property name" value="arbitrary string"/> <boolean name="property name" value="true/false"/> <float name="property name" value="float value"/> <integer name="property name" value="integer value"/> <vector name="property name" value="x, y, z"/> <point name="property name" value="x, y, z"/> <color name="property name" value="r, g, b"/>
<!-- Linear transformations use a different syntax -->
<transform name="property name">
<!-- Any sequence of the following operations: -->
<translate value="x, y, z"/>
<scale value="x, y, z"/>
<rotate axis="x, y, z" angle="deg."/>
<!-- Useful for cameras and spot lights: -->
<lookat origin="x,y,z" target="x,y,z" up="x,y,z"/>
</transform>
The top-level element of any scene file is usually a <scene> tag, but this is not always the case. For instance, some of the programming assignments will ask you to run statistical tests on BRDF models or rendering algorithms, and these tests are also specified using the XML scene description language, like so:
<?xml version="1.0"?>
<test type="chi2test">
<!-- Run a χ2 test on the microfacet BRDF model (@ 0.01 significance level) -->
<float name="significanceLevel" value="0.01"/>
<bsdf type="microfacet">
<float name="alpha" value="0.1"/>
</bsdf>
</test>
In Nori, rendering algorithms are referred to as integrators because they generally solve a numerical integration problem. The remainder of this section explains how to create your first (dummy) integrator which visualizes the surface normals of objects.
We begin by creating a new Nori object subclass in src/normals.cpp with the following content:
#include <nori/integrator.h>
NORI_NAMESPACE_BEGIN
class NormalIntegrator : public Integrator {
public:
NormalIntegrator(const PropertyList &props) {
m_myProperty = props.getString("myProperty");
std::cout << "Parameter value was : " << m_myProperty << std::endl;
}
/// Compute the radiance value for a given ray. Just return green here
Color3f Li(const Scene *scene, Sampler *sampler, const Ray3f &ray) const {
return Color3f(0, 1, 0);
}
/// Return a human-readable description for debugging purposes
std::string toString() const {
return tfm::format(
"NormalIntegrator[\n"
" myProperty = \"%s\"\n"
"]",
m_myProperty
);
}
protected:
std::string m_myProperty;
};
NORI_REGISTER_CLASS(NormalIntegrator, "normals");
NORI_NAMESPACE_END
To try out this integrator, we first need to add it to the CMake build system:
for this, open CMakeLists.txt and look for the command
add_executable(nori, # Header files include/nori/bbox.h ... # Source code files src/bitmap.cpp ... )
Add the line src/normals.cpp at the end of the source file list and recompile. If everything goes well, CMake will create an executable named nori (or nori.exe on Windows) which you can call on the command line.
Finally, create a small test scene with the following content and save it as test.xml:
<?xml version="1.0"?>
<scene>
<integrator type="normals">
<string name="myProperty" value="Hello!"/>
</integrator>
<camera type="perspective"/>
</scene>
This file instantiates our integrator and creates the default camera setup. Running nori with this scene causes two things to happen:
$ ./nori test.xml
Property value was : Hello!
Configuration: Scene[
integrator = NormalIntegrator[
myProperty = "Hello!"
],
sampler = Independent[sampleCount=1]
camera = PerspectiveCamera[
cameraToWorld = [1, 0, 0, 0;
0, 1, 0, 0;
0, 0, 1, 0;
0, 0, 0, 1],
outputSize = [1280, 720],
fov = 30.000000,
clip = [0.000100, 10000.000000],
rfilter = GaussianFilter[radius=2.000000, stddev=0.500000]
],
medium = null,
envEmitter = null,
meshes = {
}
]
Rendering .. done. (took 93.0ms)
Writing a 1280x720 OpenEXR file to "test.exr"
The Nori executable echoed the property value we provided, and
it printed a brief human-readable summary of the scene.
The rendered scene is saved as an OpenEXR file named test.exr as well as a (sRGB) tonemapped PNG image named test.png.
A word of caution: various tools for visualizing OpenEXR images exist, but not all really do what one would expect. Adobe Photoshop works correctly, but Preview.app on Mac OS for instance tonemaps these files in an awkward and unclear way. TEV by Thomas Müller is a good choice, it features useful tools such as false colors or image differences.
If in doubt, you can also use Nori as an OpenEXR viewer: simply run it with an EXR file as parameter, like so:
$ ./nori test.exr
Let's now build a more interesting integrator which traces some rays against the scene geometry. Change the file normals.cpp as shown on the left side. Invoke nori on the file scenes/pa1/bunny.xml, and you should get the image on the right. Please submit the resulting rendered image of the bunny in your homework report to receive credit for making it all the way through the tutorial.
#include <nori/integrator.h>
#include <nori/scene.h>
NORI_NAMESPACE_BEGIN
class NormalIntegrator : public Integrator {
public:
NormalIntegrator(const PropertyList &props) {
/* No parameters this time */
}
Color3f Li(const Scene *scene, Sampler *sampler, const Ray3f &ray) const {
/* Find the surface that is visible in the requested direction */
Intersection its;
if (!scene->rayIntersect(ray, its))
return Color3f(0.0f);
/* Return the component-wise absolute
value of the shading normal as a color */
Normal3f n = its.shFrame.n.cwiseAbs();
return Color3f(n.x(), n.y(), n.z());
}
std::string toString() const {
return "NormalIntegrator[]";
}
};
NORI_REGISTER_CLASS(NormalIntegrator, "normals");
NORI_NAMESPACE_END