Lazy Foo' Productions

Stretching and Filters

Last Updated 8/09/12
There's nothing that requires us to have our vertex positions to be the same size as our texture. In this tutorial we'll give different quad sizes to stretch our texture and use filtering to control how our texture looks when stretched.
From LTexture.h
void render( GLfloat x, GLfloat y, LFRect* clip = NULL, LFRect* stretch = NULL ); /* Pre Condition: -A valid OpenGL context -Active modelview matrix Post Condition: -Translates to given position and renders the texture area mapped to a quad -If given texture clip is NULL, the full image is rendered -If a stretch area is given, texture area is scaled to the stretch area size Side Effects: -Binds member texture ID */
Our render() function now takes in an additional argument, a rectangle to define how much we want to stretch the rendered texture.

We give it a default value of NULL in case we don't want to stretch our texture.
From LTexture.cpp
void LTexture::render( GLfloat x, GLfloat y, LFRect* clip, LFRect* stretch ) { //If the texture exists if( mTextureID != 0 ) { //Remove any previous transformations glLoadIdentity(); //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; }
At the top of our render() function, our texture coordinates work the same as they did before.
From LTexture.cpp
//Handle Stretching if( stretch != NULL ) { quadWidth = stretch->w; quadHeight = stretch->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(); } }
What we're doing differently is giving different vertex coordinates. If the function has a stretch rectangle, we have our quad dimensions to be equal to the size of the stretch rectangle. Now when our textured quad renders, it will stretch the texture to fit the new quad size.
From LUtil.cpp
//Version: 011 #include "LUtil.h" #include <IL/il.h> #include <IL/ilu.h> #include "LTexture.h" //Stretched texture LTexture gStretchedTexture; //Stretch size LFRect gStretchRect = { 0.f, 0.f, SCREEN_WIDTH, SCREEN_HEIGHT }; //Texture filtering GLenum gFiltering = GL_LINEAR;
At the top of LUtil.cpp we declare some global variables. "gStretchedTexture" is a texture that we're going to load that's 160x120 pixels in size. "gStretchRect" is the stretch rectangle that we're going to use to stretch the texture to the size of the screen.

"gFiltering" will control how our texture is filtered when rendered. The best way to explain how filtering works is through demonstration. We'll get to that in our key handler.
From LUtil.cpp
bool loadMedia() { //Load and color key texture if( !gStretchedTexture.loadTextureFromFile( "11_stretching_and_filters/mini_opengl.png" ) ) { printf( "Unable to load mini texture!\n" ); return false; } return true; } void update() { } void render() { //Clear color buffer glClear( GL_COLOR_BUFFER_BIT ); //Render texture stretched gStretchedTexture.render( 0.f, 0.f, NULL, &gStretchRect ); //Update screen glutSwapBuffers(); }
In loadMedia() we load our texture and in render() we stretch it to the size of the screen.
From LUtil.cpp
void handleKeys( unsigned char key, int x, int y ) { //If q is pressed if( key == 'q' ) { //Bind texture for modification glBindTexture( GL_TEXTURE_2D, gStretchedTexture.getTextureID() ); //Toggle linear/nearest filtering if( gFiltering != GL_LINEAR ) { glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); gFiltering = GL_LINEAR; } else { glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); gFiltering = GL_NEAREST; } //Unbind texture glBindTexture( GL_TEXTURE_2D, NULL ); } }
In this tutorial we'll use key presses to toggle our texture filtering.

When the user presses q, the texture we loaded is bound so we can do operations on it. Even though we're outside of the LTexture class, as long as we have the texture ID we can do operations on the texture.

Then we toggle and set the filtering for the texture. These function calls should look familiar because we call them in our texture loading function. "GL_TEXTURE_MAG_FILTER" controls how the texture is filtered when the texture is stretched. The default we set when we load the texture is "GL_LINEAR". This means our texture blends the pixels when stretching it which results in this (on an ATI card):

When "GL_TEXTURE_MAG_FILTER" is set to "GL_NEAREST", OpenGL just grabs the nearest texel value which results in a grainier look:

Note that we also set "GL_TEXTURE_MIN_FILTER" which controls how the texture is filtered when it's made smaller. "GL_TEXTURE_MAG_FILTER" and "GL_TEXTURE_MIN_FILTER" don't have to be the same value if you want different filters applied in different situations.

Finally after toggling the filtering, we unbind the texture.
Download the media and source code for this tutorial here.
Back to OpenGL Tutorials