Lab 2: Texture, viewing and shader variations
Goal: Learn how to use texture mapping, to apply projection
and viewing, how to work with multiple models in a scene, and try some
alternative ways to use shaders.
If you run into problems, you can either look in the textbook, or
visit http://www.opengl.org.
There you will, among many other things, find the entire OpenGL
Programming Guide in on-line version.
1) Procedural texture mapping
Using the results from lab 1, you
should now add texure mapping. First of all, we need texture
coordinates. These may be stored in the model. Some models, like the
bunny you used before, come without texture coordinates. We need a new
model, and to make things simple I prepared a bunny that has texture
coordinates. That file is in this archive with files for this lab:
lab2012-2.zip
The archive includes some new and some updated source files. There are
quite a few files in it but most models and textures are optional. The
updated source files are more important. Please
replace any old VectorUtils2 copies with these ones! This version
avoids problems with transposed matrices, so you always work with
row-based matrices, which means that the matrix upload is always the
same, by the book.
The model file you should use is called "bunnyplus.obj". Load the new bunny model and
draw it. To use its texture coordinates, you need to add a few lines to
the upload of the model:
if (m->texCoordArray != NULL)
{
glBindBuffer(GL_ARRAY_BUFFER, bunnyTexCoordBufferObjID);
glBufferData(GL_ARRAY_BUFFER,
m->numVertices*2*sizeof(GLfloat), m->texCoordArray,
GL_STATIC_DRAW);
glVertexAttribPointer(glGetAttribLocation(program, "inTexCoord"), 2,
GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(glGetAttribLocation(program, "inTexCoord"));
}
This will deliver your texture cooridinates to a vec2 named "inTexCoord" in the vertex shader.
Pass the texture coordinates to an interpolated ("out") variable, and
use that (as "in") in the fragment shader to produce some kind of
visual effect. Optionally, you can also pass time information to get an
animated pattern. There are infinite possibilities here, be creative!
Questions:
- How are the textures coordinates mapped on the bunny? Can you see what geometry was used?
- What kind of procedural texture did you make?
2) Texture mapping
Remember to copy files, including shaders, and update your makefiles accordingly.
Mapping a texture image is slightly trickier, but not so bad when we only use a single texture at a time.
The file "LoadTGA.c" loads TGA images to textures. Load a texture with
void LoadTGATextureSimple(char *filename, GLuint *tex);
That is, you must declare a texture reference as GLuint, and pass that by referece, with a file. So the call can look like this:
LoadTGATextureSimple("maskros512.tga", &myTex);
A set of textures is available in the archive linked in part 1 above.
LoadTGATextureSimple will create the texture object, so you don't need
to initialize the reference. You activate a texture object using
glBindTexture(GL_TEXTURE_2D, myTex);
This will bind the texture to the current texture unit. For now, we can safely assume that that is number 0.
In order to use it in a shader, we need a texture sampler.
This is really just the texture unit number. In order not to assume too
much, we pass that number from the CPU to a sampler variable. We set it from the CPU like this:
glUniform1i(glGetUniformLocation(program, "texUnit"), 0); // Texture unit 0
In the shader, it should be declared:
uniform sampler2D texUnit;
and you can get texture data like this:
outColor = texture(tex, texCoord);
Put a texture on the bunny!
Questions:
- Can we modify how we access the texture? How?
- Why can't we just pass the texture object to the shader? There is a specific reason for this, a limited resource. What?
3) Projection
So far, we have been limited to a cube world from -1 to 1 in all
directions, and parallel projection. This is not fun, we want a
realistic projection. Use this matrix:
#define near 1.0
#define far 30.0
#define right 0.5
#define left -0.5
#define top 0.5
#define bottom -0.5
GLfloat projectionMatrix[] = { 2.0f*near/(right-left), 0.0f, (right+left)/(right-left), 0.0f,
0.0f, 2.0f*near/(top-bottom),
(top+bottom)/(top-bottom), 0.0f,
0.0f, 0.0f, -(far + near)/(far -
near), -2*far*near/(far - near),
0.0f, 0.0f, -1.0f, 0.0f };
In order to make this editable for you, the frustum dimensions are
given as #defines. Pass this to a uniform matrix in the vertex shader.
(Warning! #define is OK for constants, but do not mess it up with
variables or you can get complex errors.)
When you run, the bunny disappears. Why? Add a translation to put the rabbit in view.
To make this easy, you can use the VectorUtils2 package. This is a
simple vector/matrix package with the bare essentials such as dot and
cross product and matrix multiplication. To work with matrices, you can
do like this:
Declare matrices:
GLfloat rot[16], trans[16], total[16];
Set matrices to a rotation and translation:
T(1, 2, 3, trans);
Ry(a, rot);
Multiply these matrices:
Mult(rot, trans, total);
Upload to GPU!
glUniformMatrix4fv(glGetUniformLocation(program, "mdlMatrix"), 1, GL_TRUE, total);
Note: When working with VectorUtils, you don't need * for matrices,
since they are arrays, but vectors (Point3D) need that. In both cases,
data is by reference.
Questions:
- How did you move the bunny to get it in view?
4) Viewing, building a look-at matrix
The task above should give you a useful world-to-view matrix, but for
the complete chain you also want a world-to-view matrix, for camera
placement. Although it is possible to make one with rotations and
translations, it is better to use a "look-at" matrix as described in
the book (section 6.5, page 56 in the 2012 edition).
Define a function like this:
void lookAt(GLfloat px, GLfloat py, GLfloat pz,
GLfloat lx, GLfloat ly, GLfloat
lz,
GLfloat vx, GLfloat vy, GLfloat
vz,
GLfloat *m)
{
Point3D p, l, n, v, u;
GLfloat r[16], t[16];
SetVector(px, py, pz, &p);
SetVector(lx, ly, lz, &l);
SetVector(vx, vy, vz, &v);
// Place for your code
}
This incomplete function mimicks the old (but extremely useful) gluLookAt function.
The argument m is to a matrix that should be declared as a 16 element array in the calling function.
Write code that produces a working "look-at" matrix. VectorUtils2.c holds many useful functions for the purpose.
If you are not used to C syntax: *m means a pointer (or array, same
thing for C) in this case to a GLfloat. &p is the address to p, that is it
creates a pointer to the data.
Note: It is easy to misread the book here. Make sure you get a backward
vector, not a forward vector. There is a minus sign in the
normalization formula that must be accounted for.
Upload the matrix to the vertex shader and use it as world-to-view
matrix. Try placing the camera some distance from origin, looking at
origin.
Questions:
- Given a certain vector for v, is there some place you can't place the camera?
-) PLACEHOLDER
Ignore this section for now. We consider moving diffuse shading here.
5) Building a scene + camera movement
Next step is to build a simple scene. It should include at least two different models, and the camera should circle around them.
You will need to create one model-to-world matrix for each model. Use translations and rotations for that.
These matrices should be handled with the same code in your shader. The
shaders does only have to see one matrix at a time, but you change the
matrix in the shader by uploading different matrices between drawing
each model.
1) Update model-to-view matrix for model 1
2) Upload this matrix to model-to-view matrix in shader
3) Draw model 1
4) Update model-to-view matrix for model 2
5) Upload this matrix to model-to-view matrix in shader
6) Draw model 2
If you just duplicate the model loading code, you will most likely find
that tedious, your code explodes. To avoid that, we have provided a
function that manages upload to the GPU as well as loading from disc.
Look at the end of loadobj.c and loadobj.h. (If they are not there,
check out the latest upload from the course page.)
Questions:
- If you rotate an object or rotate the camera, what matrices are affected?
Extra) Vertex shader fun
We have been using the vertex shader in the standard way, for vertex
transformations using the usual chain of matrices and for sending data
to interpolations. But you can also do other things with it. Try
deforming the Stanford Bunny with a vertex shader. The deformation
should not just be a global scaling but vary over the shape. You can,
for example, make it wave like the "snake bunny" animation I showed at
the lecture.
That concludes lab 2. In the next lab, you will expand the scene and make even better light.