$25
In this lab, we will extend the ray tracer developed in lab-07 to generate planar surfaces, reflections, patterns and textures.
I. RayTracer.cpp from Lab07
1. In Lab07, we developed a basic ray tracer for rendering a scene containing a set of spheres, and implemented ambient, diffuse, and specular reflections and shadows (Fig. 1).
Fig. 1.
2. We will now generate reflections on the blue sphere. Reflectivity is one of the attributes of a sceneObject. The material properties of scene objects such as color, reflectivity, and specularity are specified in the initialize() function. Please add the following statement in this function:
sphere1-setReflectivity(true, 0.8);
The second parameter in the above function provides the value of the coefficient of reflection r . For scene objects that are marked as reflective, we need to generate secondary rays along the direction of reflection at points of intersection and recursively call the trace() function to get the reflected colour values. This is done by including the following code segment in the trace() function (after computing the shadow color, before returning the final color value). See also Lec08-Slide 18:
if (obj-isReflective() && step < MAX_STEPS)
{
float rho = obj-getReflectionCoeff(); glm::vec3 normalVec = obj-normal(ray.hit);
glm::vec3 reflectedDir = glm::reflect(ray.dir, normalVec); Ray reflectedRay(ray.hit, reflectedDir); glm::vec3 reflectedColor = trace(reflectedRay, step + 1); color = color + (rho * reflectedColor);
}
The variable MAX_STEPS is used to limit the depth of recursion to a prespecified value. A sample output with reflections is shown in Fig. 2. Note that with reflections, the program takes significantly more time to render a scene.
Fig. 2.
Plane.cpp
The Plane class is a subclass of SceneObject, and has a constructor that takes either three or four vertices that form the boundary vertices for a planar region. The header file Plane.h and the implementation file Plane.cpp for this class, are provided. Being a subclass of SceneObject, the Plane class must provide implementations for the functions intersect() and normal().
Let us take a look at the functions in Plane.cpp. The intersect() function uses the following equation to find the point of intersection between the ray and the plane (Slides 27, 29), and returns the value of the ray parameter t at the point of intersection.
(A p0)n
t (1)
d.n
The surface normal vector n of the plane is computed as (CB)(AB) (Fig. 3), where A, B, C, D are the four vertices the plane defined in an anti-clockwise sense with respect to the required direction of the normal vector.
Even though the normal of a plane is independent of the point at which it is computed, we need to use the standard signature of the normal function (normal(p)) as specified in the SceneObject class.
The function isInside(q) implements a point inclusion test to determine whether a point q is inside the (triangular or quadrilateral) region on the plane's surface, given by the user specified (three or four) boundary points. This test uses a set of vectors computed using the boundary points, as shown on slide Lec-08Slide 29.
3. In the initialize() function of the ray tracer, create a pointer to a plane object as shown below. The four parameters define the vertices of the floor plane.
Plane *plane = new Plane (glm::vec3(-20., -15, -40), //Point A glm::vec3(20., -15, -40), //Point B glm::vec3(20., -15, -200), //Point C
glm::vec3(-20., -15, -200)); //Point D and add this to the list of sceneObjects:
sceneObjects.push_back(plane);
Remember to add the statement #include “Plane.h” at the beginning of the program. You should get an output similar to that shown in Fig. 4:
Fig. 4.
4. Please note that the plane shown in the above figure is an infinite plane. The intersect() method does not use the boundary vertices of the plane. We need to check if the point of intersection given by the ray parameter t computed by the intersect() method lies within the boundaries of the plane.
Please edit the file "Plane.cpp" and replace the last statement (return t;) of the intersect() method with the following code:
glm::vec3 q = p0 + dir*t; //Point of intersection
if( isInside(q) ) return t; //Inside the plane else return -1; //Outside
You should now get a finite plane bounded by the vertices, as shown in Fig. 5.
Fig. 5 Fig. 6
Specular reflections from the floor plane can be disabled (Fig. 6) by setting the specularity property of the floor plane in the initialize() function:
plane-setSpecularity(false);
5. We will now generate a stripe pattern on the floor plane by modifying the plane's material colour value at each point. We will use the method shown in Fig. 7 to generate the pattern.
0 20 40 60 80 100 x
int ix = x/20; int k = ix%2;
ix=0 ix=1 ix=2 ix=3 ix=4
Fig. 7 k=0 k=1 k=0 k=1 k=0
A range of values along any coordinate axes (x in the above example) can be subdivided into sub-ranges of fixed widths (20 in the above example) by using an integer division of the coordinate value by the width. Using a modulo operation (2 in the above example) we can convert the range to a repeating pattern of integers corresponding to the number of colour values of a pattern.
We will use the above method to convert the z-coordinate values on the plane into values 0 or 1, and assign colour values to those points. Include the following code segment in the trace() function after the statement "obj = sceneObjects[ray.index]":
if (ray.index == 4)
{
//Stripe pattern int stripeWidth = 5; int iz = (ray.hit.z) / stripeWidth;
int k = iz % 2; //2 colors
if (k == 0) color = glm::vec3(0, 1, 0); else color = glm::vec3(1, 1, 0.5); obj-setColor(color);
}
The above code assumes that the index of the plane is 4. Please use the correct index from the initialize() function based on the position of the plane in the sceneObjects list. The output with the stripe pattern is shown in Fig. 8.
Fig. 8
6. Texture mapping requires a mapping of the coordinates (x, y, z) at the point of intersection (ray.hit) to a pair of texture coordinate values in the range 0-1. Regions on a two-dimensional plane can be easily mapped to this range using a simple linear transformation as shown in Fig. 9 below (see also Lec08-Slide33) .
texcoords = (ray.hit.x x1)/(x2x1); texcoordt = (ray.hit.z z1)/(z2z1);
Fig. 9
The files "TextureBMP.h", and "TextureBMP.cpp" required for loading a texture in bitmap (.bmp; 24 bits per pixel) format, are provided. Add the statement
#include “TextureBMP.h” at the beginning of the program. Also, add the declaration statement for a texture object at the beginning of the program.
TextureBMP texture;
In the initialize() function, include the statement
texture = TextureBMP("Butterfly.bmp");
On page 4, we provided the code for modifying the colour values of the plane using an "if" statement to generate a stripe pattern. Please modify this code block as follows.
if (ray.index == 4)
{
//Stripe pattern ... obj-setColor(color);
//Add code for texture mapping here float texcoords = float texcoordt =
if(texcoords 0 && texcoords < 1 && texcoordt 0 && texcoordt < 1)
{
color=texture.getColorAt(texcoords, texcoordt); obj-setColor(color);
}
}
In the space shown in the code snippet above, add statements for computing the texture coordinates as given in Fig. 9, for a region of the floor plane shown in Fig.
10. The expected output of the ray tracer is shown in Fig. 11.