CSCI 441 - Computer Graphics

Fall 2016 - Side Quest 2 - Raytracing



|   Home |  Syllabus |  Assignments |  Leaderboard |  Schedule |  Resources   |
This Side Quest is due by October 24, 2016 by 11:59pm to earn the achievement.


Part I - Raytracing


For this Side Quest, you will write a basic raytracer and implement some of the functionality that raytracers enable you as a programmer to add with relative ease. You will start the assignment with base code, and will implement a number of elements of functionality: the ability to calculate a camera ray for a given pixel, the ability to intersect rays against spheres and planes, Phong reflectance to shade the point that gets hit by a camera ray, the addition of a shadow ray cast at each intersection point to implement hard shadows, and the addition of a reflection ray to simulate shiny objects. Your final product will be able to support multiple objects, multiple lights, and varying material properties, all of which can be read in from file.

a rendered frame from the raytracer.

The base code that you are given is a basic OpenGL program that can load spheres and planes in from file and display them with OpenGL in a "preview mode"; the user can navigate around the scene using a camera that follows the free-camera movement paradigm, and the objects drawn in the OpenGL preview accurately reflect the object data that is read in from file. When the user presses the 'r' key, a callback is triggered that will perform the raytracing code. This callback takes the list of objects and lights in the scene, as well as the camera object, and generates, as its output, a framebuffer that is the size of the window when it was triggered, containing the final image. That image is then attached to a texture, which is rendered over a quad covering the screen. The frame is also written to the file "output.ppm" in the current directory.

the previous frame, in OpenGL preview mode.


Base Code


You can dowload the base code this assignment here. There are a number of classes that encapsulate the work that needs to be done in an OpenGL pipeline; familiar to you should be the Point, FreeCamera, Material, and PointLight classes, which are all very similar to the classes you have used or created for previous or upcoming homework assignments. There are a few new classes that are also particularly relevant to this assignment. They are:

Ray: this class is a storage class for rays: it holds two points, representing an origin and a direction. Note that it doesn't have member functions to ensure that direction is unit when set or read; adding these might be helpful.

Shape: this is an abstract class that allows us to lump all of our renderable objects into the same vector inside of main.cpp. Instead of having separate vectors for Spheres, Planes, Triangles, Meshes, etc., all of those classes extend the Shape class and can be used interchangeably. (i.e. shapes[i]->draw(); regardless of which object type it is.) All classes that inherit from the Shape class must implement the draw() function, which draws the object using OpenGL calls, and the intersectWithRay function, which computes the intersection between a ray and the object and returns information about the intersection, if it ocurred.

Sphere: has a position (center of the sphere) and a radius. Already contains an implemented draw() function so that it can be used in Preview Mode. You will need to implement its intersectWithRay function.

Plane: defined by a point on the plane and the normal of the plane. Already contains an implemented draw() function, but note that it draws the plane as a finite, bounded rectangle (in reality, the plane extends infinitely). Because OpenGL does lighting per-vertex, this means that the plane preview will likely be much darker than the real plane (since the far-off vertices will be at sharp angles to the light source). You will need to implement its intersectWithRay function.

IntersectionData: this is a storage class to hold information about an intersection, if one occurs. It is returned from the intersectWithRay() functions: if the ray passed into that function intersected that object, then the wasValidIntersection boolean member variable should be set. If this intersection was valid, then its member variables will hold a) the point of intersection, b) the surface normal of the object at the point of intersection, and c) a copy of the object's Material object. Your intersection functions must fill these member variables.

Places where you need to write code are indicated with large, block comments. Pressing 'r' calls a raytraceImage() function which is located in helper.cpp. This function needs to allocate an output image and fill each pixel with a color based on the scene.


Camera Rays


The first step of your raytracer is computing the camera ray (primary ray) for each pixel of an image. You will need to fill in the FreeCamera::getPrimaryRay() function in freeCamera.cpp. (See also the getPrimaryRayThroughPixel function.) For more information on computing primary rays, see this website. Note that thanks to our OpenGL formulation, we have the vertical field of view and must compute the horizontal field of view.

You may find it easier to think of the terms involved in camera ray computation as a vector math problem. If we assume that the image plane is a unit distance away from the camera, we can get a point on the center of the image plane by adding the camera's position to its direction. We can compute the width and height of the image plane using the tangent functions as described in the link above. Lastly, we can determine the vectors that lie in the image plane -- the first is the camera's local X axis, perpendicular to the viewing direction and global up vector (0,1,0), and the second is the camera's local Y axis, perpendicular to its local X axis and local Z axis (direction) -- and move along these vectors based on the percentage of the image plane's width and height.

Intersection Testing and Shading


After being able to calculate the camera ray through each pixel, you need to intersect that ray against all objects in the scene and save the nearest intersection, if any. This can be done by iterating over each object and calling its intersectWithRay() function -- but you will need to implement these functions for the Sphere and Plane classes. Start with Sphere and move on to Plane after that is confirmed to work.

Once you know a ray has intersected with an object, and your intersectWithRay() function has appropriately filled in the intersection point, surface normal, and material in the IntersectionData object, you must compute a final color for the pixel. You must do this using the Phong Reflectance Model as discussed in class. Basically, shading a pixel requires iterating over all lights and summing the contribution from each light. The diffuse intensity is the dot product of the surface normal and light direction (or 0, if negative), and this component is multiplied by the diffuse colors of the material and the light. Specularity calculation is carried out as in the slides as well, as the dot product of the viewing vector (camera ray) and reflection vector raised to the shininess exponent. Ambient terms are simply added to the point (light ambient * material ambient). Note that the Point class included with the base code overrides the * operator for two points to mean the element-wise product of those two vectors (which you will likely use a lot in your lighting calcluations).


