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: Z-Buffer
By: Carl Warwick [Email] [Web: Freeride Designs]
Written: July 3 2000
Download: IM_ZBuffer.Zip (7kb)


Contents


Introduction

Welcome to the third installment of my D3DIM tutorials. Once again this is a fairly simple tutorial, where I'll show you how to create a Z-buffer, and how to scale, rotate and move an object.

Don't worry if you don't know what a Z-buffer is, its pretty simple to understand. Basically a Z-buffer holds the depth of all your objects and ensures that they are drawn to the screen in the correct order, you don't want a tree thats miles in the distance being drawn in front of your main character who's stood just a few feet away now do you. Z-buffers are very easy to use, the hardest part is initialising them, and after that you can pretty much forget its there, the only thing you need to know is that our Clear_Device routine has changed slightly so that it clears the z-buffer aswell.

screenshot


Declarations

You guessed it, our project will have 1 form "frmMain", and 1 module "modMain", and all our declarations will go in "modMain".

Public ZBuffer As DirectDrawSurface7     'Z-buffer

Public LVertexList(23) As D3DLVERTEX    'Array of vertices (L = Lit)
Public CubeScale As D3DVECTOR            'The cubes scale (size)
Public CubePosition As D3DVECTOR         'The cubes position

Public KeyUp As Boolean                  'Used to check for key presses
Public KeyRight As Boolean               'True if key is pressed, false if its not
Public KeyDown As Boolean
Public KeyLeft As Boolean
Public KeyAdd As Boolean
Public KeySubtract As Boolean

Its all nice and simple, we have our Z-Buffer, which is just a DirectDrawSurface7 type, but obviously will will need to set different flags and caps for it than we would do for a texture or image.

Then we have an array of the D3DLVertex type, this stores all our vertices for our cube. Why do we have 24 vertices when there are only 8 corners in a cube? Well thats because we need to render 6 sides of the cube, and each side has 4 vertices, so 6*4=24. This is a waste of memory and can be solved by using DrawIndexedPrimitive which I will cover in the next tutorial, but for now we'll stick with using the 24 vertices.

Next we have 2 variables of the D3DVECTOR type, these are self explanatory, but basically CubeScale holds the X,Y and Z values to scale the cube by. Set the values to 1 for normal size, less than 1 to make it smaller, and more than 1 to make it bigger. CubePosition holds the co-ordinates of the cubes position in world space.

I have set up six booleans that are set to true if the key is pressed, and false if its not pressed. I'll use VBs Key_Down and Key_Up events to check for the key presses, then call a separate Get_Keys routine from the main loop to check the state of the keys, and then to move or scale the cube as desired. The reason for calling a separate Get_Keys routine is to synchronise the input with our main loop, if we we're to just change the values in VBs Key_Pressed event then it could change the values at any time during the loop, and obviously this is not desireable. The reason I'm not using DInput is for simplicity, in a full game I would be using DInput, but thats for another tutorial.


Initialisation

We need to initialise the Z-buffer during the Init_D3D sub. Here's our local variables used to set up the Z-buffer.

    'variables used for creating our Z-buffer
    Dim i As Integer
    Dim ZEnum As Direct3DEnumPixelFormats
    Dim pxf As DDPIXELFORMAT
    Dim ddsd2 As DDSURFACEDESC2

And here is the code to initialise the Z-buffer, we'll make sure we get a Z-buffer with a depth of at least 16-bits. This code goes between creating the backbuffer and creating the Direct3D device.

    '=================================================
    'This is where we will create our Z-Buffer
    'Create the z-buffer after creating the backbuffer
    'and before creating the Direct3D device.
    '=================================================

    Set ZEnum = Direct3D.GetEnumZBufferFormats(Guid)
    
     'Loop through until we find a Z-buffer that has a depth of at least 16-bits
    'you don't have to check the bitdepth, but for quality we'll go for 16-bit minimum

    For i = 1 To ZEnum.GetCount()
        Call ZEnum.GetItem(i, pxf)
        If pxf.lFlags = DDPF_ZBUFFER And pxf.lZBufferBitDepth >= 16 Then
            Exit For
        End If
    Next i
    
'Prepare and create the z-buffer surface.
    ddsd2.lFlags = DDSD_CAPS Or DDSD_WIDTH Or DDSD_HEIGHT Or DDSD_PIXELFORMAT
    ddsd2.ddsCaps.lCaps = DDSCAPS_ZBUFFER
    ddsd2.lWidth = ScreenWidth
    ddsd2.lHeight = ScreenHeight
    ddsd2.ddpfPixelFormat = pxf
    
     'For hardware devices, the z-buffer should be in video memory. For software
    'devices, create the z-buffer in system memory.

    If Guid = "IID_IDirect3DRGBDevice" Then
        ddsd2.ddsCaps.lCaps = ddsd2.ddsCaps.lCaps Or DDSCAPS_SYSTEMMEMORY
    Else
        ddsd2.ddsCaps.lCaps = ddsd2.ddsCaps.lCaps Or DDSCAPS_VIDEOMEMORY
    End If
       
'Create the Z-buffer surface
    Set ZBuffer = DD.CreateSurface(ddsd2)
    
'Attach the Z-buffer to the backbuffer
    BackBuffer.AddAttachedSurface ZBuffer

We loop through all the Z-buffers, filling up a DDPIXELFORMAT type with the Z-buffers data until we get to one that meets our requirements, then the DDPIXELFORMAT type is used to create the Z-buffer.

If we are using a software device then the Z-buffer needs to be in System memory, otherwise it should be in video memory.

Then we need to create the Z-buffer and attach it to the backbuffer (our render target).

    'Enable our Z-buffer
    Device.SetRenderState D3DRENDERSTATE_ZENABLE, D3DZB_TRUE

And then after creating our Direct3D Device we need to enable the Z-buffer (this bit of code is at the very end of our Init_D3D sub).


Making our cube

Next we need to make our cube, in a real application we would load our objects from files rather than making them all by hand, in a future tutorial I will show you how to load D3Ds native format .x files. For now we will make our cube by hand to save any confusing file handling routines.

Public Sub Initialise_Geometry()
    'Front
    Call DX.CreateD3DLVertex(-10, -10, -10, DX.CreateColorRGB(1, 0, 0), 1, 0, 0, LVertexList(0))
    Call DX.CreateD3DLVertex(-10, 10, -10, DX.CreateColorRGB(1, 0, 0), 1, 0, 0, LVertexList(1))
    Call DX.CreateD3DLVertex(10, -10, -10, DX.CreateColorRGB(1, 0, 0), 1, 0, 0, LVertexList(2))
    Call DX.CreateD3DLVertex(10, 10, -10, DX.CreateColorRGB(1, 0, 0), 1, 0, 0, LVertexList(3))
    'Right
    Call DX.CreateD3DLVertex(10, -10, -10, DX.CreateColorRGB(0, 1, 0), 1, 0, 0, LVertexList(4))
    Call DX.CreateD3DLVertex(10, 10, -10, DX.CreateColorRGB(0, 1, 0), 1, 0, 0, LVertexList(5))
    Call DX.CreateD3DLVertex(10, -10, 10, DX.CreateColorRGB(0, 1, 0), 1, 0, 0, LVertexList(6))
    Call DX.CreateD3DLVertex(10, 10, 10, DX.CreateColorRGB(0, 1, 0), 1, 0, 0, LVertexList(7))
    'Back
    Call DX.CreateD3DLVertex(10, -10, 10, DX.CreateColorRGB(0, 0, 1), 1, 0, 0, LVertexList(8))
    Call DX.CreateD3DLVertex(10, 10, 10, DX.CreateColorRGB(0, 0, 1), 1, 0, 0, LVertexList(9))
    Call DX.CreateD3DLVertex(-10, -10, 10, DX.CreateColorRGB(0, 0, 1), 1, 0, 0, LVertexList(10))
    Call DX.CreateD3DLVertex(-10, 10, 10, DX.CreateColorRGB(0, 0, 1), 1, 0, 0, LVertexList(11))
    'Left
    Call DX.CreateD3DLVertex(-10, -10, 10, DX.CreateColorRGB(1, 1, 0), 1, 0, 0, LVertexList(12))
    Call DX.CreateD3DLVertex(-10, 10, 10, DX.CreateColorRGB(1, 1, 0), 1, 0, 0, LVertexList(13))
    Call DX.CreateD3DLVertex(-10, -10, -10, DX.CreateColorRGB(1, 1, 0), 1, 0, 0, LVertexList(14))
    Call DX.CreateD3DLVertex(-10, 10, -10, DX.CreateColorRGB(1, 1, 0), 1, 0, 0, LVertexList(15))
    'Top
    Call DX.CreateD3DLVertex(-10, 10, -10, DX.CreateColorRGB(1, 0, 1), 1, 0, 0, LVertexList(16))
    Call DX.CreateD3DLVertex(-10, 10, 10, DX.CreateColorRGB(1, 0, 1), 1, 0, 0, LVertexList(17))
    Call DX.CreateD3DLVertex(10, 10, -10, DX.CreateColorRGB(1, 0, 1), 1, 0, 0, LVertexList(18))
    Call DX.CreateD3DLVertex(10, 10, 10, DX.CreateColorRGB(1, 0, 1), 1, 0, 0, LVertexList(19))
    'Bottom
    Call DX.CreateD3DLVertex(-10, -10, 10, DX.CreateColorRGB(0, 1, 1), 1, 0, 0, LVertexList(20))
    Call DX.CreateD3DLVertex(-10, -10, -10, DX.CreateColorRGB(0, 1, 1), 1, 0, 0, LVertexList(21))
    Call DX.CreateD3DLVertex(10, -10, 10, DX.CreateColorRGB(0, 1, 1), 1, 0, 0, LVertexList(22))
    Call DX.CreateD3DLVertex(10, -10, -10, DX.CreateColorRGB(0, 1, 1), 1, 0, 0, LVertexList(23))

    'Set the cube to its normal size
    CubeScale = MakeVector(1, 1, 1)
