Lab 4: Lighting and Forward Shading

Learning Objectives: The purpose of this set of exercises is to get acquainted with lighting and shading in WebGL and GLSL. We compute the local illumination of an object based on ambient, diffuse and specular material properties, and a light source.

When working on the following exercises, please consider the Wiki with clarifications for the textbook.

Tasks:


Part 1: Draw a Sphere in Perspective View

We can start this lab using the same code as presented in Part 2 of Worksheet 3, where three wireframe cubes are presented in perspective view. Let's get rid of the other cubes and switch to drawing triangles instead of wireframe by;

With this skeleton code we can now start implementing the exercise in the lab. This exercise is about using the technique known as recursive subdivision. This technique is used for approximations to curves and surfaces of a provided accuracy. This accuracy is determined by the amount of subdivisions (explained later). We with a tetrahedron composed of four equilateral triangles determined by the four vertices:

These vertices translated to code are:

var a = vec4(0.0, 0.0, 1.0, 1);  
var b = vec4(0.0, 0.942809, -0.333333, 1);  
var c = vec4(-0.816497, -0.471405, -0.333333, 1);  
var d = vec4(0.816497, -0.471405, -0.333333, 1);

In order to make the tetrahedron we will be using the code that has been provided in the course book for the recursive subdivision. First thing that happens is that we take the origin points for the shape and send them to a function which will divide each of the faces into triangles as shown below:

function tetrahedron(a, b, c, d, n) {
        divideTriangle(a, b, c, n);
        divideTriangle(d, c, b, n);
        divideTriangle(a, d, b, n);
        divideTriangle(a, c, d, n);
    }

each of the phases can be divided into a number of triangles determined by the subdivision count. This subdivision count controls the exit condition to the following function which conducts the division of the triangle into sub triangles:

function divideTriangle(a, b, c, count) {
        if (count > 0) {
            var ab = normalize(mix(a, b, 0.5), true);
            var ac = normalize(mix(a, c, 0.5), true);
            var bc = normalize(mix(b, c, 0.5), true);

            divideTriangle(a, ab, ac, count - 1);
            divideTriangle(ab, b, bc, count - 1);
            divideTriangle(bc, c, ac, count - 1);
            divideTriangle(ab, bc, ac, count - 1);
        }
        else {
            triangle(a, b, c);
        }
    }

The final point to the recursive subdivision into triangles is the triangle function which will update the vertices that need to be rendered. This function is shown in the code below:

 function triangle(a, b, c) {
        vertices.push(a);
        vertices.push(b);
        vertices.push(c);
        
        index += 3;
    }

We can now render this tetrahedron directly on the screen using our skeleton code to have a shape as shown in the figure below (please note that each of the triangles are assigned a different color).

In order to add the two button to increase the number of subdivisions being made we have to reorganize our code so that the updated data can be rendered on screen. The buttons can be called using the following code:

// The button for increment the subdivision level
$("#btn-inc-1").click(function (e) {
    e.preventDefault();

    if (numSubdivisions == 6) {
        alert("Warning! \n Low performance from triangle count!")
    }
  
    numSubdivisions++;

    index = 0;
    vertices = [];
    normals = [];

    tetrahedron(a, b, c, d, numSubdivisions);

    updateUI();
    updateData();
    render();
});
// The decrement button
$("#btn-dec-1").click(function (e) {
        e.preventDefault();
        
        if (numSubdivisions <= 0) {
            alert("Subdivision count CANNOT be LOWER than 0!");
        } else {
            numSubdivisions--;
            
            index = 0;
            vertices = [];
            normals = [];
  
            tetrahedron(a, b, c, d, numSubdivisions);
            
            updateUI();
            updateData();
            render();
        }
})

The updateUI method simply writes to an HTML p with the number of subdivisions being made. While the code for the updateData is responsible for the bufferSubdata calls being made to the shader. The updateData is also responsible for assigning a new color to the triangles being rendered using the following code:

        var triangle = -1;
        for (var i = 0; i < vertices.length; i++) {
            if (i % 3 == 0) {
                triangle++;
                
                if (triangle >= colors.length) {
                    triangle = 0;
                }
            }

            // Buffer the color data
            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
            var color = vec4(colors[triangle]);
            gl.bufferSubData(gl.ARRAY_BUFFER, 16 * (i), flatten(color));    
        }

Result

Current Subdivision Count

0

Error: Your browser does NOT support WebGL!

