An OpenGL 3d Texturing Tutorial
This tutorial is intended to demonstrate 3-dimensional texturing (not to be confused with simply texturing a 3d object) in OpenGL and familiarize the reader with its use. Familiarity with OpenGL 2D textures is assumed. The provided example was developed on Visual Studio .NET under Windows XP Professional. Questions and comments welcome, flames should be sent to /dev/null. Though three dimensional texturing has been adopted into the OpenGL 1.2 specification by the ARB, at the time of this writing it is still necessary to the use extension header files available at http://oss.sgi.com/projects/ogl-sample/sdk.html glxext.h is for X, wglext.h is for Windows, glext.h is for all operating systems.
- A texture element, in the same sense that a pixel is a picture element.
Setting up a Window
In order to render a 3D textured object using OpenGL it is necessary to have a window ready to render OpenGL. (Note: The provided example and the next few steps use a Windows Platform SDK window. To create one of these projects in Visual Studio .NET go to File->New->Project, in the left pane select Visual C++ Projects, in the right pane select Win32 Project, in the next window select Application Settings, select Windows Application and uncheck empty project). If you need portability or don't feel like dealing with Windows SDK code, GLUT, SDL and GLFW are good alternatives. It is also easier to use and many good tutorials already exist on its use so I won't go into it here.) Without going into too much detail outside the scope of this tutorial, under Windows Platform SDK the steps to setting up a Window that will render OpenGL are as follows:
- Given a handle to a Window with style including (WS_CLIPSIBLINGS | WS_CLIPCHILDREN), get a Handle to the window Device Context (HDC)
- Find an acceptable pixel format and set it (note: a pixel format can only be set once for a particular window)
- Use the HDC with the new pixel format to get a Handle to an openGL Rendering Context (HGLRC)
- Make this context the "current" context, the one in which we want to render our OpenGL.
The steps in MFC are similar. The simplest way to test that these steps have been successful is to clear the color buffer to something other than the default color and then swap the buffers (double-buffering assumed). If the window background color is successfully cleared to the specified color OpenGL is rendering to your window. At this point you can set up your OpenGL state, don’t forget to set up your projection matrix. (I always forget :))
Setting up OpenGL 3d texturing
The first and most obvious step is to enable 3d texturing with a fairly simple call:
The next, not-so-obvious step is we will need to get a pointer to the function glTexImage3D() because it is not part of the standard OpenGL libraries (yet) and has to be loaded at run-time, fortunately glext.h defines a pointer type called PFNGLTEXIMAGE3DPROC specifically for this purpose. It’s usually convenient to make the function pointer the same name as the actual function:
After declaring the pointer to the function we need to find the address of that function in the GL DLL and put it in:
glTexImage3D = (PFNGLTEXIMAGE3DPROC) wglGetProcAddress("glTexImage3D");
The potential exists that the environment the program is being run on does not support 3D texturing, which would cause us to get a NULL address back, and attempting to use a NULL pointer is A Bad Thing so make sure to check for it and respond appropriately (the provided example exits with an error).
Building a simple 3d texture
In simplest terms, a 3d textures is a series of (width * height * depth * bytes per texel) bytes where width, height, and depth are powers of 2. You might think of it as a series of two dimension textures where an extra parameter (depth, AKA the R texture coordinate) specifies which 2D texture will be used. Notably, like other texture coordinates, the R-coordinate is not required to be an integer, the (closest 2 texels)^(3 dimensions) = 8 texels will be accounted for in the calculation based on the selected filter scheme, which is basically a fancy way of saying you get to pick if it takes the nearest texel to what it needs or takes a weighted average of all the ones around it (or a couple other options). The example code uses a simple 3D texture consisting of 4 layers (depth = 4) of 4x4 textures with 1 byte of red + 1 byte of blue + 1 byte of green = 3 bytes per texel. This texture requires 4*4*4*3 = 192 bytes of memory. After setting up 3D texturing we’re ready to use the glTexImage3D function in our texture’s creation. Similar to creating a 2D texture object, the steps to creating an OpenGL 3d texture object are as follows:
- Load or generate the texels (really can be done anywhere before the last step)
- [edited by david l, may 28th, 2008] To make a 3D texture from 2D textures, you just have to fill the memory with the data of the first texture, then the data of the second texture, etc... Do not forget that all 3 dimensions must be a power of 2, so your 2D textures must have the same size and this size be a power of 2 AND the number of layers (=2D textures) you use to create your 3D texture must be a power of 2 too. So if you have only 3 layers you need to get the memory for a fourth one even if it will not be used. [end]
- Get a name for the texture from OpenGL
- Bind to this name
- Specify the wrapping, filtering, and any other parameters
- Finally, call glTexImage3D
//(generate texel code omitted) unsigned int texname; glGenTextures(1, &texname); glBindTexture(GL_TEXTURE_3D, texname); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT); glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB8, WIDTH, HEIGHT, DEPTH, 0, GL_RGB, GL_UNSIGNED_BYTE, texels);
(The comments in the example source go into more detail on the arguments)
To make the texture active (the texture that our geometry should be using), we bind it. Binding a 3D texture is just like Binding a 2D texture except we specify 3D in our bind call:
(As you may have noticed this function has multiple uses)
After binding the texture you want you can start to specify vertices with three dimensional texture coordinates (you need to be within a glBegin()/glEnd() pair of course):
glTexCoord3d(texS, texT, texR); glVertex3d(vertX, vertY, vertZ);
- [edited by david l, may 28th, 2008] texR value : The texS and texT values are the same than if you would use a 2D texture. For the texR value, if your 3D texture has n layers and you want to display the i-th layer (i between 0 and n-1), the formula is texR = (0.5 + i) / n [end]
And there you have it, your geometry specified with 3-dimensional textures.
Debugging texture code
- The teapot (glaux.h: void auxSolidTeapot(GLdouble)) has texture coordinates built in. If you can render it textured you can eliminate that texture as a potential cause of the problem.
Things to try with the sample code
- The texture coordinate for the apex of the pyramid varies with the point, setting the texture coordinate to a constant will mean the texture will remain constant on the faces and will stretch when the face changes shape rather than glide over the surface (you could accomplish the same effect with a 2D texture so this isn’t really a good use of 3d textures)
- Change the texture so that all texels are unique, or load in some image files
- A terrain generation program that uses 3D textures to make low forests/grasslands look different than high mountains and have seamless transition between them
Doug Sheets (firstname.lastname@example.org) November 9th, 2004
GLUT conversion by Ciro Durán (ciro -dot- duran -at- gmail -dot- com) Updated March 27th, 2005