Ask the ZMan: Applying Textures, Part 2

Published 31 October 06 09:50 AM | Coding4Fun 
  Part 2 of a three part series explaining the addition of textures to DirectX Primitive shapes.This column covers how to get a different texture on each side of the box.
The ZBuffer

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: Free
Software: Visual Studio Express Editions
Hardware:
Download:
Welcome to Ask The ZMan, a new column on the Coding4Fun game portal. The ZMan is here to solve your Managed DirectX programming problems. If you have a question for the ZMan send it to zman@thezbuffer.com

Since Coding4Fun has only just launched, there aren't any specific questions to answer so I thought I would start with a question that occurs frequently on the Managed DirectX newsgroup:

"I've created my first Managed DirectX project and can successfully draw shapes created using the Mesh.{shape} primitives. However, when I try to apply a texture to them a) nothing happens b) the shapes go black or c) the shapes disappear. How can I add a texture to the DirectX primitive shapes?"

Last time I covered how texture coordinates are stored inside the mesh object, the relationship between the texture coordinates and the texture, and the most useful of the texture sampling states. In this column I will cover how to get a different texture on each side of the box.

A New Start Point

I left you last time with an exercise to add rotation into the crate texture viewer program. Nobody sent me their homework on time so I'm not sure if it was too easy or too hard.

Download the sample code using the link at the top of this article to see my solution (see figure 1).  You will need Beta2 of Visual C# Express and the April 2005 DirectX SDK. You can get Visual C# 2005 Express Edition Beta 2 by visiting http://msdn.microsoft.com/express. You can also use other Express Editions depending on the language of your choice, but this demonstration will utilize C#.

From the picture you can see I've added both rotation and translation. It is now possible to position the texture anywhere on the face of the box.


Figure 1. A solution to the exercise from the last column 

You will see that I have refactored the code to begin creating a reusable library. I would have preferred to create a new class inheriting from the DirectX mesh . However, the DirectX mesh class is sealed so this is not possible. I chose to create a static library which takes DirectX mesh objects as parameters.

Take a look at SetBoxTexture in TexturedMesh.cs and you will see some resemblance to the TextureMesh function from last time. To add in the rotation and translation I made the code a little more generic. I also parameterized the function rather than picking up the global variables.

First, I created a unit square which for the texture coordinates. Unchanged, these coordinates would cause the whole texture to cover each face.

Visual C#

Vector2[] corners = new Vector2[4] 
{
new Vector2(0f, 0f), //bottom left
new Vector2(1f, 0f), //top left
new Vector2(1f, 1f), //top right
new Vector2(0f, 1f) //bottom right
};

Visual Basic

Dim corners As Vector2() = New Vector2() { _
New Vector2(0.0!, 0.0!),
New Vector2(1.0!, 0.0!),
New Vector2(1.0!, 1.0!),
New Vector2(0.0!, 1.0!)}

Then I applied a transformation to each of these and stored the result into the texture coordinates of the mesh.

Visual C#

//apply the transformation to each corner and store the result back 
//into the texture coordinates
for (int j=0; j<4; j++)
{
corners[j].TransformCoordinate(transform);
verts[i + j].Tu = corners[j].X;
verts[i + j].Tv = corners[j].Y;
}

Visual Basic

For j As Integer = 0 To 3
'apply the transformation to each corner and store the result back
' into the texture coordinates
corners(j).TransformCoordinate(transform)
verts(i + j).Tu = corners(j).X
verts(i + j).Tv = corners(j).Y
Next

The transformation was created by concatenating together transformation matrices for each of the input parameters.

Visual C#

Matrix transform =
Matrix.Translation(offsetX, offsetY, 0f)
* Matrix.Translation(-0.5f, -0.5f, 0f) //Translate the center of rotation
* Matrix.RotationZ(angle)
* Matrix.Scaling(1 / scaleX, 1 / scaleY, 1f)
* Matrix.Translation(.5f, .5f, 0f); //and move back the center of rotation

Visual Basic

Dim transform As Matrix = Matrix.Translation(offsetX, offsetY, 0.0F) * _
Matrix.Translation(-0.5F, -0.5F, 0.0F) * Matrix.RotationZ(angle) * _
Matrix.Scaling(1 / scaleX, 1 / scaleY, 1.0F) * _
Matrix.Translation(0.5F, 0.5F, 0.0F)

I also added a TexturedMesh.Box() function which creates a mesh with texture coordinates already set to a useful default.

Displaying a Different Texture on Each Side

For boxes, it often makes sense to show the same texture on each side. But does this make sense for other shapes? Most of the meshes you encounter in a video game don't really have the same concept of sides that a box does and so it's worth finding out how to draw different things on different faces. Some of you may have heard that meshes can have multiple textures applied to them so it is theoretically possible to load up 6 textures, one for each face, and blend them together in some way. This technique is more often used to apply different kinds of textures to the whole object (for example, a base texture followed by a shadow map to darken some areas).

What I am going to describe is how to wrap a single texture around the box. Remember that the box is made up of 6 square faces, each of which is made up of 2 triangles. If you take these 6 squares and lay them side by side over a single texture it is possible to deduce the correct texture coordinates for each face.

You will eventually have many textures so it's important to make them as small as possible. This means leaving as little space between the squares as possible. DirectX also prefers textures to be as square as possible so the layout in figure 2 is optimal. This bitmap is what is normally called a texture map.