Part 2: Use Depth Buffer and Back Face Culling to Remove Hidden Surfaces

Although it is a nice visual demonstration into seeing how the Recursive Subdivision works by having each of the individual triangles that are being drawn a different color, it is quite straining on the eyes to visualize the shape at high subdivision levels. Instead we can color the shape a rainbow of colors based on their position to see how face culling works.

To make this change will require the addition of the gl_FragColor to be changed in the fragment shader to the following formula:

gl_FragColor = 0.5 * fPosition + 0.5;

Doing so will require the addition of the fPosition varying variable in both the fragment shader and the vertex shader. The position should be passed after the position has been calculated by the following code:

gl_Position = projectionMatrix * viewMatrix * modelMatrix * vPosition;

It also requires the enabling of face culling and depth check. Both of which are done simply by adding the following code:

gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

The cullFace method is used to not draw parts of a 3D object that are not visible to the camera, depending on what parameter we send. In our case we see that sometimes when having large subdivision numbers we get computer slowdown. Culling the back faces or the faces not towards the camera can mitigate this a little bit as seen in the result below. For a more in depth explanation on face culling see the OpenGL reference.

Result

Current Subdivision Count

0

Error: Your browser does NOT support WebGL!

Part 3: Use Gouraud Shading to Draw a Diffuse Sphere

The result in Part 1 is flat shading in where all the triangles have a constant color assigned from the CPU. Where as the result from Part 2 all colors are rendered at the same lighting intensity making the viewer perceive the sphere as quite dull and unrealistic. As such we have to change the color values of the sphere depending on where the light source will be coming from. First we have to understand that lighting works by using a light source. These light-sources can be defined by the Illumination Function $I(x,y,z,\theta,\phi,\lambda)$ in where the variables are as follows:

This light-source will hit an object and alter the color that the human looking at it will perceive. In computer graphics we assume that most things can be colored with three different color components, $r,g,b$. Using these components we can obtain the corresponding color components that a human sees with the Luminance Function shown below:

$$ I=\begin{bmatrix} I_r \ I_g \ I_b \end{bmatrix} $$

When it comes to lighting an object it is not only the light that hits an object directly from a source but also the light that is reflected from scattered illumination around the room. It is unrealistic to try and calculate all objects reflecting light in a room. Instead computer graphics uses a principle that instead will have a baseline at which all objects in a room are light. This uniform lighting is called Ambient Lighting. This changes the luminance function to the following in where $I_a$ represents RGB light coming from the ambient.

$$ I=\begin{bmatrix} I_{ar} \ I_{ag} \ I_{ab} \end{bmatrix} $$

Lastly we can talk about how distant lighting works. Normally we would have to calculate the $\theta,\phi$ vector for every point of an object to understand how light is affecting the color of that object. However, with distant lights we can instead assume that points that are close together will be using a similar vector for the lighting. As such we do not have to recalculate this vector. To let the computer know we assign x,y,z values to a light and follow it of with a 1 for close point light sources or a 0 if it is a distant light.

The lighting technique we will be using in this part of the lab is Gouraud Shading which can be used to achieve smooth lighting on low-polygon surfaces without having to calculate lighting for each pixel. This technique uses the calculation of surface normals which are perpendicular vertices to the object's surface. These normals are calculated at the vertex where polygons meet and the average normal is defined by the formula below:

$$ n = \frac{n_1 + n_2 + n3 + n_4}{|n_1 + n_2 + n3 + n_4|} $$

This means that each of the polygons that share that vertex will share the normalized vector n which is the average of the polygon normals as shown in the figure below:

img not found!

Furthermore Gouraud's Model for shading says that with this average normal at each vertex we can apply Phong Model to interpolate vertex shades across each polygon. We will be by using an approximation of the Phong Model as calculating the dot product of $$r \cdot v$$ for every point will be computationally expensive. Instead calculating the halfway vector between the viewer vector and the light-source vector we can avoid the calculation of r by using the following calculation:

$$ h=\frac{l+v}{|l+v|} $$

We can then avoid having to recalculate r for every point. Instead use the halfway vector in the calculation for the spectacle term. We have to additionally consider that the size of the specular highlights will be smaller which can be mitigated by replacing the value of the exponent e with a value e' so that $$(n \cdot h)^{e`}$$ is closer to $$(r \cdot v )^e$$ making the intensities similar.

In order to code this we need a way to keep track of what vertices make up a polygon. To do this we use a data structure that keeps track of polygons, vertices, normals , and material properties. We simply use variables in our JavaScript code and update them when creating each of the triangles to keep track of the true normals as seen below:

var pointsArray = [];
var normalsArray = [];

 // Create a single triangle
    function triangle(a, b, c) {
        var t1 = subtract(b, a);
        var t2 = subtract(c, a);

       // If using the calculated normals
       // var normal = normalize(cross(t2, t1));
       // normal = vec4(normal);

        // Else if using true analytical normals  
        normalsArray.push(a);
        normalsArray.push(b);
        normalsArray.push(c);
  
        pointsArray.push(a);
        pointsArray.push(b);
        pointsArray.push(c);

        index += 3;
    }

We now have smooth shading in our result using these arithmetic calculated normals. This solution will not be a full implementation of Gouraud Shading though as the normals being sent are still the calculated normals at each of the polygons. We instead need to calculate the average of these normals at each vertex intercept. So to do this we can take the hint given in the book and know that three polygons will intercept at the vertex created for subdivision. Meaning that in the code above vertices a,b,c correspond to three polygons each intercepting. The simplest way to create a data structure that keeps track of the vertices for each polygon is by implementing a 2D array where one dimension represents each of the polygons and the other is each of the polygons' vertices. Since we know that our polygons are triangles which are made at this triangle method this is a trivial implementation if we use a object orientated programming approach. First we make the class Polygon as shown below:

var polygonArray = [];
class Polygon {
    constructor(va, vb, vc, name) {
        this.vertices = [va, vb, vc];
        this.name = name;
    }
}

Then we store the vertices of the polygon inside the triangle method as shown below:

// Make each of the polygons using the vertex index number as identifier
var p = new Polygon(a, b, c, index);
polygonArray.push(p);

Now we can use the logging code below to make sure that each of the vertices only share three polygons which from the image below the code we see is true.

polygonArray.forEach(polygon => {
            polygon.vertices.forEach(vertex => {
                console.log("Vertex: " 
                            + vertex
                            + " is the intersect between:");
                console.log("Polygon: " + polygon.name);
                polygonArray.forEach(polygon2 => {
                    if (polygon.name != polygon2.name) {
                        polygon2.vertices.forEach(vertex2 => {
                            if (vertex == vertex2) {
                                console.log("Polygon: " + polygon2.name);
                            }
                        });
                    }
                });
            });
        });

![[Pasted image 20221024113230.png]]

Making sure that our code is correctly identifying the polygons that share a vertex then we can apply the average function for normals and submit those to be used for Gouraud Shading as shown in the code below:

function findGouraudNormals() {
        var vertexNormals = [];
        polygonArray.forEach(polygon => {
            polygon.vertices.forEach(vertex => {
                vertexNormals.push(polygon.normal);
                polygonArray.forEach(polygon2 => {
                    if (polygon.name != polygon2.name) {
                        polygon2.vertices.forEach(vertex2 => {
                            if (vertex == vertex2) {
                                vertexNormals.push(polygon2.normal);
                            }
                        });
                    }
                });
                
                // // Calculate the average of the normals
                var sum;
                vertexNormals.forEach(n => {
                    if (sum == undefined) {
                        sum = n;
                    } else {
                        sum = add(sum, n);
                    }
                });

                var abs = vec4(Math.abs(sum[0]),
                            Math.abs(sum[1]), 
                            Math.abs(sum[2]), 
                            Math.abs(sum[3]));

                var normal = vec4(sum[0] / abs[0], 
                                sum[1] / abs[1], 
                                sum[2] / abs[2], 
                                sum[3] / abs[3]);
                
                normalsArray.push(normal);

                // Reset the normals being looked at
                vertexNormals = [];
            });
        });
    }

We have now setup the calculation for the correct normals and can continue with the rest of the lab. As per the requirements of the lab we require to make our light source to be a white distance directional light. First we can start with declaring the distance and directional light. A light is directional instead of a point light if the last element in a vector is a 1 as stated previously, then the first three elements will determine the light's direction. So for a distant light in the direction $(0,0,-1)$ the code declaration will be:

var lightPosition = vec4(0.0, 0.0, -1.0, 0.0); // le

Now to determine the light color we use the light's ambient, specular, and diffuse properties. A white light is simply the presence of all RGB values so a white light with very little ambient light is declared as follows:

var lightAmbient = vec4(0.1,0.1,0.1,1.0); // la
var lightSpecular = vec4(1.0,1.0,1.0,1.0); // ls 
var lightDiffuse = vec4(1.0, 1.0, 1.0, 1.0); // ld 

