Textured Quad

Introduction

Loading A Texture

Setting The Active Texture

Texture Stage States

Texture Coordinates

Limitations

Lesson Downloads

MSDN Links For Functions/Concepts Discussed Here

Introduction

Up until now we've coloured our objects by setting the per-vertex diffuse component. This is fine for simple examples, but overall it's very limiting. In this lesson we're going to take the next step.

We're going to load an image from disk and apply it to a quad. The image is called a texture and the process of applying it to an object is texturing. Sometimes in artist circles it's called skinning, but since that term can also mean a number of other things we won't use it here.

To keep things simple, we're going to use a screen space quad as our object.

Loading A Texture

With D3DX there are a variety of ways to load a texture. In this lesson we'll use D3DXCreateTextureFromFile.

HRESULT WINAPI D3DXCreateTextureFromFile(
   LPDIRECT3DDEVICE9 pDevice,
   LPCTSTR pSrcFile,
   LPDIRECT3DTEXTURE9 *ppTexture
);
pDevice
A pointer to your Direct3D device
pSrcFile
The filename of the texture you wish to load
ppTexture
The address of your texture pointer. It will be set to the loaded texture.
IDirect3DTexture9 *g_texture=NULL;

   D3DXCreateTextureFromFile(g_d3d_device,   //Direct3D Device

                             "DH.png",       //File Name

                             &g_texture);    //Texture handle

Like all DirectX resources you have to remember to call the texture's Release method when you are done with it. Back in the Vertex Buffer tutorial we discussed memory pools. Textures created with D3DXCreateTextureFromFile are automatically put in the Managed pool. If you need more control over the texture loading you can use D3DXCreateTextureFromFileEx, which is an advanced version of D3DXCreateTextureFromFile. D3DXCreateTextureFromFileEx will be covered in a later lesson.

Setting The Active Texture

Now that the texture is loaded, you need a way to tell Direct3D to use it. Setting the active texture is done with a call to the SetTexture method of the device. Each texture is bound to a Stage or Sampler.

HRESULT SetTexture(
   DWORD Sampler,
   IDirect3DBaseTexture9 *pTexture
);
Sampler
This is the sampler or texture stage that the texture should be assigned to. Advanced uses of texturing require multiple textures to be used at a time. In this lesson we only use a single texture, so we bind our texture to stage 0.
pTexture
A pointer to the texture to be bound
IDirect3DTexture9 *g_texture;
IDirect3DDevice9 *g_d3d_device;

   g_d3d_device->SetTexture(0,g_texture);

Texture Stage States

The Texture Stage States declare how the colour (and alpha, not covered here) channels are to be processed. We haven't had to set these in the past because they default to taking the colour from the diffuse colour.

Each stage has an operation and 2 arguments that are used to define the processing that will occur. In this lesson we want the colour to come completely from the texture with no other processing. To do this we set the Operation (D3DTSS_COLOROP) to select the first argument (D3DTOP_SELECTARG1) and we set the first argument (D3DTSS_COLORARG1) to be the texture (D3DTA_TEXTURE). The second argument (D3DTSS_COLORARG2) isn't needed in this case, but we set it anyway to a safe value (D3DTA_DIFFUSE). Some drivers misbehave when both arguments aren't set to good values even if they aren't used.

HRESULT SetTextureStageState(
   DWORD Stage,
   D3DTEXTURESTAGESTATETYPE Type,
   DWORD Value
);
Stage
Which stage these setting apply to. This is the same as the stage in SetTexture, so in this lesson we set it to 0.
Type
There are about 18 of these but the ones you'll most commonly use are:
  • D3DTSS_COLOROP
  • D3DTSS_COLORARG1
  • D3DTSS_COLORARG2
  • D3DTSS_ALPHAOP
  • D3DTSS_ALPHAARG1
  • D3DTSS_ALPHAARG2
Value
What goes in this parameter depends on the Type above. If the type is D3DTSS_COLOROP ( or D3DTSS_ALPHAOP) then common values are:
  • D3DTOP_SELECTARG1
  • D3DTOP_SELECTARG2
  • D3DTOP_MODULATE
For the "ARG" Types, common values are:
  • D3DTA_DIFFUSE
  • D3DTA_TEXTURE
  • D3DTA_TFACTOR

Here's how we set it up in the example code.

   g_d3d_device->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);
   g_d3d_device->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
   g_d3d_device->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_DIFFUSE);   //Ignored

Texture Coordinates

At this point the texture is loaded and the device states are set up for texturing. But we still haven't specified how the texture is supposed to be applied to our quad. This is where texture coordinates come in. Here is our new vertex definition.

struct textured_vertex{
    float x, y, z, rhw;  // The transformed(screen space) position for the vertex.

    float tu,tv;         // Texture coordinates

};

//Transformed vertex with 1 set of texture coordinates

const DWORD tri_fvf=D3DFVF_XYZRHW|D3DFVF_TEX1;

The diffuse component is gone and 2 new members have been added:tu, tv. These 2 coordinates specify where in the texture the given vertex is mapped. The top left of the texture is referenced as (0,0), the bottom right is referenced as (1,1). These 2 coordinates are the texture space equivalent of x,y. Given that, you can probably work out that the texture coordinates for the top right of the texture are (1,0) while the bottom left is (0,1).

No matter how large your texture is, the values range from 0.0 to 1.0. Also, there is no requirement that the entire texture be used. If you set the top left corner of the quad to have texture coordinates of (0,0) and the bottom right corner to have (0.5,0.5) then the top left quarter of your texture would be used to cover the entire quad. Similarly you can use values outside of the 0.0 to 1.0 range for other effects.

I recommend playing with the texture coordinates in the example code to get a good feeling for them.

Limitations

There are size limits on the textures you can load. Old cards such as the Voodoo 2 had a maximum size of 256x256. Modern cards can handle textures larger than 1024x1024. This limit is a hardware limitation. You can check the size limits by querying the device caps with the GetDeviceCaps method of your device.

D3DCAPS9 caps;

   g_d3d_device->GetDeviceCaps(&caps);

To check the maximum size you card can handle, check the MaxTextureWidth member of the D3DCAPS9 structure. There is also a MaxTextureHeight field, though I've never seen it differ from the MaxTextureWidth. Also, if the TextureCaps field has the D3DPTEXTURECAPS_SQUAREONLY flag set, then you textures must be square as well.

Another common limitation is that the texture size must be a power of 2: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, etc. If the device has D3DPTEXTURECAPS_POW2 flag set, then textures must have their width and height as a power of 2. There is an exception though. A card that sets the D3DPTEXTURECAPS_POW2 flag can also set the D3DPTEXTURECAPS_NONPOW2CONDITIONAL flag which indicates that in certain circumstances, a non-power of 2 texture can be created. The list of limits on D3DPTEXTURECAPS_NONPOW2CONDITIONAL is fairly long, so I won't get into it here. The documentation spells it out fairly clearly anyway.

Here is a quick code snippet to check the caps of your card for the above capabilities.

D3DCAPS9 caps;
int max_tex_size;

   g_d3d_device->GetDeviceCaps(&caps);

   max_tex_size=caps.MaxTextureWidth;

   if(caps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY){
      //Textures have to be square

   }else{
      //Texture do not have to be square

   }

   if(caps.TextureCaps & D3DPTEXTURECAPS_POW2){
      //Textures must be a power of 2 in size


      if(caps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL){
         //But, in certain cases textures can ignore the power of 2 limitation

      }
   }else{
      //Textures do not need to be a power of 2 in size

   }

Lesson Downloads

MSDN Links For Functions/Concepts Discussed Here

Back