Naïve approach is to assign to each vertex a color that seems as though it is lit.
Does not work if we transform the light, the object, or the camera!
Light properties:
Light properties:
Material properties:
Material properties:
Geometry properties:
Geometry properties:
For now, we will only consider a small subset of these properties:
Directional light has no position, only an orientation.
Define this as a direction (unit vector)
Specified in shader using uniform vec3, because the direction will not change for each fragment.
Similarly, the color and brightness can be specified in shader as uniform vec3.
The intensity of light that is reflected into the camera by the material of the surface depends on:

Diffuse materials scatter incident light equally in all directions, independent of the camera's orientation to the surface. (e.g., moon)

The intensity of reflected light does depend on the relative orientation of the surface to the light source. In other words, as the surface "turns away" from the light, the intensity of reflected light falls off.

This fall-off can be computed as \(\cos(\theta)\), the cosine of the angle between the surface normal (\(\n\)) and the lighting direction (\(\l\)). Another mathematically equivalent way: \(\n \cdot \l\)
// LightedCube_animation.js (c) 2012 matsuda
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'attribute vec4 a_Normal;\n' +
'uniform mat4 u_MvpMatrix;\n' +
'uniform mat4 u_NormalMatrix;\n' +
'uniform vec3 u_LightDirection;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' vec4 normal = u_NormalMatrix * a_Normal;\n' +
' float nDotL = max(dot(u_LightDirection, normalize(normal.xyz)), 0.0);\n' +
' v_Color = vec4(a_Color.xyz * nDotL, a_Color.a);\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to initialize shaders');
return;
}
//
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
// Set the clear color and enable the depth test
gl.clearColor(0, 0, 0, 1);
gl.enable(gl.DEPTH_TEST);
// Get the storage locations of uniform variables and so on
var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
if (!u_MvpMatrix || !u_NormalMatrix || !u_LightDirection) {
console.log('Failed to get the storage location');
return;
}
var vpMatrix = new Matrix4(); // View projection matrix
// Calculate the view projection matrix
vpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
vpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
// Set the light direction (in the world coordinate)
var lightDirection = new Vector3([0.5, 3.0, 4.0]);
lightDirection.normalize(); // Normalize
gl.uniform3fv(u_LightDirection, lightDirection.elements);
var currentAngle = 0.0; // Current rotation angle
var modelMatrix = new Matrix4(); // Model matrix
var mvpMatrix = new Matrix4(); // Model view projection matrix
var normalMatrix = new Matrix4(); // Transformation matrix for normals
var tick = function() {
currentAngle = animate(currentAngle); // Update the rotation angle
// Calculate the model matrix
modelMatrix.setRotate(currentAngle, 0, 1, 0); // Rotate around the y-axis
mvpMatrix.set(vpMatrix).multiply(modelMatrix);
gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
// Pass the matrix to transform the normal based on the model matrix to u_NormalMatrix
normalMatrix.setInverseOf(modelMatrix);
normalMatrix.transpose();
gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);
// Clear color and depth buffer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Draw the cube
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
requestAnimationFrame(tick, canvas); // Request that the browser ?calls tick
};
tick();
}
function draw(gl, n, angle, vpMatrix, u_MvpMatrix, u_NormalMatrix) {
}
function initVertexBuffers(gl) {
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
// Coordinates
var vertices = new Float32Array([
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0, // v0-v1-v2-v3 front
1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0, // v0-v3-v4-v5 right
1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0, // v1-v6-v7-v2 left
-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0, // v7-v4-v3-v2 down
1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0 // v4-v7-v6-v5 back
]);
// Colors
var colors = new Float32Array([
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v1-v2-v3 front
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v5-v6-v1 up
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v1-v6-v7-v2 left
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v7-v4-v3-v2 down
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0 // v4-v7-v6-v5 back
]);
// Normal
var normals = new Float32Array([
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);
// Indices of the vertices
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);
// Write the vertex property to buffers (coordinates, colors and normals)
if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;
if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) return -1;
if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;
// Unbind the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// Write the indices to the buffer object
var indexBuffer = gl.createBuffer();
if (!indexBuffer) {
console.log('Failed to create the buffer object');
return false;
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return indices.length;
}
function initArrayBuffer(gl, attribute, data, num, type) {
// Create a buffer object
var buffer = gl.createBuffer();
if (!buffer) {
console.log('Failed to create the buffer object');
return false;
}
// Write date into the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// Assign the buffer object to the attribute variable
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('Failed to get the storage location of ' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// Enable the assignment of the buffer object to the attribute variable
gl.enableVertexAttribArray(a_attribute);
return true;
}
// Rotation angle (degrees/second)
var ANGLE_STEP = 30.0;
// Last time that this function was called
var g_last = Date.now();
function animate(angle) {
// Calculate the elapsed time
var now = Date.now();
var elapsed = now - g_last;
g_last = now;
// Update the current rotation angle (adjusted by the elapsed time)
var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
return newAngle %= 360;
}
Transforming a point \(\x\) (vector) by the transformation matrix \(M\) is done by simple matrix-vector multiplication
\[M \x\]
Transforming a direction \(\d\) (unit vector) by the transformation matrix \(M\) is done by simple matrix-vector multiplication followed by a normalization (make result unit vector!)
\[\frac{M \d}{||M \d||}\]
However, transforming a normal \(\n\) (perpendicular unit vector) by the transformation matrix \(M\) is done by multiplying with the inverse transpose of \(M\) followed by a normalization.
\[\frac{(M^T)^{-1} \n}{||(M^T)^{-1} \n||}\]
Proof:
Surface normal can be computed as normalized cross product of two independent surface tangents
For triangle, choose two edges as tangents.
Recall that Gourard shading is done by computing the "color" at each vertex of a triangle and interpolating these colors across the surface (varying vec3 v_color).
Phong shading interpolates the normal of each vertex across the triangle and computes the "color" at each fragment (varying vec3 v_normal).
