Home   Donators   Contact Us       

<< Previous Tutorial     Next Tutorial >>

Tutorial 5 - Texture mapping


Theory

Texture mapping is a technique used to "paint" an image onto a polygon. For example, if we wanted to convert a basic cube into a wooden box, we simply paint wooden panels onto the individual polygons. A typical texture that we might use for this is shown below.

It is recommended to create your texture images as PNG files. PNG image files have the ability to store alpha values (transparency settings), which is an invaluable feature when creating advanced models and objects. ALWAYS ensure that the dimensions of your texture images are in power of two (e.g. 32x32, 64x64, 128x128). The image shown above is a 256x256 PNG image file.

Once you've created your texture, the painting of your texture onto polygons is a very straight forward process. We simply map the coordinates of the texture to the vertices of your polygon, and OpenGL will automatically map the texture to your polygon. The image below illustrates how the coordinates of a texture might be mapped to the coordinates of the front polygon in a cube.

So how exactly do we map the texture coordinates to the vertices of the polygon? The diagram below illustrates how the coordinate system of the texture image relates to the vertices of the polygon. Remember that polygon vertices are always given in an anti-clockwise direction.

It is easy to see that texture coordinate (1, 0) maps to vertex 1, texture coordinate (0, 0) maps to vertex 2, texture coordinate (0, 1) maps to vertex 3 and texture coordinate (1, 1) maps to vertex 4. You will notice this is the exact same order we use to texture map the front polygon in the tutorial.

The texture coordinates is better known as UV-coordinates.

Tutorial Steps
Tutorial created with Real Studio 2011 Release 4.3.
1. Open Real Studio.
2. Choose the "Desktop" project template.
3. Save your project.
4. Download texture.png and save it next to your project file.
5. Import the picture into your project file. (Select "File > Import..." from the main menu)
6. On the Project tab, select the imported picture and change its name to "imgTexture".
7. Add an OpenGLSurface control to Window1.
8. Resize and position OpenGLSurface1 to fill the whole form.
9. Tick the LockRight and LockBottom properties of OpenGLSurface1.
10. Add a property named "TextureID" of type Integer to Window1. (Double click the titlebar of Window1 and select "Add Property")
11. Add a method named "R3DT_LoadTexture" to Window1. (Double click the titlebar of Window1 and select "Add Method")
12. Add "pic As Picture" to the parameter list of method Window1.R3DT_LoadTexture.
13. Set the return type of Window1.R3DT_LoadTexture to Integer.
14. Add the following code to Window1.R3DT_LoadTexture:
// IMPORTANT: Image dimensions must be in power of 2 (e.g. 8x8, 16x16, 32x32, 64x64, ...)

Dim x, y, offset As Integer
Dim surfCol As Color
Dim maskCol As Color
Dim textureBitmap As MemoryBlock
Dim alpha As Byte
Dim glTextureID As Integer
Dim idMB As MemoryBlock

' before we can load the picture into OpenGL memory, we need to convert it into a format that OpenGL understands

' create a MemoryBlock for the OpenGL format

textureBitmap = new MemoryBlock(pic.Height * pic.Width * 4)

' loop through all the pixels of the picture

offset = 0

for y = 0 to pic.Height - 1

  for x = 0 to pic.Width - 1

    ' read the values of the current pixel
  
    surfCol = pic.RGBSurface.Pixel(x,y) ' get the color of the current pixel
    maskCol = pic.Mask.RGBSurface.Pixel(x, y) ' get the mask (alpha) color of the current pixel
  
    ' calculate the OpenGL alpha values, using the mask values of the pixel
  
    alpha = 255 - (maskCol.Red + maskCol.Green + maskCol.Blue) / 3
  
    ' store the color and alpha values into our OpenGL texture bitmap
  
    textureBitmap.Byte(offset) = surfCol.Red
    textureBitmap.Byte(offset + 1) = surfCol.Green
    textureBitmap.Byte(offset + 2) = surfCol.Blue
    textureBitmap.Byte(offset + 3) = alpha
  
    offset = offset + 4 ' move to the next pixel in our OpenGL texture bitmap
  
  next x

next y

' ask OpenGL for an ID that we can use for our texture

idMB = new MemoryBlock(4) ' create a memory block into which OpenGL will store the ID value
OpenGL.glGenTextures(1, idMB) ' get the ID from OpenGL
glTextureID = idMB.Long(0) ' store the value returned by OpenGL in an integer

' specify to OpenGL how this texture should be rendered

OpenGL.glBindTexture(OpenGL.GL_TEXTURE_2D, glTextureID) ' select the texture id that OpenGL allocated for our texture
OpenGL.glTexParameteri(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, OpenGL.GL_LINEAR) // set up settings
OpenGL.glTexParameteri(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, OpenGL.GL_LINEAR) // set up some more settings

