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
69 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
 Reconfiguring our
 Direct3D application

 Basic 3D Geometry
 Vertex Buffers and
 Index Buffers

 Rendering Summary

 Source code
 Printable version
 Discuss this article

The Series
 Part 1
 Part 2
 Part 3

Getting Started With Basic 3D Geometry

Now things start to get more fun. What we’ve done so far could be seen as being fairly boring, just setting up the foundations for bigger things really…

Luckily, now that the initialisation process is sorted out quite nicely adding the ability to use 3D geometry will be quite straight forward, if you read the first article properly you’ll find that a lot of the ground work for geometry has already been laid down. To summarise the relevent key points brought up in the first article:

  • 3D Geometry is still made up of vertices and triangles.
  • Triangles must be created in a clockwise order (unless you change it to CCW/none).

Simple as that really, the only major change is going to be the switch from using 2D vertex coordinates into using 3D vertex coordinates, which cant be too hard can it? You also need to remember that it’s all relative to the camera viewpoint rather than to the screen (2D) coordinates.

The next part of the sample program we’re going to design will render a small 3D cube on the screen, then we’ll use this as a base to learning more of the manipulation features that Direct3D exposes for us (Matrices). First, take the user defined type and FVF description from the last article:

Const FVF_LVERTEX = (D3DFVF_XYZ Or D3DFVF_DIFFUSE Or D3DFVF_SPECULAR Or D3DFVF_TEX1)
Private Type LITVERTEX
    X As Single
    Y As Single
    Z As Single
    Color As Long
    Specular As Long
    tu As Single
    tv As Single
End Type

This particular vertex is pre-lit by us, that means that we must specify the colour of the vertex rather than let Direct3D light it for us (lighting will be covered later). We’ll also use a little helper function CreateLitVertex() to help us initialise the structures, this is very similar to the one in the previous article, if you are unclear as to the exact nature of it have a look at the sample code for this article…

Now that we have the vertex descriptor/UDT set up we need to define the vertex structures for our cube. For this sample we’re going to use 36 vertices:

Dim CubeVerts(0 To 35) As LITVERTEX

Why are we using 36 vertices for a 6 sided/8 cornered object? A cube has 6 square faces, to make up a square we require 2 triangles, therefore we have 12 triangles in total. Each triangle requires 3 vertices, hence the 36 figure. Later on we’ll learn how to reduce the number of vertices in the cube to 8, but for a first example this will do fine…

Private Sub InitialiseGeometry()
'//0. Any Variables
'//1. Define the colours at each corner
  Const Corner000 As Long = &HFF0000   'red
  Const Corner001 As Long = &HFF00     'green
  Const Corner010 As Long = &HFF       'blue
  Const Corner011 As Long = &HFF00FF   'magenta
  Const Corner100 As Long = &HFFFF00   'yellow
  Const Corner101 As Long = &HFFFF     'cyan
  Const Corner110 As Long = &HFF8000   'orange
  Const Corner111 As Long = &HFFFFFF   'white


