Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
66 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Using Textures
 in Direct3D

 Loading 3D Models
 into Direct3D

 Using Direct3D
 Lighting


 Printable version
 Discuss this article
 Get the source

The Series
 Part 1
 Part 2
 Part 3

Welcome back for part 3 in my 3 part series on DirectXGraphics… First off, I’m sorry for the rather long delay between parts 2 and 3 (8 months or so!), I’ve just been phenomenally busy lately. Hopefully to make up for this gap I’ll cover everything you need to know to give you a strong foundation in Direct3D8 programming. Direct3D9 / DirectX9 is about to enter it’s first beta-testing stage, so it may seem that DX8/D3D8 is getting a little old (it’s a 1 year old API now), but you would be foolish to think like that. From what I’ve seen here-and-there about D3D9 it seems to be not much more than an extension of D3D8, whereas v7 to v8 was a big jump, v8 to v9 is more of a revision. Also, D3D9 wont be much use for a long time yet as there will be very little hardware to support its new features, and very few end-users owning this hardware. Direct3D8’s revolutionary pixel/vertex shader technology only exists on 2 or 3 cards (GeForce 3 and the ATI Radeon’s) and isn’t really being that extensively used yet, so if we haven’t even caught up with that properly, why do we need Direct3D9…?

I’ll stop moaning now, and get on with what this article is actually supposed to be about. Part 2 was quite a complicated and fast paced article, and don’t expect any let-off just yet - I’m going to be keeping up the pace for this article too. This is the outline:

  1. Using textures in Direct3D
  2. Loading 3D Models from files
  3. Using the Direct3D lighting engine

Doesn’t look like much does it? Haha, more fool you if that’s what your thinking. These 3 topics alone deserve an article (or two) each… less talk, more learning!

Using Textures in Direct3D

So far we’ve seen some basic 3D geometry - a spinning cube, you should be aware that the colour of the vertices depicts what colours you actually see when it’s finally rendered. Yet you should also be aware that you cant really do more than create pretty-coloured gradients with it. Say we want to turn our 3D Box into something more interesting - say a wooden crate for example.

I don’t think I need to tell you that it’s almost impossible to create a decent wooden box appearance using just vertices and their colours. So we’re going to use a bitmap to display the colours. As you should be aware, 3D geometry is made up of triangles, and a simple fact of a triangle defined in 3 dimensions is that it is planar - a 2D surface that doesn’t have curves or anything like that. Thus it is perfectly suited for projecting a 2D bitmap image onto. This is the basis of texturing - we use a 2D bitmap applied to the 3D triangles in order to make the overall model appear to look more detailed / look like something.

The first step to using textures is to load the texture from the hard-drive / CD-ROM into texture memory. This causes one slight complication already - texture memory is a finite resource, yet art-work tends to happily consume an infinite amount of space! Therefore we can only fit a potentially small amount of art work into memory at any one time. This amount is indicated by the amount of memory the graphics card has "onboard". 32mb is common these days, with 16mb being a past favourite and 64mb being standard on all the new high-tech boards. It is quite easy to work out how much space you are using - based on the internal texture format and the dimensions of the texture itself, also dependent on any additional space required for mip-mapping.

I discussed the CONST_D3DFORMAT enumeration in the previous article - what the letters mean, what the numbers are for… if you cant remember that then go read the previous article. As you are aware, a bitmap is made up of a 2D grid of pixels, we need to use the CONST_D3DFORMAT enumeration to tell Direct3D how to store the colour for each of those pixels - 32 bit, 16 bit… As you should be aware, 32 bit = 4 bytes, 16 bits = 2 bytes. If we store a standard 256x256 bitmap with 32 bits per pixel we’ll need 256kb of texture memory, however, if we store it at 16 bits per pixel we’ll only need 128kb of texture memory. This may seem fairly trivial for only one texture - and it is; but if you have 200 textures it’s the difference between 50mb and 25mb - suddenly it means a lot more! 25mb will fit into most recent video cards, 50mb will only fit into the (current) top of the range 3D cards. The bottom line being that you must be clever with your choice of texture format. As a general note, you will tend to find that your game runs much faster if the display mode format and the texture formats are the same - as it saves any last minute format conversions from being done (which is just a tiny bit more work to be done).

This following little piece of code will allow you to check what texture formats can be used by the currently installed 3D board. The last parameter (D3DFMT_X8R8G8B8 in this case) indicates the texture format you want to test.

If D3D.CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _
                         D3DWindow.BackBufferFormat, 0, D3DRTYPE_TEXTURE, _
                         D3DFMT_X8R8G8B8) = D3D_OK Then

  Debug.Print "32 Bit textures with no alpha are supported"