Firgure 2. A texture map template for a box

The larger typeface signifies which of the box faces applies to which part of the texture map. The smaller typeface is to give you an idea of the orientation of the texture and indicates which face touches that edge. The highlighted face is the example I will use to generate texture coordinates.

I have added a new overload to SetBoxTexture(). This one takes the mesh and a parameter called technique. Right now there is just one technique called Divide which means that the texture will be divided evenly between the 6 faces. Most of the function is similar to the other overloads. This version hard codes the texture coordinates for each face. There really is no easy mathematical way to do this. Remember texture coordinates go from 0.0 to 1.0 so the face texture coordinates had to be 0, 1/3, 2/3, 1.0 in the x direction and 0, 0.5, 1.0 in the y direction. I guessed which face was which and rendered the box. Once I saw where I was wrong I simply switched the indexes of the vertices around until the faces were in the correct place and had the correct orientation. This process took about 15 minutes to complete. Last time I mentioned how texture mapping was hard to do by hand. Imagine doing this for a more complex shape.

To set up the texture coordinates I simply assign the correct values to each vertex. There is a block of code similar to this for each face of the box. You should be able to match the vertex indices to those in figure 2 and confirm that the texture coordinates are correct.

Visual C#

//Front
verts[20].Tu = 1f / 3f; verts[20].Tv = 1f;
verts[21].Tu = 1f / 3f; verts[21].Tv = 0.5f;
verts[22].Tu = 2f / 3f; verts[22].Tv = 0.5f;
verts[23].Tu = 2f / 3f; verts[23].Tv = 1f;

Visual Basic

'Front
verts(21).Tu = 1.0F / 3.0F
verts(21).Tv = 0.5F
verts(22).Tu = 2.0F / 3.0F
verts(22).Tv = 0.5F
verts(23).Tu = 2.0F / 3.0F
verts(23).Tv = 1.0F
verts(20).Tu = 1.0F / 3.0F
verts(20).Tv = 1.0F

To complete the render (see figure 3) a new mesh is created and its texture coordinates are set using the new function.

Visual C#

MyBox = TexturedMesh.Box(e.Device, 2.0f, 2.0f, 2.0f);
TexturedMesh.SetBoxTexture(MyBox, TextureTechnique.Divide);

Visual Basic

MyBox = TheZBuffer.DirectX.Direct3D.TexturedMesh.Box(e.Device, 2.0F, 2.0F, 2.0F)
TheZBuffer.DirectX.Direct3D.TexturedMesh.SetBoxTexture(MyBox, _
TheZBuffer.DirectX.Direct3D.TextureTechnique.Divide)

There are a couple of extra lines of code to load the template.bmp texture and ensure it is set before the mesh is rendered.


Figure 3 - Textured box showing the texture template

To make use of the template provided, load it up into your favorite image editor and replace the labels with graphics of your choice. Like most developers, I am not a great graphic artist so I chose to make a dice as an example since it only involves drawing circles. The final solution can be found by downloading the zmanTex04.msi file using the link at the top of this article. In the media folder you will find dice.bmp. If you load that texture instead of template.bmp you should see something like figure 4. 


Figure 4. Final render of the dice

Exercise for the Reader
You have seen what the texture map template for the box looks like. What do you think a texture map for a cylinder or a sphere would look like?

Future texturing questions coming soon:
How can  I texture the cylinder, sphere, torus, and teapot?

Credits:
Thanks to
Chris Zastrow (czastrow@newlogicmedia.com) for the crate textures found at http://www.planetquake.com/subverse/
Carlos Aguilar (web@carlosag.net) for the code colorizer found at http://www.carlosag.net/Tools/CodeColorizer/Default.aspx   

Copyright © 2005 TheZBuffer.com

Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Rapid said on April 20, 2007 11:45 PM:

Hi Mr. Zman,

I have a question that has bothered me for 2 weeks: how to render/rotate text using directx fast/efficiently? (e.g. render 'hello world' from (1, 0, 0) to (0, 0, 0)) I have tried the following but none of them is good:

1. Use GDI+ to draw text on a bitmap and convert to texture

2. Use a mesh from a string to render

3. Use Surface.GetGraphics method to get GDI+ graphics to render

4. Directly draw the rotated text using GDI+ after rendering directx stuff.

5. Tried Sprite.Transform (not successfully and I do not believe it will work as I need)

Could you please help me? Also, is there any simple way to detect collisions among texts in a 3D space? (Currently I have to convert the 3d coordinates of texts to screen coordinates and calculate rectangles or use ray trace for meshes. Very slow and ugly)

Thanks a million and I am looking forward to hearing from you.

Best Regards,

# Marcos said on May 3, 2007 12:39 AM:

Hi.

If I have one single mesh, with six subsets (each one is one side of the box), can I apply a different texture to each side? I would not like to use the same file, because my textures are separated, one per file.

I've done a single test, like this:

private void DrawMesh()

{

   if (mesh != null)

   {

       for (int i = 0; i < mesh.NumberAttributes; i++)

       {

           if (textures[i] != null)

               device.SetTexture(0, textures[i]);

           device.Material = materials[i];

           mesh.DrawSubset(i);

       }

   }

}

But all the SubSets are drawn with the same (the first) texture.

Could you help me? :-)

Thanks.

Leave a Comment

(required) 
(optional)
(required) 
Page view tracker