1Advanced 3D Game Programming with DirectX 9.0
by Peter Walsh
ISBN:1556229682
Wordware Publishing
© 2003 (525 pages)
Designed for programmers who are new to graphics and game programming, this book covers Direct 3D,
DirectInput, and DirectSound, as well as artificial intelligence, networking, multithreading, and scene
management.
Companion Web Site
Table of Contents
Advanced 3D Game Programming Using DirectX 9.0 Introduction Chapter 1
- Windows
Chapter 2
- Getting Started with DirectX
Chapter 3
List of Figures List of Tables List of Code Examples
Advanced 3D Game Programming
Using DirectX 9.0
Peter Walsh
2
Wordware Publishing, Inc.
Library of Congress Cataloging-in-Publication Data
Walsh, Peter (Peter Andrew), 1980-
Advanced 3D game programming with DirectX 9.0 / by Peter Walsh.
p. cm.
ISBN 1-55622-968-2 (pbk.)
1. Computer games–Programming. 2. DirectX. I. Title.
QA76.76.C672W382 2003
794.8'167768–dc21 2003007140
CIP
Copyright © 2003 Wordware Publishing, Inc.
All Rights Reserved
2320 Los Rios Boulevard
Plano, Texas 75074
No part of this book may be reproduced in any form or by any means without permission in writing from
Thanks to Phil Taylor on the DirectX team at Microsoft for agreeing to do the tech check and also to
Wolfgang Engel and Bruno Sousa for their technical support. Of course, thank you to my wonderful
fiancee Lisa for helping to keep me motivated while working on the book, when I just wanted to give up
and party!
Where would I be without thanking all my friends and family, who keep me sane during the many
months that I spent researching and writing these massive books. So thank you Jon-Paul Keatley,
Stewart Wright, Andrew McCall, Todd Fay, Mike Andrews, Laz Allen, and all my other friends around
the world that I don't have room to list! Also, who would I be writing a book and not mentioning my soon-
to-be family-in-law? So thank you Liam and Ann Sullivan for giving me permission to marry your
beautiful daughter (also to Joanne, Pauline, Liam Jr., and the rest of the family). Of course, thanks to
my parents Simon and Joy Walsh for being so supportive during my younger years and to this day.
The worst thing about writing acknowledgments is that you always forget someone who helped you until
the day the book goes to print. So thank you to everyone else I forgot—please accept my apologies; my
poor brain is worn out after all this work!
Peter Walsh
This book couldn't have been completed without the help and guidance of a whole lot of people. I'll try to
remember them all here. First, thanks to Wes Beckwith and Jim Hill at Wordware Publishing. They were
extremely forgiving of my hectic schedule, and they helped guide me to finishing this book. I also must
thank Alex Dunne for letting me write an article in 1998 for Game Developer magazine. If I hadn't written
that article, I never would have written this book.
Everything I know about the topics in this book I learned from other people. Some of these people were
mentors, others were bosses, and still others were professors and teachers. Some were just cool
people who took the time to sit and talk with me. I can't thank them enough. Paul Heckbert, Tom
4
Funkhouser, Eric Petajan, Charles Boyd, Mike Toelle, Kent Griffin, David Baraff, Randy Pausch, Howie
Choset, Michael Abrash, Hugues Hoppe, and Mark Stehlik: You guys rock. Thank you.
Thanks to Microsoft, ATI, nVidia, id Software, and Lydia Choy for helping me with some of the images
used in the text.
Many people helped assure the technical correctness and general sanity of this text. Ian Parberry and
5
One night, in the middle of December, Chris called me up. The lab report that was due the next day
required results from the experiment we had done together in class, and he had lost his copy of our
experiment results. He wanted to know if I could copy mine and bring them over to his place so he could
finish writing up the lab. Of course, this was in those heinous pre-car days, so driving to his house
required talking my parents into it, finding his address, and various other hardships. While I was willing
to do him the favor, I wasn't willing to do it for free. So I asked him what he could do to reciprocate my
kind gesture.
"Well," he said, "I guess I can give you a copy of this game I just got."
"Really? What's it called?" I said.
"Doom. By the Wolf 3D guys." "It's called Doom? What kind of name is that??"
After getting the results to his house and the game to mine, I fired the program up on my creaky old 386
DX-20 clone, burning rubber with a whopping 4 MB of RAM. As my space marine took his first tenuous
steps down the corridors infested with hellspawn, my life changed. I had done some programming
before in school (Logo and Basic), but after I finished playing the first time, I had a clear picture in my
head of what I wanted to do with my life: I wanted to write games, something like Doom. I popped onto a
few local bulletinboards and asked two questions: What language was the game written in, and what
compiler was used?
Within a day or so, I purchased Watcom C 10.0 and got my first book on C programming. My first C
program was "Hello, World." My second was a slow, crash-happy, non-robust, wireframe spinning cube.
I tip my hat to John Carmack, John Romero, and the rest of the team behind Doom; my love for creating
games was fully realized via their masterpiece. It's because of them that I learned everything that I have
about this exceptionally interesting and dynamic area of computer acquired programming. The
knowledge that I have is what I hope to fill these pages with, so other people can get into graphics and
game programming.
I've found that the best way to get a lot of useful information down in a short amount of space is to use
the tried-and-true FAQ (frequently asked questions) format. I figured if people needed answers to some
questions about this book as they stood in their local bookstore trying to decide whether or not to buy it,
these would be them.
Who are you? What are you doing here?
in another field or college students looking to embark on some side projects.
Who should not read this book?
This book was not designed for beginners. I'm not trying to sound arrogant or anything; I'm sure a
beginner will be able to trudge through this book if he or she feels up to it. However, since I'm so
constrained for space, often- times I need to breeze past certain concepts (such as inheritance in C++).
If you've never programmed before, you'll have an exceedingly difficult time with this book.
What are the requirements for using the code?
The code was written in C++, using Microsoft Visual C++ 6.0. The .DSPs and .DSWs are provided on
the downloadable files (http://www.wordware.com/files/dx9
); the .DSPs will work with versions previous
to 6.0, and the .DSWs will work with 6.0 and up. If you choose to use a different compiler, getting the
source code to work should be a fairly trivial task. I specifically wrote this code to use as little non-
7
standard C++ as possible (as far as I know, the only non-standard C++ I use is nameless structures
within unions).
Why use Windows? Why not use Linux?
I chose to use Win32 as the API environment because 90 percent of computer users currently work on
Windows. Win32 is not an easy API to understand, especially after using DOS coding conventions. It
isn't terribly elegant either, but I suppose it could be worse. I could choose other platforms to work on,
but doing so reduces my target audience by a factor of nine or more.
Why use Direct3D? Why not use OpenGL?
For those of you who have never used it, OpenGL is another graphics API. Silicon Graphics designed it
in the early '90s for use on their high-end graphics workstations. It has been ported to countless
platforms and operating systems. Outside of the games industry in areas like simulation and academic
research, OpenGL is the de facto standard for doing computer graphics. It is a simple, elegant, and fast
API. Check out http://www.opengl.org
for more information.
But it isn't perfect. First of all, OpenGL has a large amount of functionality in it. Making the interface so
simple requires that the implementation take care of a lot of ugly details to make sure everything works
accelerator capable of handling all the code in this book can be purchased for under $100.
How hardcore is the C++ in this book?
Some people see C++ as a divine blade to smite the wicked. They take control of template classes the
likes of which you have never seen. They overload the iostream operators for all of their classes. They
see multiple inheritance as a hellspawn of Satan himself. I see C++ as a tool. The more esoteric
features of the language (such as the iostream library) I don't use at all. Less esoteric features (like
multiple inheritance) I use when it makes sense. Having a coding style you stick to is invaluable. The
code for this book was written over an eleven-month period, plus another three for the revision, but I can
pick up the code I wrote at the beginning and still grok it because I commented and used some good
conventions. If I can understand it, hopefully you can too.
What are the coding conventions used in the source?
One of the greatest books I've ever read on programming was Code Complete (Microsoft Press). It's a
handbook on how to program well (not just how to program). Nuances like the length of variable names,
design of subroutines, and length of files are covered in detail in this book; I strongly encourage anyone
who wants to become a great programmer to pick it up. You may notice that some of the conventions I
use in this book are similar to the conventions described in Code Complete; some of them are borrowed
from the great game programmers like John Carmack, and some of them are borrowed from source in
DirectX, MFC, and Win32.
I've tried really hard to make the code in this book accessible to everyone. I comment anything I think is
unclear, I strive for good choice in variable names, and I try to make my code look clean while still trying
to be fast. Of course, I can't please everyone. Assuredly, there are some C++ coding standards I'm
probably not following correctly. There are some pieces of code that would get much faster with a little
obfuscation.
9
If you've never used C++ before or are new to programming, this book is going to be extremely hard to
digest. A good discussion on programming essentials and the C++ language is C++ Primer (Lippman et
al.; Addison-Wesley Publishing).
Class/Structure Names
MFC names its classes with a prefixed C. As an example, a class that represents the functionality of a
10
http://www.wordware.com/files/dx9
These files include the source code discussed in the book along with the game Mobots Attack!. Each
chapter (and the game) has its own workspace so you can use them independently of each other.
Chapter 1: Windows
Overview
Welcome, one and all, to the first stage of the journey into the depths of advanced 3D game
development with DirectX 9.0. Before you can start exploring the world of 3D game programming, you
need a canvas to work on. Basic operations like opening and closing a program, handling rudimentary
input, and painting basic primitives must be discussed before you can properly understand more difficult
topics. If you're familiar with the Windows API, you should breeze through this chapter; otherwise, hold
on to your seat! In this chapter you are going to learn about:
The theory behind Windows and developing with the Win32 API
How Win32 game development differs from standard Windows programming
Messages and how to handle them
The infamous message pump
Other methods of Windows programming such as MFC
COM, or the component object model
And much more!
A Word about Windows
Windows programs are fundamentally different in almost every way from DOS programs. In traditional
DOS programs, you have 100 percent of the processor time, 100 percent control over all the devices
and files in the machine. You also need an intimate knowledge of all of the devices on a user's machine
(you probably remember old DOS games, which almost always required you to input DMA and IRQ
settings for sound cards). When a game crashed, you didn't need to worry too much about leaving
things in a state for the machine to piece itself together; the user could just reboot. Some old
320x200x256 games would crash without even changing the video mode back to normal, leaving the
user screen full of oversized text with the crash information.
In Windows, things are totally different. When your application is running, it is sharing the processor with
programming language; it is an application programming interface (API). In other words, it is a set of C
functions that an application uses to make a Windows-compliant program. It abstracts away a lot of
difficult operations like multitasking and protected memory, as well as providing interfaces to higher-
level concepts. Supporting menus, dialog boxes, and multimedia have well-established, fairly easy-to-
use (you may not believe me about this!) library functions written for that specific task.
Windows is an extremely broad set of APIs. You can do just about anything, from playing videos to
loading web pages. And for every task, there are a slew of different ways to accomplish it. There are
some seriously large books devoted just to the more rudimentary concepts of Windows programming.
Subsequently, the discussion here will be limited to what is relevant to allow you to continue on with the
rest of the book. Instead of covering the tomes of knowledge required to set up dialogs with tree
controls, print documents, and read/write keys in the registry, I'm going to deal with the simplest case:
creating a window that can draw the world, passing input to the program, and having at least the
beginnings of a pleasant relationship with the operating system. If you need any more info, there are
many good resources out there on Windows programming.
Hungarian Notation
All of the variable names in Windows land use what is called Hungarian notation. The name came from
its inventor, Charles Simonyi, a now-legendary Microsoft programmer who happened to be Hungarian.
12
Hungarian notation is the coding convention of just prefixing variables with a few letters to help identify
their type. Hungarian notation makes it easier to read other peoples' code and easy to ensure the
correct variables are supplied to functions in the right format. However, it can be really confusing to
people who haven't seen it before.
Table 1.1
gives some of the more common prefixes used in most of the Windows and DirectX code that
you'll see in this book. Table 1.1: Some common Hungarian notation prefixes
b (example: bActive) Variable is a BOOL, a C precursor to the Boolean type found in
Figure 1.1: Notepad.exe— as basic as a window gets
The windows I show you how to create will be similar to this. A window such as this is partitioned into
several distinct areas. Windows manages some of them, but the rest your application manages. The
partitioning looks something like Figure 1.2
.
Figure 1.2: The important GUI components of a window
The main parts are:
Title
Bar
This area appears in most windows. It gives the name of the window and provides access
to the system buttons that allow the user to close, minimize, or maximize an application.
The only real control you have over the title bar is via a few flags in the window creation
process. You can make it disappear, make it appear without the system icons, or make it
thinner.
14
Menu
Bar
The menu is one of the primary forms of interaction in a GUI program. It provides a list of
commands the user can execute at any one time. Windows also controls this piece of the
puzzle. You create the menu and define the commands, and Windows takes care of
everything else.
Resize
Bars
Resize bars allow the user to modify the size of the window on screen. You have the
option of turning them off during window creation if you don't want to deal with the
DWORD time;
POINT pt;
} MSG;
hwnd
Handle to the window that should receive the message
message
The identifier of the message. For example, the application receives a msg object when
the window is resized, and the message member variable is set to the constant
WM_SIZE.
wParam
Information about the message; dependent on the type of message
lParam
Additional information about the message
time
Specifies when the message was posted
pt
Mouse location when the message was posted
Explaining Message Processing
What is an HWND? It's basically just an integer, representing a handle to a window. When a Windows
application wants to tell another window to do something, or wants to access a volatile system object
like a file on disk, Windows doesn't actually let it fiddle with pointers or give it the opportunity to trounce
on another application's memory space. Everything is done with handles to objects. It allows the
application to send messages to the object, directing it to do things. A good way to think of a handle is
like a bar code. That is, a handle is a unique identifier that allows you, and Windows, to differentiate
between different objects such as windows, bitmaps, fonts, and so on.
So how does an application process messages? Windows defines a function that all programs must
implement called the window procedure (or WndProc for short). When you create a window, you give
Windows your WndProc function in the form of a function pointer. Then, when messages are processed,
they are passed as parameters to the function, and the WndProc deals with them. So, for example,
when theWndProc function gets passed a message saying "Paint yourself!" that is the signal for the
window to redraw itself.
When you send a message, Windows examines the window handle you provide, using it to find out
where to send the message. The message ID describes the message being sent, and the parameters to
the ID are contained in the two other fields in a message, wParam and lParam. Back in the 16-bit days,
wParam was a 16-bit (word sized) integer and lParam was a 32-bit (long sized) integer, but with Win32
they're both 32 bits long. The messages wait in a queue until the application receives them.
The window procedure should return 0 for any message it processes. All messages it doesn't process
should be passed to the default Windows message procedure, DefWindowProc(). Windows can start
behaving erratically if DefWindowProc doesn't see all of your non-processed messages. Don't worry if
you're not getting all of this just yet; it will become clearer over the course of this book.
Hello World—Windows Style
To help explain these ideas, let me show you a minimalist Win32 program and analyze what's going on.
This code was modified from the default "Hello, World" code that Visual C++ 6.0 will automatically
generate for you, but some of the things were removed, leaving this one of the most stripped-down
Windows programs you can write.
17
Listing 1.1: One of the simplest possible Windows programs
/*******************************************************************
* Advanced 3D Game Programming using DirectX 9.0
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Title: HelloWorld.cpp
* Desc: Simple windows app
* copyright (c) 2002 by Peter A Walsh and Adrian Perez
{
return FALSE;
}
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
} //
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
//COMMENTS:
//
// This function and its usage is only necessary if you want this code
// to be compatible with Win32 systems prior to the 'RegisterClassEx'
// function that was added to Windows 95. It is important to call this
// function so that the application will get 'well formed' small icons
// associated with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
19
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
20
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(
szWindowClass, // Name of the window class to use for this
window
// registered in MyRegisterClass
szTitle, // Title of the application
WS_OVERLAPPEDWINDOW, // Style that Windows should make our window
with
// (this is the 'default' window style for windowed apps)
20, // Starting X of the window
20, // Starting Y of the window
640, // Width of the window
480, // Height of the window
NULL, // Handle of our parent window (Null, since we have none)
NULL, // Handle to our menu (Null, since we don't have one)
hInstance, // Instance of our running application
NULL); // Pointer to window-creation data (we provide none)
if (!hWnd)
{
return FALSE;
}
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt,
DT_CENTER | DT_VCENTER | DT_SINGLELINE );
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
22
}
return 0;
} It's easy to get worried when you think this is one of the simplest Windows programs you can write, and
it's still over 100 lines long. The good thing is that the code above is more or less common to all
Windows programs. Most Windows programmers don't remember the exact order everything goes in;
they just copy the working Windows initialization code from a previous application and use it like it is
their own!
Explaining the Code
Every C/C++ program has its entry point in main(), where it is passed control from the operating system.
In Windows, things work a little differently. There is some code that the Win32 API runs first, before
letting your code run. The actual stub for main() lies deep within the Win32 DLLs where you can't touch
it. However, this application starts at a different point: a function called WinMain(). Windows does its
setup work when your application is first run, and then calls WinMain(). This is why when you debug a
Windows app "WinMain" doesn't appear at the bottom of the call stack; the internal DLL functions that
called it are. WinMain is passed the following parameters (in order):
is the "Hey you! Quit! Now!!" message and has the message ID WM_QUIT. If there is a message in the
queue, GetMessage will remove it and fill it into the message structure, which is the "msg" variable
above. Inside the while loop, you first take the message and translate it using a function called
TranslateMessage.
This is a convenience function. When you receive a message saying a key has been pressed or
released, you get the specific key as a virtual key code. The actual values for the IDs are arbitrary, but
the namespace is what you care about: When the letter "a" is pressed, one of the message parameters
is equivalent to the #define VK_A. Since that nomenclature is a pain to deal with if you're doing
something like text input, TranslateMessage does some housekeeping, and converts the parameter
from "VK_A" to "(char)'a' ". This makes processing regular text input much easier. Keys without clear
ASCII equivalents, such as Page Up and Left Arrow, keep their virtual key code values (VK_PRIOR and
VK_LEFT respectively). All other messages go through the function and come out unchanged.
The second function, DispatchMessage, is the one that actually processes it. Internally, it looks up
which function was registered to process messages (in MyRegisterClass) and sends the message to
that function. You'll notice that the code never actually calls the window procedure. That's because
Windows does it for you when you ask it to with the DispatchMessage function.
Think of this while loop as the central nervous system for any Windows program. It constantly grabs
messages off the queue and processes them as fast as it can. It's so universal it actually has a special
name: the message pump. Whenever you see a reference to a message pump in a text, or optimizing
message pumps for this application or that, that's what it is in reference to.
Registering the Application
MyRegisterClass() fills a structure that contains the info Windows needs to know about your application
before it can create a window, and passes it to the Win32 API. This is where you tell Windows what to
make the icon for the application that appears in the taskbar (hIcon, the large version, and hIconSm, the
smaller version). You can also give it the name of the menu bar if you ever decide to use one. (For now
24
there is none, so it's set to 0.) You need to tell Windows what the application instance is (the one
received in the WinMain); this is the hInstance parameter. You also tell it which function to call when it
processes messages; this is the lpfnWndProc parameter. The window class has a name as well,
taskbar.
dwStyle
A set of flags describing the style for the window (such as having thin borders,
25
being unresizable, and so on). For these discussions windowed applications will
all use WS_OVERLAPPEDWINDOW (this is the standard-looking window, with a
resizable edge, a system menu, a title bar, etc.). However, full-screen
applications will use the WS_POPUP style (no Windows features at all, not even
a border; it's just a client rectangle).
x, y
The x and y location, relative to the top left corner of the monitor (x increasing
right, y increasing down), where the window should be placed.
nWidth,
nHeight
The width and height of the window.
hWndParent
A window can have child windows (imagine a paint program like Paint Shop Pro,
where each image file exists in its own window). If this is the case and you are
creating a child window, pass the HWND of the parent window here.
hMenu
If an application has a menu (yours doesn't), pass the handle to it here.
hInstance
This is the instance of the application that was received in WinMain.