End If

The other rule for textures is their size. Whilst it’s not so important with new 3D cards, it is very important if you want to be backwards compatible. It’s also generally much faster to stick to using the old style texture size conventions.

  1. Stick to using 2n texture dimensions, that is 2,4,8,16,32,64,128,256 and so on… anything above 256x256 is getting a little risky - the very popular Voodoo3 chipset doesn’t support textures above 256x256 in size, which instantly causes a problem with compatibility. 256x256 is also the optimal size for a texture and tends to give the best all round performance.
  2. Textures don’t have to be square. This may well cause some problems with very, very old graphics, but we cant be compatible with everyone now…
  3. Where possible, group small textures onto one larger texture, this is known as texture-paging sometimes. For example 64 32x32 tile pictures will be okay as 64 different textures, but it’ll run much faster to store them all as one 256x256 texture.

This following piece of code retrieves the maximum texture sizes available to the device:

D3D.GetDeviceCaps D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, DevCaps
Debug.Print "MAX TEX SIZE: ", DevCaps.MaxTextureWidth & "x" & DevCaps.MaxTextureHeight

Enough talking now, let's load a texture into memory. Textures are stored in a Direct3DTexture8 object, and can be loaded using one of two main functions (provided by the D3DX8 library):

CreateTextureFromFile( _
     Device As Direct3DDevice8, _
     SrcFile As String) As Direct3DTexture8

CreateTextureFromFileEx( _
    Device As Direct3DDevice8, _
    SrcFile As String, _
    Width As Long, _
    Height As Long, _
    MipLevels As Long, _
    Usage As Long, _
    Format As CONST_D3DFORMAT, _
    Pool As CONST_D3DPOOL, _
    Filter As Long, _
    MipFilter As Long, _
    ColorKey As Long, _
    SrcInfo As Any _
    Palette As Any) As Direct3DTexture8

As you can see, the first function is much simpler than the second. This is deliberate - sometimes you really don’t need that much control over the texture creation process. However, I strongly suggest that you get used to using the second function from square 1. Many of the parameters are fairly simple and don’t change much between different uses. The following code is the fairly general implentation:

Set CubeTex = D3DX.CreateTextureFromFileEx(D3DDevice, _
                        App.Path & "\cube_tex.jpg", _
                        256, 256, 1, 0, _
                        DispMode.Format, D3DPOOL_MANAGED, _
                        D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, _
                        0, ByVal 0, ByVal 0)

Looks a little complicated doesn’t it? Well, the first parameter associates the texture with our device (simple enough), the second parameter points to the file where the data is stored (BMP, TGA, JPG allowed). The third and fourth textures indicate the size of the texture in memory, if the file is of different dimensions then D3DX will resize it for you. The fifth and sixth parameters indicate the mip-map levels and the usage - leave these both to 0 in most cases, although in this case I’ve set the MipLevels parameter to be 1 - I only want one iteration in the mipmap sequence… if I let it do more (setting it to 0 indicates a full chain) then it’ll start chewing up my memory! The seventh parameter indicates the format of the texture, as I said earlier, keeping it the same as the device format is best - so that’s what I’ve done. The eighth parameter indicates the memory pool - managed (copied to video memory when needed/moved back to system memory when not needed), default (lets the driver decide where it should go) and system memory (stores it in system memory, which isn’t accessible to the 3D Device, yet can be used for some other functions). The ninth parameter and tenth parameters indicate how D3DX filters the input data to fit the memory data - should the two sizes be different; D3DX_FILTER_LINEAR will do fine here unless you’re resizing the image by more than 2x or 3x the original. The eleventh parameter is the colorkey and isn’t being used here - but will be explained later. The last two parameters aren’t really that interesting and have been known to cause errors on some systems - so leave them as ByVal 0 unless you really need to.

The above code will now have loaded the following image into texture memory:


Cube_Tex.Jpg

It’s not an amazingly interesting texture, but it’ll look alright on our box. The next thing that I need to discuss is texture coordinates.

Texture coordinates are a fun topic, well actually, they’re not - because you either get it or you don’t; if you don’t then you’re screwed! I’m only going to go over it briefly here - hopefully you will follow, otherwise, ask some people in the forums on this site, or go in search of some other more in-depth texturing tutorials. The following diagram is required for reference:

