# CS315: Illumination 2

## Highlights of this lab:

This lab extends your knowledge of basic illumination

Assignment:

After the lab lecture, you have approximately one week to:
• Isolate the Blinn-Phong Specular Lighting code from example 1's shader and add it to example 2's shader
• Create and name a material
• Light it with a pleasing light

## Lab Notes

In last week's lab you saw these pictures. They show a full implementation of the Blinn-Phong reflection equation being applied in three different ways. The first is flat shading - one colour is used for one whole primitive (triangle). The second is Gouraud shading - reflection is only calculated at the vertices, and colours are interpolated. The last is Phong shading - the full lighting equation is performed per fragment in the fragment shader. This week you will see results that look like all three columns, but you will always be doing Gouraud shading. You will add the Blinn-Phong specular component to a vertex shader, which produces the whitish highlight you see. The smoothness or flatness you will observe will be created entirely in the model - either by using the same normal for all vertices of a primitive (flat-like results) or high resolution meshes (Phong-like results).

### A. Blinn-Phong Equation Revisited

When you start to work with lighting, you move beyond color to normals, material properties and light properties. Normals describe what direction a surface is facing at a particular point. Material properties describe of what things are made of — or at least what they appear to be made of — by describing how they reflect light. Light properties describe the type and colour of the light interacting with the materials in the scene. Lights and materials can interact in many different ways. Describing these many different ways is one reason shaders are so important to modern 3D graphics APIs.

One common lighting model that relates geometry, materials and lights is the Blinn-Phong reflection model. It breaks lighting up into three simplified reflection components: diffuse, specular and ambient reflection. In this week's lab we will focus on specular reflection. The other two are left here for your reference.

##### Specular Reflection

Specular reflection represents the shine that you see on very smooth or polished surfaces. Phong specular reflection takes into account both the angle between your eye and the direction the light would be reflected. As your eye approaches the direction of reflection, the apparent brightness increases. It assumes that light will be scattered toward the mirror reflection direction. A special shininess parameter is used to control how tight this scattering is. The Blinn-Phong specular reflection is very similar to Phong, but it fixes some technical shortcomings having to do with backscatter (compare the curves you see if figure 5). Instead of using the reflection vector it uses a vector that is halfway between the light and the eye. This vector is then compared to the normal.

The Blinn-Phong specular component is calculated by these equations:

h = (e+l)/|(e+l)|
Is = ms Ls (h · n)s

Where:

• Is is the intensity of specular reflection
• ms is the material's specular colour
• Ls is the light's specular colour
• l is the normalized direction to the light
• e is the normalized direction to the eye
• n is the surface normal
• s is the exponent that controls shininess.

 Figure 6: Blinn-Phong at Varying Angles.
##### Putting Things Together

This specular colour is added to the diffuse and ambient colours described in last week's lab. The illumination of an object from one light, then, is the sum of each of these components.

I = Is + Id + Ia

A vertex shader that implements all of this is included in Demo 1. Its code is shown below. The new specular lighting code is highlighted for your convenience:
```
//diffuse and ambient multi-light shader

//inputs
attribute vec4 vPosition;
attribute vec3 vNormal;

//outputs
varying vec4 color;

//structs
struct _light
{
vec4 diffuse;
vec4 ambient;
vec4 specular;
vec4 position;
};

struct _material
{
vec4 diffuse;
vec4 ambient;
vec4 specular;
float shininess;
};

//constants
const int nLights = 1; // number of lights

//uniforms
uniform mat4 p;     // perspective matrix
uniform mat4 mv;    // modelview matrix
uniform bool lighting;  // to enable and disable lighting
uniform vec4 uColor;    // colour to use when lighting is disabled
uniform _light light[nLights]; // properties for the n lights
uniform _material material;    // material properties

//globals
vec4 mvPosition; // unprojected vertex position
vec3 N; // fixed surface normal

//prototypes
vec4 lightCalc(in _light light);

void main()
{
//Transform the point
mvPosition = mv*vPosition;  //mvPosition is used often
gl_Position = p*mvPosition;

if (lighting == false)
{
color = uColor;
}
else
{
//Make sure the normal is actually unit length,
//and isolate the important coordinates
N = normalize((mv*vec4(vNormal,0.0)).xyz);

//Combine colors from all lights
color.rgb = vec3(0,0,0);
for (int i = 0; i < nLights; i++)
{
color += lightCalc(light[i]);
}
color.a = 1.0; //Override alpha from light calculations
}
}

vec4 lightCalc(in _light light)
{
//Set up light direction for positional lights
vec3 L;

//If the light position is a vector, use that as the direction
if (light.position.w == 0.0)
L = normalize(light.position.xyz);
//Otherwise, the direction is a vector from the current vertex to the light
else
L = normalize(light.position.xyz - mvPosition.xyz);

//Set up eye vector
vec3 E = -normalize(mvPosition.xyz);

//Set up Blinn half vector
vec3 H = normalize(L+E);

//Calculate specular coefficient
float Ks = pow(max(dot(N, H),0.0), material.shininess);

//Calculate diffuse coefficient
float Kd = max(dot(L,N), 0.0);

//Calculate colour for this light
vec4 color =  Ks * material.specular * light.specular
+ Kd * material.diffuse * light.diffuse
+ material.ambient * light.ambient;

return color;
}
```

