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

Immediate Mode: Basics
By: Carl Warwick [Email] [Web: Freeride Designs]
Written: June 16 2000

Download: IM_Basics.Zip (7kb)


Contents


Introduction

This is my first tutorial of many on using Direct 3D Immediate Mode (DirectX 7) using Visual Basic. If you are new to DirectX programming then I would suggest that you check out some DDraw tutorials before venturing into the world of 3D.

In this tutorial we are first going to initialise DDraw and D3DIM in fullscreen, exclusive mode, if you are used to using DDraw then you'll have a bit of a headstart but I'll try and explain what everything does. The reason we need DDraw is because D3DIM renders everything to a DDraw surface, this surface will usually be the backbuffer. Then we enter the main loop, this is where our main game code would go, but for this tutorial all we'll do is clear the screen to black and draw a rotating square with a different color at each corner.

screenshot.gif (1274 bytes)


Before the code

Before we start programming I'm going to try and explain some of the basics of D3DIM.

Vertices - Vertices (plural of Vertex) are the corners or points that you define to make up your object, a flat square has 4 vertices, and a cube has 8 vertices etc. In D3D there are 3 different types of vertex, you can also create your own vertex type, but with the 3 you've got there isn't really much point. Here's an explanation of the 3 vertex types:

  • D3DTLVertex - TL stands for Transformed and lit, which means YOU have to transform the co-ordinates to screen space, and YOU are responsible for lighting the vertex. You can also specify the tu and tv values which are the texture co-ordinates, but we'll talk about these in another tutorial.
  • D3DLVertex - L stands for lit, this means that you specify the co-ordinates of the vertex in worldspace and D3D will convert it to screen space for you, but you still have to light the vertex yourself.
  • D3DVertex - This vertex type also requires you to specify the co-ordinates in worldspace, but you also have to specify the vertex normal so that D3D can light the vertex for you. I'll cover what a normal is in another tutorial, so don't worry about it for now.

In this tutorial we will cover the D3DTLVertex.

Space - I've mentioned screen and world space so if you don't know the difference then I'll describe them here.
Screen space is the co-ordinates that you see on the screen, (0, 0) is the top-left corner, and (screen width, screen height) is at the bottom-right corner.
World space is defined by 3 co-ordinates (x, y, z), there are many ways of arranging these, but in D3D the left-hand co-ordinate system is used. Its probably easier to imagine it from a picture, so here it is.

worldspace.gif (3991 bytes)

That's probably enough for now, I'll explain any more concepts when we get to them.


Declarations

Finally, we start coding.
Our project will have 1 form "frmMain", and 1 module "modMain".

All our declarations will go in "modMain".

Option Explicit

Public DX As DirectX7                          'Direct X
Public DD As DirectDraw7                     'Direct Draw
Public Primary As DirectDrawSurface7     'Primary surface
Public BackBuffer As DirectDrawSurface7 'Backbuffer
Public Direct3D As Direct3D7                 'Direct 3D
Public Device As Direct3DDevice7       'Direct 3D Device

Public TLVertexList(3) As D3DTLVERTEX  'Array of vertices (TL = Transformed and Lit)

Public bRunning As Boolean               'Boolean to check if program is still running

Public Const pi = 3.14                   'Constant to hold the value of pi
Public Const Rad = pi / 180              'multiply degrees by Rad to get angle in radians

These are the basic declarations that you will use time and time again in your D3D projects, the primary and backbuffer are of the DirectDrawSurface7 type, and will be used for rendering to and displaying the scene.
We then declare TLVertexList(3), this is an array used to store the data of the four corners of our square.
bRunning is a simple boolean that is true whilst the program is running and then set to false if a key is pressed so that we can exit the program.


Initialisation

The first part of the code that is called is "Sub Main" which is inside "modMain", so its from here that we will call our initialisation and main_loop routines. We pass the values "640", "480" and "16" to our initialisation routine, these are just values that specify the screen width, height and color depth, and can be changed to suit your needs.

Private Sub Main()
    frmMain.Show
    
    'Initialize DirectX with screen 640x480x16
    Call Initialize_DX(640, 480, 16)
    
    'Start the main loop
    bRunning = True
    Call Main_Loop
    
    'End the program and return to your normal life
    Call Endit
End Sub

The initialisation routine is a bit more complicated.