'//2. Define the faces
  'top
  CubeVerts(0) = CreateLitVertex(-1, 1, -1, Corner010, 0, 0, 0)
  CubeVerts(1) = CreateLitVertex(1, 1, -1, Corner110, 0, 0, 0)
  CubeVerts(2) = CreateLitVertex(-1, 1, 1, Corner011, 0, 0, 0)

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

  'bottom
  CubeVerts(6) = CreateLitVertex(-1, -1, -1, Corner000, 0, 0, 0)
  CubeVerts(7) = CreateLitVertex(1, -1, -1, Corner100, 0, 0, 0)
  CubeVerts(8) = CreateLitVertex(-1, -1, 1, Corner001, 0, 0, 0)
  CubeVerts(9) = CreateLitVertex(1, -1, -1, Corner100, 0, 0, 0)
  CubeVerts(10) = CreateLitVertex(1, -1, 1, Corner101, 0, 0, 0)
  CubeVerts(11) = CreateLitVertex(-1, -1, 1, Corner001, 0, 0, 0)

  'left
  CubeVerts(12) = CreateLitVertex(-1, 1, -1, Corner010, 0, 0, 0)
  CubeVerts(13) = CreateLitVertex(-1, 1, 1, Corner011, 0, 0, 0)
  CubeVerts(14) = CreateLitVertex(-1, -1, -1, Corner000, 0, 0, 0)

  CubeVerts(15) = CreateLitVertex(-1, 1, 1, Corner011, 0, 0, 0)
  CubeVerts(16) = CreateLitVertex(-1, -1, 1, Corner001, 0, 0, 0)
  CubeVerts(17) = CreateLitVertex(-1, -1, -1, Corner000, 0, 0, 0)

  'right
  CubeVerts(18) = CreateLitVertex(1, 1, -1, Corner110, 0, 0, 0)
  CubeVerts(19) = CreateLitVertex(1, 1, 1, Corner111, 0, 0, 0)
  CubeVerts(20) = CreateLitVertex(1, -1, -1, Corner100, 0, 0, 0)

  CubeVerts(21) = CreateLitVertex(1, 1, 1, Corner111, 0, 0, 0)
  CubeVerts(22) = CreateLitVertex(1, -1, 1, Corner101, 0, 0, 0)
  CubeVerts(23) = CreateLitVertex(1, -1, -1, Corner100, 0, 0, 0)

  'front
  CubeVerts(24) = CreateLitVertex(-1, 1, 1, Corner011, 0, 0, 0)
  CubeVerts(25) = CreateLitVertex(1, 1, 1, Corner111, 0, 0, 0)
  CubeVerts(26) = CreateLitVertex(-1, -1, 1, Corner001, 0, 0, 0)

  CubeVerts(27) = CreateLitVertex(1, 1, 1, Corner111, 0, 0, 0)
  CubeVerts(28) = CreateLitVertex(1, -1, 1, Corner101, 0, 0, 0)
  CubeVerts(29) = CreateLitVertex(-1, -1, 1, Corner001, 0, 0, 0)

  'back
  CubeVerts(30) = CreateLitVertex(-1, 1, -1, Corner010, 0, 0, 0)
  CubeVerts(31) = CreateLitVertex(1, 1, -1, Corner110, 0, 0, 0)
  CubeVerts(32) = CreateLitVertex(-1, -1, -1, Corner000, 0, 0, 0)

  CubeVerts(33) = CreateLitVertex(1, 1, -1, Corner110, 0, 0, 0)
  CubeVerts(34) = CreateLitVertex(1, -1, -1, Corner100, 0, 0, 0)
  CubeVerts(35) = CreateLitVertex(-1, -1, -1, Corner000, 0, 0, 0)
End Sub

Doesn’t that look so pretty! Well, not really…

There's nothing more to this that brute force calculations, I went through (took me about 10 minutes) working out which vertex would have what coordinate, and then what colour it would have. I used a set of constants to set the colours, as it makes it much easier when you want to change the colour of a given corner - saves you having to search through all 36 changing the wrong/old values for the new ones. Now that the array of vertices is completed we can render them in an almost identical way to last time:

D3DDevice.BeginScene
  'All rendering calls go between these two lines
  D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLELIST, 12, CubeVerts(0), Len(CubeVerts(0))
D3DDevice.EndScene

Hardly complicated really is it? However, if you now run the project to see what it looks like you’ll be surprised by the result - a big yellow/magenta/white square covering the entire screen. Great - not like the pretty coloured cube we were aiming for. This is because we "forgot" (or I didn’t tell you) about one major thing that needs to be set up: Projection, World and View Matrices.