Appropriate changes should be made to get and set the specular colour and shininess uniforms. The process is similar to what you observed with diffuse and ambient lighting.

#### Specifying Material Properties

Classic OpenGL has five material properties affect a material's illumination. They are introduced in the Blinn-Phong model section and implemented in the shaders in lab demo 2. They are explained below.

Diffuse and ambient properties:
The diffuse and ambient reflective material properties are a type of reflective effect that is independent of the viewpoint. Diffuse lighting describes how an object reflects a light that is shining on the object. That is, it is how the surface diffuses a direct light source. Ambient lighting describes how a surface reflects the ambient light available. The ambient light is the indirect lighting that is in a scene: the amount of light that is coming from all directions so that all surfaces are equally illuminated by it. Both properties are usually set to the similar colours, though the ambient colour is usually scaled down or darker than the diffuse colour.

Specular and shininess properties:
The specular and the shininess properties of the surface describe reflective effects that are affected by the position of the viewpoint. Specular light is reflected light from a surface that produces the reflective highlights in a surface. The shininess is a value that describes how focused the reflective properties are.

Emissive property:
Emissive light is the light that an object gives off by itself. Objects that represent a light source are typically the only objects that you give an emissive value. Lamps, fires, and lightning are all objects that give off their own light.

Our shader does not support emissive values as such, but we can simulate it by disabling lighting and using a uniform colour. The light sphere in this week's sample is represented in this way.
##### Choosing the Material Properties for an Object

The steps are as follows:

1. Decide on the diffuse and ambient colors;
2. Decide on the shininess of the object.
3. Decide whether the object is giving off light. If it is, assign it the emissive properties

Seeing the effects of varying material properties may help you select the ones you want.

In this week's first demo - seen below and in phongShading.html or gouraudShading.html - specular, diffuse and ambient material properties have been implemented. They have been declared for you globally, given default values and sent to the shader. The program provides convenient colour pickers to allow you to select different specular and diffuse/ambient colours interactively. It also provides sliders to rotate the light about the Y-axis and change the shininess value. You should take some time to get familiar with the effects of the different properties.

Click here to view demo on its own.

### Hemisphere Lighting

Specular lighting adds a lot to the diffuse and ambient lighting you learned last week. But there's more to add to diffuse lighting. The ambient components are a hack to simulate global illumination, and they do a pretty poor job. Unless you use textures, there's no detail to be seen in ambient light - only a flat silhouette. Hemisphere is a better looking hack that extends the light all around the object. Like diffuse/ambient it uses two light colours, but each is considered to be in opposite hemispheres shining down on the object. Toward the middle, their colours blend smoothly. These colours are intended to represent the sky and the ground, but many implementations allow you to specify the direction of the north pole, or top hemisphere, so it becomes simple to specify, directional, two colour global illumination.

This picture illustrates the intent:

Figure 5: Hemisphere lighting's concept. Light falls on the object from two opposite hemispheres and blends across the object.

Downward facing normals are lit entierly by the bottom hemisphere. Upward facing normals are lit entirely by the upper hemisphere. Angled normals are linearly blended (or lerped) between the two based on their angle to the "north pole" or top direction.

The proper equation for this is:

Color = a · TopColor + (1 - a) · BottomColor

Where:

a = 1.0 - (0.5 · sin(Θ)) for Θ ≤ 90°
a = (0.5 · sin(Θ)) for Θ > 90°

This can be simplified without too much error to:

a = 0.5 + (0.5 · cos(Θ))

Which replaces an expensive sin() with a cos() and saves a branch instruction. Remember, both paths through a branch are always executed in WebGL shader programs. Since this method of global illumination is already approximate, the error is mostly harmless. Here are the two curves overlaid on each other:

Figure 6: Comparison of real vs. fake a calculation for hemisphere lighting.

The following shader implements hemisphere lighting. You will find it in L5-2.html:

Hemisphere Lighting Vertex Shader
```
//hemisphere lighting shader with optional diffuse lighting path

//inputs
attribute vec4 vPosition;
attribute vec3 vNormal;

//outputs
varying vec4 color;

//structs
struct global_ambient
{
vec4 direction;  // direction of top colour
vec4 top;        // top colour
vec4 bottom;     // bottom colour
};

struct _material
{
vec4 diffuse;
};

//uniforms
uniform mat4 p;     // perspective matrix
uniform mat4 mv;    // modelview matrix

uniform _material material; // material properties
uniform global_ambient global;	// global ambient uniform

//globals
vec4 mvPosition; // unprojected vertex position
vec3 N; // fixed surface normal

void main()
{
//Transform the point
mvPosition = mv*vPosition;  //mvPosition is used often
gl_Position = p*mvPosition;

//Make sure the normal is actually unit length,
//and isolate the important coordinates
N = normalize((mv*vec4(vNormal,0.0)).xyz);

//Make sure global ambient direction is unit length
vec3 L = normalize(global.direction.xyz);

//Calculate cosine of angle between global ambient direction and normal
float cosTheta = dot(L,N);

//Calculate global ambient colour
float a = 0.5+(0.5*cosTheta);
color = a * global.top * material.diffuse
+ (1.0-a)* global.bottom * material.diffuse;
color.a = 1.0; //Override alpha from light calculations - only needs to be done once
}
```

