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:


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:


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:


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:


-) 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:


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.