Tutorial 6: Lighting Calculations
Exercises:
In this tutorial:
- Compile and run example code to apply per-vertex lighting on the CPU
- Extend this code to include specular lighting (solution now included in sinewave3D-glm.cpp)
- Write a shader to apply per-vertex lighting on the GPU
- Extend the shader to perform per-pixel lighting
Example code
Note that in order to perform lighting calculations, a math library is needed. For this tute we will be using the glm math library, which is a C++ library. This means you will now need to compile your code using g++ instead of gcc. Your existing C code should not need to be changed.
- Download sinewave3D-glm.cpp and compile and run. The glm library is source code entirely in header files, used by #include in the program so it does not need to be specified as a library to be linked, i.e. no -lglm is necessary.
- Note that some fixed functionality has been replicated using glm, including default matrix stack and transformations, as well as default lighting. You can switch between fixed pipeline lighting and CPU based lighting calculations using the 'f' key.
Specularity
Note: the sinewave program now includes specular lighting, but this is kept here for interest and background
Currently the program does not use fixed functionality
OpenGL lighting, instead it computes ambient/diffuse
per-vertex lighting on the CPU in the
function computeLighting(...)
which is then
given as the value to glColor
. We will modify
this function to add a specular calculation using
the Blinn-Phong
method. Note that these calculations should only be done if
the dot product value (dp
) from the diffuse
calculation is greater than 0 - signifying that the surface
is front and not back facing.
-
The Blinn-Phong lighting calculations in OpenGL use the
“half vector” H between the light and viewer
direction vectors to approximately model the direction
where the center of the specular highlight occurs. For now
this is just the light vector + viewer direction:
H = lEC + vEC
(and don't forget to normalize H afterwards) wherelEC = glm::vec3(0, 0, 1)
is the default light direction for GL_LIGHT0 andvEC = glm::vec3(0, 0, 1)
is the default view vector as the viewer is looking down thez
axis and is at infinity (see glLightModel and GL_LIGHT_MODEL_LOCAL_VIEWER setting and experiment with fixed lighting changing between infinite and local viewer) -
Next calculate the dot product (
NdotH
) of the half vector with the normal vectornEC
, clamped between 0 and 1 as it could be negative - why? -
Then the intensity of the specular highlight
is
NdotH ^ shininess
where shininess is an arbitrary value, for now set this to 50. -
Finally the specular highlight is calculated
as
specularColor * intensity
where the specular color is a combination i.e. product of the light and material's specular colors. For now set the light's specular color to (1,1,1) since that is what OpenGL uses by default for LIGHT0 and assume the material's specular color is also (1, 1, 1) and shininess i.e. power is 50. -
Now add the specular contribution to the
glm::vec3 color
equation, and compare your result with the fixed functionality version (using the keyf
, you will need to set theGL_SPECULAR
property value of the material and provide it with a shininess value of 50), they should look identical. - Celebrate that you now know the fundamentals of how OpenGL implements its ambient, diffuse and specular (ADS) lighting calculations.
Per-Vertex Lighting using Shaders
-
Using the techniques from last week, copy the shader
programs (vert and frag files) and load and use them
inside
init()
by callinggetShader()
and glUseProgram. Compile the programs using C++, i.e.g++ -std=c++14 shaders.c sinewave3D-glm.cpp -lglut -lGLU -lGL -lm
(compiling all source files with the C++ compiler). You will need to add#include "shaders.h"
,#define GL_GLEXT_PROTOTYPES
and a declaration ofshaderProgram
to sinewave3D-glm.cpp (and look at shaders.c and tute-shaders.c to see how and where). -
Now modify the program to bind the shader (using
glUseProgram) only when rendering the sine wave (ie not
the axes or the OSD). The shader program applies a red
ambient color, and since modelview transforms are now done
on the CPU,
gl_Vertex
that is passed to the vertex shader will already be in eye space. -
Add a
varying vec4 color
variable that passes color from the vertex to the fragment shader, and is applied togl_FragColor
, set this color to green in the vertex shader to confirm that it works. -
Copy and modify the function
computeLighting
from the sinewave program to the vertex shader to do the ambient and diffuse calculations, setting thecolor
variable. You can get the normal vector in the vertex shader fromgl_Normal
. Confirm that it looks the same as default OpenGL ambient/diffuse lighting. - Apply the specular calculation. Again, confirm that it looks the same as default OpenGL lighting.
- Add an interactive control 'g' (for GPU) to control whether shaders are used or not - use glUseProgram(0) to 'unbind' the shader program. Check that you get the same visual results when toggling shaders on/off. Also check you get the same results when toggling fixed functionality 'f'.
- Again, celebrate that you now know the fundamentals of how OpenGL implements its ambient, diffuse and specular (ADS) lighting calculations - but this time using shaders.
Extra: Per-Pixel Lighting (Optional)
- Per-pixel lighting requires the lighting vectors (normal, light, viewer) to be interpolated instead of the colour from each vertex. Then the colour for each pixel is generated in the fragment shader using these interpolated vector values. Pass these values to the fragment shader, instead of the color value.
- Now move the lighting calculation from the vertex shader to the fragment shader. Confirm that your lighting colour is being applied correctly (it should look different to the per-vertex version).
Extra: Phong Lighting (Optional)
-
Instead of using the half vector for the specular
highlight, Phong lighting calculations use the correct
reflected vector of the light vector around the
normal. GLSL has a
reflect()
function that can be used for this. - Implement Phong lighting using per-vertex and then per-pixel shading.
- Look closely at the shape and spread of the specular highlight. What differences are there?
- Note that differences are expected. Manually scaling shininess in an attempt to match methods is incorrect.