Next in order to apply the Phong Model we need to declare the material properties which will make up the color that the object will take once light is reflected away. For our case the lab did not specify so we chose the following variables making the scene look like it's illuminating a planet we also change the clear color to black to fit this change:

var materialAmbient = vec4(1.0, 0.0, 1.0, 1.0); // k_a
var materialDiffuse = vec4(0.385, 0.520, 0.419, 1.0); // k_d
var materialSpecular = vec4(1.0, 1.0, 1.0, 1.0); // k_s
var materialShininess = 50.0;
gl.clearColor(0.1, 0.0, 0.1, 0.85);

We can now send the normalization and lighting variables to the vertex shader in the render method by using the following code:

 normalMatrix = [
    vec3(modelViewMatrix[0][0], modelViewMatrix[0][1], modelViewMatrix[0][2]),
    vec3(modelViewMatrix[1][0], modelViewMatrix[1][1], modelViewMatrix[1][2]),
    vec3(modelViewMatrix[2][0], modelViewMatrix[2][1], modelViewMatrix[2][2])
  ];

gl.uniformMatrix4fv(uniformLocations.normalMatrix, false, flatten(N));
gl.uniform4fv(uniformLocations.lightPosition, flatten(lightPosition));

With these variables being sent to the vertex shader we now have to implement these variables and use them to calculate the shading principles for the lighting. This is done by the following vertex shader code:

attribute vec4 vNormal; // Normals for each vertex
attribute vec4 vPosition; // Vertex positions

varying vec4 fColor;

// Lighting variables
uniform vec4 lightPosition;
uniform vec4 ambientProduct, diffuseProduct, specularProduct;
uniform float shininess;

// MVP matrices
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;

// Edge normals in accordance to eye coordinates
uniform mat3 normalMatrix;
  
void main() {
    // Convert the light and position of the vertex into eye coordinates
    vec3 pos = (modelViewMatrix * vPosition).xyz;
    vec3 light = lightPosition.xyz;
    
    // vector from vertex position to light source
    // Light determined if directional light or not
    vec3 L;
    if(lightPosition.w == 0.0) L = normalize(lightPosition.xyz);
    else L = normalize( lightPosition.xyz - pos );
    
    // Because the eye point is at the origin the vector from the vertex position to the eye is
    vec3 E = -normalize(pos);
    
    // halfway vector
    vec3 H = normalize( L + E );
     
    // Transform vertex normal into eye coordinates
    vec3 N = normalize( normalMatrix * vNormal.xyz );
    
    // Illumination equation
    vec4 ambient = ambientProduct;
                         
    float KdLd = max( dot(L, N), 0.0);
    vec4 diffuse = KdLd * diffuseProduct;
                
    float KsLs = pow( max( dot(N,H), 0.0), shininess);
    vec4 specular = KsLs * specularProduct;
                
    if(dot(L,N) < 0.0) {
        specular = vec4(0.0,0.0,0.0,1.0);
    }
    
    gl_Position = projectionMatrix * modelViewMatrix * vPosition;
    
    // The part of code that gets sent to the fragment shader
    fColor = ambient + diffuse + specular;
    fColor.a = 1.0;
}

The effect is now shading the vertices of the shape using Gouraud Shading. The last request by the lab is to make the camera orbit around the object by simple adding the following code:

// Orbiting camera
var radius = 5;
var theta += 0.01;
var phi = 0.0;
eye = vec3(radius * Math.sin(theta) * Math.cos(phi)
           ,radius * Math.sin(theta) * Math.sin(phi)
           , radius * Math.cos(theta));

This code makes use of the eye part of the viewMatrix in order to change the position of the camera. The radius is the distance at which the camera will orbit around the object, while theta is the change in angle every iteration. We can now call the render method using the format we learned in Lab 1 of window.requestAnimFrame(render) to continuously render the changes.

Result

Current Subdivision Count

0

Error: Your browser does NOT support WebGL!

Part 4: Implement the Full Phong Reflection Model

In the previous part we were using the modified Phong Reflection Model to get a quicker implementation of the Gouraud Model in this part we will use the full reflection model without calculating the half vectors in order to show what the Full Phong Reflection Model looks like. The Phong Model uses four vectors as shown in the figure below to calculate color for a point 'p' on the surface. The vector 'n' is the normal. The vector 'v' is in the direction from 'p' to the viewer. The vector 'l' is in the direction of the light source. The vector 'r' is the result of $$r = 2(l \times n)n - 1$$ which shows the angle of reflection.

