Difficulty |
4 |
Prerequisites |
|
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() * 0.1,
"diffuse": Colors.white() * 0.8,
"reflectivity": 0.5 ] )
var left_wall_material = Materials.uniform( [ "ambient": Colors.red() * 0.1,
"diffuse": Colors.red() * 0.8 ] )
var right_wall_material = Materials.uniform( [ "ambient": Colors.green() * 0.1,
"diffuse": Colors.green() * 0.8 ] )
var back_wall_material = Materials.uniform( [ "ambient": Colors.blue() * 0.1,
"diffuse": Colors.blue() * 0.8 ] )
var ceiling_material = floor_material
var sphere_material = Materials.uniform( [ "ambient": Colors.blue() * 0.1,
"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() * 0.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.v8()
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. Background Information
First, familiarize yourself with refraction.
Also go take a look at MaterialProperties
: you’ll find you’ll need the member variables
refractive_index
and transparency
for this extension.
Correctly implementing refraction is not an easy task. We choose to introduce the following simplifications:
-
Technically, we should find out what medium the light ray originates in. In our implementation, we will make the assumption we always start in vacuum.
-
When encountering a transparent material, we assume that, after bending the ray, the next intersection brings us back to the outside world (i.e. \(n = 1\)). In reality, it can happen that upon exiting the \(n_1\)-material, we immediately enter a new \(n_2\)-material, but we ignore this possibility.
-
We ignore total internal reflection. Whenever it arises, we simply return black.

2. Implementation
Refraction is a new feature that will take its place next to compute_ambient
, compute_reflection
and compute_translucency
.
2.1. MaterialProperties
We need a way to express some materials let light through.
We already used translucency
, so let’s settle for transparency
.
disclaimer
|
Technically, transparent means "lets most of the light through" and translucent means "lets some of the light through". Here, however, we simply use these terms to refer to two different ways of computing "light going through stuff". There is nothing conventional about this usage: don’t expect anyone else to use the terms this way. |
Add extra fields to
|
Add an extra method
The default values are |
Update |
2.2. RayTracerV8
2.2.1. Setting Things Up
Create files |
2.2.2. compute_refraction
The algorithm for refraction then looks as follows:
def refract(i, n, n1, n2):
ox = ...
if internal_refraction:
return None
oy = ...
o = ...
return o
def compute_refraction(scene, material_properties, hit, ray, weight):
transparency = material_properties.transparency
if transparency > 0
n1 = 1
n2 = material_properties.refractive_index
# Ray enters transparent object, compute how it is bent at point P1
P1 = ...
normal_at_P1 = ...
refracted_direction = refract(ray.direction, normal_at_P1, n1, n2)
if not refract_direction:
return black
refracted_ray = Ray(P1, refracted_direction)
# Find exit point P2
exit_hit = find_hit(scene, refracted_ray)
if not exit_hit:
# There is no exit point (1)
return black
P2 = exit_hit.location
normal_at_P2 = ... # (2)
refracted_direction = refract(refracted_ray.direction, normal_at_P2, n2, n1)
if refracted_direction is None:
return black
exit_ray = Ray(P2, refracted_direction)
# Continue tracing ray after it left the transparent object
# Don't forget to decrease weight
return trace(scene, exit_ray, weight * transparency) * transparency
-
This can occur if we’re dealing with a plane: a ray will not go through the same plane twice.
-
Make sure to check that the normal points in the opposite direction of
refracted_ray.direction
Implement
|
2.2.3. determine_color
Let’s not forget to update determine_color
.
Override
|
2.2.4. Finishing Touches
Create a factory function |
Expose |
3. Evaluation
Reproduce the scene below. |