' now we load the image bitmap

OpenGL.glTexImage2d(OpenGL.GL_TEXTURE_2D, 0, 4, pic.Width, pic.Height , 0, OpenGL.GL_RGBA, OpenGL.GL_UNSIGNED_BYTE, textureBitmap)

' finally we return the texture id to the calling function, so that the id can be stored

return glTextureID
15. Add a method named "R3DT_DeleteTexture" to Window1. (Double click the titlebar of Window1 and select "Add Method")
16. Add "texID as Integer" to the parameter list of method Window1.R3DT_DeleteTexture.
17. Add the following code to Window1.R3DT_DeleteTexture:
Dim texturePtr As MemoryBlock

' first we empty the OpenGL buffers

OpenGL.glFlush

' now we store the texture id in a MemoryBlock

texturePtr = new MemoryBlock(4)
texturePtr.Long(0) = texID

' then we instruct OpenGL to remove texture from memory

OpenGL.glDeleteTextures(1, texturePtr)
18. Add the following code to the Open event of OpenGLSurface1 (You can access the Open event by double clicking on OpenGLSurface1 and then selecting the event from the list):
' make sure only back faces are culled and enable culling

OpenGL.glCullFace OpenGL.GL_BACK
OpenGL.glEnable OpenGL.GL_CULL_FACE

' enable the use of textures

OpenGL.glEnable OpenGL.GL_TEXTURE_2D

' load the texture image into OpenGL memory

TextureID = R3DT_LoadTexture(imgTexture)
19. Add the following code to the Close event of OpenGLSurface1 (You can access the Close event by double clicking on OpenGLSurface1 and then selecting the event from the list):
' remove the texture from OpenGL memory (the one we loaded in the Open event)

R3DT_DeleteTexture TextureID
20. Add the following code to the Render event of OpenGLSurface1 (You can access the Render event by double clicking on OpenGLSurface1 and then selecting the event from the list):
OpenGL.glPushMatrix ' save matrix

' clear the background

OpenGL.glClearColor(0, 0, 0, 1)
OpenGL.glClear(OpenGL.GL_COLOR_BUFFER_BIT)

' move back a bit so that we can see the object

OpenGL.glTranslatef 0.0, 0.0, -5.0

' rotate our model

OpenGL.glRotated(30, 1, 0, 0) ' 30 degrees around x-axis
OpenGL.glRotated(30, 0, 1, 0) ' and then 30 degrees around y-axis

' bind to the texture that we want to use when mapping to our polygons

OpenGL.glBindTexture(OpenGL.GL_TEXTURE_2D, TextureID)

' draw texture mapped cube

OpenGL.glNormal3d 0, 0, 1 ' front polygon normal

OpenGL.glBegin OpenGL.GL_POLYGON ' front polygon

OpenGL.glTexCoord2d 1, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, 1, 1
OpenGL.glTexCoord2d 0, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, 1, 1
OpenGL.glTexCoord2d 0, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, -1, 1
OpenGL.glTexCoord2d 1, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, -1, 1

OpenGL.glEnd

OpenGL.glNormal3d 1, 0, 0 ' right polygon normal

OpenGL.glBegin OpenGL.GL_POLYGON ' right polygon

OpenGL.glTexCoord2d 0, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, 1, 1
OpenGL.glTexCoord2d 0, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, -1, 1
OpenGL.glTexCoord2d 1, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, -1, -1
OpenGL.glTexCoord2d 1, 0' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, 1, -1

OpenGL.glEnd

OpenGL.glNormal3d -1, 0, 0 ' left polygon normal

OpenGL.glBegin OpenGL.GL_POLYGON ' left polygon

OpenGL.glTexCoord2d 1, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, 1, 1
OpenGL.glTexCoord2d 0, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, 1, -1
OpenGL.glTexCoord2d 0, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, -1, -1
OpenGL.glTexCoord2d 1, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, -1, 1

OpenGL.glEnd

OpenGL.glNormal3d 0, 0, -1 ' back polygon normal

OpenGL.glBegin OpenGL.GL_POLYGON ' back polygon

OpenGL.glTexCoord2d 0, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, 1, -1
OpenGL.glTexCoord2d 0, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, -1, -1
OpenGL.glTexCoord2d 1, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, -1, -1
OpenGL.glTexCoord2d 1, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, 1, -1

OpenGL.glEnd

OpenGL.glNormal3d 0, 1, 0 ' top polygon normal

OpenGL.glBegin OpenGL.GL_POLYGON ' top polygon

OpenGL.glTexCoord2d 1, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, 1, 1
OpenGL.glTexCoord2d 1, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, 1, -1
OpenGL.glTexCoord2d 0, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, 1, -1
OpenGL.glTexCoord2d 0, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, 1, 1				  
OpenGL.glEnd