The above diagram can be imagined as the Cube_Tex.jpg shown above. You should be familiar with coordinates in a normal 2D image - X and Y, measured in pixels. We now replace this coordinate system with a scalar system - all pixels are referred to on a 0.0 to 1.0 scale; this is unaffected by the actual pixel dimensions - 256x256 or 128x256, it doesn’t matter - they both still use the 0.0 to 1.0 scale. This makes things surprisingly easier actually. Both for us, and for the 3D accelerator. It means that we can interchangeably use different sized textures (a low-res version and high-res version) with the same piece of code, and expect to get an almost identical result. It also makes it much easier to algorithmically generate texture coordinates (a bit more advanced).

In the above diagram the four corners are labelled with their respective coordinates. I’ve also drawn a simple triangle on the diagram marked with three vertices, A B and C. At a guess, I’m thinking that A will have coordinates of [0.4,0.3], B will be [0.6,0.1] and C will be [0.75,0.3] - it’s only a rough guess, and you could calculate it exactly if you wanted… but I didn’t! If this new coordinate system really confuses you still you can use a simple conversion formula: (1/Width)*X, (1/Height)*Y, where width, height, x and y are all pixel measurements. On a final note, texture coordinates are usually denoted using U,V and W rather than X,Y and Z - however U=X, V=Y, W=Z. It is advised to stick to convention so that other people understand what you’re doing.

Now that we’ve covered loading textures and their coordinate system we can actually try rendering something with it! I’m going to use the cube from the second part of this series - the one with no indices and no vertex/index buffers. There is a good reason for this - I want each vertex to have it’s own, different, texture coordinate. This gets difficult when using indices, as in the case of the cube, 3 sides share each vertex, between 3 and 6 triangles as well; therefore I’d need to express up to 6 texture coordinates as a single coordinate - not easy, or in this case, just not possible. Therefore I’m going to have to use the lots-of-vertices cube.

The first step is to assign texture coordinates to each of the vertices, I’ve only copied out the code for the first face, because it’s identical for the other 5, and only takes up lots of space:

CubeVerts(0) = CreateLitVertex(-1, 1, -1, Corner010, 0, 0, 0)
CubeVerts(1) = CreateLitVertex(1, 1, -1, Corner110, 0, 1, 0)
CubeVerts(2) = CreateLitVertex(-1, 1, 1, Corner011, 0, 0, 1)

CubeVerts(3) = CreateLitVertex(1, 1, -1, Corner110, 0, 1, 0)
CubeVerts(4) = CreateLitVertex(1, 1, 1, Corner111, 0, 1, 1)
CubeVerts(5) = CreateLitVertex(-1, 1, 1, Corner011, 0, 0, 1)

The parameters in bold are the two texture coordinates.

The second step is to actually render the cube with the texture applied - which is actually very very easy.

The final result looks like this:

Hmm, so what’s gone wrong here then? It’s red and yellow? Not much like the picture of the texture above… well, actually, nothing has gone wrong - you’ve just seen the effects of Direct3D lighting. As you may remember, the original cube geometry had 8 different colours for the corners, well it’s these colours that are blending with the texel data to form the final rendered image. This can be used to create brilliant effects - as we shall see later on in the lighting section. If we replace the vertex colours with white then the original texture wont be affected at all - and you’ll get an image like this next one:

Which probably looks much more like what you expected.

Okay, so that’s texturing covered. Well, as much as you need for a foundation. There is literally tonnes and tonnes more to learn about texturing - but leave it alone till you have this part sorted out in your head. The main areas of advanced texturing come under these headings:

Alpha channel effects - opacity/transparency effects in textures.
Altering pixel data - generating procedural textures, or applying per-pixel effects.
Compression - by default the D3DFMT_DXT* formats.
The Texture cascade - where you can apply up to 8 textures to each triangle - capable of creating some stunning effects (bump mapping, specular lighting to name two).
Pixel shaders - very, very advanced texture effects - brand new to Direct3D8, and quite likely to become a big part of Direct3D 9 and 10…

To get you started on tutorials for some of those features I have the following links:
http://www.ancientcode.f2s.net - has a good article on binary manipulation, and it’s uses in altering pixel data.
http://www.vbexplorer.com/directx4vb/Tutorials/DirectX8/GR_Lesson12.asp - a tutorial on the texture cascade and the effects it presents.
http://www.vbexplorer.com/directx4vb/Tutorials/DirectX8/GR_Lesson14.asp - a tutorial on accessing and manipulating texture memory.
http://www.vbexplorer.com/directx4vb/Tutorials/DirectX8/GR_Dot3Bump.asp - a tutorial on using the texture cascade to do Dot-3 bump mapping.



Next : Loading 3D Models into Direct3D