Lab 3: Virtual world and specular shading
Goal: In this lab, you will expand your view to a richer virtual world, including herarcical models and a skybox. You will also
implement the Phong lighting model.
This lab can be pretty demanding, parts 3 and 4 in
particular.
Lab files:
lab3.tar.gz
Old version:
lab2014-3.zip
You will need the "common" files from previous labs, as well as using your results as starting points.
If you run into problems, there are several resources. You have
your textbook. OpenGL
Reference Pages describe all the OpenGL API functions. OpenGL
Shading Language Specification (found via
http://www.opengl.org/documentation/specs/,
"OpenGL Shading Language Specification") describes GLSL.
1) Hierarcical modelling, the windmill
Among the files,
there is a windmill in two parts, the mill and the wings. Build a
working windmill from the parts. Four wings should be placed at the
appropriate place and rotate around it. Create appropriate rotation and
translation matrices to make suitable model-to-world transformations.
Questions:
- How can you get all four blades to rotate with just one
time-dependent rotation matrix?
- How do you make the wings follow the body's movements?
- You may have to tweak some numbers to make the placement right.
How can you do that without making a lot of small changes in numbers in
the code followed by recompilations?
2) Manual viewing controls
The "look-at" function is useful for
more than placing the camera in some fixed place. You can use
glutPassiveMotionFunc to write a control based on mouse movements. The
function glutPassiveMotionFunc() takes a callback argument, a pointer
to a function that accepts the x and y coordinates of the mouse as
parameters. For keyboard controls, there are old GLUT functions for
accepting keydown and keyup events, but for many purposes you are
better off with knowing what keys are presently down. That is provided
by the function glutKeyIsDown, e.g. if
(glutKeyIsDown('a')) { (something happens) }.
Questions:
- What kind of control did you implement?
- Can you make this kind of control in some other way than manipulating a "look-at" matrix?
3) Virtual world and skybox
Using the manual controls above, you should expand your virtual
universe to a simple "virtual world" with a set of basic features.
- Add a "ground" as a fairly large textured polygon.
- The manual controls should allow you to move around in the "world"
- Several objects should be included, most of them stationary.
- Finally,
add a "skybox". For this purpose, a skybox model and texture are
provided. To implement a skybox, the skybox should follow the camera
and seem to be drawn at the back. To do this, you should draw the skybox
first, with Z-buffer turned off (glDisable(GL_DEPTH_TEST))
. The skybox should be rotated as the camera, but not translated. You
can do this with a copy of the camera matrix where you zero out the
translation component. Culling needs to be off; you always want to draw
the skybox (and the model is not designed for culling). Finally, don't
forget to turn the Z-buffer on again after drawing the skybox.
Note!
Your skybox may not look 100% perfect with the model that comes with
the lab. Pretty good, but it has a (somewhat minor) problem, when
working with a flat floor like we do here. What would you do to fix
this?
Your program is growing now. You may want to look into ways to structure it a bit. There are many ways to do that.
Questions:
- How did you handle the camera matrix for the skybox?
- How did you represent the objects? Is this a good way to manage a scene or would you do it differently for a "real" application?
- What special considerations are needed when rendering a skybox?
- What is the problem with the skybox object used in the lab? (The problem doesn't have to be corrected.)
4) Specular shading, external light sources
Now you have a nice
scene but you need better light. Implement specular Phong shading in
your shaders. Not only should it include a specular component, but it
should also do that using light sources that are specified by the CPU. (This is a challenging task, but quite rewarding.)
Here are specifications of four light sources:
Point3D lightSourcesColorsArr[] = { {1.0f, 0.0f, 0.0f}, // Red light
{0.0f, 1.0f, 0.0f}, // Green light
{0.0f, 0.0f, 1.0f}, // Blue light
{1.0f, 1.0f, 1.0f} }; // White light
GLfloat specularExponent[] = {10.0, 20.0, 60.0, 5.0};
GLint isDirectional[] = {0,0,1,1};
Point3D lightSourcesDirectionsPositions[] = { {10.0f, 5.0f, 0.0f}, // Red light, positional
{0.0f, 5.0f, 10.0f}, // Green light, positional
{-1.0f, 0.0f, 0.0f}, // Blue light along X
{0.0f, 0.0f, -1.0f} }; // White light along Z
Upload to shader:
glUniform3fv(glGetUniformLocation(program, "lightSourcesDirPosArr"), 4, &lightSourcesDirectionsPositions[0].x);
glUniform3fv(glGetUniformLocation(program, "lightSourcesColorArr"), 4, &lightSourcesColorsArr[0].x);
glUniform1fv(glGetUniformLocation(program, "specularExponent"), 4, specularExponent);
glUniform1iv(glGetUniformLocation(program, "isDirectional"), 4, isDirectional);
Thus, I upload as arrays of three-component vectors and arrays of scalars. Declarations in shader:
uniform vec3 lightSourcesDirPosArr[4];
uniform vec3 lightSourcesColorArr[4];
uniform float specularExponent[4];
uniform bool isDirectional[4];
Debugging light like this requires some care. Take special care in
keeping track on what coordinate system you work in.
Model data starts in model coordinates, light sources are given in world coordinates. All lighting
calculations take place in any coordinate system, model, world or view (camera) coordinates
(not projected though) but you must decide which one.
Select one and stick to it. I recommend that you use view coordinates.
Make sure normal vectors, light direction and viewing direction are all
given in the same coordinate system.
The viewing direction requires that you create a vector from the
surface to the camera. For that, you need the position of the surface.
You get that by interpolating the vertex positions using varying ("out"
in vertex, "in" in fragment) variables.
Start out working on a single light source, diffuse component only.
When that works, switch to the specular component. Once that works for
one positional and one directional light source, chances are good that
everything works. Working on one at a time will help you not to get
distracted by too much information.
Questions:
- How do you generate a vector from the surface to the eye?
- Which vectors need renormalization in the fragment shader?
5) Multitexturing
Using multiple textures on a model can be
very useful for many purposes. The difference from the texture mapping
introduced in lab 2 is that you must bind textures to specific texture
units:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureId);
This enables the textures as texture unit 0.
If the texture is called "tex" in your shader, you can pass the texture unit to that variable like this: Go back to init() again. After the
shaderTimeLocation has been located, perform the following operation:
glUniform1i(getUniformLocation(shaderProgram, "tex"), 0);
Note the "0". That is, again, the texture unit number, here sent to the "tex" variable in the shader.
Load two textures and apply them to an object. Build from your specular shader, and combine lighting and texture.
Questions:
- How did you choose to combine the texture colour and the lighting
colour?
- How did you choose to combine the two textures?
Extra) Managing transparency
In your virtual world, make at least two object semi-transparent. Move
around. You should be able to find locations where the transparency
(combined with Z buffering) causes problems. Solve these problems.
Questions:
- How did you remove the errors caused by transparency?
That concludes lab 3. Good work! In the next lab, we will make the ground more interesting, a 3D terrain.