The shader program for both examples are the same:
VERTEX SHADER:
attribute vec3 vertexCoords;
attribute vec3 normalCoords;
uniform mat4 modelview;
uniform mat4 projection;
varying vec3 viewCoords;
varying vec3 normal;
void main() {
vec4 coords = modelview*vec4(vertexCoords,1.0);
viewCoords = coords.xyz;
gl_Position = projection * coords;
normal = normalCoords;
}
FRAGMENT SHADER:
precision mediump float;
struct materialProperties {
vec3 ambient;
vec3 diffuse;
vec3 specular;
vec3 emissive;
float shininess;
};
struct lightProperties {
vec4 position;
vec3 intensity;
vec3 ambient;
bool enabled;
};
uniform materialProperties material;
uniform lightProperties light[8];
uniform mat3 normalTransform;
uniform bool lit;
uniform vec3 globalAmbient;
varying vec3 viewCoords;
varying vec3 normal;
vec3 lighting(vec3 vertex, vec3 V, vec3 N) {
vec3 color = material.emissive + material.ambient * globalAmbient;
for (int i = 0; i < 8; i++) {
if (light[i].enabled) {
color += material.ambient * light[i].ambient;
vec3 L;
if (light[i].position.w == 0.0)
L = normalize( light[i].position.xyz );
else
L = normalize(light[i].position.xyz/light[i].position.w - vertex);
if ( dot(L,N) > 0.0) {
vec3 R;
R = (2.0*dot(N,L))*N - L;
color += dot(N,L)*(light[i].intensity*material.diffuse);
if ( dot(V,R) > 0.0)
color += pow(dot(V,R),material.shininess) *
(light[i].intensity * material.specular);
}
}
}
return color;
}
void main() {
if (lit) {
vec3 tnormal = normalize(normalTransform*normal);
if ( gl_FrontFacing == false )
tnormal = -tnormal;
gl_FragColor = vec4(lighting(viewCoords, normalize(-viewCoords),tnormal),1.0);
}
else {
gl_FragColor = vec4(material.diffuse, 1.0);
}
}
This program uses arrays and structs of uniforms. Unfortunately, a uniform location on
the JavaScript side can only refer to one of the built-in shader language types. This means
that to work with a uniform variable that is a struct, you need a separate location for
each item in the struct. And to work with a uniform variable that is an array, you need
a separate location for each element of the array. In the sample programs, I build objects
and arrays of uniform locations that mirror the structure of the variables in the shader
program. At the same time, I assign default values to all uniforms that represent light
and material properties:
materialUniformLocation = {
ambient: gl.getUniformLocation(prog, "material.ambient"),
diffuse: gl.getUniformLocation(prog, "material.diffuse"),
specular: gl.getUniformLocation(prog, "material.specular"),
emissive: gl.getUniformLocation(prog, "material.emissive"),
shininess: gl.getUniformLocation(prog, "material.shininess")
};
lightUniformLocation = [];
for (var i = 0; i < 8; i++) {
var light = {
position: gl.getUniformLocation(prog, "light[" + i + "].position"),
intensity: gl.getUniformLocation(prog, "light[" + i + "].intensity"),
ambient: gl.getUniformLocation(prog, "light[" + i + "].ambient"),
enabled: gl.getUniformLocation(prog, "light[" + i + "].enabled")
};
if (i == 0) {
gl.uniform1i(light.enabled, true);
gl.uniform3f(light.intensity, 1, 1, 1);
}
else {
gl.uniform1i(light.enabled, false);
gl.uniform3f(light.intensity, 0, 0, 0);
}
gl.uniform3f(light.ambient, 0, 0, 0);
gl.uniform4f(light.position, 0, 0, 1, 0)
lightUniformLocation.push(light);
}
gl.uniform3f(globalAmbientUniformLocation, 0.1, 0.1, 0.1);
gl.uniform3f(materialUniformLocation.ambient, 0.8, 0.8, 0.8);
gl.uniform3f(materialUniformLocation.diffuse, 0.8, 0.8, 0.8);
gl.uniform3f(materialUniformLocation.specular, 0.2, 0.2, 0.2);
gl.uniform3f(materialUniformLocation.emissive, 0, 0, 0);
gl.uniform1f(materialUniformLocation.shininess, 25);
Note how the names for the uniforms in the shader program are the full names of
individual items in the values, such as material.diffuse and
light[3].position.