ch3_Sphere_Goraud_Lambert.html
in your favorite HTML5 browser.<body>
of the page. uLightDirection
. uMaterialDiffuse
, which represents the diffuse color of the sphere. Here we use a color selection widget so you can try different colors. The updateObjectColor
function receives the updates from the widgets and updates the uMaterialDiffuse
uniform. uLightDiffuse
, which represents the diffuse color of the light source. There are no reasons as to why the light color has to be white; however for the sake of simplicity, in this case, we are using a slider instead of a color to restrict the light color to the gray scale. We achieve this by assigning the slider value to the RGB components of uLightDiffuse
while we keep the alpha channel set to 1.0
. We do this inside the updateLightDiffuseTerm
function, which receives the slider updates.We have seen an example of a simple scene illuminated using Goraud interpolation and a Lambertian reflection model. We have also seen the immediate effects of changing uniform values for the Lambertian lighting model.
We have mentioned before that we use matrices to move the camera around the scene. Well, we can also use matrices to move lights!
ch3_Sphere_Moving.html
using your favorite source code editor. The vertex shader is very similar to the previous diffuse model example. However, there is one extra line:vec4 light = uMVMatrix * vec4(uLightDirection, 0.0);
Here we are transforming the uLightDirection
vector to the light variable. Notice that the uniform uLightDirection
is a vector with three components (vec3) and that uMVMatrix
is a 4x4 matrix. In order to do the multiplication, we need to transform this uniform to a four-component vector (vec4). We achieve this with the construct:
vec4(uLightDirection, 0.0);
The matrix uMVMatrix
contains the Model-view-transform. We will see how all this works in the next chapter. However, for now, let's say that this matrix allows us to update vertices positions and also, as we see in this example, lights positions.
drawScene
function is invoked, we rotate the matrix mvMatrix
a little bit in the y axis:mat4.rotate(mvMatrix, angle * Math.PI / 180, [0, 1, 0]);
mvMatrix
is mapped to the uniform:uMVMatrix:gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
initLights
function and change the light orientation so the light is pointing in the negative z-axis direction:gl.uniform3f(prg.uLightDirection, 0.0, 0.0, -1.0)
uLightDirection
so it goes back to its initial value:gl.uniform3f(prg.uLightDirection, 0.0, 0.0, -1.0)
drawScene
and change the line:mat4.rotate(mvMatrix, angle * Math.PI / 180, [0, 1, 0]);
with:
mat4.rotate(mvMatrix, angle * Math.PI / 180, [1, 0, 0]);
What can you conclude? As you see, the vector that is passed as the third argument to mat4
.rotate determines the axis of the rotation. The first component corresponds to the x-axis, the second to the y-axis and the third to the z-axis.
In contrast with the Lambertian reflection model, the Phong reflection model considers three properties: the ambient, diffuse, and specular. Following the same analogy that we used in the previous section:
Final Vertex Color = Ia + Id + Is
where:
Ia = Light Ambient Property * Material Ambient Property Id = Light Diffuse Property * Material Diffuse Property * Lambert coefficient Is = Light Specular Property * Material Specular Property * specular coefficient
Please notice that:
Ia, Id
, and Is
receive contributions from their respective light and material properties.Based on our knowledge of the Phong reflection model, let's see how to calculate the specular coefficient in ESSL:
float specular = pow(max(dot(R, E), 0.0), f );
where:
E
is the view vector or camera vector.
R
is the reflected light vector.
f
is the specular exponential factor or shininess.
R
is calculated as:
R = reflect(L, N)
where N
is the vertex normal considered and L
the light direction that we have been using to calculate the Lambert coefficient.
Let's take a look at the ESSL implementation for the vertex and fragment shaders.
Vertex shader:
attribute vec3 aVertexPosition; attribute vec3 aVertexNormal; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat4 uNMatrix; uniform float uShininess; uniform vec3 uLightDirection; uniform vec4 uLightAmbient; uniform vec4 uLightDiffuse; uniform vec4 uLightSpecular; uniform vec4 uMaterialAmbient; uniform vec4 uMaterialDiffuse; uniform vec4 uMaterialSpecular; varying vec4 vFinalColor; void main(void) { vec4 vertex = uMVMatrix * vec4(aVertexPosition, 1.0); vec3 N = vec3(uNMatrix * vec4(aVertexNormal, 1.0)); vec3 L = normalize(uLightDirection); float lambertTerm = clamp(dot(N,-L),0.0,1.0); vec4 Ia = uLightAmbient * uMaterialAmbient; vec4 Id = vec4(0.0,0.0,0.0,1.0); vec4 Is = vec4(0.0,0.0,0.0,1.0); Id = uLightDiffuse* uMaterialDiffuse * lambertTerm; vec3 eyeVec = -vec3(vertex.xyz); vec3 E = normalize(eyeVec); vec3 R = reflect(L, N); float specular = pow(max(dot(R, E), 0.0), uShininess ); Is = uLightSpecular * uMaterialSpecular * specular; vFinalColor = Ia + Id + Is; vFinalColor.a = 1.0; gl_Position = uPMatrix * vertex; }
We can obtain negative dot products for the Lambert term when the geometry of our objects is concave or when the object is in the way between the light source and our point of view, in either case the negative of the light-direction vector and the normals will form an obtuse angle producing a negative dot product, as shown in the following figure:
For that reason we are using the ESSL built-in clamp function to restrict the dot product to the positive range. In the case of obtaining a negative dot product, the clamp function will set the lambert term to zero and the respective diffuse contribution will be discarded, generating the correct result.
Given that we are still using Goraud interpolation, the fragment shader is exactly as before:
#ifdef GL_ES precision highp float; #endif varying vec4 vFinalColor; void main(void) { gl_FragColor = vFinalColor; }
In the following section, we will explore the scene and see what it looks like when we have negative Lambert coefficients that have been clamped to the [0,1] range.
3.145.47.253