To render our geometry and take advantage of our normals, we require a shader that implements the lighting equation. This is a more sophisticated application of GLSL. Here is a summary of GLSL that covers everything we'll need to know.
The uniforms configure the transformation matrices and the lighting parameters. The attributes configure the vertices. The varyings define the values to be interpolated by the rasterizer. Pay close attention to their types.
precision mediump floatuniform mat4 projectionMatrixuniform mat4 viewMatrixuniform mat4 modelMatrixuniform vec4 lightPositionattribute vec4 vertexPositionattribute vec3 vertexNormalvarying vec3 fragmentNormalvarying vec3 fragmentLightvarying vec3 fragmentViewThe vertex shader uses the scene parameters defined by the attributes and uniforms to calculate $n$, $l$, and $v$, the inputs to the lighting model. Note, lighting calculations are done in eye space because this allows us to infer that the viewer sits at the origin.
normalize(mat3(modelViewMatrix) · vertexNormal)normalize(vec3(q − p))normalize(vec3(−p))gl_Position ← projectionMatrix · modelViewMatrix · vertexPositionThe lighting equation itself is implemented in the fragment shader. Here are the inputs.
precision mediump floatvarying vec3 fragmentNormalvarying vec3 fragmentLightvarying vec3 fragmentViewuniform vec3 modelColoruniform vec3 lightColorAnd here is the calculation. We arbitrarily choose a constant specular exponent of 10, but this could easily be another uniform.
normalize(fragmentNormal)normalize(fragmentLight)normalize(fragmentView)normalize(l + v)max(l · n, 0)max(h · n, 0)10gl_FragColor ← vec4(fragmentColor, 1.0)As usual, don't forget to use gl.getUniformLocation, gl.uniformMatrix4f, gl.uniform3f, and gl.uniform4f in the application code to query the locations of each of these uniforms and give them a value.