Public Sub Initialize_DX(Optional Width As Integer = 640, Optional Height As Integer = 480, Optional BPP As Byte = 16)
    Dim ddsd As DDSURFACEDESC2
    Dim caps As DDSCAPS2
    Dim DEnum As Direct3DEnumDevices
    Dim Guid As String
    
    'Create the DirectDraw object and set the application
    'cooperative level.

    Set DX = New DirectX7
    Set DD = DX.DirectDrawCreate("")
    
    'Make a fullscreen, exclusive application
    DD.SetCooperativeLevel frmMain.hWnd, DDSCL_EXCLUSIVE Or DDSCL_FULLSCREEN Or DDSCL_ALLOWREBOOT
    DD.SetDisplayMode Width, Height, BPP, 0, DDSDM_DEFAULT
    
    'Prepare and create the primary surface.
    ddsd.lFlags = DDSD_BACKBUFFERCOUNT Or DDSD_CAPS
    ddsd.ddsCaps.lCaps = DDSCAPS_COMPLEX Or DDSCAPS_FLIP Or DDSCAPS_3DDEVICE Or DDSCAPS_PRIMARYSURFACE
    ddsd.lBackBufferCount = 1
    
    Set Primary = DD.CreateSurface(ddsd)
    
    'Attach the backbuffer. the DDSCAPS_3DDEVICE tells the
    'backbuffer that its to be used for 3D stuff

    caps.lCaps = DDSCAPS_BACKBUFFER Or DDSCAPS_3DDEVICE
    Set BackBuffer = Primary.GetAttachedSurface(caps)

    
    Set Direct3D = DD.GetDirect3D
    
    'Get the last device driver (last one is usually the best one)
    'you could specify or search for a particular device, eg RGB or HAL device

    Set DEnum = Direct3D.GetDevicesEnum
    Guid = DEnum.GetGuid(DEnum.GetCount)
    
    'Set the Device
    Set Device = Direct3D.CreateDevice(Guid, BackBuffer)
    
End Sub

Firstly we create a new DirectX7 object, and then create our DDraw object.
Then we create a fullscreen and exclusive application, and then set the display mode to the passed variables.
Next we create our primary surface, and tell it that it will have 1 backbuffer that is to be flipped, we also tell it that its to be used for 3D operations (DDSCAPS_3DDEVICE). Then we attach a backbuffer to the primary surface.
Now we create our D3DIM object. Then we need to find a device to use, so we fill DEnum with all the different devices information, then we set Guid (a string) to hold the Guid of the last device that was found, the reason for using the last device is because in most cases its the best one. Finally we create our device using the Guid that we just found.


Main Loop

Now lets take a look at our Main_Loop.

Public Sub Main_Loop()
    Dim stepval As Integer
    
    'Start our loop, press the 'Esc' key to exit
    Do Until bRunning = False
    
        
        'decrease stepval by 1 to rotate the rectangle clockwise
        'If stepval is less than 0 degrees then make stepval = 360 degrees

        stepval = stepval - 1
        If stepval <= 0 Then stepval = 360
        
        
        'Create the four vertices (corners) of the square
        Call DX.CreateD3DTLVertex(320 + Sin(stepval * Rad) * 50, 240 + Cos(stepval * Rad) * 50, 0, _
                                             1, DX.CreateColorRGB(1, 0, 0), 1, 0, 0, TLVertexList(0))
        Call DX.CreateD3DTLVertex(320 + Sin((stepval - 90) * Rad) * 50, 240 + Cos((stepval - 90) * Rad) * 50, 0, _
                                             1, DX.CreateColorRGB(0, 1, 0), 1, 0, 0, TLVertexList(1))
        Call DX.CreateD3DTLVertex(320 + Sin((stepval + 90) * Rad) * 50, 240 + Cos((stepval + 90) * Rad) * 50, 0, _
                                             1, DX.CreateColorRGB(0, 0, 1), 1, 0, 0, TLVertexList(2))
        Call DX.CreateD3DTLVertex(320 + Sin((stepval + 180) * Rad) * 50, 240 + Cos((stepval + 180) * Rad) * 50, 0, _
                                             1, DX.CreateColorRGB(1, 1, 0), 1, 0, 0, TLVertexList(3))
        
    
        'Clear the device AFTER all the changes to any vertices, and BEFORE
        'calling Device.BeginScene and any bltting.

        Call Clear_Device
        
        
        'Begin the scene, must do this after Calling Clear_Device, and before
        'calling Blt3D()

        Device.BeginScene
            
            
            'Render the rectangle to the device (backbuffer)
            Call Device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_TLVERTEX, TLVertexList(0), 4, D3DDP_WAIT)
            
            
        'Call EndScene when finished using D3D routines
        Device.EndScene
                    
        
        'Flip the primary surface to display the scene
        Primary.Flip Nothing, DDFLIP_WAIT
        
        'Let windows do its stuff
        DoEvents
    Loop
End Sub