If you think about it logically, we have not yet told Direct3D where we’re looking from, or where we’re looking at - or what properties our camera has. Which is what we now need to set up. Matrices are used a lot in Direct3D (as in most 3D API’s I do believe), they control transformations of world geometry (we’ll see that later), they control the configuration of the camera, and the information about where the camera is/where it’s looking at. The actual maths behind matrices is quite complicated, and you don’t really need to know why they work - or even how they do what they do, you can get along fine in Direct3D by just knowing how to use them. There are 3 types of matrix that we need to set up, they are:

The Projection Matrix

This matrix is usually configured during initialisation and left alone for the rest of the time, you can alter it other times to achieve effects such as zooming, but in general you probably wont need to.

D3DXMatrixPerspectiveFovLH(
    MOut as D3DMatrix,
    fovy as Single,
    Aspect as Single,
    zn as Single,
    zf as Single )

Above is the function prototype for the projection matrix altering function. There are others, but for 99% of Direct3D applications this one will be perfectly acceptable. The parameters should be used like so:

MOut: An empty (not a requirement though) D3DMatrix structure that will be filled with the relevant details of a projection matrix

fovy: This is the view angle for the camera, the wider the angle this is set at the more "stuff" you’ll see on the screen. This is often set at 45, 60 or 90 degrees - but as it’s measured in radians (not degrees) you should specify it in terms of PI, so the above list becomes Pi/4 , Pi/3 and Pi/2 - there are 2Pi radians in 360 degrees, therefore 1 Pi radians in 180 degrees, so dividing by 2 (for example) would give us the equivalent value (in radians) of 90 degrees.

Aspect: This is the aspect ratio for the projection matrix. Leave this at 1 unless you’re trying to do weird stuff ! experiment setting it to <1 or >1 value (as in 0.1 or 2, not 20000), you’ll notice that things look a little bit strange afterwards. In general, less than 1 stretches the geometry vertically, greater than 1 stretches the geometry horizontally.

zn: This is the near clipping plane. Any geometry closer than this to the camera will automatically be not-rendered, or clipped so that only the visible area is rendered. Leave this to something like 0.1 for the best results.

zf: this is the far clipping plane, as with the near plane, anything beyond this point (from the camera) will not be rendered, or will be clipped. If you are going to have a lot of things on screen it’s best to keep this as close as possible - otherwise you’ll end up rendering lots and lots of stuff that might not be visible.

The set up used in the example source code is:

D3DXMatrixPerspectiveFovLH matProj, PI / 3, 1, 0.1, 75

Finally, we’ve created the matrix structure (matProj in this case) - but Direct3D doesn’t necessarily know it exists - so we must tell it. We use the following line, which must only be called AFTER a successful device creation:

D3DDevice.SetTransform D3DTS_PROJECTION, matProj

From now until the next time this call is made (with a different matrix) all rendering will take place using this matrices configuration - 60 degrees view and a 75 meter draw depth.

The View Matrix

This is basically for configuring the camera. When we set this up we can tell Direct3D where the camera is, and where it’s looking at. Direct3D will then use this matrix in conjunction with the projection matrix to decide what is going to be rendered - and how it’s projected onto the screen.

D3DXMatrixLookAtLH (
    MOut as D3DMATRIX,
    VEye as D3DVECTOR,
    VAt as D3DVECTOR,
    VUp as D3DVECTOR );

Not too complicated really. The MOut parameter will be the structure that’s filled with the relevant information. Veye is the position of the camera - where the player is looking from. Vat is the position of the target - the center of the screen (in 2D) will be this point in 3D Space. Vup is a proper vector (rather than a position) that tells D3D which way is up, using this you could make it render upside down…

The line from the sample code looks like:

D3DXMatrixLookAtLH matView, MakeVector(0, 5, 2), MakeVector(0, 0, 0), MakeVector(0, 1, 0)

Above we’re using a simple inline function that returns a D3DVECTOR based on the parameters, it’s quite simple and you can see it in the sample code. The actual code here tells D3D that the camera is place a little bit up and forward of the origin, and looking at the origin, and up is in the positive Y axis. As with the projection matrix you need to tell D3D that you want to use this camera set up for the duration until you change it again:

