DirectX Graphics:
Getting Started
Author
: Jack Hoxley
Written : 30th November 2000
Contact : [Email]
Download : Graph_01.Zip
[8 Kb]
Contents
of this lesson:
1. Introduction
2. Getting Started
3. Rendering
4. The Main Loop
5. Cleaning Up
6. Extending this sample to use fullscreen
mode.
1.
Introduction
Welcome
to the first lesson of many on DirectX8-Graphics.
There are three likely reasons why you're reading
this: You're completely new to the world of DirectX
and VB, you're interested in upgrading from DirectX
7, you're interested in upgrading from DirectX7,
have looked in the DirectX 8 SDK and gotten very
confused. Either way you're here.
The
DirectX 8 graphics implementation is quite different
to what you may well be used to if you're a veteran
of earlier versions; the main thing you'll have
noticed is that there is no DirectDraw interfaces
left. In DirectX 7 it was possible to use 3D (and
the hardware accelerator) to generate 2D graphics;
it wasn't easy - but it was possible; In DirectX
8 this is the only way to generate 2D graphics.
Dont worry about this part if you're new to DirectX
completely.
DirectX
8 offers us many incredible features, sadly alot
of them are extremely complicated and unlikely
to be used by anyone except a professional. Things
such as the microprogrammable architecture for
per-pixel processing and vertex processing will
make games more lifelike; Mesh skinning will make
the representations of players more realistic
and various other features. This site will try
to explain as many of these as is possible.
If
you're a seasoned DirectX 7 programmer you can
jump straight to part 2; for those of you not
familiar with DirectX - a brief explanation follows.
DirectX is a collection of classes and interfaces
that allow low-level access to hardware. They
get their speed almost entirely from the fact
that they are very simple and very "thin".
A call to DirectX will get to the hardware much
faster than if you use the traditional windows
API calls, which go through several stages before
appearing to do anything. DirectX also supports
almost every feature imaginable from the current
crop of 3D cards, sound cards, internet connection
and input devices; that is until the next batch
are released... From a programmers point of view
we create an instance of a DirectX component,
be it DirectX in general, Direct3D, DirectSound,
DirectMusic. Once our application has access to
this we can start playing around, initially we'll
set the hardware up - in graphics we'll set the
display mode, any rendering options, load textures
and geometry. Once we're done with that part we'll
enter a tight loop (in most DirectX apps) where
we'll update anything that needs updating and
usually render the next frame onto the screen
- this is the basis of a frame rate; a frame rate
of 70 indicates that this loop will be processed
70 times a second...
The
rest you'll learn as you go along. Hopefully.
2.
Getting Started
The
first part of our DirectX graphics series is to
learn how to setup a basic framework. Should you
keep with DirectX the code you learn here will
be used time and time again, eventually you'll
probably be able to recite it in your sleep (depending
on what sort of person you are!). If you dont
follow this code now, you'll be lost later on;
go over and over this until you understand it
inside out - later lessons will assume that you
can set up a basic framework.
2a.
Attach DirectX8 Library and start our project.
Start a new project in Visual Basic, Standard
EXE will do fine. You should see that a single
form is added - This should be the norm for you,
as it's unwise to venture into DirectX if you
have little VB experience. Go to the project menu
and select "references". Scroll down
until you find an entry in the list called "DirectX
8 for Visual Basic Type Library" and check
the box next to it - then click okay. Save you're
project if you want. This project is now fully
capable of utilising DirectX 8. Should you happen
to have any problems with the above code; there
are several solutions:
1. Have you got DirectX 8 installed? If not, the
entry wont appear in the list.
2. Have you got Visual Basic 5 or above? DirectX
uses the Component Object Model (COM) which was
only implemented in Visual Basic 5 and onwards.
3. You have both of the above, and it still doesn't
appear. Click on the "Browse" button
and point it towards the file C:\Windows\System\dx8vb.dll
and click okay - and it should be fine.
4. The above instructions are fine for VB 5 and
6, but at time of writing VB 7 (or VB.Net) has
not been released - so slight changes in the interface
may well apply...
2b.
The Variables
This part goes in the Declarations section of
a form:
'//The variables Required
Dim Dx as DirectX8 'The master Object, everything comes from here
Dim D3D as Direct3D8 'This controls all things 3D
Dim D3DDevice as Direct3DDevice8 'This actually represents the hardware doing the rendering
Dim bRunning as boolean 'Controls whether the program is running or not...
'//These aren't really required - they'll just show us what the frame rate is...
Private Declare Function GetTickCount Lib "kernel32" () As Long '//This is used to get the frame rate.
Dim LastTimeCheckFPS As Long '//When did we last check the frame rate?
Dim FramesDrawn As Long '//How many frames have been drawn
Dim FrameRate As Long '//What the current frame rate is.....
|
|
|
|
2c.
The Initialisation
After this part you'll need to initialise your
objects, and set some basic parameters. Nothing
complicated yet, but for now we'll stick to using
windowed mode (Not fullscreen, as used by most
games).
'// Initialise : This procedure kick starts the whole process.
'// It'll return true for success, false if there was an error.
Public Function Initialise() as Boolean
On Error Goto ErrHandler:
Dim DispMode as D3DDISPLAYMODE '//Describes our Display Mode
Dim D3DWindow as D3DPRESENT_PARAMETERS '//Describes our Viewport
Set Dx = New DirectX8 '//Create our Master Object
Set D3D = Dx.Direct3DCreate() '//Make our Master Object create the Direct3D Interface
D3D.GetAdapterDisplayMode D3DADAPTER_DEFAULT, DispMode '//Retrieve the current display Mode
D3DWindow.Windowed = 1 '//Tell it we're using Windowed Mode
D3DWindow.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC '//We'll refresh when the monitor does
D3DWindow.BackBufferFormat = DispMode.Format '//We'll use the format we just retrieved...
'//This line will be explained in detail in a minute...
Set D3DDevice = D3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, FrmMain.Hwnd, _
_ D3DCREATE_SOFTWARE_VERTEXPROCESSING, _
_ D3DWindow)
Initialise = True '//We succeeded
Exit Function
ErrHandler:
'//We failed; for now we wont worry about why.
Initialise = False
End Function
|
|
|
|
This
function should, if all goes according to plan,
create all the objects required - ready for use
by us. But there are several things that need
to be explained, and several things that could
already go wrong...
The
main thing that can go wrong is the CreateDevice
call. There are several parameters in this call
that are hardware dependant. Lets analyse this
line:
The prototype for this function looks like this:
object.CreateDevice(Adapter As Long, DeviceType
As CONST_D3DDEVTYPE, hFocusWindow As Long, BehaviorFlags
As Long, PresentationParameters As D3DPRESENT_PARAMETERS)
As Direct3DDevice8
Adapter
: This is either primary or secondary. D3DADAPTER_DEFAULT
always represents the primary adapter; we'll cover
the usage of secondary adapters in a later lesson.
Basically all they do is switch between a primary
video card (a standard 2D/3D card) or a secondary
card (such as the 3D-only voodoo 1's and 2's)
DeviceType : This basically allows you
to choose between the HAL (Hardware Accelerator)
and the reference rasterizer (A debugging tool).
There is a third option, software rendering, which
is designed to allow plugin support for custom
renderers; the DirectX DDK (Driver Development
Kit) has information on doing this; but if you
can write your own 3D renderer you're unlikely
to be using VB... :) Specify either D3DDEVTYPE_HAL
or D3DDEVTYPE_REF. Bare in mind that if there
is no support for the HAL available this call
will fail and you wont create a device.
hFocusWindow : This is just a value that
DirectX can use to track your applications status.
This value comes from the Form.hWnd value; the
value specified must be a valid, created form
with nothing fancy about it - no circular forms
or MDI forms please :)
BehaviorFlags : This just sets how the
Direct3D engine processes textures, vertices,
lighting and so on. The best option to use here
would be D3DCREATE_PUREDEVICE - but very few graphics
cards will support this (even the relatively new
TnL GeForce 256 cards), using this option will
mean that the 3D card does almost everything -
transformation, shading, lighting, texturing and
rasterization. If you can't use this on you're
hardware the next best thing will be D3DCREATE_HARDWARE_VERTEXPROCESSING
- this uses hardware acceleration as much as possible;
most recent 3D cards will support this. Failing
this you could try D3DCREATE_MIXED_VERTEXPROCESSING
which will use hardware when possible, but if
the hardware can't handle it then the software
components will kick in. Last of all is the plain
software rasterizer; should there be no 3D hardware
available this is likely to be the only option.
It's almost always very slow, and not a nice thing
to use - should you want to : D3DCREATE_SOFTWARE_VERTEXPROCESSING.
PresentationParameters : This depicts what
display mode you want to use. Pass the current
display mode (as we have earlier) and you can
use windowed mode; alter these settings (shown
later) and you can enter fullscreen mode.
As
you should now understand, there are several hardware
dependant variables; to solve this problem we'll
use a process called enumeration to work out what
the host computer can do. That's in a later lesson
though; this one's just the beginning.
3.
Rendering
This
will eventually become the main part of you're
program. The code you write here will be executed
as many times as possible in a loop; unclean code
will mean poor performance, clean and fast code
will result in blistering frame rates and smooth
gameplay. After you've got the initialisation
process sorted out and setup how you require it
your work will center mostly on this section.
Rendering
is almost always started in one procedure; other
procedures may well be called from this procedure,
but it will tend to be mostly in one procedure.
It'll almost always follow the same pattern:
1.
Update any changes to vertices, Camereras, textures
etc...
2. Clear the screen - removing the last frames
work
3. Draw the new frame - this is the lengthy part.
4. Update any final variables
5. Copy the rendered image to the screen (Primary.Flip
for veteran DirectX7 programmers)
So,
for this simple lesson we'll create a basic framework
for this procedure:
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, &HCCCCFF, 1#, 0
'the hexidecimal value in the middle is the same as when you're using colours in HTML - if you're familiar
'with that.
'//2. Next we would render everything. This lesson doesn't do this, but if it did it'd look something
' like this:
D3DDevice.BeginScene
'All rendering calls go between these two lines
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
|
|
|
|
This
code excerpt is very simple, it doesn't draw anything
and it doesn't update any variables. But you'll
start to see how this changes in later lessons.
One
thing to be mentioned here is the use of "ByVal
0" for some of the parameters. When you type
"D3DDevice.Present " and the tooltip
appears showing the parameters you'll notice that
it SourceRect, DestRect and DirtyRegion are all
defined as "Any". This means that you
could, theoretically, pass any type of data as
the parameter. If we just put 0 in as the parameter
visual basic would interpret it as meaning "Nothing"
- which is not what we want; we actually want
the value 0 to be passed. If we stick the ByVal
part in visual basic will make sure it goes through
as a number, rather than "Nothing".
4.
The Main Loop
The
main loop is basically a small piece of code that
executes a very tight loop; for every loop we
do we'll update the graphics, AI, Sound, Physics
(or whatever else the game needs to do). The faster
this code executes the higher the frame rate -
Simple as that. Because of the way a loop operates
we can also include the initialisation and termination
code in the same procedure. It'll look like this:
Private Sub Form_Load()
Me.Show '//Make sure our window is visible
bRunning = Initialise()
Debug.Print "Device Creation Return Code : ", bRunning 'So you can see what happens...
Do While bRunning
Render '//Update the frame...
DoEvents '//Allow windows time to think; otherwise you'll get into a really tight (and bad) loop...
'Calculate the frame rate; how this is done isn't greatly important
'So dont worry about understanding it yet...
If GetTickCount - LastTimeCheckFPS >= 1000 Then
LastTimeCheckFPS = GetTickCount
FrameRate = FramesDrawn '//Store the frame count
FramesDrawn = 0 '//Reset the counter
Me.Caption = "DirectX-Graphics: Lesson 01 {" & FrameRate & "fps}" '//Display it on screen
End If
FramesDrawn = FramesDrawn + 1
Loop
'//If we've gotten to this point the loop must have been terminated
' So we need to clean up after ourselves. This isn't essential, but it'
' good coding practise.
On Error Resume Next 'If the objects were never created;
' (the initialisation failed) we might get an
' error when freeing them... which we need to
' handle, but as we're closing anyway...
Set D3DDevice = Nothing
Set D3D = Nothing
Set Dx = Nothing
Debug.Print "All Objects Destroyed"
'//Final termination:
Unload Me
End
End Sub
|
|
|
|
You'll
notice that all of this code is in the form load
procedure; this'll mean that DirectX is initialised
and setup when the application is first loaded,
and it'll go straight into the main game loop.
One immediate thing to remember is that you MUST
include the "Me.Show" procedure as one
of the very first lines. In the normal life of
a VB application the form isn't displayed until
after the Form_Load code is completed - but in
our case it'll only be completed when the application
closes. With the "DoEvents" line included
the form will eventually be shown, but it wont
be very tidy.
The
main loop structure is based completely on a boolean
variable, bRunnning, whilst this is set to true
the main loop is executed. As soon as it turns
false we'll leave the loop and continue executing
the Form_Load code; which, as you can see, will
quickly clean up DirectX (explained later) and
close the application. You can think of this variable
as an off switch - and the way the loop works
it'll respond very quickly; at 60 frames per second
it should respond in 1/60th of a second to your
request.
There
are two other things that you need to think about
with the main loop in this example. The main one
being what order the procedures are called in
- which isn't important here (only 1 procedure)
but in a full game structure you'll have several
procedures processed on every loop. A good example
is when you're graphics engine depends on the
input from the keyboard - if you call the graphics
engine THEN the input engine you're graphics engine
will always be 1 frame behind what the user has
just done, if you call the input engine first,
then the graphics engine everything will be synchronized.
Finally, the frame rate calculation. This is quite
useful as you can instantly get an idea of what
speed your application is running at. All it does
is increment a counter on each loop and every
second copies it to a more persistant variable
and sets the counter back to 0.
The
last thing to note about the main loop is the
"DoEvents" line. Without this simple
call things would go pair shaped very quickly.
This line allows windows to "Breath"
so to speak; without it almost everything will
stop - forms won't appear, mouse/keyboard input
wont be registered and setting any properties
will be delayed (such as altering the caption
in this example). This is particularly bad when
you require a key/mouse event to terminate the
main loop - if no keyboard and mouse events are
registered then you cant terminate the loop, if
you cant terminate the loop it just keeps on going
- until it crashes (which is likely to happen
after a while). For safety just leave the line
there - dont think about it, just remember it.
5.
Cleaning Up
Cleaning
up is the last thing that you shoudl do; although
it's not always the end of the world if you forget
it's good practise to do it. When cleaning up
you should free all the objects in the reverse
order that you created them. You've actually already
seen the code for cleaning up this lesson's code:
On Error Resume Next
Set D3DDevice = Nothing
Set D3D = Nothing
Set Dx = Nothing
|
|
|
|
Simple
really, to free up an object you just code "Set
<Object Name> = Nothing" and it's fine.
I've included the on error resume next part because
you can sometimes get an error if you set an object
to nothing when it's never been created - which
will happen in this sample if the initialisation
fails.
6.
Extending this sample to use fullscreen mode
If
you run the sample program at this point you'll
realise that it's running in windowed mode (I
did tell you as well). Although a lot of games
have an option for using windowed mode, by default
they'll use fullscreen mode. There are many reasons
for this, but it's basically down to look and
feel - A game looks more real and feels better
if you cant see "My Documents" or the
start-bar in the background.
Switching
to fullscreen mode isn't very complicated, we
just need to re-design our structure slightly:
DispMode.Format = D3DFMT_X8R8G8B8
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
|
|
|
|
Not
too complicated really; the above code goes in
place of the existing "D3DWindow" initialisation
code that we were using earlier.
I'll
assume that you know what a resolution is (640x480,
1024x768 and so on), so I wont go on about that,
but there is one thing that you'll need to pay
attention to when using the above code.
The
Display mode format - DispMode.Format - as you
can see at the top of the code it is set to being
"D3DFMT_X8R8G8B8". So what the heck
does that mean? Well, all textures and surfaces
(backbuffers, primary buffers, depth buffers etc...)
are stored in a certain format in memory. You've
probably come across bit depths when using resolutions
- 8 bit, 16 bit, 24 bit, 32 bit. These tell you
how many bits of memory are required for each
pixel, there are 8 bits in a byte, therefore 32
bit colour requires 4 bytes of memory for every
pixel stored. The higher the bit depth the more
memory is required (but the nicer it'll look).
So what relevence has this information? the "D3DFMT_X8R8G8B8"
flag specifies how this memory is setup - in this
case 8888 format = 32 bit colour. There are several
other formats that you'll come across a little
later on, but right now we'll keep things simple.
With the code above you'll need to have a computer
that supports 640x480 in 32 bit colour, if you
dont then it'll fail. Also, you'll need to bare
in mind the actual screen width/height - if you
know that the hardware doesn't support the specified
resolution then you'll get an error... A later
lesson will discuss enumeration - the process
of working out what the hardware supports.
You
can download the sample code for this tutorial
from the top of the page.
Assuming
you understood all of this, you're ready to move
onto Lesson 02 : Enumerating
the display adapters and available display modes.
|