Sample spheres with single reflection (by Martin Bertrand)

The Phong Reflection Model

Overview
The next step in the rendering process would be to find the local light intensity of a given point. Numerous lighting models exist to complete this task, but we will choose the Phong Reflection model as it is one the simpler ones.
The Phong model was developed by Bui Tuong Phong in the mid 70's[2]. It basically computes the illumination of a point by a combination of 3 different types of light; Ambient, diffuse and specular. As a first step, we will describe these light types in further detail.

Ambient: The ambient light is considered to illuminate an object completely and evenly. It is this light, in opposition the diffuse and specular, that gives the object its colour.
Diffuse: The diffuse light is obtained from reflecting a light source onto a rough surface. The diffuse light will vary according to the roughness of the reflective surface and  the angle of the incident rays. The colour of this light is independent from that of the object that reflects it.
Specular: The specular light is the small highlights obtained from the reflection of shiny surfaces. A high specular level will create a small specular reflection as opposed to a lower specular level which will increase the size of the highlight. The following illustration compares the three types of lights. The specular light, is also independent from the diffuse and ambient lights.
Image courtesy of Wikipedia
The Phong formula
The basic Phong reflection formula: The light intensity I at point p is given by



I_p = k_a i_a + \sum_\mathrm{m \; \in \; lights} (k_d (\hat{L}_m \cdot \hat{N}) i_{m,d} + k_s (\hat{R}_m \cdot \hat{V})^{\alpha}i_{m,s}).
The Phong Reflection model equation
(Courtesy of Wikipedia)
Ka, Kd ans Ks are the reflection ratio of incoming light of type ambient, diffuse and specular respectively.

ia, im,d, im,s are the ambient, diffuse and specular light intensity. The subscript m relates to a specific light m element of all light sources involved in the scene.
Lm, is a unit vector originating at the point with a direction towards the light source m
N is the unit vector normal at the point
Rm is a unit vector of a perfectly reflected Lm vector for a light source m
V is a unit vector from the point towards the viewpoint

α is a brightness factor, where a higher value means a smaller but precise specular reflection


It is interesting to notice that only the ambient portion is independent to the number of light sources.


RGB colours and light: 
The light values follow an established RGBA model where A is a transparency parameter. We must apply our Phong Reflection to the three RGB channels separately. This is easily done, as each intensity data type is built as a size three array. As a convention, the intensity indices will represent the following RGB values
intensity[0] = red
intensity[1] = green
intensity[2] = blue


Linear Algebra: Normals and dot products
One of the main pedagogical aspects of implementing the ray tracer lies in the practical use of diverse vector operations; Some of these operations being so common and used repeatedly in the program, they will have their own methods. 
The normal
Finding the normal of different of different objects like planes or spheres varies according to the object. Glassner (pp37) defines the normal of a sphere at a given point on its surface.The RayTracer class implements the following method:

public double[] get_normal_for_sphere(Sphere sphere, double[] point){

    double[] normal = new double[3];
    for(int i = 0 ; i < 3 ; i++)
       normal[i] = (sphere.centre[i] - point[i]) / sphere.radius;
    return normal;
}
This normal is needed to find the Rm reflection vector. All we need to find a perfectly reflected vector is the vector of incidence and the normal at that point. Finding this reflected vector makes use of another linear algebra concept, the dot product.
The dot product
The dot product returns a scalar value equal to the magnitude of each component multiplied by the cosine of the angle between the two vectors. It is easily calculated by adding the multiplication of each vector's components. The RayTracer class implements the following:

