This programming assignment creates 3D imagery using ray tracing. In most other respects, its logistics are similar to the rasterizer assignment: you code in any language you want and your program reads a text file and produces an image file.
Implement a raytracer with spheres, diffuse lighting, and shadows. In particular, this means
Write a program that reads a .txt
file and produces
a .png
file. It will be invoked as e.g. ./yourprogram exampleInput.txt
,
via a makefile.
Handle the input file keywords png
,
color
, sphere
, and one sun
, with
proper handling of sRGB gamma.
Implement the ray-sphere intersection algorithm. These algorithms are defined down to the pixel in almost all contexts, and should match the provided input files and their outputs very closely. Almost all successful submissions follow our ray-sphere intersection pseduocode closely.
Implement shadows with shadow rays, including preventing shadow acne.
You may stop after implementing the core parts. Elective parts may be implemented in any set of MPs, and getting 0 electives here is fine if you do extra electives elsewhere.
Pt | Task | Prereqs | Keywords | Test cases |
---|---|---|---|---|
0.5 | exposure | expose |
ray-expose1.txt and ray-expose2.txt | |
0.5 | suns | more than one sun |
ray-suns.txt and ray-shadow-suns.txt | |
1 | camera | eye , forward , up |
ray-view.txt | |
1 | lenses | fisheye , panorama |
ray-fisheye.txt and ray-panorama.txt | |
1 | plane | plane |
ray-plane.txt and ray-shadow-plane.txt | |
2 | triangle | plane | xyz ,tri |
ray-tri.txt and ray-shadow-triangle.txt |
1 | map | texture |
ray-tex.txt | |
2 | barycentric | map, triangle | texcoord |
ray-trit.txt |
2 | bulb | suns | bulb |
ray-bulb.txt and ray-shadow-bulb.txt and ray-neglight.txt |
2 | reflect | shininess , bounces |
ray-shine1.txt and ray-shine3.txt and ray-bounces.txt | |
2 | refract | reflect | transparency , ior |
ray-trans1.txt and ray-trans3.txt and ray-ior.txt |
2 | rough | reflect | roughness |
ray-rough.txt |
2 | antialias | aa |
ray-aa.txt | |
1 | focus | antialias | dof |
ray-dof.txt |
3 | global | antialias, triangle | gi |
ray-gi.txt |
3 | BVH | suns | render ray-many.txt in under 1 second on our testing server (a 7GHz AMD processor) by using a bounding volume hierarchy, a fast-to-run programming language, and general code optimization |
For this MP you submit one program, in any language of your choosing, that implements all of the core and any elective functionality you choose. The program will be executed as follows:
make build
make run file=rast-grey.txt
make run file=rast-smallgap.txt
# ...
make run file=rast-points2.txt
See the associated warm-up for more on how to set up a Makefile and generate PNG images.
It is tedious to grade output files for inputs you haven’t
implemented. Because of that you’ll be asked to submit a file named
implemented.txt
which lists the optional parts you
implemented; in particular, it should be a subset of the following
exposure
suns
camera
lenses
plane
triangle
barycentric
map
bulb
reflect
refract
rough
antialias
focus
global
BVH
Submitting a file that says you implemented something you didn’t may result in a small professionalism penalty for wasting grader time.
All test input files, reference output files, and supporting files can be downloaded as a zip
Input | Output | Notes |
---|---|---|
ray-sphere.txt | Because there is no sun, nothing is lit. If you see the central sphere but not the one in the corner, the most common reason is failing to normalize ray directions when they are first created. | |
ray-sun.txt | Numbers generated for pixel (55, 45)Ray origin: (0, 0, 0)Ray direction: (0.0990148, -0.0990148, -0.990148) Intersection depth: 0.724832 Intersection point: (0.0717691, -0.0717691, -0.717691) Surface normal: (0.23923, -0.23923, 0.94103) Sun direction: (0.57735, 0.57735, 0.57735) Lambert dot product: 0.543304 Linear color: (0.543304, 0.543304, 0.543304) sRGB color: (194, 194, 194) Numbers generated for pixel (82, 70)Ray origin: (0, 0, 0)Ray direction: (0.481108, -0.451039, -0.751731) Intersection depth: 1.20665 Intersection point: (0.58053, -0.544246, -0.907077) Surface normal: (-0.838941, 0.511507, 0.185845) Sun direction: (0.57735, 0.57735, 0.57735) Lambert dot product: -0.0817462 Lienar color: (0, 0, 0) sRGB color: (0, 0, 0) |
|
ray-color.txt | ||
ray-overlap.txt | ||
ray-behind.txt | There’s another sphere behind the camera that should not be visible. | |
ray-shadow-basic.txt |
Input | Output | Notes |
---|---|---|
ray-expose1.txt | low exposure; note the lit colors for some pixels exceed 1 before exposure but are darkened by exposure to a distinguishable range | |
ray-expose2.txt | high exposure | |
ray-suns.txt | several colored suns | |
ray-shadow-suns.txt | multiple suns means multiple shadows | |
ray-view.txt | moved and rotated camera | |
ray-fisheye.txt | ||
ray-panorama.txt | ||
ray-plane.txt | two intersecting planes | |
ray-shadow-plane.txt | multiple planes and spheres intersecting with shadows | |
ray-tri.txt | tests that triangle boundaries line up with vertex locations | |
ray-shadow-triangle.txt | triangles casting and receiving shadows | |
ray-tex.txt | spheres with textures; uses earth.png and moon.png as texture maps | |
ray-trit.txt | triangles with textures; uses earth.png and moon.png as texture maps | |
ray-bulb.txt | two bulbs, one sun; if your image is darker than this one, make sure negative dot products during lighting are clamped to 0 | |
ray-shadow-bulb.txt | spheres with a light between them should not shadow one another | |
ray-neglight.txt | one of the lights has negative color, emitting darkness; impossible in the real world but not in a computer | |
ray-shine1.txt | colorless reflectivity | |
ray-shine3.txt | colored reflectivity | |
ray-bounces.txt | custom levels of recursion | |
ray-trans1.txt | colorless transparency | |
ray-trans3.txt | colored transparency | |
ray-ior.txt | different indices of refraction on each sphere | |
ray-rough.txt | different roughness with both reflections and diffuse light; based on random sampling so each run will be slightly different | |
ray-aa.txt | anti-aliasing; based on random sampling so each run will be slightly different | |
ray-dof.txt | depth-of-field effects; based on random sampling so each run will be slightly different | |
ray-gi.txt | global illumination; based on random sampling so each run will be slightly different | |
ray-many.txt | 10,000 spheres; will be run single-threaded only; a simple BVH adds between a 10× and 50× speedup to this scene |
Rays will be generated from a point to pass through a grid in the
scene. This corresponds to flat projection,
the same kind that
frustum matrices achieve. Given an image w pixels wide and h pixels high, pixel (x, y)’s ray will be based the following
scalars:
s_x = {{2 x - w} \over {\max(w, h)}}
s_y = {{h - 2 y} \over {\max(w, h)}}
These formulas ensure that s_x and s_y correspond to where on the screen the pixel is: s_x is negative on the left, positive on the right; s_y is negative on the bottom, positive on the top. To turn these into rays we need some additional vectors:
The ray for a given (s_x, s_y) has origin \mathbf{e} and direction \vec f + s_x \vec r + s_y \vec u.
Each ray will might collide with many objects. Each collision can be characterized as o + t \vec{d} for the ray’s origin point o and direction vector \vec{d} and a numeric distance t. Use the closest collision in front of the eye (that is, the collision with the smallest positive t).
Raytracing requires every ray be checked against the full scene of
objects. As such your code will proceed in two stages: stage 1 reads the
input file and sets up a data structure storing all objects; stage 2
loops over all rays and intersects each with objects in the scene.
Unlike the rasterizer, there is no explicit draw
instruction: all
scene geometry in the input file is drawn after you read the whole
file.
Many of the elective parts of this assignment have rays spawn new rays upon collision. As such, most successful implementations have a function that, given a ray, returns which object it hit (if any) and where that hit occurred (the t value may be enough, but barycentric cordinates may also be useful); that ray-collision function is called from a separate function that handles lighting, shadows, and so on.
Basic illumination uses Lambert’s law: Sum (object color) times (light color) times (normal dot direction to light) over all lights to find the color of the pixel.
Make all objects two-sided. That is, if the normal points away from the ray (i.e. if \vec d \cdot \vec n > 0), invert the normal before doing lighting.
Illumination will be in linear space, not sRGB, and in 0–1 space, not 0–255. If a color would be brighter than 1 or dimmer than 0, clamp it to the 0–1 range. You’ll need to convert RGB (but not alpha) to sRGB yourself prior to saving the image, using the official sRGB gamma function: L_{\text{sRGB}} = \begin{cases} 12.92 L_{\text{linear}} &\text{if }L_{\text{linear}} \le 0.0031308 \\ 1.055{L_{\text{linear}}}^{1/2.4}-0.055 &\text{if }L_{\text{linear}} > 0.0031308 \end{cases} If you implement anti-aliasing, reflections, transparency, or other techniques that blend multiple colors, make sure you don’t convert to sRGB until after all the colors are combined.
To help the files not get messy when many different properties are specified, many options operate on a notional state machine.
For example, when you open the file the current color is
white (1,1,1). Any sphere
or triangle
you see will be given the current color as its
color. When you see a color
command you’ll change the
current color. Thus in this file
png 20 30 demo.png
sphere 0 0 0 0.1
color 1 0 0
sphere 0.5 0 0 0.2
sphere 0.3 0 0 0.3
color 0 1 0
the first sphere is white, the second and third spheres are red and
the last color
doesn’t do anything.
The file may have three types of keywords:
png
keyword will always be firstNo drawing happens until after the entire file is read.
png
width height
filename.png
bounces
dforward
f_x f_y f_zup
u_x u_y u_zforward
for how the real up vector is computedeye
e_x e_y e_zexpose
vFancier exposure functions used in industry graphics, such as FiLMiC’s popular Log V2, are based on large look-up tables instead of simple math but are conceptually similar to this function.
dof
focus lenseye
which is perpendicular to the
forward vector \vec faa
npanorama
dof
or
fisheye
fisheye
dof
, panorama
,
or non-unit-length forward
gi
dbounces
keywordcolor
r g btexcoord
u vxyz
keywordstexture
filename.pngtri
and
sphere
keywordstexcoord
texture none
disables textures, reverting to
color
none
if not providedroughness
\sigmatri
and sphere
keywordsshininess
s_r s_g s_bsets the ratio of light that is reflected instead of being diffused, absorbed, or refracted
if given with just one parameter, like
shininess 0.4
, use it for all three color channels
from page 153 of the glsl spec, the reflected ray’s direction is \vec{I} - 2(\vec{N} \cdot \vec{I})\vec{N} where \vec{I} is the incident vector, \vec{N} the unit-length surface normal
reflected rays that hit nothing should be treated as opaque black
when combining transparency and shininess, shininess takes precedent
the input snippet
shininess 0.6
transparency 0.2
combines to make an object 0.6 shiny, (1 − 0.6) × 0.2 = 0.08
transparent, and (1 − 0.6) × (1 − 0.2) = 0.32 diffuse
see bounces
for preventing infinite recursion when
two shiny objects reflect one another
defaults to 0 if not provided
transparency
t_r t_g t_btransparency 0.4
, use it for all three color channelsshininess
insteadior
for the index of refraction to usebounces
for preventing infinite recursion when two
shiny objects reflect one anotherior
\etasphere
x y z rsun
x y zbulb
x y zplane
A B C Dxyz
x y ztri
commandstexcoord
tri
, not xyz
tri
tri
i_1 i_2 i_3xyz
commandsWe have a few larger scenes you can try if you want something a bit more challenging: