Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

2D Graphics Part 1 - The Basics
Author : Jack Hoxley
Written : 7th December 2000
Contact : [Email]
Download : Graph_03.Zip [10 Kb]


Contents of this lesson:
1. Introduction
2. Some Theory
3. Getting Started
4. Initialising the Geometry
5. Rendering the Triangles


1. Introduction

Assuming that you've been following these lessons in the correct order (1...2...3 :) you'll have noticed that we haven't really done anything interesting yet - at least to look at. By the end of this tutorial you should be capable of doing some interesting things in DirectX Graphics.

One thing that I dislike about DirectX8 is the way that everyone is forced straight into the world of 3D. Whenever anyone started working with DirectX 7 they'd be told to start with DirectDraw (the old 2D component) because it's easier; then to move onto 3D. Having said that, it is [just about] possible to learn the basics of DirectXGraphics without getting too confused with all the 3D terminology. If you have any intentions of writing a fully-2D game you'd be wise to consider using either a hybrid of DirectX7 and 8, or just going straight directX 7.

Having said all that though, it's important to know how all this is done; because no matter how 3D your game is it'll probably need text, icons, pictures, health bars and so on... All of which will need to be done using these techniques.

One thing to note when you work through the initial tutorials on 2D graphics is that they are essentially 3D. I'll go through alot of things very quickly - explaining only what you need to know about them in order to do 2D rendering; and you can read the later tutorials for a full lesson on each individual technique.


2. Some Theory

Vertices
These can be thought of as defining points. You can define a triangle using 3 points - 3 vertices. Direct3D will draw a straight line between the vertices, and in most cases it will shade the area defined by the vertices. Each vertex can have additional properties such as texture coordinates, colours, specular values (for relections/shiny objects) - all of which are interpolated (blended) across the distance between the two points; for example, if vertex 0 is blue and vertex 1 is red all the points on the line joining them will fade from blue to red depending on the distance from each vertex.

Textures
Textures are applied to polygons (3 or more vertices) to make them more lifelike; without textures all you'd be able to do is blend colours. Textures come in the form of 2D bitmaps, loaded into video (or system) memory and are then stretched or squashed across the area of the triangle. Two things to remember when dealing with texturs: 1. The coordinates are on a scale of 0.00 to 1.00 and aren't on a pixel scale (more on this later) and 2: all textures must be of a size to the power of 2 (^2); for example: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048 - although it's uncommon to use textures with dimensions greater than 256..

Coordinates
Initially we'll only be using 2D coordinates - X and Y, but when we move to full 3D you'll have to use the 3rd Dimension (funny that) - Z. Coordinates are defined in world space and dont really have a maximum or minimum value (apart from that inherited from the variable size). coordinates are in Meters, but when in 2D they are in pixels.

Transforming
Transformations are what takes place when Direct3D converts our 3D vertices onto our 2D screen amongst other things. This will be explained in more detail later; as there is a lot to learn as far as this topic goes. In this lesson you will come across Transformed [and lit] Vertices - Theses are vertices that you have already transformed into 2D space - which, if you think about it, is basically a 2D vertex.

Lighting
Lighting is lighting - you should know what lighting is !! Direct3D will generate fairly realistic lighting for you, or you can do it yourself. The only reason I bring this up now is because we'll be using Transformed (see above) and lit vertices - the lit part meaning that we have already calculated it's colour....

You'll meet many more things like this as you go along; but right now you should be able to survive with this much.


3. Getting Started

I'm going to jump in straight at where we left off in lesson 01. You should either be able to remember the initialisation code, or grab your template - but I'm not going to go over it again now.

We need to make a few modifications to the existing frame work that we [should] have. First there are some new declarations:

'This is the Flexible-Vertex-Format description for a 2D vertex (Transformed and Lit)
Const FVF = D3DFVF_XYZRHW Or D3DFVF_TEX1 Or D3DFVF_DIFFUSE Or D3DFVF_SPECULAR

'This structure describes a transformed and lit vertex - it's identical to the DirectX7 type "D3DTLVERTEX"
Private Type TLVERTEX
    X As Single
    Y As Single
    Z As Single
    rhw As Single
    color As Long
    specular As Long
    tu As Single
    tv As Single
End Type

Dim TriStrip(0 To 3) As TLVERTEX '//We need 4 vertices to make a simple square...
Dim TriList(0 To 5) As TLVERTEX '//Note that, like in TriStrip, we're generating 2 triangles - yet using 2 more vertices...
Dim TriFan(0 To 5) As TLVERTEX

Okay; nothing greatly complicated in there. The first part is probably new to you though - Flexible Vertex Formats. This was possible in DirectX 7, but we now have to use them all the time. When we send data to the rendering device it wants to know what format it's in - what data is to be found. The order in which you combine the flags and define your vertex type ARE important. The following Diagram demonstrates the order that they must go in:

POSITION : X,Y,Z coordinates as 'Single' (Transformed or Untransformed)
RHW : Reciprocal Homogeneous W as 'Single'
BLEND1 : Blending Weights for vertex manipulation... all as 'single'
BLEND2
BLEND3

NX : Vertex Normals, all as 'Single'
NY
NZ
POINT SIZE
: For point Sprites only - defined as 'Single'
DIFFUSE : vertex Diffuse colour as a 32bit ARGB long value
SPECULAR : Vertex Specular data as a 32bit ARGB long
TEXCOORD1 : First set of texture coordinates
- - -
TEXCOORD8 : Final possible set of Texture coordinates

alot of the stuff in there is far in advance of where we're currently at. But you'll be taught what they mean as far as you're concerned when you need to know. When it comes to flexible vertex formats you're unlikely to need to play with them unless you're after something very advanced/special. All standard vertex formats will be discussed in these lessons as and when they're needed... You just need to remember them :)

Carrying on now, we need to make three changes to the General "Initialise" function; shown below:

'###################################################################
'## FIRST WE NEED TO CHANGE HOW WE ACTUALLY CREATE THE OBJECT 
'###################################################################

'//We're going to use Fullscreen mode because I prefer it to windowed mode :)

'DispMode.Format = D3DFMT_X8R8G8B8
DispMode.Format = D3DFMT_R5G6B5 'If this mode doesn't work try the commented one above...
DispMode.Width = 640
DispMode.Height = 480

D3DWindow.SwapEffect = D3DSWAPEFFECT_FLIP
D3DWindow.BackBufferCount = 1 '//1 backbuffer only
D3DWindow.BackBufferFormat = DispMode.Format 'What we specified earlier
D3DWindow.BackBufferHeight = 480
D3DWindow.BackBufferWidth = 640
D3DWindow.hDeviceWindow = frmMain.hWnd

'########################################################
'## THEN WE'LL NEED TO CONFIGURE THE RENDERING DEVICE 
'########################################################

'//Set the vertex shader to use our vertex format
D3DDevice.SetVertexShader FVF

'//Transformed and lit vertices dont need lighting
'   so we disable it...
D3DDevice.SetRenderState D3DRS_LIGHTING, False

'############################################
'## FINALLY WE'LL IMPLEMENT A NEW FUNCTION 
'############################################

'//We can only continue if Initialise Geometry succeeds;
'   If it doesn't we'll fail this call as well...
If InitialiseGeometry() = True Then
    Initialise = True '//We succeeded
    Exit Function
End If

 

So, we've now configured our application to kick straight into 640x480 in 16bit mode, we set a couple of rendering options and we've implemented a new function. The rendering options (D3DDevice.SetRenderState) will be used more and more as we go along - we can control almost anything we would ever need to through this one call.

I've now introduced to you the main new part of our code - InitialiseGeometry() So I suppose I might as well explain it...


4. Initialising the Geometry

This is basically what the entire lesson is about - this is the main part you have to learn. Unfortunately it means that we also need to learn some more theory - this time it starts getting complicated (well, sort of).

