$\newcommand{\A}{\mat{A}}$ $\newcommand{\B}{\mat{B}}$ $\newcommand{\C}{\mat{C}}$ $\newcommand{\D}{\mat{D}}$ $\newcommand{\E}{\mat{E}}$ $\newcommand{\F}{\mat{F}}$ $\newcommand{\G}{\mat{G}}$ $\newcommand{\H}{\mat{H}}$ $\newcommand{\I}{\mat{I}}$ $\newcommand{\J}{\mat{J}}$ $\newcommand{\K}{\mat{K}}$ $\newcommand{\L}{\mat{L}}$ $\newcommand{\M}{\mat{M}}$ $\newcommand{\N}{\mat{N}}$ $\newcommand{\One}{\mathbf{1}}$ $\newcommand{\P}{\mat{P}}$ $\newcommand{\Q}{\mat{Q}}$ $\newcommand{\Rot}{\mat{R}}$ $\newcommand{\R}{\mathbb{R}}$ $\newcommand{\S}{\mathcal{S}}$ $\newcommand{\T}{\mat{T}}$ $\newcommand{\U}{\mat{U}}$ $\newcommand{\V}{\mat{V}}$ $\newcommand{\W}{\mat{W}}$ $\newcommand{\X}{\mat{X}}$ $\newcommand{\Y}{\mat{Y}}$ $\newcommand{\argmax}{\mathop{\text{argmax}}}$ $\newcommand{\argmin}{\mathop{\text{argmin}}}$ $\newcommand{\a}{\vec{a}}$ $\newcommand{\b}{\vec{b}}$ $\newcommand{\c}{\vec{c}}$ $\newcommand{\d}{\vec{d}}$ $\newcommand{\e}{\vec{e}}$ $\newcommand{\f}{\vec{f}}$ $\newcommand{\g}{\vec{g}}$ $\newcommand{\mat}[1]{\mathbf{#1}}$ $\newcommand{\min}{\mathop{\text{min}}}$ $\newcommand{\m}{\vec{m}}$ $\newcommand{\n}{\vec{n}}$ $\newcommand{\p}{\vec{p}}$ $\newcommand{\q}{\vec{q}}$ $\newcommand{\r}{\vec{r}}$ $\newcommand{\transpose}{{\mathsf T}}$ $\newcommand{\tr}[1]{\mathop{\text{tr}}{\left(#1\right)}}$ $\newcommand{\s}{\vec{s}}$ $\newcommand{\t}{\vec{t}}$ $\newcommand{\u}{\vec{u}}$ $\newcommand{\vec}[1]{\mathbf{#1}}$ $\newcommand{\x}{\vec{x}}$ $\newcommand{\y}{\vec{y}}$ $\newcommand{\z}{\vec{z}}$ $\newcommand{\0}{\vec{0}}$ $\renewcommand{\v}{\vec{v}}$ $\renewcommand{\hat}[1]{\widehat{#1}}$

Computer Graphics - Ray Tracing

Deadline: Oct. 25 2024, 22:00

Any questions or comments are welcome at julie.artois@ugent.be and in CC glenn.vanwallendael@ugent.be and bert.ramlot@ugent.be

Background

Read Sections 4.5–4.9 of Fundamentals of Computer Graphics (4th Edition).

Many of the classes and functions of this assignment are reused from the previous ray casting assignment. For example viewing_ray(),first_hit() and many more. Check the README for more info on this.

Unlike the previous assignment, this ray tracer will produce approximately accurate renderings of scenes illuminated with light. Ultimately, the shading and lighting models here are useful hacks. The basic recursive structure of the program is core to many methods for rendering with global illumination effects (e.g., shadows, reflections, etc.).

Running ./raytracing ../data/sphere-and-plane.json should produce this image.

Floating point numbers

For this assignment we will use the Eigen::Vector3d to represent points and vectors, but also RGB colors. For all computation (before finally writing the .ppm file) we will use double precision floating point numbers and 0 will represent no light and 1 will represent the brightest color we can display.

Floating point numbers \(≠\) real numbers, they don’t even cover all of the rational numbers. This creates a number of challenges in numerical method and rendering is not immune to them. We see this in the need for a fudge factor (for example 1e-6) to discard ray-intersections when computing shadows or reflections that are too close to the originating surface (i.e., false intersections due to numerical error).

Dynamic Range & Burning

Light obeys the superposition principle. Simply put, the light reflected of some part of an objects is the sum of contributions from light coming in all directions (e.g., from all light sources). If there are many bright lights in the scene and the object has a bright color, it is easy for this sum to add up to more than one. At first this seems counter-intuitive: How can we exceed 100% light? But this premise is false, the \(1.0\) does not mean the physically brightest possible light in the world, but rather the brightest light our screen can display (or the brightest color we can store in our chosen image format). High dynamic range (HDR) images store a larger range beyond this usual [0,1]. For this assignment, we will simply clamp the total light values at a pixel to 1.

This issue is compounded by the problem that the Blinn-Phong shading does not correctly conserve energy as happens with light in the physical world.

Once you have finished the assignment, running ./raytracing ../data/bunny.json (Linux/Macos) or raytracing.exe ..\..\..\data\bunny.json (Windows) should result in a PNG file similar to the one below. This might take a few minutes (e.g. 5 minutes in Release mode on a decent CPU). Notice the “burned out” white regions where the collected light has been clamped to [1,1,1] (white).

Question: Can we ever get a pixel value less than zero?

Hint: Can a light be more than off?

Side note: This doesn’t stop crafty visual effects artists from using “negative lights” to manipulate scenes for aesthetic purposes.

Whitelist

There are many ways to “multiply” two vectors. One way is to compute the component-wise multiplication: \(\mathbf{c} = \mathbf{a} \circ \mathbf{b}\) or in index notation: \(c_i = a_i b_i\). That is, multiply each corresponding component and store the result in the corresponding component of the output vector. Using the Eigen library this is accomplished by telling Eigen to treat each of the vectors as “array” (where matrix multiplication, dot product, cross product would not make sense) and then using the usual * multiplication:

Eigen::Vector3d a,b;
...
// Component-wise multiplication
Eigen::Vector3d c = (a.array() * b.array()).matrix();

The .matrix() converts the “array” view of the vector back to a “matrix” (i.e., vector) view of the vector.

Eigen also has a built in way to normalize a vector (divide a vector by its length): a.normalized().

C++ standard library includes a value for \(∞\) via #include <limits>. For example, for double floating point, use std::numeric_limits<double>::infinity().

Tasks

PointLight::direction in src/PointLight.cpp

Compute the direction to a point light source and its parametric distance from a query point.

DirectionalLight::direction in src/DirectionalLight.cpp

Compute the direction to a direction light source and its parametric distance from a query point (infinity).

src/raycolor.cpp

Make use of first_hit.cpp to shoot a ray into the scene, collect hit information and use this to return a color value. Use recursion to add mirror reflections.

src/blinn_phong_shading.cpp

Compute the lit color of a hit object in the scene using Blinn-Phong shading model. This function should also shoot an additional ray to each light source to check for shadows.

Important: On slide 39 of lecture CG_02_raytracing-split.pdf, the formula for the diffuse shading component is given:

However, in your code, you should omit the division by r². Otherwise your scenes will appear dim.

Running raytracing on sphere-and-plane.json

It is recommended to add and debug each term in your shading model. The ambient term will look like a faint object-ID image. The diffuse term will illuminate the scene, and create a dull, Lambertian look to each object. The specular term will add shiny highlights. Then, mask the diffuse and specular terms by checking for shadows. Finally, add a recursive call to account for mirror reflections.

src/reflect.cpp

Given an “incoming” vector and a normal vector, compute the mirrored, reflected “outgoing” vector.

Running raytracing on sphere-packing.json

./raytracing ../data/sphere-packing.json should produce this
image of highly reflective, metallic looking
surfaces.
This should produce an image of highly reflective, metallic looking surfaces.

Pro Tip: After you’re confident that your program is working correctly, you can dramatically improve the performance simply by enabling compiler optimization:

cmake .. -DCMAKE_BUILD_TYPE=Release
make