Out: Mar 12. Due: Mar 26.
In your first assignment, you will create a simple ray tracer. Ray tracing is a very powerful algorithm capable of creating complex and realistic images. While you will not be able to generate realistic images in your first assignment, your code will create a lot of interesting effects.
You are to perform this assignment using C++. To ease your development, we are providing a C++ framework to load a scene, perform basic mathematical calculations, and save your image results. Included with the framework are scene files in JSON file format, the correct output images, and scripts to generate the output images from the scene files.
Also you can use the layout of the code as an indication of how to structure your raytracer. We have removed the code of each of the functions you are expected to fill in. If you feel this structure does not suit your needs, feel free to change it, but please leave comments as to what your new functions are meant to do to help us evaluate your code in case of errors.
The final result of this assignment is a simple ray tracer with support for the following features:
Search for the function
put_your_code_here to know what parts you are required to implement. In implementing these requirements, do not worry about malformed input. For example, your camera will never be placed inside a sphere. Also do not worry about hitting the backside of surfaces. Below is a itemized summary of these requirements.
Basic Raytracing. Implement a basic raytrace algorithm that will sample the image plane and generate a picture. Your algorithm should support shadows and reflections. You are not required to implement refraction.
Camera Rays. Implement the functions to generate and transform rays (
camera.h) given an image plane position specified in normalized coordinates, i.e., in [0,1]x[0,1], and the local frame of the camera.
Ray-Scene intersection code. Add code to the
intersect_primitive_first function in
intersect.cpp to support intersection of rays with
PrimitiveGroup. Make sure that you appropriately transform the ray to and from the local frame.
Surface will transform the ray and then intersect the contained
PrimitiveGroup is a list of primitive and you should implement the code that finds the first intersection.
Ray-Sphere and Ray-Triangle intersection code. Add code to the
intersect_shape function in
intersect.cpp to support intersection of spheres and triangles. As a guidance, we have implemented for you
Quad as examples.
Phong. Add code to
reflectance_brdf function in
material.h to support Phong. As a guidance, we have implemented for you
Lambert as examples.
Point light. Implement point lights with falloff in
light.h. To do, implement the computation of light color in
source_radiance and the computation of shadow rays in
source_shadow_ray. For the source you will implement
PointSource. As a guidance, we have implemented for you
DirectionalSource as examples.
Reflections. Add code to
_raytrace_scene_ray function in
raytrace.cpp to support reflections.
Antialiasing. Implement an antialias raytracer by sampling multiple times the image for each pixel. Follow the psuedocode given in class for this. Turn on antialiasing with
RaytracingOpts.samples > 1.
We suggest you use our framework to create your raytracer. We have removed from the code all the function implementations your will need to provide, but we have left the function declarations which can aid you in planning your solution.
Following is a brief description of the framework provided for the raytracer. We have taken various steps to simplify the framework code as much as possible. In particular, you will notice that many functions are "global" and pointers are passed without ownership (e.g. no
shared_pointer) or counting references. Please note that this is not good programming practice! We have done this to help make the code more readable by reducing the length and verbosity of the files. The code is organized in a few directories.
common contains general utilities, such as reading and writing JSON files, simplify the inclusion of standard libraries and debugging facilities. You are not likely to use these directly.
ext contains external libraries we brought into our project. You are not likely to use these directly.
vmath contains low lvel math primitives such as points, vectors, frames, matrices, rays and transformations. We advice you to use these facilities for low level math manipulation. Three dimensional points, vectors and colors (RGB) are implemented with
vec3f. Frames are implemented in
frame3f and defined by their origin
o, and axes
z. Rays are implemented in
ray3f'. To transform points and vectors using a frame, you can use the routines intransform.h`. You will notice that we support vectors and matrices in multiple dimensions. This will become useful later.
apps contains application code. The main entry point of you raytracer is in
trace.cpp. It will load the scene file and invoke the raytracer to generate the image. The image will be saved as a PNG file with a name similar to the scene filename (unless it is specified explicitly). To run the code from the command line terminal, first copy the compiled executable binary (
trace.exe) into the scenes folder. Then
cd into the scenes folder. On Unix, Linux, or OSX, run
./trace scene.json, while on Windows run
trace.exe scene.json. We have provided scripts (the .sh and .bat files) to generate the images for the assignment.
igl contains the data and functions for graphics, namely your raytracer. A graphic scene in stored in
Scene and is comprised of a set of lights
LightGoup, a set of primitives
PrimitiveGroup (thing of them as objects for now) and a camera
Camera. All scene object derived from
Node that provides basic facilities for type selection and serialization. The latter is implemented in
serialize.cpp. We use an extensible JSON format specification that is human rradable for easy debugging. If you need to modify the serialization code please ask.
Camera contains a frame of reference, namely its position and orientation, and a
Lens that describe the light rays. For now, we only have the simplest lens, a pinhole. We suggest that you generate rays in the lens local frame and them use the camera frame to transform them.
Light contains a frame of reference, namely its position and orientation, and a
Source that describes the light color and light ray direction. As before, we suggest that you transform incoming point to the local
Lens coordinate system and outgoing rays back to world coordinates in the
Light. All computation in the local frame are then simplified.
Primitive represent visibible geometry in the scene. We have two types, a
PrimitiveGroup that contains a list of other primitives and a
Surface that represent a single individual object. Each surface is defined by a frame of reference, its shape
Shape and its material
Material. We will support various shapes during the course, but for now you have
Material for now include only the
Reflectance of a surface, manely its BRDF. Later in the course we will add more to it.
The intersection routines used by your raytracer are implemented in
intersect.cpp. Hit information, namely local frame and material, are stored in an
intersection3f. Note that the intersection function should check for the ray limits described below.
C++ is a very large language that very few master fully. In preparing this framework we have attempted to strike a balance code readability and the use of many C++ features. The framework went through many iterations. In the end, we attempted to write that is closer to C and use C++ facilities where we felt the code was so significantly more readable to warrant them. In particular, we make use of
vector<T> instead of naked arrays (think of it as Java's
nullptr instead of 0 for null pointers, operator overloading for basic types, inheritance to define type tags, RTTI to finds types at runtime (see
is<T> shortcuts) and class member initialization (ala Java members). Some of these features are quarely in the C++11 camp, some others can probably considered bad programming practice (such the use of
switch on types). This minimal use of C++ means that if people come from C/Java, it should be reasonably quick to work on our codebase.
We suggest to implement the raytracer following the same steps presented in class. Start simple, by implementing lens ray generation, raytracer sampling, scene intersection and sphere intersection test. Use one of the given scenes to check this code. Now add and test the triangle intersection. Once you are done with it, you can try to test the scene intersection and make sure yo are getting this right. Now add shading code, by implementing the material and light functions. Once you are done, add shadows and reflections. Finally, add the supersampling code to remove the jaggies from your images.
Remember that the framework code we are suppling is for your own benefit. If you feel you should write code differently, go ahead and do so. The important thing is for you to get the images right.
Check your output with the scenes and results attached to the framework. Add new scene as needed to debug specific bugs. Also use images to store debugging information. Think of them as the printout statement of your renderer.
Please send your code as well as the images generated by your code to pellacini-at-di-dot-uniroma1-dot-it. We will run your code by calling the main method provided. We will your images for correctness with ours.
Add a new capped-cylinder primitive, composed of a cylinder lateral surface and two disk caps. Demonstrate your code with a scene containing a Phong cylinder.
Add refraction to your code by augmenting the
_raytrace_scene_ray function (similar to reflective code above). You can find the details on how to generate refraction rays in Shirley's book. The reflection code is a good start for this. Demonstrate your code with a new scene, which should contain a glass sphere.