End Sub

Here we just define the positions and colours of each vertex in our array, 4 vertices for each side of the cube. Then make sure our cube is set to the correct scale.


Translate and scale routines

Here are our two routines that make the translation (position) and scale matrices.

'***********************************************************
'TranslateMatrix, Call this to position an object at position specified by pVector
'***********************************************************

Public Sub TranslateMatrix(pMatrix As D3DMATRIX, pVector As D3DVECTOR)
  DX.IdentityMatrix pMatrix
  pMatrix.rc41 = pVector.X
  pMatrix.rc42 = pVector.Y
  pMatrix.rc43 = pVector.Z
End Sub

'***********************************************************
'ScaleMatrix, Call this to scale an object by values specified in pVector
'***********************************************************

Public Sub ScaleMatrix(pMatrix As D3DMATRIX, pVector As D3DVECTOR)
  DX.IdentityMatrix pMatrix
  pMatrix.rc11 = pVector.X
  pMatrix.rc22 = pVector.Y
  pMatrix.rc33 = pVector.Z
End Sub

Nothing complicated, just pass the matrix you want changing and the vector that holds the position/scale that you want, and it will make the matrix for you.


Main Loop

Here is where we scale, rotate and transform our world matrix to alter the scale, rotation and position of our cube. I must stress how important it is to remember to multiply the matrices in the correct order, Scale - Rotate - Translate, if you do it in a different order then you may find some strange and unwanted effects occuring.

        '======================================================
        'It is important to remember to multiply the matrices in the correct order.
        'The order is:-
        '  1) Scale
        '  2) Rotation (usually Y, Z, X)
        '  3) Translation
        '======================================================
        
        'First we must Scale the object

        Call ScaleMatrix(matWorld, CubeScale)           'Scale matrix
        
        'Then we want to rotate the object around its Z-axis and then X-axis
        DX.RotateZMatrix tmpMatrix, stepval * Rad       'Rotate Z matrix
        DX.MatrixMultiply matWorld, matWorld, tmpMatrix 'Multiply matrices together
        DX.RotateXMatrix tmpMatrix, stepval * Rad       'Rotate X matrix
        DX.MatrixMultiply matWorld, matWorld, tmpMatrix 'Multiply matrices together
        
        'Finally we need to move our object
        Call TranslateMatrix(tmpMatrix, CubePosition)   'Translation matrix
        DX.MatrixMultiply matWorld, matWorld, tmpMatrix 'Multiply matrices together
        
      'Transform the world
        Device.SetTransform D3DTRANSFORMSTATE_WORLD, matWorld

We must send each side to D3D to render individually, but when we start using DrawIndexedPrimitive we will only need to send the object once to D3D for rendering.

'Render the 6 sides of the cube to the device (backbuffer),
'they are triangle strips.

Call Device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_LVERTEX, LVertexList(0), 4, D3DDP_DEFAULT)  'Front side
Call Device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_LVERTEX, LVertexList(4), 4, D3DDP_DEFAULT)  'Right side
Call Device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_LVERTEX, LVertexList(8), 4, D3DDP_DEFAULT)  'Back side
Call Device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_LVERTEX, LVertexList(12), 4, D3DDP_DEFAULT) 'Left side
Call Device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_LVERTEX, LVertexList(16), 4, D3DDP_DEFAULT) 'Top side
Call Device.DrawPrimitive(D3DPT_TRIANGLESTRIP, D3DFVF_LVERTEX, LVertexList(20), 4, D3DDP_DEFAULT) 'Bottom side
 

Conclusions

Like I said, this was quite a simple tutorial, and we didn't actually need the Z-buffer to display our cube, but when we start using more than 1 object in our worlds then we will need a Z-buffer. You now have a solid foundation to work on, you can initialise DDraw, and D3DIM, create a Z-buffer, and manipulate objects by changing the world matrix. Next time we will look at lights, D3DVertex (instead of D3DLVertex), and DrawIndexedPrimitive.

As you progress through these tutorials I hope you will try and do some experimenting of your own, don't just read through the tutorial and code, but modify the code and make your own programs.

Lets hope your all having fun and learning something, good bye for now.

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