img not found!

When using the Phong Model we have to look at the following lighting conditions:

We have actually implemented most of this when implementing the Gouraud Model so now we have to get rid of the average vector calculation in the triangle method as shown below and go back to non interpolated normals:

var pointsArray = [];
var normalsArray = [];

 // Create a single triangle
    function triangle(a, b, c) {
        var t1 = subtract(b, a);
        var t2 = subtract(c, a);

       // If using the calculated normals
        var normal = normalize(cross(t2, t1));
        normal = vec4(normal);

        // Else if using true analytical normals  
        normalsArray.push(a);
        normalsArray.push(b);
        normalsArray.push(c);
  
        pointsArray.push(a);
        pointsArray.push(b);
        pointsArray.push(c);

        index += 3;
    }

We can then add the sliders by following the HTML structure for a range input as shown below, where the label is used to tell the user what the current value of the variables are:

<div class="container-lbl">
    <label for="slide-kd-4">$$K_d$$</label>
    <p id="lbl-kd-4">= 0.5</p>
</div>
<input
id="slide-kd-4"
type="range"
min="0"
max="1.0"
step="0.1"
value="0.5" />

We can then implement them in JavaScript by using the oninput method as shown below:

// The slider for the Le
    var sliderLe = document.getElementById("slide-Le-5");
    sliderLe.oninput = function () {
        $('#lbl-Le-5').text(" = " + this.value);
        le = this.value;
        index = 0;
        pointsArray = [];
        normalsArray = [];
        init();
    }

Which will update the variables used at the top of the init function as shown below:

// The Slider variables
    var ka = 0.1;
    var kd = 0.4;
    var ks = 0.6;
    var al = 50.0;
    var le = 0.5;

lightAmbient = vec4(le / 10, le / 10, le / 10, 1.0);
lightDiffuse = vec4(le, le, le, 1.0);
lightSpecular = vec4(le, le, le, 1.0);

materialAmbient = vec4(ka, ka, ka, 1.0);
materialDiffuse = vec4(kd, kd, kd, 1.0);
materialSpecular = vec4(ks, ks, ks, 1.0);

materialShininess = al;

Result

Error: Your browser does NOT support WebGL!

Current Subdivision Count

0

= 0.5

= 0.5

= 0.5

= 50

= 0.5


Part 5: Phong Shading

In order to have Phong shading we need to combine the Gouraud Shader and move the Phong Reflection Model calculations to be in the fragment shader so that instead of the vertices being assigned the color it is the polygon edges. This will flip our vertex and fragment shaders in the sense that the vertex shader will now be quite empty as normalizations will need to happen in the fragment shader. Below are the corresponding fragment and vertex shaders:

<script id="vertex-shader-5" type="x-shader/x-vertex">
attribute vec4 vPosition; // Vertex positions

// MVP matrices
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;

// Variables being sent to the fragment shader
varying mat4 fModel;
varying mat4 fProjection;
varying vec4 fNormal;
varying vec4 fPosition;

attribute vec4 vNormal; // Normals for each vertex

void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vPosition;
    fNormal = vNormal;
    fPosition = vPosition;
    fProjection = projectionMatrix;
    fModel = modelViewMatrix;
}
</script>
<script id="fragment-shader-5" type="x-shader/x-fragment">
precision mediump float;

varying mat4 fModel;
varying mat4 fProjection;
varying vec4 fNormal;
varying vec4 fPosition;

// Lighting variables
uniform vec4 lightPosition;
uniform float shininess;

// Dot products of material and lighting variables
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;

// Edge normals in accordance to eye coordinates
uniform mat3 normalMatrix;

void main()
{
vec3 pos = (fModel * fPosition).xyz;

// check for directional light
vec3 L;
if(lightPosition.w == 0.0) L = normalize(lightPosition.xyz);
else L = normalize( lightPosition.xyz - pos );

vec3 E =  -normalize(pos);
vec3 N = normalize( normalMatrix*fNormal.xyz);


vec4 fColor;

vec3 H = normalize( L + E );
vec4 ambient = ambientProduct;

float Kd = max( dot(L, N), 0.0 );
vec4  diffuse = Kd*diffuseProduct;

float Ks = pow( max(dot(N, H), 0.0), shininess );
vec4  specular = Ks * specularProduct;

if( dot(L, N) < 0.0 ) specular = vec4(0.0, 0.0, 0.0, 1.0);

fColor = ambient + diffuse +specular;
fColor.a = 1.0;

gl_FragColor = fColor;
}
</script>

Result

Error: Your browser does NOT support WebGL!

Current Subdivision Count

0

= 0.5

= 0.5

= 0.5

= 50

= 0.5


Part 6: Lab Questions

What is the Difference Between Phong Shading and Phong Lighting (the Phong Reflection model)?

The Phong Reflection Model is a way of making an illumination of surfaces in order to simulate the way that surfaces reflect light. It breaks down reflection into three main aspects; ambient light, diffuse lighting, and specular reflection. It is calculated per vertex and gives a result which can be later interpreted by the fragment shader.

Phong Shading takes the result from the Phong Reflection model to produce a smooth shading technique to blend the colors of a shape at every pixel.

What is the Difference Between Flat Shading, Gouraud Shading, and Phong Shading? List Pros and Cons of Each. Is Gouraud or Phong Shading the Best Method for Simulating Highlights? Explain.

Flat shading is the simplest form of shading an object in where each of the polygons are given their own color by the application. Flat shading can give nice results and is simple to understand but requires a lot of manual labor by the programmer in order to get a nice result. Also flat shading results in just that shading that is less smooth at the edges of each polygon. Part 4 shows this type of shading.

Gouraud Shading takes the flat shading model and averages the shading of each neighboring polygon by taking the average of their normals then applies shading by using the modified Phong Model. This technique is fast to calculate by the computer and fast simple to implement by the programmer. However, it will be calculated in the application and therefore the CPU since it needs to keep track of the vertices in where polygons meet making it not desirable for real-time rendering of detailed scenes. Part 3 shows this type of shading.

Phong Shading builds upon Gouraud shading by finding the vertex normals and instead interpolates vertex average normals across edges and now interpolate the normals inside the polygon given the edge normals and apply the shading at every point corresponding to every fragment. Phong Shading will give a much smoother and accurate shading with less visible edges of the polygons. This type of shading is more work and also needs data structures to represent meshes to obtain the vertex normals. Part 5 shows this type of shading.

In order to simulate highlights the normalization of the vectors needs to be distinct enough that high values of the vector $l$ are allowed through. In special cases such as the apple shape there is a circular highlight that occurs known as a Specular Highlight which occurs because the colors around that spot are not being blended across it. This means that a shading system that depends on edge normals such as Gouraud Shading can sometimes blend across it and make this specular highlighting disappear. Making Phong Shading more desirable when it comes to highlights.

What is the Difference Between a Directional Light and a Point Light?

A point light is a light source with given positions in the scene it then illuminates the scene in every direction from it's surface. A point light's rays will fade out over a set distance. A light bulb is often used as analogous to a point light in the sense that a light bulb in your home will only illuminate objects around the light bulb not the entire home.

Directional light is supposed to represent a far away light ray originating at a light source. It is meant to be interpreted as coming from the same direction regardless of where the object or the viewer are. The most common analogy to a directional light is the sun. This is due to the sun being so far away we don't consider the location of the sun when calculating a shadow we instead understand the lighting from the sun as different rays that are hitting our planet. As such directional lighting is used for global lighting scenes in where every object is being affected in the same way by the directional light source.

Does the Eye Position Influence the Shading of an Object in Any Way?

Due to the eye position being key to the calculations such as the view direction, halfway vectors, and the normals. Changing the eye position will influence the illumination direction on the object making it be shaded in a different manner. This is most obvious with the specular highlights as moving the eye would make the highlight visible or invisible, as seen in the rotation of the sphere.

What is the Effect of Setting the Specular Term to (0,0,0)?

The Specular term of an object as described in the Phong Reflection Model is meant to refer the mirror properties of the object. A mirror is an object that reflects light in a narrow angle back to the viewer. If the specular term is set to 0 then the object will not reflect any light back resulting in no shine or highlights.

What is the Effect of Increasing the Shininess Exponent (𝛼)?

The shininess component is in control of the angle at which light is reflected the higher the value is the narrower the angle of reflection and thus the angle at which the reflection can be seen would be.

In What Coordinate Space Did You Compute the Lighting?

The book introduced the lighting in object space but later we see ways to make it more efficient by using local coordinate systems which is what were used for Part 3 through Part 5.

Lab Finished!

Report Finished!

Report Merged!


Next Lab: Worksheet 5