Although you'll find 100's of references to the usage of polygons in 3D scenes we'll only ever use triangles. Which is still the same thing really (A triangle is the simplest polgon possible), but I prefer to think of them as being triangles. The reason we use triangles is purely down to the Direct3D rendering engine - and general rendering techniques used by most engines, triangles are always convex, never concave, this makes them relatively easy to render. If you think about it, you can make any shape you so choose by using triangles. A square is just 2 triangles, therefore any quadrilateral can be made of two triangles: The following illustration explains...

As you can see, some primatives are easier to work out than others. (Please Excuse my rather poor drawings!)

When we're defining data about triangles we'll need to know what format they go in, well, there are several different methods for rendering triangles (and vertex based primatives):

Triangle Strip (D3DPT_TRIANGLESTRIP)
Triangle Strips are one of the more common types of arrangements. Using this method you can render multiple triangles using less vertices than you would if you'd chosen to define every single triangle. As you can see from the above illustration we're defining 6 triangles with 8 vertices (we'd need 18 if we'd defined each one). The only disadvantage is that it can introduce some lighting artifacts; more on that when we actually get around to doing lighting....
Triangle List (D3DPT_TRIANGLELIST)
Triangle lists are slightly older than the triangle strip arrangements; but can offer much much more flexibility. When using this method you'll use many more vertices, but you can gain the advantage to rendering triangles that aren't connected to each other by vertices OR lighting. As we'll see later on, if you try and do a sharp edge (like a corner of a corridoor) using triangle strips you'll start finding that the lighting may well leak around the corner - and not look very pretty. You can also use this method to group triangles all with the same textures or properties - then only setting the texture/options once yet rendering every triangle that'll use them...

Triangle Fan (D3DPT_TRIANGLEFAN)

Triangle Fans tend to be used for things intended to be circular, as you can see from the diagram. Attempting to render circular or circle like shapes using triangle strips or lists would be incredibly difficult. Triangle fans are basically a series of triangles defined so that the first point is always entry 0 in the array. In this example the triangles are defined by the points 0,1,2 - 0,2,3 - 0,3,4
Line Strip (D3DPT_LINESTRIP)
A linestrip is extremely useful; using a linestrip you can outline all the triangles you have sent; or more importantly you can replace trianglestrip calls with linestrip calls to see a wireframe-like image - excellent for debugging.
Line List (D3DPT_LINELIST)
A linestrip is rarely used in a proper game, except with the rare exception of lightning and sparks - although they tend to be done by the use of billboards.
Point List (D3DPT_POINTLIST)
A point list can, to a certain extent, be used to render particles - but that's now replaced with the inclusion of Point sprites in DirectX. You can, like LineStrips, use these for debugging, but it's not always that easy to see what's going on.
 

 

Okay; so hopefully now you should know what geometry is; and be able to make a reasonable choice of data-arrangement based on what you want to render. Either way, we're going to move onto the actual code for setting up our geometry:

'//This basically just fills out the relevent array of vertices
'with the information relevent to what we want to display - A square!
Private Function InitialiseGeometry() As Boolean
    '//Geometry is basically vertices; you'll find it being
    '   used to represent vertices/triangles/meshs as we go along
    
On Error GoTo BailOut: '//Setup our Error handler
'##################
'## TRIANGLE STRIP 
'#################

            'We're going to create a square (easily adapted to be a rectangle)
            'by defining 4 vertices in a particular order - ClockWise.
            'If you define them in the wrong order they either wont appear at all
            'or wont appear as you intended....
            
            '0 - - - - - 1
            '             /
            '           /
            '         /
            '       /
            '     /
            '   /
            ' /
            '2 - - - - - 3
            
            '//Ignore the ' parts down the side... Only the Z shape should be there...
            
            '//As you can see this is basically two triangles.
            
            'vertex 0
            TriStrip(0) = CreateTLVertex(10, 10, 0, 1, RGB(255, 255, 255), 0, 0, 0)
            
            'vertex 1
            TriStrip(1) = CreateTLVertex(210, 10, 0, 1, RGB(255, 0, 0), 0, 0, 0)
            
            'vertex 2
            TriStrip(2) = CreateTLVertex(10, 210, 0, 1, RGB(0, 255, 0), 0, 0, 0)
            
            'vertex 3
            TriStrip(3) = CreateTLVertex(210, 210, 0, 1, RGB(0, 0, 255), 0, 0, 0)


'#################
'## TRIANGLE LIST
'################

'We're going to make an arrow type shape from two triangles; whilst this is still perfectly possible
'using a triangle strip - it's a good enough example of using triangle lists....


    '// TRIANGLE 1
    
    'vertex 0
    TriList(0) = CreateTLVertex(210, 210, 0, 1, RGB(0, 0, 0), 0, 0, 0)
    
    'vertex 1
    TriList(1) = CreateTLVertex(400, 250, 0, 1, RGB(255, 255, 255), 0, 0, 0)
    
    'vertex 2
    TriList(2) = CreateTLVertex(250, 250, 0, 1, RGB(255, 255, 0), 0, 0, 0)
    
    '//TRIANGLE 2
    
    'vertex 3
    TriList(3) = CreateTLVertex(210, 210, 0, 1, RGB(0, 255, 255), 0, 0, 0)
    
    'vertex 4
    TriList(4) = CreateTLVertex(250, 250, 0, 1, RGB(255, 0, 255), 0, 0, 0)
    
    'vertex 5
    TriList(5) = CreateTLVertex(250, 400, 0, 1, RGB(0, 255, 0), 0, 0, 0)
    
    
'#################
'## TRIANGLE FAN
'################

'We're now going to set up a triangle fan; these are best used for circular
'type shapes. We'll just generate a hexagonal type shape..

    '//MAIN POINT
    TriFan(0) = CreateTLVertex(300, 100, 0, 1, RGB(255, 0, 0), 0, 0, 0)
    
    '//Subsequent vertices around the shape
    TriFan(1) = CreateTLVertex(350, 50, 0, 1, RGB(0, 255, 0), 0, 0, 0)
    TriFan(2) = CreateTLVertex(450, 50, 0, 1, RGB(0, 0, 255), 0, 0, 0)
    TriFan(3) = CreateTLVertex(500, 100, 0, 1, RGB(255, 0, 255), 0, 0, 0)
    TriFan(4) = CreateTLVertex(450, 150, 0, 1, RGB(255, 255, 0), 0, 0, 0)
    TriFan(5) = CreateTLVertex(350, 150, 0, 1, RGB(255, 255, 255), 0, 0, 0)

InitialiseGeometry = True
Exit Function
BailOut:
InitialiseGeometry = False
End Function




'//This is just a simple wrapper function that makes filling the structures much much easier...
Private Function CreateTLVertex(X As Single, Y As Single, Z As Single, rhw As Single, color As Long, _
                                               specular As Long, tu As Single, tv As Single) As TLVERTEX
    '//NB: whilst you can pass floating point values for the coordinates (single)
    '       there is little point - Direct3D will just approximate the coordinate by rounding
    '       which may well produce unwanted results....
CreateTLVertex.X = X
CreateTLVertex.Y = Y
CreateTLVertex.Z = Z
CreateTLVertex.rhw = rhw
CreateTLVertex.color = color
CreateTLVertex.specular = specular
CreateTLVertex.tu = tu
CreateTLVertex.tv = tv
End Function

 

One of your first observations (I hope) is that there are quite a lot of lines in there to do something fairly simple. Imagine trying to write code to generate a complex shape... This is one of the main reasons why games use pre-generated models and load them into the program at runtime; more on object loading in a later tutorial.

All the above code does is fill out a relevent data structure for each entry in the array - I'll now go over what each part of the structure means:

