BMP Image to GL Texture
We must start with a valid BMP image file in order for this procedure to work. Any image of the BMP format will suffice, but for the following examples I shall use this image:
|
As a middle step, the image will be loaded as an SDL_Surface. This procedure is similar to the one described in the SDL post; but to recap, we need a valid file path. This image is called "texture.bmp" and the name alone can be used to load the image file.
Finally, we can use a few OpenGL functions to translate the SDL_Surface into a working texture. Once the texture is ready, it can be applied to a surface by giving each point a texture coordinate. This is similar to coloring vertices, but will only require 2 inputs.
Putting Texture on a Square
We can refer to which texture we want OpenGL to use with an integer value. The function glBindTexture requires an enumeration and a GLuint as input. The enumeration GL_TEXTURE_2D means a two dimensional texture recognized by OpenGL. The GLuint is OpenGL's term for unsigned integer, similar to SDL's Uint32, and for this function refers to the ID of the texture we wish to use. Similar to color, we can set the currently used texture in OpenGL.
In order for OpenGL to recognize a two dimensional texture and allow us to use an integer to specify it, we must call glGenTextures. This requires the number of textures to create and an array of integers to assign ID values to. Since the examples will only use one texture, the input will be 1 followed by the address to store the integer, or texture ID, value.
Also, in order to use the enumeration GL_TEXTURE_2D, we must first enable two dimensional texturing in OpenGL. The function glEnable can allow us to use many features of OpenGL, but for 2D textures simply give it the argument GL_TEXTURE_2D. The following example should show how to use these commands:
The image loading and printing of dimensions was taken from the SDL BMP images post. Converting the SDL_Surface to a GL texture consists of several OpenGL commands: glEnable will allow us to enable 2D textures, glGenTextures will create a texture an give us its ID (GLuint), glBindTexture will set the current texture, and the others.
The function glTexParameteri can be used to change some aspect of a texture. There are many options to change, but among them are magnification filtering (GL_MAG_FILTER) and "minification" filtering (GL_MIN_FILTER). These control how the texture image will appear when expanded or contracted. The last argument is what we wish to change our property to, in this case GL_LINEAR. The reason these are set to linear filtering is because the default setting requires mipmaps, which would require many extra steps.
Most of the actual work is done in the function glTexImage2D, which reads image data and applies it to the GL texture. GL_TEXTURE_2D is the first argument since the function is being applied to the current texture. GL_RGB refers to how we want the image store in OpenGL (with red, green, and blue channels). GL_BGR and GL_UNSIGNED_BYTE refer to how the image is stored in the BMP file: 8-bit channels blue, green, and red respectively.
This function also requires information about the loaded image, which is provided with image->w (width in pixels), image->h (height), and image->pixels (the loaded pixel data). There are also parameters for level-of-detail and pixel borders, but these are set to 0 since they are not used in this example.
Finally, each vertex is given a texture coordinate. Like color, it is specified before vertices since each glTexCoord2i call will update OpenGL's current texture coordinate. The coordinates can range from 0.0 to 1.0, which spans the width and height of the texture. This example could use glTexCoord2f, but since it maps the entire image on four points, it only needs the integers 0 and 1.
The result is flipped vertically, but by inverting the second values (0 to 1 and 1 to 0), it can be made upright. Also, the command glDeleteTexures requires the same arguments as glGenTextures in order to remove the GL texture from memory. This is not always required though since OpenGL tends to manage its own memory usage. It is included just to be sure though.
Filtering
As mentioned above, we can set filter options for the current texture with the function glTexParameteri. Previously, both magnification and minification were set to linear, but they can also use nearest-neighbor filtering. Linear filtering tends to look the best, but certain situations may call for nearest-neighbor filtering.
Since texture images can be scaled and rotated to any degree, there must be some way to account for when the texture image pixels don't align with the screen. Imagine that the texture image will appear twice as big on the screen: this will require four times as many pixels to display the image as are present in the image. The new pixels must be given a color value, so this is where filtering comes in handy.
Linear filtering will take the four nearest pixels and use a weighted average of their colors for the new pixel. Nearest-neighbor will take the nearest existing image pixel and simply use its color in the new pixel. When the image is scaled above its normal dimensions, this is referred to as magnification while minification refers to a smaller version of the original. For more information about OpenGL filtering, see http://gregs-blog.com/2008/01/17/opengl-texture-filter-parameters-explained/.
Unfortunately, it appears that these options can only be set during the creation of the texture, so in order to see any changes, code may have to be rewritten. The following example showcases nearest filtering for magnification, but feel free to change it to linear to see its effect as well.
/*thanks to tohtml.com for syntax highlighting*/ #include <SDL/SDL.h> #include <SDL/SDL_opengl.h> #include <stdio.h> SDL_Surface* screen; SDL_Surface* image; /*will store the loaded image*/ SDL_Event input; int loop = 1; GLuint texture; /*holds the texture ID*/ int main(int argc, char** argv) { SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/ screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL); /*try to load image, exit if there are problems*/ image = SDL_LoadBMP("texture.bmp"); if (image == NULL) { printf("Image failed to load\n"); SDL_Delay(3000); SDL_Quit(); /*shutdown SDL*/ fflush(stdout); /*update stdout*/ return 0; } /*print dimensions of loaded image*/ printf("image dimensions: %d x %d\n", image->w, image->h); /*convert SDL_Surface into GL texture*/ glEnable(GL_TEXTURE_2D); /*enable 2D texturing*/ glGenTextures(1, &texture); /*create a texture, store ID*/ glBindTexture(GL_TEXTURE_2D, texture); /*set current texture*/ /*filtering options: try changing between LINEAR and NEAREST*/ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image->w, image->h, 0, GL_BGR, GL_UNSIGNED_BYTE, image->pixels); /*set clear color to purple*/ glClearColor(0.25, 0.0, 0.75, 1.0); /*set origin to lower left, use 4:3 ratio*/ glMatrixMode(GL_PROJECTION); gluOrtho2D(0, 4, 0, 3); while (loop) { glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/ while (SDL_PollEvent(&input)) { if (input.type == SDL_QUIT) loop = 0; } /*draw triangles*/ glBegin(GL_TRIANGLES); /*set color to white*/ glColor3ub(255, 255, 255); /*triangle*/ glTexCoord2f(0, 0.2); /*Texture Coordinate before vertex*/ glVertex2f(1, 0.5); glTexCoord2f(0.1, 0); glVertex2f(2, 2.5); glTexCoord2f(0.2, 0.2); glVertex2f(3, 0.5); glEnd(); SDL_GL_SwapBuffers(); SDL_Delay(20); /*wait 20ms*/ } /*print that the program is exitting*/ printf("User has quit the program\n"); /*remove texture-related stuff from memory*/ glDeleteTextures(1, &texture); SDL_FreeSurface(image); /*perform final commands then exit*/ SDL_Quit(); /*close SDL*/ fflush(stdout); /*update stdout*/ return 0; } | ||
prompt/stdout: image dimensions: 256 x 256 User has quit the program | ||
output inside window:
|
Shrinking the coordinates into the 0.0 to 0.2 range causes the resulting texture to only be part of the original image. Since this small part is stretched over a normal-size triangle, we see magnification at work. Also, the nearest filtering results in a pixelated texture. If it were changed to linear filtering, it would appear smooth and blurry due to all the pixels with linearly interpolated colors.
A triangle is used simply to show that texture can be applied to arbitrary shapes. However, for the texture to display well, the texture coordinates must be changed in a way similar to the vertices. For this example, two vertices were exchanged for one by averaging their positions, so the texture coordinates were averaged as well.
Wrapping
For texture coordinates beyond the 0 to 1 range, we can tell OpenGL what to do. This property is called texture wrapping and OpenGL can simply stop values beyond 0 to 1, or use them to repeat the texture. Stopping the coordinates is called clamping while continuing the pattern is called repeating.
Notice that the texture selected for this tutorial can be placed next to itself in order to form a larger pattern. For most images, it would be obvious where one starts and the other begins. An ideal texture will seamlessly blend in when placed next to itself. For a collection of other seamless or tiled textures, see http://www.cgtextures.com/.
To set the wrapping behavior for OpenGL, the function glTexParameteri will be used again. This time, the properties will be GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, where S and T are the two axes used for 2D texture coordinates (similar to x, y, and z for vertices or r, g, and b for colors). The following example should show clamp and repeat wrapping behaviors:
/*thanks to tohtml.com for syntax highlighting*/ #include <SDL/SDL.h> #include <SDL/SDL_opengl.h> #include <stdio.h> SDL_Surface* screen; SDL_Surface* image; /*will store the loaded image*/ SDL_Event input; int loop = 1; GLuint texture; /*holds the texture ID*/ int main(int argc, char** argv) { SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/ screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL); /*try to load image, exit if there are problems*/ image = SDL_LoadBMP("texture.bmp"); if (image == NULL) { printf("Image failed to load\n"); SDL_Delay(3000); SDL_Quit(); /*shutdown SDL*/ fflush(stdout); /*update stdout*/ return 0; } /*print dimensions of loaded image*/ printf("image dimensions: %d x %d\n", image->w, image->h); /*convert SDL_Surface into GL texture*/ glEnable(GL_TEXTURE_2D); /*enable 2D texturing*/ glGenTextures(1, &texture); /*create a texture, store ID*/ glBindTexture(GL_TEXTURE_2D, texture); /*set current texture*/ /*filtering options: try changing between LINEAR and NEAREST*/ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); /*wrapping options: try different combinations of repeat and clamp*/ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); /*defines texture image with SDL_Surface data*/ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image->w, image->h, 0, GL_BGR, GL_UNSIGNED_BYTE, image->pixels); /*set clear color to purple*/ glClearColor(0.25, 0.0, 0.75, 1.0); /*set origin to lower left, use 4:3 ratio*/ glMatrixMode(GL_PROJECTION); gluOrtho2D(0, 4, 0, 3); while (loop) { glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/ while (SDL_PollEvent(&input)) { if (input.type == SDL_QUIT) loop = 0; } /*draw quadrilaterals*/ glBegin(GL_QUADS); /*set color to purple*/ glColor3f(1, 0, 1); /*start texturing*/ glEnable(GL_TEXTURE_2D); /*textured square*/ glTexCoord2f(0, 0); /*Texture Coordinate before vertex*/ glVertex2f(0.5, 0.5); glTexCoord2f(0, 2); glVertex2f(0.5, 2.5); glTexCoord2f(2, 2); glVertex2f(2.5, 2.5); glTexCoord2f(2, 0); glVertex2f(2.5, 0.5); /*stop texturing*/ glDisable(GL_TEXTURE_2D); /*untextured trapezoid*/ glVertex2f(3, 0.5); glVertex2f(3, 2.5); glVertex2f(3.5, 2); glVertex2f(3.5, 1); glEnd(); SDL_GL_SwapBuffers(); SDL_Delay(20); /*wait 20ms*/ } /*print that the program is exitting*/ printf("User has quit the program\n"); /*remove texture-related stuff from memory*/ glDeleteTextures(1, &texture); SDL_FreeSurface(image); /*perform final commands then exit*/ SDL_Quit(); /*close SDL*/ fflush(stdout); /*update stdout*/ return 0; } | ||
prompt/stdout: image dimensions: 256 x 256 User has quit the program | ||
output inside window:
|
Focusing on the left square, we see that the texture image appears twice on the left and some lines make up the right of the square. The S axis is clamped, which means any coordinates beyond 1 simply become 1, also anything below 0 becomes 0. The T axis repeats the texture, which is why we see the texture repeat in the Y direction. The 0 to 2 range for the T coordinate means 2 texture images will appear.
A few other features of this demonstration are the use of colors and non-textured shapes. Notice that the quadrilaterals are colored purple and that the texture is affected by this color. The color of the shape will multiply with the color of the texture, which works mathematically if we use the 0 to 1 range for colors. Since white (1, 1, 1) is multiplied with purple (1, 0, 1), it becomes purple.
The last thing to mention is that shapes can be drawn without texture by calling glDisable with the input GL_TEXTURE_2D. Any shape drawn after this call will have no texture. Since the program works in loops, we need to also call glEnable with GL_TEXTURE_2D before shapes with texture.
I am sorry if this post seems rushed, but hopefully it helps clarify GL textures. For other tutorials, check out http://www.swiftless.com/tutorials/opengl/texture_under_windows.html and http://www.videotutorialsrock.com/opengl_tutorial/textures/text.php. For documentation about glTexImage2D, see http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml. For information about glTexParameter, see http://www.opengl.org/sdk/docs/man/xhtml/glTexParameter.xml.
Thank you for reading and happy new year! The next posts may revisit SDL and possibly C, otherwise it may discuss OpenGL transformations.
P.S. I recently discovered that while the image I have for "texture.bmp" is a BMP file, it apparently becomes a JPG when uploaded to a post. I will try to fix this, but for now readers may want to use a valid BMP or convert the JPG to a BMP. I apologize for this and will make a note on the SDL BMP image post as well.
No comments:
Post a Comment