3D-programming with Visual Basic and DirectX 8

Welcome to this tutorial on 3D programming with VB. I hope you are already familiar with Visual Basic and its Synthaxes. Anyways, this tutorial will only cover a part of the DirectX libary, the 3D part also known as Direct3D. Instead of Direct3D you could also use OpenGL, but i sugest you use DirectX. :) Well, DirectX wasn't always accessable by Visual Basic, only since version 7.0 was released, Visual Basic programmers could use the power of DirectX. It has many useful things, like DirectPlay, for Multiplayer or DirectSound, for Sounds.
Before you can use DirectX in VB, you have to reference it, by selecting "Project" from the menu, then click on "Reference" and in the list, scroll down to "DirectX 8 for Visual Basic Type Libary", and select it.
Now Let's start, by creating the main Object for DirectX, the DirectX8 object:
    Dim DX as DirectX8
    
From this object we can control alot of things in the DirectX world. But since we are using Direct3D, we have to declare this too, and while we are at it, we will declare another essential part of Direct3D, the Direct3D-Device, which stands for the hardware, that renders all the 3D stuff.:
    Dim D3D as Direct3D8
    Dim D3DDevice as Direct3DDevice8
    
we have now declared our Main Objects, now we will have to "create" them. :)
    Public Function Init(hWnd As Long) As Boolean     'This function will return false if it failed
    	On Error GoTo ErrHandle			      'Just to be on the safe side.

	Set DX = New DirectX8                         'We could have done this earlier by declaring
                                                      'Dim DX as New DirectX8, but this would be 
                                                      'a bit slower.
        Set D3D = DX.Direct3DCreate()                 'Here we create our Direct3D Object
        If D3D is Nothing Then GoTo ErrHandle         'if D3D = Nothing that means it wasn't created
        
        Dim Mode As D3DDISPLAYMODE                    'This variable is only temporarly to find our
                                                      'display mode
        D3D.GetAdapterDisplayMode D3DADAPTER_DEFAULT, Mode
                                                      'This fills our Mode Variable with the Display
                                                      'mode
	Dim D3Dpp as D3DPRESENT_PARAMETERS            'Another temporary Variable to store our
                                                      'Present Parameters (like how everything will
                                                      'be displayed)
        With D3Dpp
            .Windowed = 1			      '1 = True, for now we will display everything
                                                      'in a window ;)
            .SwapEffect = D3DSWAPEFFECT_COPY_VSYNC    'This is how the BackBuffer will be 'Swapped' 
                                                      'With the Screen.
            .BackBufferForemat = Mode.Format          'This is the Format of the Backbuffer (the color
                                                      'format) we could have typed it in manually, but
                                                      'this way we will be safer ;)
        End With

        Set D3DDevice = D3D.CreateDevice(D3DADAPTER_DEFAULT, _ 'Finally we create our D3DDevice.
                               D3DDEVTYPE_HAL, hWnd, _         'HAL = Hardware, REF = Software rendering
                               D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3Dpp)                
						      'We give it the Present Parameters, and how it
                                                      'will Process the Vertices, this can be done
                                                      'with Hardware or Software.         
        If D3DDevice = Nothing then GoTo ErrHandle

        Init = True
        Exit Function
    
    ErrHandle:
        Init = False

    End Function    
    
Now we have Created all Necessary Objects, it is time, i show you how to use them. Or at least how one would render a scene:
    Public Function Render()
        D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, &H0&, 1#, 0  '&H0& is the color the Screen will
                                                      'have while it is empty.
        D3DDevice.BeginScene
                                                      'Between these 2 Functions come the Rendering
                                                      'calls and stuff.
        D3DDevice.EndScene
        
        D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0 'This will 'Swap' the Backbuffer with the 
                                                      'Screen
    End Function
    
If you would now Initialise all Objects and Render the Scene, you would end up with a Black Screen. let's try it out ;)
    Private Sub Form_Load()
        Me.Show                                       'If we don't do this, nothing will be shown
                                                      'on the form, untill all commands in the Form_Load()
                                                      'sub have been executed.
        DoEvents                                      'This gives the Computer to do whatever he needs to
                                                      'do
        If Init(Me.hWnd) = False then                 'We could also pass it the handle to a PictureBox
            MsgBox "Error initialising the Objects"   'This is only called when the Init() function didn't
                                                      'work.
            End
        End if

        Render                                        'And of course, the Render call.
    End Sub
    
Now we have got us a blank Screen. Wow. well we don't want this, we want shapes. And that is what we will do next. Direct3Ds most simplest "shapes" or what we will call them now, Polygons, are Triangles. Each Triangle needs 3 Points to represent where it is in space. Direct3D allows us a simple way to create triangles and to store them. If we have a Array of Vertices, we can pass them to something called a Vertex-Buffer, which can then easily Render them. The Vertex Buffer needs some Information tough, about how the Triangles are arranged, with this i mean, if we just pass it triangles, or we want to create a triangle "fan" which uses less Vertices, which means it uses less Memory, but at least 2 Triangles have to share 1 Vertex. There are also Triangle Strips, which work similarly. Anyways, we have to declare our Vertexbuffer as well, like we did with our other Objects:
    Dim VB as Direct3DVertexBuffer8
    
And like the other Objects, we have to Set this one as well. But before we can set it, we need Vertices. (At least 3 :)). DirectX doesn't have a predefined Vertex, so the user has alot of flexebility in choosing what it's Vertices can do. For new we will just use a Vertex with a Coordinate and a color. Before we can use them, we have to Create them ;) :
    Public Type CVERTEX
        position as D3DVERTEX
        color as Long
    End Type
    
    Public Const D3DFVF_CVERTEX = (D3DFVF_XYZ Or D3DFVF_DIFFUSE)
    
You can call the Vertex what you want. The constant is important to show what the Vertex can do. Let's say we have an Array of Vertices already, stored as Vertices(1 to 3) and now we want to fill them into the Vertexbuffer, here is how we do it:
    Public Function ReloadVB()
        Dim VSize as Long                             'This is the size of one Vertex in Bytes.
        
        VSize = Len(Vertices(1))                      'Here we pass the size of one Vertice to our Variable.
        
        Set VB = D3DDevice.CreateVertexBuffer(VSize * 3, 0, D3DFVF_CVERTEX, _
                      D3DPOOL_DEFAULT)                'This will Create a VertexBuffer with enough memory
                                                      'for our 3 Vertices. The First Param tells it how 
                                                      'much memory we need. :)
        D3DVertexBuffer8SetData VB, 0, VSize * 3, 0, Vertices(1) 'And now we Set our Data. The First Param
                                                      'Is were we want our Vertices to go, the 3 tells
                                                      'it the size again, and the Last Param tells it where
                                                      'to start.
    End Function
    
Now all we need to know is how to Render the Triangles, and we would be able to write a fully functioning game. :)
Now back to our Rendering function. Between BeginScene and EndScene, we will have to add Following code, to normally render our Triangles stored in the VertexBuffer:
    Dim V as CVERTEX
    Dim VSize as Long
    
    VSize = Len(V)
    
    D3DDevice.SetStreamSource 0, VB, VSize             'This sets the VB from where the next Vertices
                                                       'will be coming.
    D3DDevice.SetVertexShader D3DFVF_CVERTEX           'We need to let Direct3D know what Vertexshader
                                                       'to use.
    D3DDevice.DrawPrimitive D3DPT_TRIANGLELIST, 0, 1   'This draws a Triangle list, starting with Vertex 0
                                                       'and drawing 1 Primitiv.
    
Well, that is all one needs to know about making a game with non-textured polygons :) For textured Polygons, we have to change our Vertex declaration a bit, and our FVF.
    Public Type CVERTEX
        position as D3DVERTEX
        color as Long
        tu as Single
        tv as Single
    End Type

    Public Const D3DFVF_CVERTEX = (D3DFVF_XYZ Or D3DFVF_DIFFUSE Or D3DFVF_TEXTURE1)
    
The Tu and Tv describe the Textures position on the Triangle. Tu and Tv must be a number between 0 and 1.

First thing we do now is we Declare a Object to hold our texture.
    Public Texture As Direct3DTexture8
    
Now, like before, we will have to create it from a File, a image file to be exact ;).
    Set Texture = D3DX.CreateTextureFromFile(D3DDevice, App.Path + "\texture.bmp")
    
That wasn't all to hard, was it? D3DX is only an Object that has many useful functions but we haven't declared it yet.
    Public D3DX As D3DX8
    Set D3DX = New D3DX8
    
and now we did. This Object has alot of useful functions, like texture loading and so on. Now, to use our Texture, we only have to add a command to our Rendering Function:
 
    D3DDevice.SetStreamSource 0, VB, VSize
    D3DDevice.SetVertexShader D3DFVF_CVERTEX
    D3DDevice.SetTexture 0, Texture                     'This will set the texture the D3DDevice will
                                                        'use to render all Triangles, until you select a new
                                                        'texture.
    D3DDevice.DrawPrimitive D3DPT_TRIANGLELIST, 0, 1
    
I suggest that at the end in your Init Function you add following line:
    D3DDevice.SetRenderState D3DRS_ZENABLE, 1
    
Without this, your game will not be fully 3D. There are many great things you can do in DirectX. We haven't coverd one important thing. Matrices. As you most likely have noticed that if you start the program at it's current state, nothing will be displayed. There are 2 very important Matrices. One is the View Matrices, which describes the Position of the Viewer and what he is looking at. The second one is the Projection Matrix, which describes, how all objects are rendered. Here is a simple function which creates these 2 Matrices:
    Public Function SetupMatrices()
        Dim EyePosition as D3DVECTOR, EyeLookAt as D3DVECTOR, UP as D3DVECTOR
        Dim matView As D3DMATRIX

	EyePosition.Z = -10                              'Our Viewer is Standing 10 "steps behind" the 0-Offset
                                                         'We don't change EyeLookAt, because we want to look at
                                                         'point 0,0,0.
        UP.Y = 1                                         'This describes what way is up.     

        D3DXMatrixLookAtLH matView, EyePosition, EyeLookAt, UP 'This creates the View Matrix for us.
    
        D3DDevice.SetTransform D3DTS_VIEW, matView       'And here we set it.

        Dim matProj As D3DMATRIX                         'now our Projection Matrix
    
        D3DXMatrixPerspectiveFovLH matProj, 3.14159275180032 / 3, 1, 1, 1000  'If you change the pi/3 part,
                                                         'you can create special effects, like zooming.
                                                         'the last parameter describes how far we can look.
        D3DDevice.SetTransform D3DTS_PROJECTION, matProj 'And, of course, we set the Matrix.
    End Function 
   
This are all the important things you need to know about programming with DirectX. To rotate the camera, you just have to change the EyeLookAt Vertex, and reload the Matrices. You do not have to reload the Matrices every round, only when you want to change them ;).