home

Tutorial Index

DirectX tutorial 8: Textures

So, we have done a great deal so far, we can draw images on the screen and use them as buttons and we can also create 3D objects, give them color(s), draw them so they do look 3 dimensional, and also light them! There is one last big thing we have to do to be able to use our skills to create a decent game, and that is applying textures

Obviously creating hundreds of polygons to give the right image to a surface would be, other than very hard, a very heavy load on the machine. Even using a different model for a different part of the same item (e.g. metal corners of a wooden box) would be very inefficient, not to mention the fact that some patterns like those on wooden or rocky surfaces are just too complex to bother creating with brute force. The solution? Textures!

Textures are images that are stretched, or applied to be more correct, on a 3D surface to give it the colors and looks it deserves. Their importance is most apparent in old games with truly low-poly models (models with a low polygon count, with few triangles). Take a look at GTA III, notice to what extent facial features are curved and to what extent they are given through textures, imagine it without textures but rather just one matt color on each surface, it would look pretty bad.

Even today with our amazingly high-poly models, textures do tons of work, and we have found much more advanced techniques of using them. For example bump mapping uses a texture’s data to represent offset from the surface, and is a much faster way of adding depth to a surface than adding polygons. But for now we will only bother with simple texture mapping.

A question that may have come to mind is how does DirectX know where to put which part of the image. The answer is UV coordinates, and their assignment is often called UV mapping. Each vertex of the model has specific UV coordinates, which refer to coordinates on the image. UV coordinates range from 0 to 1, so you can change texture resolution and keep the same UVs.
This means that, as I said in the previous tutorial, we will have to change our FVF one more time. Add two FLOAT variables, U and V. In the FVF add D3DFVF_TEX1 to the definition of CUSTOMFVF.

Now, to apply a texture, you need a texture. The formats DirectX supports are Bitmap (.bmp), which is the simplest but largest as well, PNG (.png, portable network graphics) which is both compressed and has support for Alpha, JPEG (.jpg) which has very good compression but no support for Alpha, Targa (.trg) which is often used by the professionals and sometimes is also the final form of the format shipped with commercial games, but requires more professional hardware (I doubt anything below GIMP will support it), DirectDrawSurfce files (.dds, pronounces DDS usually) which are quite obviously DirectX’s closest match, not really that easy to find a way to open, they do often ship with games (Stronghold 2 has .dds files which you can directly change), and finally Windows’ Device Independent Bitmap (.DIB) which is a pretty much rasterized format, i.e. it can be drawn as-is, which I have never come across in my life, and due to the rasterization I am guessing is worse than the simple BMP file in size. Create one, preferably in PNG format, I suggest as always explicit file locations in the program, but you can try putting it in the project folder and in the debug folder and using relative location. I’m going to use directx-logo.jpg which I have stored in my C drive, this location is not accessible in Windows 8, so change the sample code to fit your needs if you run it.

This time we’ll place the code in Render() and the loading code in the end of init_graphics(), as it is not much code and we will anyway restructure the program in the next tutorial, so we do not have to worry about getting messy. As always of course, back up your project so you can go back if you change mind.
Declare a new public LPDIRECT3DTEXTURE9 variable, I named mine tex. In init_graphics(), to load it, call D3DXCreateTextureFromFile(device, filename, &texture). The arguments are evidently the device, a string with the file name (remember that the LPCWSTR variable type can be achieved by adding an L prefix to the string when explicitly declared) and the texture object we want to load to.

You can also create a texture from a resource, this will embed the texture into your .exe, the function is D3DXCreateTextureFromResource(device, HMODULE hSourceModule, string pSrcResource, &texture). Since the resource lies in the same exe file, the module is set to NULL, the string after it is the name of the resource. I will not implement an example of this, however you may find the Win32Api tutorials to be a bit more helpful, as there bitmaps are loaded from the resource file.

In Render() we will have to inform DirectX that we are going to use the specific texture. First we assign it a number (remember, all functions are member functions of the device), so we call SetTecture(0, tex). Next we need to inform DirectX of the usage, the function we will be using for this is SetTextureStageState(texNumber, stage, state). Since we only have one texture and it is assigned number 0, the first parameter of all the calls will be 0. The first call sets D3DTSS_COLOROP to D3DTOP_SELECTARG1. This indicates that we will be using the first argument of texture #0 for color operations. Essentially we associate our texture with ARG1, as you will see. Next, we set D3DTSS_COLORARG1 to D3DTA_TEXTURE, indicating that the first color argument, if you will, the first rendering task, is to apply the texture. Next we assign D3DTSS_COLORARG2 to D3DTA_DIFFUSE, so we also add a task of rendering with diffuse color. Note that diffuse is generally ignored, but do put it there in hopes that one of us will one day get it (and that includes some of the biggest DirectX resoures pages you can find).

Finally, we need to set D3DTSS_TEXTURETRANSFORMFLAGS to D3DTFF_COUT2, which enables the UV coordinate system on our textures. Mostly this is commented as enabling the textures if you look around the internet, and indeed without this you won’t be seeing much.

Now, hop into SetMatrices(). If you run the application now, you will most probably see a white cube rendered. Though the exact thing that is going on is somewhat obscure since it depends on many factors such as what transformations have taken place and what projection is used,  what happens is that the texture does not follow the cube in terms of transformations, i.e. you have to transform the texture too. We will not bother much with this, multiply finalM with projectionM and then the result with lookAtM, and then call SetTransform(D3DTS_TECTURE0, &textureM), note that textureM is what I named the matrix with the texture transformations.

You should see a reasonable rendering of the texture on the cube – UV coordinates are to take most the blame for the bloody mess you are probably looking at, but nevertheless, it is recognizable that it is the texture you wanted. UV mapping is done within the 3D modeling package though, so we do not bother with that either.

Note: if UVs are definitely not the problem, the next thing to look at is the texture transform matrix. Unfortunately, not only is it hard to find out which exact operations are needed, it is hard to even find a mention of it!

Well, that’s about it with texture mapping. I am afraid it was not spectacular, because well, we aren’t actually making a game, so we care to see that our methods work, and not that the models are correct. However, we should look cheerful, as the next tutorial will introduce meshes, and that’s gonna allow us to import meshes created efficiently, and – hopefully – working properly and looking good straight away!

‘Till the next tutorial, you can get the code right over here – if you get the UVs right, contact me through IndieDB, I’ll put a great big mention of your name at the top of my tutorial for it!