Before you start

This assignment builds on the solutions for either assignment 3 or 4. If yours worked, I strongly recommend using your own code as a starting point for this assignment. If you were not able to get yours working, you can use the "island" solution for assignment 3 in GitHub repository. Whichever you choose, be sure to do all of your work for this assignment in the "GLapp" directory of your repository.

The Assignment

For this assignment, you will be using the auxilliary textures provided in the data directory to add an ambient occlusion map, gloss map, and normal map to each object. This will require changes in both C++ and shader code. To help isolate the individual effects, pressing the '1' key should toggle between the color texture and a uniform grey surface, pressing the '2' key should toggle the normal map on and off, pressing the '3' key should toggle the gloss map on and off, and pressing the '4' key should toggle the ambient occlusion on and off. Start with all of the effects enabled.

no effects color only ambient occlusion map
No effects Color only AO map
gloss map normal map all effects
Gloss map Normal map All effects


You will need to make changes to the C++ code to load the additional textures and get them into the shader. Try displaying each in place of the ColorTexture to ensure it is loaded correctly. You'll also need to make C++ changes to get the flags to turn each feature on and off to the shader, and to enable the key controls to change them.

Ambient Occlusion

Ambient occlusion (AO) modifies the ambient term based on the local surface or normal map shape, so corners will receive less ambient than flat regions. The AO term is in the blue channel of the property map. When AO is enabled, scale the overall ambient intensity (passed in LightDir.a) by the AO value. In the initial GLapp sample code, the ambient intensity is changed with the 'I' key. To make sure you can see the AO effect, change the default ambient intensity to 0.4.

Gloss Map

A gloss map changes the shininess across the surface. You will need to implement normalized Blinn-Phong specular reflection (aka scaled proportional to the specular power) modulated by the Fresnel reflectance using the Schlick approximation with F0=0.04.

For the specular exponent, use 212*gloss (so a gloss value of 0 in the map is a specular exponent of 1, and a gloss value of 1 in the map is a speculare exponent of 4096). When the gloss map is disabled, use a constant specular exponent of 64.

Fresnel = 0.04 + 0.96 * (1 - N•V)5

SpecPower = 212*gloss

Specular = N•HSpecPower * (SpecPower+1)/2

Color = mix(Ambient + Diffuse, Specular, Fresnel)

Normal Map

The normal map changes the per-pixel normal used for shading to correspond to a higher detail surface than the underlying polygonal model. The components of the new normal are packed into a 0-1 texture as nmap = 0.5*Ntang + 0.5, and can unpacked as Ntang = 2*nmap - 1. It is defined in a tangent space. In tangent space, the X axis, commonly called the tangent vector (T), is tangent to the surface in a direction aligned with changes with the u texture coordinate (∂P/∂u). The tangent-space Y axis, commonly called the bitangent vector (B), is tangent to the surface in a direction aligned with the v texture coordinate (∂P/∂v). The tangent-space Z axis, just called the normal vector (N), is just the original surface normal. This means an un-bumped normal is (0,0,1) in tangent space. To convert a tangent space normal from the normal map to a world space normal to use for shading, you transform it through a TBN matrix whose columns are the normalized tangent, bitangent, and normal. In shader code, the mat3 constructor builds matrices by column, so TBN=mat3(T,B,N), giving Nworld = TBN * Ntang.

The N vector is already provided per-vertex by the existing object code. You will need to add C++ code to compute per-vertex T and B vectors and pass them as additional vertex data to the vertex shader. In the vertex shader, remember that vectors like T and B transform on the right by the top-left 3x3 portion of the WorldFromModel, while the N vector is transformed on the left by the top-left 3x3 portion of inverse(WorldFromModel).

For the plane and sphere, you can directly compute the derivatives of position to find expressions for the T and B vector. For the island, the position relies on random values, which makes direct computation by derivative more difficult. Knowing U and V are aligned with the and Y axes, and as tangent vectors, T and B need to be perpendicular to N, you can instead orthogonalize (1,0,0) and (0,1,0) against the computed per-vertex normal.

634 only

Find a texture on the cc0textures web site that looks reasonable on the ocean (there are no ocean textures there, so you may need to decide it is actually an large flat area of grass, dirt, or rocks, with a mountain poking up). You will need to pack the auxilliary maps into a property map yourself. Implement a specular antialiasing technique. For this, you should explicitly generate the MIP levels for the normal and gloss map (instead of using glGenerateMipmap, baking the variance in the normals into gloss at each level.

Extra Credit
(435 & 634)

For up to 20 points of extra credit, replace one of the textures with a totally procedural texture. The look is inspired by this shadertoy. Note that there are some significant differences between the shadertoy and ours, so even if it were allowed, you would not be able to just use that code, but you are allowed to refer to the code there as a reference. Implement this in a separate fragment shader from the other objects.

Divide texture space into uniform cells and bomb a random seed point into each one using a hash chosen from the Common tab of this shadertoy (you can copy this code). Create colored tiles, where color and gloss both come from a hash of the closest seed.

seam profileCreate a wide seam between tiles with hemispherical normal map profile. To find the seam, you'll need to compute the perpendicular distance of the pixel UV from the line mid-way between the closest seed and second closest, as well as the closest seed and third closest. Note that the shadertoy example does not use the third-closest seed, so has artifacts near corners if you make the boundaries between cells wider. To be guaranteed to find the second and third closest seeds, you'll need to check within ±sqrt(3.25) cells of the UV location of the pixel.

horizon tangentThe ambient occlusion is the integral of cos(φ) over the portion of the hemisphere above the surface that is not blocked by other geometry. The unblocked region for the surface this shader models is bounded by a spherical polygon, which is analytically computable, but somewhat complicated to compute. Instead, you should approximate ambient occlusion as a linear blend between the ambient occlusion for unblocked areas (1.0) and for that blocked by a single vertical wall (0.5). On the flat tiles, use the height of the tangent to the circular profile through the pixel UV. It should have an ambient occlusion near 0.5 when the tangent point is near the base of the cylinder, and an AO near 1 when the tangent point is near the top of they cylinder. On the hemispherical seams, use the z component of the tangent-space normal, with ambient occlusion of 1 at the top of the cylinder and 0.5 where it joins the colored tile.

no effects color only ambient occlusion map
No effects Color only AO map
gloss map normal map all effects
Gloss map Normal map All effects

What to turn in

Turn in this assignment electronically by pushing your source code to your class git repository by 11:59 PM on the day of the deadline and tagging the commit assn5. Do your development in the GLapp directory so we can find it.

Also include an assn5.txt file at the top level of your repository telling us about your assignment. Tell us what works, and what does not. Also tell us what (if any) help you received from books, web sites, or people other than the instructor and TA. Finally, if you did anything toward the extra credit, be sure to tell us about it in this file.

You must make multiple commits along the way with useful checkin messages. We will be looking at your development process, so a complete and perfectly working assignment submitted in a single checkin one minute before the deadline will NOT get full credit. Individual checkins of complete files one at a time will not count as incremental commits. Do be sure to check in all of your source code, but no build files, log files, generated images, zip files, libraries, or other non-code content.