Lazy Foo' Productions

Matrix Transformations

Last Updated 8/09/12
Up until now, we've done texture rotation and scaling in hackish ways. In this tutorial we'll combine matrix tranformations to transform a textured quad without changing the original geometry.
From LTexture.cpp
void LTexture::render( GLfloat x, GLfloat y, LFRect* clip ) { //If the texture exists if( mTextureID != 0 ) { //Texture coordinates GLfloat texTop = 0.f; GLfloat texBottom = (GLfloat)mImageHeight / (GLfloat)mTextureHeight; GLfloat texLeft = 0.f; GLfloat texRight = (GLfloat)mImageWidth / (GLfloat)mTextureWidth; //Vertex coordinates GLfloat quadWidth = mImageWidth; GLfloat quadHeight = mImageHeight; //Handle clipping if( clip != NULL ) { //Texture coordinates texLeft = clip->x / mTextureWidth; texRight = ( clip->x + clip->w ) / mTextureWidth; texTop = clip->y / mTextureHeight; texBottom = ( clip->y + clip->h ) / mTextureHeight; //Vertex coordinates quadWidth = clip->w; quadHeight = clip->h; } //Move to rendering point glTranslatef( x, y, 0.f ); //Set texture ID glBindTexture( GL_TEXTURE_2D, mTextureID ); //Render textured quad glBegin( GL_QUADS ); glTexCoord2f( texLeft, texTop ); glVertex2f( 0.f, 0.f ); glTexCoord2f( texRight, texTop ); glVertex2f( quadWidth, 0.f ); glTexCoord2f( texRight, texBottom ); glVertex2f( quadWidth, quadHeight ); glTexCoord2f( texLeft, texBottom ); glVertex2f( 0.f, quadHeight ); glEnd(); } }
The LTexture render() function has returned to it's pre stretching/rotating state with the origin of the textured quad at the top left. What different this time is that there's no call to glLoadIdentity() in this version of render(). If we did that, any transformations made before this call would be pointless.

In modern OpenGL applications, vertex data is set in chunks (known as vertex buffers) to the GPU. It's inefficient to send different vertex data over and over again when we're essentially rendering the same quad that's just transformed in different ways. So it's important to know how to get the results you need without sending redundant vertex data.
From LUtil.cpp
//OpenGL texture LTexture gRotatingTexture; //Rotation Angle GLfloat gAngle = 0.f; //Transformation state int gTransformationCombo = 0;
At the top of LUtil.cpp we have a couple global variables. We have our rotating texture, the angle at which it will rotate, and lastly the transformation state.

In this tutorial, we'll be using different sets of matrix transformations. "gTransformationCombo" is just a variable to indicate which one we're using right now.
From LUtil.cpp
bool loadMedia() { //Load texture if( !gRotatingTexture.loadTextureFromFile( "13_matrix_transformations/texture.png" ) ) { printf( "Unable to load OpenGL texture!\n" ); return false; } return true; } void update() { //Rotate gAngle += 360.f / SCREEN_FPS; //Cap angle if( gAngle > 360.f ) { gAngle -= 360.f; } }
As with last time, loadMedia() loads are texture and update() updates our angle of rotation.
From LUtil.cpp
void render() { //Clear color buffer glClear( GL_COLOR_BUFFER_BIT ); //Reset transformation glLoadIdentity(); //Render current scene transformation switch( gTransformationCombo ) {
At the top of our render() we clear the color buffer then load the identity matrix. Remember that the LTexture render() function no longer calls glLoadIdentity(), so now we have to call it before we render our textured quad.

Then, depending on which transformation combination is selected, we apply some tranformations to the modelview matrix.
From LUtil.cpp
case 0: glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f ); glRotatef( gAngle, 0.f, 0.f, 1.f ); glScalef( 2.f, 2.f, 0.f ); glTranslatef( gRotatingTexture.imageWidth() / -2.f, gRotatingTexture.imageHeight() / -2.f, 0.f ); break;
Here's the main transformation of the demo: making the double sized textured quad spin around it's center at the center of the screen. Let's step through this transformation to see how it works. First here's our untransformed textured quad:

First thing we do is translate to the center of the screen:

Since the origin the quad is at the top left, the top left corner is touching the center of the screen.

Then we rotate the quad:

Remember: glRotate() rotates around the current point of rotation.

Since we want our quad to be double in size, we scale it using glScale():

The arguments in this call for glScale() say that we want to double it in size along the x and y axis.

We want the quad to rotate around its center, so we translate back half the span of the quad:

It's important to know when you apply a transformation, it transforms the transformations after it. Since we called glRotate before we translated half the span of the quad, the translation was also rotated. Since we called glScale to stretch the quad, the translation of half the span of the quad was also scaled.

A side note about order of operations: notice that the negation sign is by the float in gRotatingTexture.imageWidth() / -2.f. The function imageWidth() returns an unsigned int, which with a negation sign produces undesired results.
From LUtil.cpp
case 1: glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f ); glRotatef( gAngle, 0.f, 0.f, 1.f ); glTranslatef( gRotatingTexture.imageWidth() / -2.f, gRotatingTexture.imageHeight() / -2.f, 0.f ); glScalef( 2.f, 2.f, 0.f ); break;
In this combination, we call glScale() last. What this does is when we the translate of half the span of the quad, the translation isn't scaled so the quad rotation is off.
From LUtil.cpp
case 2: glScalef( 2.f, 2.f, 0.f ); glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f ); glRotatef( gAngle, 0.f, 0.f, 1.f ); glTranslatef( gRotatingTexture.imageWidth() / -2.f, gRotatingTexture.imageHeight() / -2.f, 0.f ); break;
In this combination we call glScale() first, which causes the quad to spin around its center but since the initial translation to the center of the screen is also scaled, the quad spins around its center at the bottom right corner of the screen.
From LUtil.cpp
case 3: glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f ); glRotatef( gAngle, 0.f, 0.f, 1.f ); glScalef( 2.f, 2.f, 0.f ); break;
In this combination, we don't translate of half the span of the quad which causes the quad to spin around its top left corner.
From LUtil.cpp
case 4: glRotatef( gAngle, 0.f, 0.f, 1.f ); glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f ); glScalef( 2.f, 2.f, 0.f ); glTranslatef( gRotatingTexture.imageWidth() / -2.f, gRotatingTexture.imageHeight() / -2.f, 0.f ); break; }
In this combination we rotate first, which cause the quad to rotate around the top left corner of the screen.
From LUtil.cpp
//Render texture gRotatingTexture.render( 0.f, 0.f ); //Update screen glutSwapBuffers(); }
Finally after applying our transformations we render our transformed quad.
From LUtil.cpp
void handleKeys( unsigned char key, int x, int y ) { //If q is pressed if( key == 'q' ) { //Reset rotation gAngle = 0.f; //Cycle through combinations gTransformationCombo++; if( gTransformationCombo > 4 ) { gTransformationCombo = 0; } } }
In our key handler, when the user presses q we reset the angle of rotation and cycle through the transformation combinations.
Download the media and source code for this tutorial here.
Back to OpenGL Tutorials