Difficulty |
1 |
Reading material |
def scene_at(now)
{
var camera = Cameras.perspective( [ "eye": pos(0,0,5),
"look_at": pos(0,0,0) ] )
var floor_material = Materials.uniform( [ "ambient": Colors.white() * 1,
"diffuse": Colors.white() * 0.8,
"reflectivity": 0.5 ] )
var left_wall_material = Materials.uniform( [ "ambient": Colors.red() * 1,
"diffuse": Colors.red() * 0.8 ] )
var right_wall_material = Materials.uniform( [ "ambient": Colors.green() * 1,
"diffuse": Colors.green() * 0.8 ] )
var back_wall_material = Materials.uniform( [ "ambient": Colors.blue() * 1,
"diffuse": Colors.blue() * 0.8 ] )
var ceiling_material = floor_material
var sphere_material = Materials.uniform( [ "ambient": Colors.blue() * 0.5,
"diffuse": Colors.blue() * 0.8,
"specular": Colors.white() * 0.8,
"specular_exponent": 10,
"transparency": 0.7,
"refractive_index": 2.5 ] )
var small_sphere_material = Materials.uniform( [ "ambient": Colors.white() * 1,
"diffuse": Colors.white() * 0.8,
"reflectivity": 0.8 ] )
var primitives = []
primitives.push_back( translate(vec(0,-2,0), decorate(floor_material, xz_plane())) )
primitives.push_back( translate(vec(0,2,0), decorate(ceiling_material, xz_plane())) )
primitives.push_back( translate(vec(-2,0,0), decorate(left_wall_material, yz_plane())) )
primitives.push_back( translate(vec(2,0,0), decorate(right_wall_material, yz_plane())) )
primitives.push_back( translate(vec(0,0,-2), decorate(back_wall_material, xy_plane())) )
var sphere_position = Animations.circular( [ "position": pos(0,0,1),
"around": pos(0,0,0),
"duration": seconds(5) ] )
primitives.push_back( decorate( sphere_material, translate(sphere_position[now] - pos(0,0,0), scale(0.5, 0.5, 0.5, sphere())) ) )
primitives.push_back( decorate( small_sphere_material, scale(0.25, 0.25, 0.25, sphere()) ) )
var root = union(primitives)
var lights = [ Lights.omnidirectional( pos(0,1.9,0), Colors.white() ) ]
create_scene(camera, root, lights)
}
var raytracer = Raytracers.v1()
var renderer = Renderers.standard( [ "width": 500,
"height": 500,
"sampler": Samplers.multijittered(2),
"ray_tracer": raytracer ] )
pipeline( scene_animation(scene_at, seconds(5)),
[ Pipeline.animation(30),
Pipeline.renderer(renderer),
Pipeline.studio() ] )
1. Introducing Ray Tracer v1
Go take a look at the code for the original ray tracer in raytracers/ray-tracer-v0
.
The trace
member function must (among other things), given a ray and a scene, determine which object in the scene is hit by the ray first
and find out what color this object has.
Ray tracer v0 is a rather lazy implementation: if there’s a hit, it simply returns white instead of actually trying to find out which color the object has. Ray tracer v1 will improve upon it.
Let’s first simply make a copy of Add the following files to the project:
Change all occurrences of Make sure everything compiles. |
We want to test this "new" ray tracer by using it in a script.
However, the scripting language has not yet been informed of RayTracerV1
's existence.
We need to add a binding.
All script-related functionality resides in the
As always, check that everything compiles.
Create a simple script that references |
2. Implementing Ambient Lighting
Ray tracer v1 adds support for ambient lighting. Be sure to read the linked material as otherwise you will not understand what you need to implement.
There are multiple ways to introduce ambient lighting to our RayTracer. We chose to associate an ambient lighting factor to materials: you can create a sphere with a lot of ambient lighting and one just next to the sphere with no ambient lighting at all. This is highly unrealistic, but it’s up to the creator of the scene to avoid such pitfalls.
In order to add support for ambient lighting, you need to update RayTracerV1::trace
.
Let’s first take a look at its current implementation (i.e., the one you copied from RayTraacerV0::trace
).
TraceResult raytracer::raytracers::_private_::RayTracerV0::trace(const Scene& scene, // (1)
const Ray& eye_ray // (2)
) const
{
Hit hit; // (3)
if (scene.root->find_first_positive_hit(eye_ray, &hit)) // (4)
{
Color hit_color = colors::white(); // (5)
unsigned group_id = hit.group_id; // (6)
double t = hit.t; // (7)
return TraceResult(hit_color, group_id, eye_ray, t); // (8)
}
else
{
return TraceResult::no_hit(eye_ray); // (9)
}
}
-
The
scene
parameter represents the entire scene, i.e., the primitives it contains, the lights and the camera. -
The
eye_ray
starts in the camera’s eye and travels through the scene. We need to find where it hits the scene and determine how to render that point. -
A
Hit
object is allocated on the stack. -
scene.root
represents the entirety of all shapes in the scene as a single root object.find_first_positive_hit
asks the root object whether theeye_ray
does intersect with it. If so,true
is returned and thehit
object is filled in with extra information. -
If there’s a hit,
v0
decides to return white as color. -
group_id
s are required to implement edge rendering. You can ignore this line. -
hit.t
tells us where oneye_ray
the ray/scene intersection occurred. -
We construct a
TraceResult
that contains all relevant information about the hit. -
Here, we deal with the case where
eye_ray
does not hit the scene anywhere. We return an "empty"TraceResult
.
We’ll do a slight bit of overengineering so that it becomes easier to build on top of it later. Visually, the structure looks as follows:
2.1. compute_ambient
Add a protected method with signature |
2.2. determine_color
Add a method
|
2.3. trace
determine_color
will need to be called by trace
in order to, well, determine the color.
Instead of assigning colors::white
to hit_color
, assign determine_color
's result.
There’s a snag though: determine_color
needs a MaterialProperties
argument.
To find out where to find these MaterialProperties
, you should keep in mind what we’re trying to implement.
The material’s properties must depend on where the eye_ray
hits the scene, and all information regarding that hit can, unsurprisingly, be found in hit
.
hit
contains a material
field of type Material
.
Clearly we’re on the right track.
The idea is that a Material
represents the material the object hit by the eye_ray
is made out of, for example wood.
However, materials are seldom uniform: wood tends to have lines and circles and shades of brown.
In other words, we need location-specific material information.
This is where Material::at
comes in: it asks a Material
"what color are you at this specific location?"
Interestingly, this at
method returns a MaterialProperties
object.
Unfortunately, at
requires a HitPosition
as argument…
Where would we find a HitPosition
?
Again, it makes sense to look around in Hit
.
Adapt |
3. Evaluation
Reproduce the scene below.

Tip
|
Ray tracer v0 did not need primitives to be assigned materials. However, v1 does (as should be apparent from your code). So, don’t forget to decorate your shapes with a material:
|
Tip
|
In the scripting language, you can add colors together using |