This assignment should be done using the Advanced student language.
In this project, you will extend your simple raytracer from Project 1 with additional object and light types, and a more general shading model.
Make sure that your submitted solution compiles; i.e., that there are no syntax errors or undefined variables. Submissions that do not compile will get 0 points!!!
Your first step should be to address any issues raised by the grader for your Project 1.
Your program should be clearly organized into sections with your data definitions at the top of your code.
For this project, you will have to both modify and add to your data definitions. Read the descriptions carefully.
A surface defines the properties of an object's surface and is defined by
For this project, you will render only two kinds of object: spheres and planes.
A sphere has a position (vec3), a radius (num), and a surface color (surface). Note that the surface is now specified as a surface value.
A plane has a normal (vec3), a signed distance from the plane to the origin in the direction of the normal (num), and a surface (surface). The normal vector determines which side of the plane is the front. For example, the plane with surface s that is defined by Z = 5, can be defined as either facing the origin
(make-plane (make-vec3 0 0 -1) 5 s)
or facing away from the origin
(make-plane (make-vec3 0 0 1) -5 s)
Notice that if the plane is facing the origin, then the distance will be positive, while if it is facing away from the origin, the distance will be negative.
A light is either a dir-light
or a point-light
A dir-light is represented by a direction vector
that points toward the light source (a unit vec3)
and a color (rgb).
A point-light is represented by a position
(vec3)
and a color (rgb).
You may find the following helper functions useful in your code (you may have to adjust the
field names to match your data definitions):
A scene has a background color (rgb),
a list of objects (list object),
a list of lights light source (list light), and
an ambient light level (rgb).
Note that both the background color and ambient light colors are now specified as RGB values.
Cameras are as in Project 1.
A hit-test is either a hit or 'miss, where
a hit consists of the distance along the ray where the hit occurs,
the surface color at the hit point (surface), and
the surface normal at the surface location (a unit vec3).
You will need to add a new intersection test for planes and you may need to modify your sphere-intersection
test for changes in your data definitions.
Given a ray R(t) = Ro + tRd and a plane defined by its normal Pn and distance to the origin d,
the point of intersection is given by the formula:
For this project, your ray tracer will model both diffuse light and reflected light.
The following picture shows the situation where a ray R(t) = Ro + tRd
has hit a surface:
The lllumination at that point is computed as follows:
where
We will break the computation of this function into three parts
To get the final light at the surface, we sum these three components.
The lighting function (described below) is responsible
for summing the illumination from the lights, as well as adding in the ambient and
reflected light.
The most complicated part of the illumination model is dealing with the lights.
You should write a function that computes the illumination (both diffuse and specular) for a single
light
This function should first test to see if the light is behind the surface (i.e., the
(N dot Lj) <= 0) or the intersection point is in shadow.
In that case, you can return black without any further computation.
Note that if (N dot Lj) > 0), then you do not need the
max calls that are in the illumination equation.
This function should use the shadowed-by? to detect shadows
(if you wish, you may merge the shadowed-by?
code into illuminate).
The Hj vector is computed by
;; light-direction : vec3 light -> vec3
;; Return the unit vector that points from the position pos
;; toward the light source
(define (light-direction pos light)
(cond
[(dir-light? light) (dir-light-dir light)]
[(point-light? light)
(v3-normalize (v3-sub (point-light-pos light) pos))]
[else (error 'light-direction "not a light")]))
;; light-rgb : light -> rgb
;; Return the color of the light
(define (light-rgb light)
(cond
[(dir-light? light) (dir-light-rgb light)]
[(point-light? light) (point-light-rgb light)]
[else (error 'light-rgb "not a light")]))
Scenes
Cameras
Hit tests
Intersection testing
Plane intersection testing
t = -(Pn dot Ro + d) / (Pn dot Rd)
If abs(Pn dot Rd) < ε (for small ε) then the ray is esentially parallel to the
plane and we will treat it as a miss.
Illumination model
;; illuminate : (list object) ray hit -> light -> rgb
;; given the objects in the scene, a ray, and a hit for the ray,
;; compute the illumination for the light
(define (illuminate objs R hit) ...)
(define Hj (v3-normalize (v3-sub Lj Rd)))
To compute the illumination Is from direction S, we recursively trace the ray that starts at the intersection position and has direction S. As with shadow rays, you will want to start a small distance off the surface to avoid precision problems. The vector S is called the reflection vector and forms the same angle with N as the vector to the viewer does (this angle is labeled θ in the picture). It can be computed by
(define S (v3-add (v3-scale (* -2 (v3-dot Rd N)) N) Rd))
(The proof that this is a unit vector is left up to the reader).
Because the tracing of rays and computation of lighting is mutually recursive, you will need a different structure for this part of the code. We suggest defining the trace function as before, with the revised type
;; trace : scene -> ray nat -> rgb ;; Given a scene, return a function for tracing the ray through the scene to compute an RGB value ;; The second argument to the tracing function specifies a recursive depth ;; cutoff. (define (trace scene) ...)
Inside the trace function, you should define two mutually recursive functions: one that computes the lighting (as described above) and one that traces a ray through the scene. The types of these functions should be
;; tr : ray nat -> rgb ;; lighting : ray hit-test nat -> rgb
To keep the computation bounded, we limit the depth of recursive calls by adding an extra argument to both functions that is decremented when lighting calls tr. When the value gets to zero, then tr should just return black instead of testing for intersections.
Finally, the raytrace function will have to be modified to take a depth limit.
;; raytrace : camera scene nat -> image ;; Generate an image for the given scene. The depth argument is ;; the cutoff for recursive ray tracing (define (raytrace cam scene depth) ...)
Here are new versions of the Project 1 test cases. They should render as before.
;; diffuse-surf : symbol -> surface ;; return a non-reflective surface with the named color (define (diffuse-surf c) (make-surface (name->rgb c) (name->rgb 'black) 0)) (define test-sphere-1 (make-sphere (make-vec3 0 0 3) 1 (diffuse-surf 'red))) (define test-camera-1 (make-camera -5 200 200)) (define test-camera-2 (make-camera -2.5 400 200)) (define test-scene-1 (make-scene (make-rgb 0 0 0) (list test-sphere-1) (list (make-dir-light (v3-normalize (make-vec3 -1 1 -1)) (name->rgb 'white))) (make-rgb 0.2 0.2 0.2))) (define test-sphere-2 (make-sphere (make-vec3 1 0 5) 1 (diffuse-surf 'orange))) (define test-scene-2 (make-scene (make-rgb 0 0 0) (list test-sphere-1 test-sphere-2) (list (make-dir-light (v3-normalize (make-vec3 -1 1 -1)) (name->rgb 'white))) (make-rgb 0.2 0.2 0.2))) (define test-sphere-3 (make-sphere (make-vec3 1 -1 4) 2 (diffuse-surf 'orange))) (define test-sphere-4 (make-sphere (make-vec3 -1 1 2.75) 0.6 (diffuse-surf 'blue))) (define test-scene-3 (make-scene (make-rgb 0 0 0) (list test-sphere-3 test-sphere-4) (list (make-dir-light (v3-normalize (make-vec3 -1 1 -0.5)) (name->rgb 'white))) (make-rgb 0.2 0.2 0.3)))
Note that you should be able to increase the depth value to 2 or 3 without changing the result on these tests.
Here are the resulting images for the scenes:
Here is an example that uses a diffuse plane and a point light.
(define test-camera-3 (make-camera -2 480 320)) (define test-plane-1 (make-plane (make-vec3 0 1 0) 1 (make-surface (make-rgb 0.2 0.8 0.2) (make-rgb 0 0 0) 1))) (define test-sphere-5 (make-sphere (make-vec3 0 -1 6) 0.6 (diffuse-surf 'blue))) (define test-scene-4 (make-scene (make-rgb 0 0 0) (list test-plane-1 test-sphere-5) (list (make-point-light (make-vec3 0 1 4) (name->rgb 'white))) (make-rgb 0.2 0.2 0.2)))
Here is an example that tests reflections.
(define test-camera-4 (make-camera -1 320 240)) (define test-scene-5 (make-scene (make-rgb 0 0 0) (list (make-plane (make-vec3 0 1 0) 3 (diffuse-surf 'white)) (make-sphere (make-vec3 -1 0 3) 1 (make-surface (make-rgb 0.7 0.2 0.3) (make-rgb 0.2 0.04 0.06) 1)) (make-sphere (make-vec3 1 1 3) 1 (make-surface (make-rgb 0.8 0.2 0.2) (make-rgb 0.2 0.04 0.04) 1))) (list (make-dir-light (v3-normalize (make-vec3 -1 1 -1)) (make-rgb 0.8 0.8 0.8))) (make-rgb 0.4 0.4 0.4)))
And here is an example that tests specular highlights.
(define test-scene-6 (make-scene (make-rgb 0 0 0) (list (make-plane (make-vec3 0 1 0) 2 (make-surface (make-rgb 0 0.6 0) (make-rgb 1 1 1) 20)) (make-sphere (make-vec3 -1.5 -2 4) 0.5 (diffuse-surf 'red))) (list (make-dir-light (v3-normalize (make-vec3 0 2 3)) (make-rgb 0.8 0.8 0.8))) (make-rgb 0.2 0.2 0.2)))
Here is a version at twice the resolution
Last revised: November 23, 2010.