OpenGL.glNormal3d 0, -1, 0 ' bottom polygon normal

OpenGL.glBegin OpenGL.GL_POLYGON ' bottom polygon

OpenGL.glTexCoord2d 1, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, -1, 1
OpenGL.glTexCoord2d 0, 0 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, -1, 1
OpenGL.glTexCoord2d 0, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d -1, -1, -1
OpenGL.glTexCoord2d 1, 1 ' set the UV coordinates to use for next vertex
OpenGL.glVertex3d 1, -1, -1

OpenGL.glEnd

OpenGL.glPopMatrix() ' restore matrix
21. Add the following code to the Resized event of OpenGLSurface1 (You can access the Resized event by double clicking on OpenGLSurface1 and then selecting the event from the list):
' set the viewport rectangle

OpenGL.glViewport 0, 0, OpenGLSurface1.Width, OpenGLSurface1.Height

' set up the perspective projection settings

OpenGL.glMatrixMode OpenGL.GL_PROJECTION
OpenGL.glLoadIdentity
OpenGL.gluPerspective 60.0, OpenGLSurface1.Width / OpenGLSurface1.Height, 1, 100.0

' select and reset the modelview matrix

OpenGL.glMatrixMode OpenGL.GL_MODELVIEW
OpenGL.glLoadIdentity
22. Add the following code to the Paint event of Window1 (You can access the Paint event by double clicking on the titlebar of Window1 and then selecting the event from the list):
' refresh the OpenGL surface

OpenGLSurface1.Render
23. Save and run the project.

Code Analysis

Let's first have a look at the two helper methods that we added to Window1, R3DT_LoadTexture and R3DT_DeleteTexture.

Before we can paint texture images on our polygons, the images need to be loaded into OpenGL memory in a format that OpenGL understands. Window1.R3DT_LoadTexture does just that. It creates and loads an OpenGL texture from a picture. It also returns an Integer value that is a unique ID that OpenGL assigned to the texture in memory. This ID is used whenever we want to access the texture. For now you do not have to concern yourself with the code inside Window1.R3DT_LoadTexture, but just take note that Window1.R3DT_LoadTexture provides an easy way to load textures from picture images and to get the ID of the loaded texture.

R3DT_DeleteTexture does the opposite of Window1.R3DT_LoadTexture, in that it removes a texture from the OpenGL memory. We simply supply the ID of the texture that we want to remove from memory to R3DT_DeleteTexture, and it will do the rest.

To summarize: First we make a call to Window1.R3DT_LoadTexture in the OpenGLSurface1.Open event to load the picture file that we've added to our project, as a texture into OpenGL memory. We store the returned ID in the Window1.TextureID property for later use. In the OpenGLSurface1.Close event we clean up the OpenGL memory by removing the texture from memory. We use the ID stored in Window1.TextureID to remove the texture. The OpenGLSurface1.Close event occurs when we exit the program.

On to our OpenGLSurface1.Render event. You will notice the code in the OpenGLSurface1.Render event is very similar to the code presented in the previous tutorials, with a few minor differences.

At the top of our drawing routine we make a call to glBindTexture, using the ID value that we stored in Window1.TextureID. This tells OpenGL that we want to use our loaded texture for texture mapping.

Then, before each glVertex3d call, we make a call to glTexCoord2d. It is the instruction that does the magical mapping of our texture to the vertices of our polygon. We pass the UV-coordinates to glTexCoord2d. OpenGL then uses these UV-coordinates for all subsequent vertices. It may be interesting to note that we use a different UV-coordinate for each individual vertex. If you look closely at the code of the front polygon you will see that we instruct OpenGL to use the (1,0) UV-coordinate for vertex (1,1,1), (0,0) for vertex (-1, 1, 1), and so forth. Always remember that polygon vertices are given in a anti-clockwise direction, so UV-coordinates are usually also given in an anti-clockwise order.

Once you understand how to use texture mapping in your 3D designs, a whole new world of possibilities opens up. See what happens when you edit the texture with your favorite 2D graphics editor. Try to map the texture upside-down on the front polygon. There is no better way to learn than by experimenting with the code.

Project Downloads

<< Previous Tutorial     Next Tutorial >>


 
All the content on Real 3D Tutorials, with the exception of the SyntaxHighlighter which is licensed under the MIT License, is provided to the public domain and everyone is free to use, modify, republish, sell or give away this work without prior consent from anybody. Content is provided without warranty of any kind. Under no circumstances shall the author(s) or contributor(s) be liable for damages resulting directly or indirectly from the use or non-use of the content.
Should you find the content useful and would like to make a contribution, you can show your support by making a donation.