And here is a working hemisphere and Lambertian sample for you to experiment with:

Observe how much detail you can make out in the shadows with Hemisphere Lighting as compared with Lambertian Directional Lighting. Notice, also, that the latter is brighter - this is because the ambient and diffuse colours are added rather than linearly blended.

### C. Distance and Positional Lights

In the real world a point light source, or positional light, will have less power to light an object the farther the two are apart. This is called attenuation. Because the light spreads out in an ever increasing sphere, the intensity of the light decreases in inverse proportion to the square of the distance. Classic OpenGL has three parameters to calculate the attenuation factor: constant, linear, and quadratic attenuation. These three factors are used as shown in this equation:

```float attenuation = 1.0;
if (/* light is positional */)
{
float lightDistance = length(light.position.xyz - mvPosition.xyz);
attenuation = 1.0/(constant + linear * lightDistance + quadratic * lightDistance*lightDistance);
}```

This calculation is applied separately to the entire colour of each light - you multiply that light's colour by the attenuation, something like this

```// where color is the color result for one light
// and attenuation is 1 for directional lights
color = attenuation * color;```
• Constant attenuation is easiest to work with for beginners, and it gives an easy way for you to dim the lights - just crank up the constant attenuation.
• Linear attenuation sometimes gives more natural results if you are not doing HDR tone mapping or some other colour correction.
• Quadratic attenuation is the physically correct term to use, but it causes extreme light differences at short distances, so it is often avoided.

Attenuation is not implemented in the shaders in this week's lab. This is left for you to do as an exercise.

## Assignment

Start with the files from Lab7.zip. This lab adds two important libraries - Apple's j3di.js, which provides an OBJ file loader, and East Desire's jscolor.js which provides the colour picker you see in the demos. They are in the lab folder for your convenience. You might want to consider putting copies in the Common folder for your own future use.

If you don't use Visual Studio Code's Live Server extension, Batman - a local .obj file - might not show up in any of the samples. You might need to to start Chrome from a Mac Terminal like this if you want to open local files (like Batman or your own OBJ):

```open /Applications/Google\ Chrome.app --args --allow-file-access-from-files
```

Goals:

• understand the two new lighting concepts introduced in this lab
• add hemisphere global illumination and distance calculations to the specular shader
• play with light and material properties to produce a pleasing result.

Instructions

1. You will make several changes to L7E.html and L7E.js. There are //Add comments scattered throughout the code to help guide you. I may have missed a couple, but I tried to be thorough.
2. Study how the specular shader in the lab notes and the lab exercise L7E.html/L7E.js works. If you like you may switch the shader from the default Gouraud shader to the sample Phong shader. The Javascript shouldn't need to be changed to do this - see the Gouraud and Phong based examples which both reference the same Javascript file.
3. Study the simplified global hemisphere shader in the lab notes and example hemisphere.html/hemisphere.js files.
4. Add global hemisphere shading code to the lighting shader in L7E.html. Specifically:
• Add the global_ambient structure where indicated
• Declare the global uniform where indicated
• Add the hemisphere based ambient calculations where indicated
• Notice they are supposed to go in main, not the lightCalc function. There is only one global hemisphere ambient, but there can be many positional or directional lights. The global hemisphere ambient will replace the black initial value of color before other lights are added.
• Set up HTML inputs for hemisphere top and bottom colours and hemisphere direction.
• Choose your own background for the canvas. You may wish to resize your chosen image or the canvas so they match. Ask if you need help with lab image software.
• The necessary code is in hemisphere.html and hemisphere.js. Look for the style on the canvas in the .html file, and be sure to enable blending and a clear color of (0,0,0,0) as is done in the .js file's init function.
5. Make the appropriate changes to L7E.js to use the modified shader and controls as indicated by the //Add comments.
6. Leave your materials a basic gray colour while you experiment with global light properties until you find a combination that fits your background.
• You might want to dim or disable the positional light while you do this.
7. Now that you've set a good ambient light, experiment with material properties until you find a material you like.
• If your ambient light is extreme, you might want to temporarily reset to white and black or use the phongShading.html or gouraudShading.html to help see the material under plain white light.
• Set your material as the default and name it - your lab instructor might show you examples of materials they like using a lab demo to help you get some ideas. Be sure to set specular color and shininess as well as diffuse and ambient.
8. Implement an attenuation calculation in your lighting shader as described in the lab notes where indicated, including constant, linear and quadratic factors.
• hard code factors that show appropriate changes in light brightness as you move the positional light with the A S D and W keys.

/10

### Deliverables

• A zip file containing a copy of your Lab7 and Common folders.
• The Lab7 folder should contain html and javascript files with a complete solutions to the exercise. Please write the name of your material with a description at the top of your HTML document.
• Please either put your solution into L7E.html and L7E.js or into files clearly marked as belonging to the exercise.