public double dot_product(double[] u, double[] v){

    double product = 0.0;
    for(int i = 0 ; i <3 ; i++)
        product = product + u[i] * v[i];
    return product;
}
Evaluating the complexity of the reflection model The dot products of Lm  and N and Rm and V are responsible for changing light's intensity at a point according to the angle it makes with the normal and the viewpoint. This has been a difficult aspect of dot products to visualize; We only have to understand that, as the angle of the rays change in relation to the fixed normal, and specular and diffuse intensity will change accordingly.
VisibleObject and Sphere class members related to the Phong model
The VisibleObject and Sphere class contain members to control some parameters of the Phong Reflection equation:
The reflection constant member is an size 3 array for the ambient, diffuse and specular reflection values of the object. These values are implemented as doubles and can range from 0.0 to perhaps 1000. A higher value would suggest for instance a larger area of diffused light, or a larger specular area. The VisibleObject class also contains an ambient_intensity member, which contains a size 3 array to store RGB values. It is through this parameter, by changing the respective RGB values, that we can control the ambient colour of the object. 
The LightSource class
The LightSource class creates light objects. Each light has a single coordinate point, and an intensity_diffuse and intensity_specular size 3 array that contain the RGB intensity values. These values, implemented as doubles (floating point) control the color of the diffuse and specular reflections. The user can control the displayed colour by setting each array's values by a range of 0 to 1. If all the values of a specific array are set equally, the resulting display would be a shade of grey. We will now display the outcome of different parameter values.

Examples

Sphere created only with ambient light.
The ambient B and G values are 0.0 while the R is 0.3
Same sphere but with diffuse light added.
The light source in the image is located
somewhere in close upper right of the sphere.
Specular lighting has been added
Changing the Alpha value changes the size of the specular reflection
Here the Alpha value has been reduced to 1.0
Alpha value of 1000
Ambient reflection almost to 0. Alpha value of 10.0.
Specular and diffuse lights are not the same colour
as they are independent.
Multiple lights and multiple objects
Once the Phong reflection model has been successfully implemented, we should consider multiple light sources potentially lighting multiple objects. This add nothing much to the complexity of the implementation; Sphere objects are created and inserted into an array so as different light sources are instantiated and inserted into another array. Each rendering process  saves the partial intensity values created and then adds them up to the final value which will be displayed at the pixel. The problem multiple light sources create is that the light intensity at a given point being built by adding all light source intensities could potentially result in creating an intensity value which goes beyond the 255 limit. The solution for this problem resides into implementing a ceiling value of 255 for all light intensities prior to rendering.  If the addition of multiple light sources add to a value of more than 255, they will appear white on the display. This simple code snippet does just that. 

for(int j = 0 ; j < 3 ; j++)
    if(intensity[j] > 255)
        intensity[j] = 255;


Rendered scene with multiple spheres and 2 light sources
Author's observations on the results
Stepping into the world of ray tracing has proven to be most challenging but also incredibly satisfying; The joy of being able to create images that somehow display the illusion of three dimensions simply by implementing geometrical and algebraic concepts is something the author had never experienced before. The results, simple in comparison to those produced by more advanced ray tracers, still have tremendous pedagogical value.
The principal evaluation of the rendering results would lie in the success of creating the illusion of 3 D. As the image above seems to well display this intention, the author's attention has been drawn to what appears to be limitations or flaws in the rendering process; One of the main elements which makes the scene look 'artificial' is the diffuse light of the large sphere appears to be too well defined for the top light source. The author cannot help thinking if a light source could actually create such light / shadow delimitation.
Another question the author would raise pertains to the colour of the diffuse light and its specular counterpart. The Phong Reflection model enables the implementer to select different colours for the diffuse components and for the specular components. The fundamental question raised here would be if in the real world, a light source can have the diffuse and specular components to be of a different colour. The author's answer here would be no, as the  diffuse light component originates from the same light source that create the specular portion, the diffuse aspect being created by the texture or roughness of the object exposed to the light.
These exposed weaknesses of the model are easy to control and alleviate; One has to simply  make the diffuse light colour the same as its specular counterpart. This would add an extra degree to of realism to the scene.
The author notes that these are minor flaws; It is obvious ray tracing can achieve an astonishing level of realism in its rendered scenes. The author is merely stating that having done the first steps in image rendering, he has acquired a better sense of where these limitations might lie.

Ray-Sphere Intersection

Linear Algebra: Basic Ray Equation
As we now know that for an object to be rendered, we must have a ray intersecting with that object and the reflection point on that object where the intersection occurred redirect the ray towards a light source. As ray tracing algorithms go, the sphere object is the easiest to deal with as other objects such as polygons, need more complex algorithms.
Of all the steps needed to render an image, one equation stands above the rest, the ray equation:
A ray equation defines a ray as:



