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.
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
|