D3DDevice.SetTransform D3DTS_VIEW, matView

The World Matrix

This is a more interesting one - it is quite likely that you’ll set up and reuse this matrix 10 or more times every frame. It also matters how you set it up - whilst there are several helper functions to set a world matrix up a lot of it is still down to you.

First off then, what is a world matrix? The world matrix is applied to all geometry that is rendered, you can have translations (moving it around), scaling (bigger/smaller) and rotations. If you have a 2x scaling matrix applied then all geometry that’s rendered after that point will be twice as big as the actual vertex coordinates.

Secondly, why do we want to use them? The world matrix is an extremely fast and efficient way of manipulating your vertices - rather that you having to rotate and translate (for example) the raw vertex structures you can set up a world matrix and it will do it all for you. You also have the added advantage of being able to manipulate a single set of vertices to be many objects. For example, the cube we’ve made - you could render it in one position with one texture, then alter the world matrix and render it again with a different texture in a different position - and whilst there is only one physical set of vertices the user will see two essentially different cubes on the screen at the same time.

So they seem to be the perfect answer to what we need (they are, that’s why!) -but before we do a quick run through of how to use them I’m going to confuse you (unless you’re a good mathematician). To combine transformation matrices (The world matrix is a combination of 1 or more of these) we multiply them together, and in the normal world of maths A x B = B x A, remember? Well, in matrix land A x B does not equal B x A. In which case it matters what order we set up our world matrix. Great - things suddenly got complicated.

There are 3 main types of transformation that you will be applying to the world matrix - Rotation, Scaling and Translation. All of them can be applied around one or more of the axis’ - X, Y or Z. Whilst there’s no reason why you cant change the order, it will tend to be Rotation then Scaling and lastly translations. The reason for translating last is that the other two are calculated about the origin - all vertices are rotated around the point [0,0,0] and all vertices are scaled from the point [0,0,0]. Therefore if you translate something to [10,0,0] first, then rotate it around the Y axis you’ll get the object doing a loop (which might be what you want), but if you rotate it then translate it then you’ll get the object at [10,0,0] and spinning on the spot.

Sometimes these things are hard to visualise - but experiment with them and see what results you get from different orders. A further analogy could be the car wheel. Imagine a car wheel that, whilst driving normally, rotates around the X axis, if the car takes damage and the wheel is bent out of place we may choose to rotate it a constant 15 degrees around the Z axis (so it appears bent inwards); if we rotate it around the Z axis first then the X axis we’ll get the wheel oscillating in and out at the top and bottom as the wheel goes around - the part that’s bent outwards will rotate around with the wheel. If we rotate around the X axis first then the Z axis the wheel will revolve normally, except appear to be leaning in one direction - depending on which way the Z axis was rotated either the top of the wheel will stick out and the bottom will stick in - or vice versa. Think about it…

Whilst I’ve already touched upon this, it is often important to make all geometry relevant to the origin. Geometry is rotated around it, scaled about it and translated relative to it - if your geometry is centred around the point [10,0,0] and you translate it by 10 units along the X axis the centre will become [20,0,0]… which is why the cube that we’ve already generated has all it’s coordinates + or - 1 unit of the origin.

Before we move onto the actual sample code for these transformations we’ll quickly discuss the structure of setting up the world matrix. Matrices are held in a structure called D3DMATRIX, we will have one master matrix and one temporary matrix. The temporary matrix will be rest each time and have a new transformation applied, then it will be combined (through matrix multiplication) with the master matrix. This looks like this:

1) Define a Temporary and master matrix
2) Set both to the identity matrix

3) Rotate the temporary matrix around the X axis
4) Multiply the master and temporary matrix

5) Set the temporary matrix to the identity matrix
6) Rotate the temporary matrix around the Y axis
7) Multiply the master and temporary matrix

8) Set the temporary matrix to the identity matrix
9) Rotate the temporary matrix around the Z axis
10) Multiply the master and temporary matrix

11) Set the temporary matrix to the identity matrix
12) Scale the temporary matrix by any amount
13) Multiply the master and temporary matrix

14) Set the temporary matrix to the identity matrix
15) Translate the temporary matrix
16) Multiply the master and temporary matrix

17) Commit the master matrix to the device so that it is applied to all subsequent rendering.

Not too complicated really - you’ll pick it up really quickly. There are two things in the above that I haven’t yet covered. The first is the identity matrix, when you create a new matrix type it will have 4x4 singles set out with a value of 0. if you made direct3D use this matrix nothing would appear - the scale Is set to 0, so at most all you’d see is a coloured dot at the origin. The identity matrix is one where the scaling values are set to 1.0, and any geometry rendered using it will not be altered at all - it appears in 3D space as it was laid out in the raw vertex data. It is important to reset the temporary matrix each time - otherwise you can get it accumulating multiple transformations in a strange way and you’ll get some truly strange results! The second thing is the multiplication part. As initially mentioned, [A][B] does not equal [B][A], so this line has to be in a specific order - you can get slightly different effects depending on which way around its done, but I strongly suggest you choose a preferred order and stick with it - I have always multiplied the world matrix by the temporary matrix - it’s worked fine for me and should do for you. The same goes with what order you rotate it - as mentioned in the wheel analogy, XYZ is very different from ZXY (or any other combination), Unless I need it to be done in another way I stick with XYZ - you can use that or change it - it’s up to you…

Okay… So I do believe we’re now ready to look at some proper code (at last!), the sample project only does rotation - you can see it as a small challenge to rewrite the sample so that the cube is translated and scaled (perhaps based on input from the user)…

There are 7 functions to perform the translations - there are lots more, but I’ll let you explore them later on (they tend to be more specialised). These are:

D3DXMatrixIdentity(MOut As D3DMATRIX)

D3DXMatrixMultiply(MOut As D3DMATRIX, M1 As D3DMATRIX, M2 As D3DMATRIX)

D3DXMatrixRotationX(MOut As D3DMATRIX, angle As Single)

D3DXMatrixRotationY(MOut As D3DMATRIX, angle As Single)

D3DXMatrixRotationZ(MOut As D3DMATRIX, angle As Single)

D3DXMatrixScaling(MOut As D3DMATRIX, x As Single, y As Single, z As Single)

D3DXMatrixTranslation(MOut As D3DMATRIX, x As Single, y As Single, z As Single)

Not too complicated really, you’ll see how to use them in a second, they all follow the same format. MOut is the resulting matrix, and angle is always in radians (not degrees).

D3DXMatrixIdentity matWorld

D3DXMatrixIdentity matTemp
D3DXMatrixRotationX matTemp, Angle * RAD
D3DXMatrixMultiply matWorld, matWorld, matTemp

D3DXMatrixIdentity matTemp
D3DXMatrixRotationY matTemp, Angle * RAD
D3DXMatrixMultiply matWorld, matWorld, matTemp

D3DXMatrixIdentity matTemp
D3DXMatrixRotationZ matTemp, Angle * RAD
D3DXMatrixMultiply matWorld, matWorld, matTemp

Angle = Angle + 1

D3DDevice.SetTransform D3DTS_WORLD, matWorld

The above excerpt is the code used in the sample program to spin the cube around on all axis by 1 degree every frame. This code is updated every frame - so any particularly complicated formula’s that you want to use may well slow things down a bit - although using the D3DX* functions are fast enough not to worry about.

If you now run the sample project (or your code if copying from the page) you should be greeted with a fairly large, colourful spinning cube… If you have turbo-charged hardware acceleration the cube may be spinning extremely fast (1 degree every frame, 360+ fps will mean it does a complete spin in 1 second!), if this is the case change the angle increment value down to 0.5 or 0.25…





Next : Vertex Buffers and Index Buffers