X : This is the screens X coordinate
Y : This is the screens Y coordinate
Z : This is a value in a 0.0 to 1.0 scale; where 0 is the front and 1 is the back. When we have a depth buffer attached we can make certain object appear underneath/behind others. If they all have the same value (as the ones above do) it's based on the rendering order - whichever is rendered last will be visible...
RHW : This can be ignored almost always; you'll very rarely want to use this (In almost 2 years I've never used it). Leave it set to 1, otherwise shading wont work properly
COLOR : This is a long value for the vertex - you can often get away with using the RGB(R,G,B) function to specify this, but where possible use hexidecimal values.
SPECULAR : This is the reflectivity of the vertex; only used when we enable specular highlighting, and isn't really used when you have transformed & Lit vertices.
TU : This is the textures X-Coordinate; More on this in the next lesson
TV : This is the textures Y-Coordinate; again, more on this in the next lesson.

Now we've done the geometry initialisation we can go about altering the rendering code.


5. Rendering the triangles

The final thing this lesson will cover is rendering the triangles. In lesson 01 you should have seen the render procedure, but at that time it didn't actually do anything. Now we'll make it do something:

Public Sub Render()
'//1. We need to clear the render device before we can draw anything
'       This must always happen before you start rendering stuff...
D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, 0, 1#, 0 '//Clear the screen black

'//2. Rendering the graphics...

D3DDevice.BeginScene
    'All rendering calls go between these two lines
    
    'Comment out some of the lines below should the screen seem
    'to clustered
    
    'First the Triangle Strip
    D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLESTRIP, 2, TriStrip(0), Len(TriStrip(0))
    
    'Next the triangle list
    D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLELIST, 2, TriList(0), Len(TriList(0))
    
    'Finally, the triangle fan
    D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLEFAN, 4, TriFan(0), Len(TriFan(0))
    
D3DDevice.EndScene

'//3. Update the frame to the screen...
'       This is the same as the Primary.Flip method as used in DirectX 7
'       These values below should work for almost all cases...
D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0
End Sub

 

You'll notice that the only real difference is the inclusion of three new lines between the "D3DDevice.BeginScene" and "D3DDevice.EndScene". These are standard calls for rendering triangles - at least as far as you need to know at this point; later on we'll come across vertex buffers.

object.DrawPrimitiveUP( _ 
    PrimitiveType As CONST_D3DPRIMITIVETYPE, _ 
    PrimitiveCount As Long, _ 
    VertexStreamZeroDataArray As Any, _ 
    VertexStreamZeroStride As Long)


Above is the parameter list for the call we've just used to render our triangles. The "UP" suffix on the end of the function name is short for "User Memory Pointer"; which basically means that you can, theoretically, send almost anything to it. The reason for it being a User Memory Pointer is that we're using a custom FVF and a custom vertex type.

Object - A Direct3DDevice8 object
PrimativeType - Just so that DirectX knows what we're sending it's way
PrimativeCount - The number of triangles there are; you'll notice that everything but the triangle fan is made up of 2 triangles
VertexStreamZeroDataArray - Long name, but simple really. This is where the first element to render is - you could make it start half way through your array should you want to, just pass the variable name with the index of the first entry.
VertexStreamZeroStride - How far (in bytes) between the beginning of each datatype; because Direct3D doesn't actually know what the structure of the datatype is, it has to use the size and the FVF to work out where the information it needs is stored. Always pass the length of one element to this one - unless something weird is happening all the elements should be the same size; so it doesn't matter which entries size you pass..

Okay; so you should now be able to render some simple geometry; at the moment it's only 2D; but you should start to see how this will work out when we move into 3 dimensions. You should also be able to see how we'll be able to use this for rendering 2D graphics - basic texturing will be covered in the next tutorial...

As a simple excercise try writing the code for the star shape in the very first diagram in section 4 (Initialising the Geometry).


You can download the sample code for this tutorial from the top of the page; seeing the code running is the best way of learning from it.

If all this information's settled, onto the next tutorial : Lesson 04 : 2D Graphics - Advanced

DirectX 4 VB © 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster