## CS424 Notes, 26 March 2012

- Light and Material in a Shader
- We will look at http://math.hws.edu/eck/cs424/s12/notes/mar26/world1.html and http://math.hws.edu/eck/cs424/s12/notes/mar26/world1.html today in class. These examples implement a somewhat simplified form of the lighting equation that we looked at in class on March 16. (They do not do attenuation or spotlights, and they separate specular and diffuse light intensities.)
- 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); } }

- The shader program illustrates several new features of the shader langauge:
- The language supports
*structs*, similar to C but not with the full complexity. GLSL structs can contain arrays but not other structs. The structure definitions for*materialProperties*and*lightProperties*define new types that can then be used to declare variables just like the built-in types. - The language supports arrays, including arrays of structs. But array indexing might be limited to constants and for-loop variables.
- Structs can be used for uniforms, but not for attributes or varyings. Arrays can be used for uniforms and varyings, but not for attributes.
- Functions are defined and used just as in C (but recursion is not allowed).

- The language supports
- Note that the calculations in the
*lighting*function are vector calculations, working with values of type*vec3*. This allows the function to do the computations for the R, G, and B components of the color all at once. - Lighting calculations are done in view coordinates. They use vertex coordinates that
have been transformed by the modelview matrix. Light positions are assumed to be in view
coordinates already. That is, they have already been transformed by the appropriate modelview
matrix. In standard OpenGL, light positions are automatically transformed by the current
modelview matrix when the position is specified, and the position is stored in view coordinates.
In this example, light positions are
**not**transformed, which means that they are given directly in view coordinates. Another way of saying this is that the light positions are given relative to the viewer, and the lights keep the same position even as the world is being rotated. - Normal vectors also have to be transformed to view coordinates before they can be used
in the lighting computation. However, normal vectors are not transformed by the modelview
matrix. Instead, they are transformed by the
*inverse transpose*of the upper-left 3-by-3 submatrix of the modelview matrix. (This can be shown mathematically and it can be shown geometrically for simple cases, but I don't know a good geometric argument for the general case.) The normalTransform matrix is computed in the JavaScript program and sent to the shader program by saying:gl.uniformMatrix3fv(normalTransformUniformLocation, false, mat3.transpose(mat4.toInverseMat3(modelview)) );

- 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*.

- Hierarchical graphics in 3D
- The second of today's examples, world2.html, uses hierarchical graphics.
We will spend some time today and Wednesday looking at this example in more detail, including
the definitions of the classes
*SimpleObject3D*and*ComplexObject*which are used to build the data structure that represents the content of the world. - The data structure is known as a
*scene graph*. The scene graph represents the content of the world. To render the world, you have to traverse the data structure and render each object that you encounter. - Transformations are also part of the scene graph, and you have to manage those transformations as you traverse the graph.
- Eventually, we would like to have viewers and lights as part of a scene graph, but there are certain complications...

- The second of today's examples, world2.html, uses hierarchical graphics.
We will spend some time today and Wednesday looking at this example in more detail, including
the definitions of the classes