Its all pretty straight forward until we get to this line:
Call DX.CreateD3DTLVertex(sx, sy, sz, rhw, color, specular, tu, tv, v)
So we'll examine it in a bit more detail here. I'll explain all the passed values:

  • sx, sy, sz - The first 3 variables are the positions on screen that the D3DTLVertex is to be displayed, sx and sy are simply the screen space positions, but sz is the depth of the vertex, this would usually be set to 0, but if a Z-buffer is attached then sz can be used to specify what order everything should be displayed in, and should always be in the range of 0 to 1. For now just set it to 0
  • rhw - As far as I can tell this is useless, and should just be set to 1.
  • color, specular - The color of our vertex, used to simulate lighting on the vertex. The color is interpolated (blended) between vertices.
  • tu, tv - The texture co-ordinates, these will be explained in another tutorial.
  • v - is the vertex that we are changing.

Unless you are familiar with trigonometry then you are probably confused by how I position the 4 vertices, so lets examine this a bit more. We set the sx value of the second vertex at 320 + Sin((stepval - 90) * Rad) * 50, the number 320 is just the horizontal position for the centre of our square.
Now comes the bit of trigonometry, we use Sin to get horizontal positions, and Cos to get the vertical positions (offset from center).
stepval is the angle in degrees that the square is rotated by, and as we all know a complete circle has 360 degrees, so to get the correct position for each vertex we need to alter the rotated angle by a set number, in this case its -90 because we want this vertex to be 90 degrees further clockwise than our first vertex.
Then you multiply the angle by our constant 'Rad', this is because VB uses radians to specify angles rather than degrees.
Finally we have to multiply our value returned by the Sin or Cos function by the value 50, this is the distance from the center point that our vertex will be, so increase this number to make a bigger square or decrease it to make it smaller, but all vertices need to be multiplied by the same number otherwise it will look very strange.

Of course if we didn't want to rotate the square then we could just specify the exact screen coordinates that we want to position the square at. But I included the rotation example because its something that I get asked about alot.

Now we must clear the device, this is done before any rendering can be done, this is a very simple routine, and you should look at the downloadable source to see how it works.
Next we call DrawPrimative to render our square to the screen:
Call Device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_TLVERTEX, TLVertexList(0), 4, D3DDP_WAIT)
Your probably thinking "What on earth does all this mean", well I'll explain

  • D3DPT_TRIANGLESTRIP - This tells the device that our vertices are in a specific order, in this case a triangle strip. (Please refer to "Primitive Types" below)
  • D3DFVF_TLVERTEX - This tells the device what format our vertices are in, in this case we are using the TLVertex, but a combination of flags can be used to define you own vertex type. FVF stands for Flexible vertex format.
  • TLVertexList(0) - The first element in an array of vertices that are to be rendered.
  • 4 - The number of vertices in the array to be rendered.
  • D3DDP_WAIT - The type of rendering to be used, your probably better off using D3DDP_DEFAULT, because WAIT is mainly used for debugging, but you won't noticed much difference.

Primitive Types

You can't just pass an array of vertices to D3D and expect it to know how they are arranged, to solve this you have to pass a constant in the form of D3DPT_*** along with your vertices, this constant describes the way your vertices are arranged. Here's a description of each type:

  • D3DPT_POINTLIST - Renders the vertices as a list of isolated points
  • D3DPT_LINELIST - Renders the vertices as a list of isolated lines, there must be more than 2, and an even number of vertices for this call.
  • D3DPT_LINESTRIP - Renders vertices as a continuous line.
  • D3DPT_TRIANGLELIST - Renders the vertices as isolated triangles, each group of 3 vertices represents a different triangle, there must be a multiple of 3 vertices in the array for this call.
  • D3DPT_TRIANGLESTRIP - Renders a series of connected triangles (As in the diagram).
    trianglestrip.gif (2414 bytes)
  • D3DPT_TRIANGLEFAN - Renders a series of triangles all connected to one vertex (As in the diagram).
    trianglefan.gif (1953 bytes)

You should choose the most appropriate method for the object you are rendering, but you should note that Trianglefan and Trianglestrip are both faster than Trianglelist.

Another thing you should remember is that vertices in a triangle need to be specified in a clockwise order, this is due to backface culling (removal). So if you take our square we have made in this tutorial, you can view it from the front fine, but if you was to rotate it around to look at the back it would just disappear, so if you want the back to be visible as well you should create an extra set of vertices for the back. This is very important to remember so that you don't accidentally create your vertices in an anticlockwise order and therefore not be able to see anything.


Conclusions

You should now have a good foundation to build on, its always hard to learn something new, especially if you've not got a good guide to the basics as I hope this tutorial is. Some aspects of 3D can be very difficult, and without this basic knowledge you would find it very hard going to learn the more complicated aspects, trust me, I struggled for ages trying to learn some of the complicated things and I eventually got there, but it would have been much easier if I already knew the basics.

Once your through this tutorial I suggest you try and experiment, try making a triangle, a hexagon, two squares rotating in opposite directions etc. etc. All these ideas are possible from just this tutorial.

Until next time, good luck.

Carl Warwick - Freeride Designs

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