R(t) = R0 + Rd * t   where t > 0 

R0 being the coordinates of the origin of the ray, Rd being the ray direction vector and t, a length scalar representing the number of units the direction vector is multiplied. R(t) is the coordinate of the point on the ray at distance  Rd *  t units from R0This equation enables us to find any coordinate that is part of the line if we possess all the other information. We will use it for finding the intersection point of our ray with the sphere object we wish to render.

Linear Algebra: Normalized Vectors
The ray equation works with any value of t, but the normalization process enables us to calculate the distance of a specified point on the ray in world units, as a normalized vector has the length of 1. The conversion of a given vector to a normalized one is a simple task; once the Euclidean length of a vector is known, each x, y, z component is divided by the vector's length. The result is a vector going in the same direction as the original, but with a length of 1. When applied to our ray equation, the normalization process makes calculations easier. We could, for example have a ray starting at the world origin [0,0,0] with a normalized direction vector of [1,1,1]  a t value of 2 would yield a R(2) = [2,2,2].

Simple Ray tracer: useful method: 
Since vector normalization is a common vector conversion, it would be useful if a java method would be implemented to serve this simple purpose; here is the normalise_vector method, a member of the RayTracer class. It inputs a vector and returns a normalized version. 

public double[] normalise_vector(double[] v){

   double length = 0.0;
   double[] v_norm = new double[3];
   for(int i = 0 ; i < 3 ; i++)
   length = length + Math.pow((v[i]), 2);
   length = Math.sqrt(length);
   for(int i = 0 ; i < 3 ; i++)
      v_norm[i] = ((v[i])) / length;
   return v_norm;
}
    
Ray / Sphere Intersection: Process
The rendering process involves basically two elements: firstly, if a ray collides with an object at a specific coordinate, and secondly, if such a coordinate exists, finding the appropriate light intensity at that point to give back to the corresponding imagebuffer pixel. We can summarize these steps:

  1. Find the appropriate direction vector for the ray equation
  2. Find if a collision occurs with the object
  3. If a collision occurs, find the world coordinates of the point of collision
  4. Find the normal vector at that point
  5. Apply the Phong shading algorithm to that point
  6. Return the found light intensity to image pixel 


Find the appropriate direction vector for the ray equation
The direction vector needed to qualify our ray is found by subtracting the viewpoint from a coordinate inscribed in viewing window plane . Since we have to do this operation for every pixel in the image, this calculation is easily implement using a double image width / image height loop. The ray tracer's ViewPoint class uses the create_unit_vector method which finds the precise coordinate of where the pixel's center would be in the viewing window, by dividing the area of the window by the number of pixels in the image. By using coordinate offsets, the coordinates of the center of each square area is used for finding the unit vector for that particular pixel. Since the viewing window is an abstract concept, we can potentially change the number of rays that can go through the window by changing the values of the offsets. We will use this technique when dealing with the antialising issue further ahead.
Direction vector for each pixel. Viewing window is divided into  areas
corresponding to each pixel in the image ( Illustration by Martin Bertrand)















It is useful to note that the create_unit_vector method returns our ray vector under a normalized form. This, once again, simplifies evaluating if results are appropriate as they are represented in world units.
Find if a collision occurs with the object
Now that we have established a normalized direction vector, the next step would be to verify if, for a particular ray, a collision occurs with a specific object. It is interesting to note that each type  of primitive object (polygon, sphere, cone, etc...) will have different formulas for calculating the intersection with the ray. 
The ray / sphere intersection is found by a combination of the ray's equation with the sphere's implicit equation we described earlier. We can then express this equation in terms of t. Since the complete details of this substitution can be found in Glassner's book (pp 36-37), we will just focus on the more general aspects of finding this t value and the interpretation of its value.
The substitution of the ray equation in the sphere's equation will yield, in terms of t, an equation of the form  A* t2 + b*t + C = 0 a quadratic equation. The values of A, B and C, are easily found from elements we already possess like, the point of origin of the ray, the normalized vector, the center coordinates of the sphere. Once again, the detailed equations can be found in Glassner (pp 36-37).
Being a quadratic equation, t will have 2 potential values; But It is important to notice beforehand that if the discriminant of the quadratic equation is negative this ray misses the sphere entirely, and no further computation are necessary for this particular ray. If the discriminant is positive, then a collision has occurred. It is easy to implement a Boolean value that verifies this.
A positive discriminant will create two t values: These t values can either positive or negative. Only positive t values will be examined as they represent the distance in world units from the viewpoint to the intersection point. If 2 positive t values are found, only the smallest one should be considered to find the point of intersection as it is the one closest to viewpoint, therefore the only visible one if our object is not translucent. 
A quick summary of values:

  • If discriminant is negative - no collision has occurred
  • If t value negative, disregard, as intersection point with object is behind viewpoint
  • Smallest positive t value is chosen to find intersection coordinate
If a collision occurs, find the world coordinates of the point of collision 
To find the actual coordinate of the point where the ray intersects is simple. It is inserted in our first ray equation. This is in fact also another useful method of the RayTracer class called get_intersection_point. It inputs a viewpoint (acting as ray origin) a unit direction vector, and the t value. It will return the coordinates of the point a distance of t from the origin.


public double[] get_intersection_point(double[] vp, double[] unit_vector, double t){
    double[] intersection = new double[3];
    for(int i = 0 ; i < 3 ; i++)
       intersection[i] = vp[i] + unit_vector[i] * t;
    return intersection;
}

This will yield the intersection's coordinate  for our particular ray and pixel. The next step would be calculate the light intensity at that particular point. To give our sphere the appearance of a 3-D volume, we will use the Phong shading algorithm at that point. We will describe this algorithm in the next section.

Implementation test
At this level, it would be useful to test if our ray / sphere intersection works properly. One simple way of doing so would be that if a ray sphere collision is detected, return a single fixed intensity value to the image buffer at the specified pixel. If the algorithm is successful, one should see a flat circle of uniform colour.
Successful Ray / Sphere intersection.
Here, a single colour value is given to all pixels.



Ray Tracing: Overview

What is Ray Tracing?
Ray tracing, as explained by Glassner[1], is an ensemble of computational and mathematical techniques used to produce a 2-D picture of 3-D elements.  It is based, in principle, on a simple camera model called the pinhole camera,  a box containing photographic film and a very small opening on the opposite side, letting only certain rays of light to come in contact with the film, thus printing a focused image on it. The ray tracer uses a variation of this pinhole camera called a frustum which we will look at below. 
Ray tracing could be considered a simulation of natural light reflection; Natural light bounces off objects in all directions but only the rays entering the eye are visible. A ray tracer, as it is incapable of dealing with an infinity of light rays, will only deal with rays that follow the path from the light source to the eye. Perhaps the most important aspect of ray tracer lies in this particular ray-eye relationship, where of instead of plotting the trajectory of a ray of light from its source to eye, we reverse the process, and plot a ray from the eye to the light source, knowing that if this ray reaches the light source it must be visible to the eye and therefore used in the rendering process. This concept enables us to eliminate dealing with stray light rays. This is referred to as backwards ray tracing as opposed to forward ray tracing, which is closest to reality, but much more difficult to implement.


The pinhole camera 
Pinhole camera principle (image courtesy of Wikipedia)

The frustum: a modified pinhole camera used in computer graphics
Viewing frustum, a modified pinhole camera. 
(Illustration by Martin Bertrand)