Shadows and Reflections


Before computing the diffuse and specular contributions from a light, you must determine whether that point is in shadow. To do this, you need to test a ray for intersection against the scene whose origin is the intersection point and whose direction points toward that light. (NOTE: to prevent rounding errors, move the intersection point a small amount along the direction vector before doing testing.) If there are any intersections, the point should receive only the ambient component of the lighting equation.

Lastly, you will notice that the Material class has one extra parameter -- reflectivity. When shading a point, if the reflectivity of an object is greater than zero, you must cast a reflection ray to determine the reflected color. You can compute the direction of this reflected ray using the equations (direction - 2*normal*(dot(direction,normal))), and the same NOTE: as with shadow rays applies: you should move the intersection point a small amount along the reflection vector to avoid rounding errors. After finding the reflected color, you must compute the final color of the point as (reflectivity * reflectedColor + (1 - reflectivity)*phongColor).


Structuring Your Code


Since the operations involved with determining shadows and reflection are recursive, it makes sense to write functions to support this. For instance, write a function called intersectRayAgainstScene that intersects a ray against all objects and returns the closest intersection, if any. This will keep your raytraceImage function much cleaner. Additionally, you will want to have a second function, shadeIntersection, that computes the color for a given intersection point and returns that. It makes sense to put this as its own function, since you will have to handle shadow rays (use the intersectRayAgainstScene function) and reflection rays (intersectRayAgainstScene with a reflection ray and then recursively call shadeInterscetion to determine the reflected color). For shadow rays, you may want to write a version of intersectRayAgainstScene that returns true as soon as possible if it intersects any object, and doesn't bother computing the nearest intersection, for speed purposes. Additionally, inside of shadePoint(), you may want to clamp your color value before returning it, in case it lies outside the range of [0,1].


scene.txt File Format


The scene.txt file format is outlined below. There are four different types of lines that are accepted as input. First, a material must be specified that will be applied to all the following shapes. Any number of spheres and planes may be specified. Lastly, at least one light must be specified, but any number can be accepted.
material diffuseR G B ambientR G B specularR G B shininess reflectance
sphere x y z radius
plane x y z normalX Y Z
light x y z diffuseR G B ambientR G B specularR G B radius
The material has the diffuse, ambient, and specular colors specified along with the shininess. The reflectance value states how reflective the material is. You can use this value to lerp between the object's material and the reflected ray's material. A value of 0.0 is no reflectance where as a value of 1.0 is full reflectance (the perfect reflector we alluded to).

The sphere is represented by some (x, y, z) point in space with a radius r.

The plane is represented by point-normal form. A point in the plane is (x, y, z) with surface normal (X, Y, Z).

The light is positioned at (x, y, z) with diffuse, ambient, and specular colors. For debug purposes when in preview mode, it has a size radius to display as a sphere.

Take a look at the included scene.txt file (which is the default file) for how these are used in conjunction. Feel free to create new scene files experimenting with different positions, materials, reflective objects, etc. If you are feeling particularly adventuresome, modify the material class to accept a refractive index and alpha component. Any object that has a refractive material would need to spawn a refracted ray and blend in the color some alpha amount to make the object transparent.


Part II - Website


Update the webpage that you submitted with GuildWars to include an entry for this Side Quest. As usual, include a screenshot (or two) and a brief description of the program, intended to showcase what your program does to people who are not familiar with the assignment.


Documentation


With this and all future assignments, you are expected to appropriately document your code. This includes writing comments in your source code - remember that your comments should explain what a piece of code is supposed to do and why; don't just re-write what the code says in plain English. Comments serve the dual purpose of explaining your code to someone unfamiliar with it and assisting in debugging. If you know what a piece of code is supposed to be doing, you can figure out where it's going awry more easily.

Proper documentation also means including a README.txt file with your submission. In your submission folder, always include a file called README.txt that lists:
  • Your Name / email
  • Assignment Number / Project Title
  • A brief, high level description of what the program is / does
  • A usage section, explaining how to run the program, which keys perform which actions, etc.
  • Instructions on compiling your code
  • Notes about bugs, implementation details, etc. if necessary
  • How long did this assignment take you?
  • How much did the lab help you for this assignment? 1-10 (1 - did not help at all, 10 - this was exactly the same as the lab)
  • How fun was this assignment? 1-10 (1 - discontinue this assignment, 10 - I wish I had more time to make it even better)


Grading Rubric


To earn the achievement, your raytracer must exhibit these qualities:
  1. Camera rays are computed correctly.
  2. Intersection testing is implemented for spheres and planes.
  3. Shading is computed in accordance to the Phong reflectance model.
  4. Shadows are implemented correctly.
  5. Reflections are implemented correctly.
IN ADDITION: IN ORDER TO EARN THE ACHIEVEMENT, THIS SIDE QUEST MUST BE SUBMITTED PRIOR TO OCT. 24, 2016 11:59PM. LATE SUBMISSIONS WILL BE ACCEPTED AND ADDED TO THE STUDENT'S PAGE, BUT THE ACHIEVEMENT WILL NOT BE AWARDED FOR A LATE SUBMISSION.


Experience Gained & Available Achievements


Web Attribute
Web +50 XP
Dr. Ray Stantz
Dr. Ray Stantz


Submission


Please update you Makefile so it produces an executable with the name sq2. When you are completed with the assignment, zip together your source code, Makefile, README.txt, and www/ folder. Name the zip file, HeroName_SQ2.zip. Upload this file to Blackboard under SideQuest2.


This Side Quest is due by October 24, 2016 by 11:59pm to earn the achievement.
Last Updated: 01/01/70 00:00


Valid HTML 4.01 Strict Valid CSS! Level Triple-A conformance, W3C WAI Web Content Accessibility Guidelines 2.0