The modified pinhole camera has the eye (or the viewpoint) separated from the object by the viewing window.  This window, in our implementation, can be built as an image buffer (or frame buffer Only objects contained inside an imaginary space of the shape of a truncated pyramid will be visible. The viewing window would be the truncated top part of this pyramid, and the viewpoint the top. This truncated pyramid is called a frustum. The frustum, we must note, does not have a base as the pyramid's sides extend to infinity. Any object inscribed inside the frustum's volume should be visible, unless hidden by another object.

The image pixel and the ray
It is at the intersection of the viewing window and the ray that the actual rendering process will be located; When the ray passes through the viewing window on its way to the object, its position in the window can easily be found. If we then calculate the correct light intensity at that point, doing so for all points available in viewing window should create a viewable image. Once an intensity value has been established, it is also easy to assign this value to a screen pixel, thus enabling us to see the image onscreen.


Viewing window with image of object
(Image courtesy of Wikipedia)


The viewing window and the image buffer
The first critical element that we can implement in our program is the viewing window: The java interface for the Simple Ray Tracer program consists of a simple GUI that can display an image of fixed dimensions: 500 pixels wide by 400 pixels high.
The Simple Ray Tracer GUI showing
an empty 500 by 400 pixel viewing window.


The program uses a BufferedImage image object embedded in a JLabel component. Each pixel of the image can easily be changed by modifying its RGB value by first creating a Colour object with separate integer values ranging from 0 to 255 and then inserting this colour value, which will represent the light intensity, at a specific X Y coordinate. There is also a fourth pixel parameter, A, which controls transparency. It is not used in the application and is permanently set to 1. Here is the application's code snippet for setting a given pixel's intensity:

Color col = new Color((int)average_intensity[0],(int) average_intensity[1],(int)average_intensity[2],1);
raytracer.screen_array.setRGB(buff_z, buff_y, col.getRGB());
The average_intensity is a size 3 double type array for RGB values which are computed separately. The values are cast for Integer before creating the colour object. The colour object built is then sent to the GUI's screen array which sets the pixel's intensity at the Z and Y coordinates (here called buff_z and buff_y). Z and Y coordinates were used instead of X and Y for practical purposes as the in-world viewing window was built parallel to the Y Z plane. The details of the world coordinates are briefly explained below.


Objects in an abstract frame of reference: 3-D vector coordinates
This viewing window / viewpoint / object relationship can best be implemented with an object oriented approach: By defining each element as a specific object with its methods and data members, new elements can easily be added to progressively build a more complex scene. Any object oriented language would be appropriate for the task at hand, but in this particular case the language of choice has been Java. 


The objects created must interact with each other within an outside frame of reference: for example, the viewpoint must be at a certain distance from the viewing window and any object behind the window will appear to be larger or smaller depending on its distance from the window. These distances must be quantified in any ray-tracing application. One possible solution would be to build a world object as a 3 dimensional array, where the array's indices would correspond to the coordinates of the objects contained in the world. Although feasible, this method would be complex and difficult to modify. A much simpler solution is giving each created object 3 dimensional coordinates building blocks, in the form of vectors expressed as [x,y,z].


For example, a viewpoint object could have a coordinate defined by the vector [0, 0, 0] and a sphere to be rendered could have a center located at [10, 23, 11] with a radius of 2. A triangular polygon object, the simplest polygon object possible, could be defined as a trio of 3D vectors, one for each vertice of the polygon. Other object attributes could easily be added like ambient colour for instance. 


Linear algebra concepts: Implicit equations
The viewing window, like other objects with multiple vertices in the world, are implicitly defined by the vertices that compose them. They are not entities that occupy actual memory space, they are simply referred to by their initialized vertice data members. Their implicit nature is in fact, critical for certain algorithms that are used. For instance, a plane object, necessary for the rendering of polygons is implicitly defined with the formula


Ax + By + Cz + D = 0


Where ABC are the x y z values of the normal vector to this plane, and D being the distance of the plane from the world's origin defined as [0,0,0].
Points that are included in the plane are considered implicit by the fact that if a point fits in the equation it is considered part of the plane. The same concept also applies to spheres: Here, a sphere is defined as a set of points that satisfy the following equation


(Xs - Xc)2 + (Ys - Yc)2 + (Zs - Zc)2 = S2

Where the sphere's surface is the set of points [Xs, Ys, Zs], the sphere's centre is
[Xc, Yc, Zcand sphere's radius is Sr. We will come back to these equations as we will use them equation to render our objects.


Basic objects and coordinate values used in the Simple Ray Tracer application
The Ray Tracer application is a combination of simple custom-designed classes:
Main class : RayTracer class
This is the main class of the program: Apart from creating the GUI components, (The image buffer and its window) it contains the main(String[] args) method, where objects and light sources to be rendered in the scene are created, but also where the other important class is instantiated, the ViewPoint class.
ViewPoint class:
The viewPoint class creates a viewpoint object, and a viewing window object. Here, our viewpoint (henceforth VP) will have a coordinate of [0, 2, 2.5]. Our viewing window, defined by 4 points is a rectangular polygon set on a plane parallel to the Y Z coordinates as mentioned earlier. The bottom left corner is established at [10, 0, 0] while the top right corner is located at [10, 4, 5] in world coordinates. It is useful to notice that the rectangle can be defined by these 2 points. The other points were simply added as a visual guide. The coordinates of the viewpoint place it in exactly in the center of the viewing window. This will facilitate approximating values as the X component of vector lengths between viewpoint and viewing window will be of value 10.


Here is a diagram of the location of the principal elements of the ray tracer
Principal elements coordinates of the Simple Ray Tracer application (Illustration by Martin Bertrand)


Inner world coordinates vs outside world coordinates
It is interesting to notice that even thought the image has a 500 by 400 size, our viewing window possesses a 5 by 4 size, expressed in world units. World units here are simply Cartesian values; they have no direct relation with the outside world. They are self contained. To match our pixel screen's position to the viewing window we will simply divide the worlds coordinates by 100. We will look at this later in more detail when we will calculate unit vectors.
VisibleObject class:
The VisibleObject class is an abstract super class from which the Sphere and Polygon class are derived. The VisibleObject class contains a center data member that can contain the center coordinate of a sphere instance. This abstract class also contains other data members, but those being related to object luminosity, colour and reflexive capabilities, these members will be best explained in the Ray / sphere intersection part of the blog, where we will explain the Phong shading algorithm used to shade the objects.
Visible Object creation process
The Simple Ray Tracer application, being at first hand an educational prototype, does not have a user friendly method of creating objects to be rendered: At this moment, any object must be declared in the main method and inserted into its appropriate ArrayList object. This is not difficult per say, and it is not the most convenient method, but, the intent being to demonstrate basic ray-tracing capabilities, it is sufficient: 
Here is a code snippet creating a sphere of radius 0.4 world units located at [6, 1.5, 3.5] . We will explain the role of the other class members in the next blog chapter.
Sphere sphere2 = new Sphere(0.4);

sphere2.centre[0] = 6.0;
sphere2.centre[1] = 1.5;
sphere2.centre[2] = 3.5;
sphere2.alpha = 30.0;
sphere2.reflection_index = 1.0; // fully reflexive
sphere2.intensity_ambient[0] = 0.0;
sphere2.intensity_ambient[1] = 0.9;
sphere2.intensity_ambient[2] = 0.0;
sphere2.reflection_constant[0]= 0.3;
sphere2.reflection_constant[2]= 3.1;
sphere2.reflection_constant[1]= 3.1;

Explorations in Ray Tracing: Introduction

Hi, and welcome to my blog. 

This blog will serve as a tool to annotate my research concerning my summer 2011 CIS4900 course, 'Topics in Computer Science' from the University of Guelph. As I progressed in my program, an area of interest became more obvious to me: 3D image rendering. The creation of photo-realistic images by computer is a complex science, and designing image rendering software even more so. 
    This blog contains observations pertaining to my implementation of a simple java coded ray tracing application that enables the user to render 3 dimensional images of simple geometric shapes like spheres and polygons. The application uses the Phong Reflection Model to create essential light and shading effects like specular reflection and diffusion. Antialiasing, or the removing of certain image imperfections will also be examined. I will discuss some keys aspects of the algorithms used along with the linear algebra concepts behind them. I will also comment on some of the more interesting implementation issues I have encountered.


It is assumed the reader is familiar with Java's object oriented language, basic computer graphic concepts like pixels and image buffers and basic linear algebra.

Most of the information needed for my implementation was taken for Glassner's[1] book, along with diverse web pages for Phong shading[2] and ray - polygon intersection algorithms[3]. All images were created by the author, unless otherwise stated.