Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Introduction to 3D Game Programming with DirectX.9.0 - F. D. Luna

.pdf
Скачиваний:
240
Добавлен:
24.05.2014
Размер:
6.94 Mб
Скачать

44 Chapter 1

The pixel with the depth value closest to the camera wins, and that pixel gets written. This makes sense because the pixel closest to the camera obscures the pixels behind it.

The format of the depth buffer determines the accuracy of the depth test. That is, a 24-bit depth buffer is more accurate than a 16-bit depth buffer. In general, most applications work fine with a 24-bit depth buffer, although Direct3D also exposes a 32-bit depth buffer.

 

D3DFMT_D32—Specifies a 32-bit depth buffer

 

 

 

 

 

 

Y

 

D3DFMT_D24S8—Specifies a 24-bit depth buffer with 8 bits

 

reserved as the stencil buffer

L

 

 

 

D3DFMT_D24X8—Specifies a 24-bit depth buffer only

 

 

 

 

M

 

 

D3DFMT_D24X4S4—Specifies a 24-bit buffer with 4 bits reserved

 

for the stencil buffer

 

 

 

 

 

 

A

 

 

 

D3DFMT_D16—Specifies a F16-bit depth buffer only

 

 

 

E

 

 

 

 

 

 

 

Note: The stencil buffer is a more advanced topic and is explained

in Chapter 8.

T

 

 

 

 

 

 

 

 

1.3.7 Vertex Processing

Vertices are the building blocks for 3D geometry, and they can be processed in two different ways, either in software (software vertex processing) or in hardware (hardware vertex processing). Software vertex processing is always supported and can always be used. On the other hand, hardware vertex processing can only be used if the graphics card supports vertex processing in hardware.

Hardware vertex processing is always preferred since dedicated hardware is faster than software. Furthermore, performing vertex processing in hardware unloads calculations from the CPU, which implies that the CPU is free to perform other calculations.

Note: Another way of saying a graphics card supports hardware vertex processing in hardware is to say that the graphics card supports transformation and lighting calculations in hardware.

1.3.8 Device Capabilities

Every feature that Direct3D exposes has a corresponding data member or bit in the D3DCAPS9 structure. The idea is to initialize the members of a D3DCAPS9 instance based on the capabilities of a particular hardware device. Then, in our application, we can check if a device supports a feature by checking the corresponding data member or bit in the D3DCAPS9 instance.

Team-Fly®

Direct3D Initialization 45

The following example illustrates this. Suppose we wish to check if a hardware device is capable of doing vertex processing in hardware (or in other words, whether the device supports transformation and lighting calculations in hardware). By looking up the D3DCAPS9 structure in the SDK documentation, we find that the bit D3DDEVCAPS_HWTRANSFORMANDLIGHT in the data member D3DCAPS9::DevCaps indicates whether the device supports transformation and lighting calculations in hardware. Our test then, assuming caps is a D3DCAPS9 instance and has already been initialized, is:

bool supportsHardwareVertexProcessing;

//If the bit is “on” then that implies the hardware device

//supports it.

if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )

{

// Yes, the bit is on, so it is supported.

supportsHardwareVertexProcessing = true;

}

else

{

// No, the bit is off, so it is not supported. hardwareSupportsVertexProcessing = false;

}

Note: DevCaps stands for “device capabilities.”

Note: We learn how we initialize a D3DCAPS9 instance based on a particular hardware device’s capabilities in the next section.

Note: We recommend that you look up the D3DCAPS9 structure in the SDK documentation and examine the complete list of capabilities that Direct3D exposes.

1.4 Initializing Direct3D

The following subsections show how to initialize Direct3D. The process of initializing Direct3D can be broken down into the following steps:

1.Acquire a pointer to an IDirect3D9 interface. This interface is used for finding out information about the physical hardware devices on a system and creating the IDirect3DDevice9 interface, which is our C++ object that represents the physical hardware device we use for displaying 3D graphics.

2.Check the device capabilities (D3DCAPS9) to see if the primary display adapter (primary graphics card) supports hardware vertex

P a r t I I

46 Chapter 1

processing or not. We need to know if it can in order to create the

IDirect3DDevice9 interface.

3.Initialize an instance of the D3DPRESENT_PARAMETERS structure. This structure consists of a number of data members that allow us to specify the characteristics of the IDirect3DDevice9 interface that we are going to create.

4.Create the IDirect3DDevice9 object based on an initialized

D3DPRESENT_PARAMETERS structure. As said, the IDirect3DDevice9 object is our C++ object that represents the physical hardware device that we use for displaying 3D graphics.

Keep in mind that in this book we use the primary display adapter for drawing 3D graphics. If your system only has one graphics card, that is the primary display adapter. If you have more than one graphics card, then the card you are presently using is the primary display adapter (e.g., the one displaying the Windows desktop, etc.).

1.4.1 Acquiring an IDirect3D9 Interface

Initialization of Direct3D begins by acquiring a pointer to an IDirect3D9 interface. This is easily done using a special Direct3D function, as the following lines of code show:

IDirect3D9* _d3d9;

_d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

The single parameter to Direct3DCreate9 should always be D3D_SDK_VERSION, which guarantees that the application is built against the correct header files. If this function fails, it returns a null pointer.

The IDirect3D9 object is used for two things: device enumeration and creating the IDirect3DDevice9 object. Device enumeration refers to finding out the capabilities, display modes, formats, and other information about each graphics device available on the system. For instance, to create the IDirect3DDevice9 object that represents a physical device, we need to create it using a configuration of display modes and formats that the physical device supports. To find such a working configuration, we must use the IDirect3D9 enumeration methods.

However, because device enumeration can be quite an involved task and we want to get up and running as quickly as possible with Direct3D, we have elected not to perform any enumeration, except for one check as shown in the next section. In order to safely skip

Direct3D Initialization 47

enumeration, we have chosen a “safe” configuration that almost all hardware devices will support.

1.4.2 Checking for Hardware Vertex Processing

When we create an IDirect3DDevice9 object to represent the primary display adapter, we must specify the type of vertex processing to use with it. We want to use hardware vertex processing if we can, but because not all cards support hardware vertex processing, we must check if the card supports it first.

To do this, we must first initialize a D3DCAPS9 instance based on the capabilities of the primary display adapter. We use the following method:

HRESULT IDirect3D9::GetDeviceCaps( UINT Adapter,

D3DDEVTYPE DeviceType, D3DCAPS9 *pCaps

);

Adapter—Specifies the physical display adapter that we are going to get the capabilities of

DeviceType—Specifies the device type to use (e.g., hardware device (D3DDEVTYPE_HAL) or software device (D3DDEVTYPE_ REF))

pCaps—Returns the initialized capabilities structure

Then we can check the capabilities, as we did in section 1.3.8. The following code snippet illustrates this:

//Fill D3DCAPS9 structure with the capabilities of the

//primary display adapter.

D3DCAPS9 caps; d3d9->GetDeviceCaps(

D3DADAPTER_DEFAULT, // Denotes primary display adapter. deviceType, // Specifies the device type, usually D3DDEVTYPE_HAL. &caps); // Return filled D3DCAPS9 structure that contains

//the capabilities of the primary display adapter.

//Can we use hardware vertex processing?

int vp = 0;

if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )

{

//yes, save in ‘vp’ the fact that hardware vertex

//processing is supported.

vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;

}

else

{

//no, save in ‘vp’ the fact that we must use software

//vertex processing.

P a r t I I

48 Chapter 1

vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

}

Observe that we save the type of vertex processing that we are going to use in the variable vp. This is because we are going to need to specify the vertex processing type that we are going to use later on when we create the IDirect3DDevice9 object.

Note: The identifiers D3DCREATE_HARDWARE_VERTEXPROCESSING and D3DCREATE_SOFTWARE_VERTEXPROCESSING are just predefined values that denote hardware vertex processing and software vertex processing, respectively.

Tip: When developing applications and using new, special, or advanced features (in other words, features that are not widely supported), it is recommended that you always check the device capabilities (D3DCAPS9) to see if the device supports the particular feature before using it. Never assume that a feature is available. Also, be aware that the sample applications in this book generally do not follow this advice — we generally do not check device capabilities.

Note: If a particular sample application isn’t working, it is most likely because your hardware doesn’t support the feature that the sample is using; try switching to the REF device.

1.4.3 Filling Out the D3DPRESENT_PARAMETERS

Structure

The next step in the initialization process is to fill out an instance of the D3DPRESENT_PARAMETERS structure. This structure is used to specify some of the characteristics of the IDirect3DDevice9 object that we are going to create, and is defined as:

typedef struct _D3DPRESENT_PARAMETERS_ {

UINT

BackBufferWidth;

UINT

BackBufferHeight;

D3DFORMAT

BackBufferFormat;

UINT

BackBufferCount;

D3DMULTISAMPLE_TYPE MultiSampleType;

DWORD

MultiSampleQuality;

D3DSWAPEFFECT SwapEffect;

HWND

hDeviceWindow;

BOOL

Windowed;

BOOL

EnableAutoDepthStencil;

D3DFORMAT

AutoDepthStencilFormat;

DWORD

Flags;

UINT

FullScreen_RefreshRateInHz;

UINT

PresentationInterval;

} D3DPRESENT_PARAMETERS;

Note: In the following data member descriptions for the D3DPRESENT_PARAMETERS structure, we only cover the flags and options that we feel are the most important to a beginner at this point.

Direct3D Initialization 49

For a description of further flags, options, and configurations, we refer you to the SDK documentation.

BackBufferWidth—Width of the back buffer surface in pixels

BackBufferHeight—Height of the back buffer surface in pixels

BackBufferFormat—Pixel format of the back buffer (e.g., 32-bit

pixel format: D3DFMT_A8R8G8B8)

 

BackBufferCount—The number of back buffers to use. Usually

 

we specify “1” to indicate that we want only one back buffer.

 

MultiSampleType—The type of multisampling to use with the

 

back buffer. See SDK documentation for details.

 

MultiSampleQuality—The quality level of multisampling. See

 

 

SDK documentation for details.

II

SwapEffect—A member of the D3DSWAPEFFECT enumerated

rt

type that specifies how the buffers in the flipping chain will be

Pa

swapped. Specifying D3DSWAPEFFECT_DISCARD is the most

 

efficient.

 

hDeviceWindow—The window handle associated with the device.

 

Specify the application window onto which you want to draw.

 

Windowed—Specify true to run in windowed mode or false for

 

full-screen mode.

 

EnableAutoDepthStencil—Set to true to have Direct3D cre-

 

ate and maintain the depth/stencil buffer automatically.

 

AutoDepthStencilFormat—The format of the depth/stencil

 

buffer (e.g., 24-bit depth with 8 bits reserved for the stencil buffer:

 

D3DFMT_D24S8).

 

Flags—Some additional characteristics. Specify zero (no flags) or

 

a member of the D3DPRESENTFLAG set. See the documentation for

 

a complete list of valid flags. Two common ones are:

 

D3DPRESENTFLAG_LOCKABLE_BACKBUFFER—Specifies that

 

the back buffer can be locked. Note that using a lockable back

 

buffer can degrade performance.

 

D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL—Specifies

 

that the depth/stencil buffer will be discarded after the next

 

back buffer is presented. By “discard” we mean just that—the

 

depth/stencil buffer memory will be discarded or invalid. This

 

can improve performance.

 

FullScreen_RefreshRateInHz—Refresh rate; use the default

 

refresh rate by specifying D3DPRESENT_RATE_DEFAULT.

 

50Chapter 1

PresentationInterval—A member of the D3DPRESENT set. See the documentation for a complete list of valid intervals. Two common ones are:

D3DPRESENT_INTERVAL_IMMEDIATE—Presents immediately

D3DPRESENT_INTERVAL_DEFAULT—Direct3D will choose the present rate. Usually this is equal to the refresh rate.

An example of filling this structure out is:

D3DPRESENT_PARAMETERS d3dpp;

 

d3dpp.BackBufferWidth

= 800;

d3dpp.BackBufferHeight

= 600;

d3dpp.BackBufferFormat

= D3DFMT_A8R8G8B8; //pixel format

d3dpp.BackBufferCount

= 1;

d3dpp.MultiSampleType

= D3DMULTISAMPLE_NONE;

d3dpp.MultiSampleQuality

= 0;

d3dpp.SwapEffect

= D3DSWAPEFFECT_DISCARD;

d3dpp.hDeviceWindow

= hwnd;

d3dpp.Windowed

= false; // fullscreen

d3dpp.EnableAutoDepthStencil

= true;

d3dpp.AutoDepthStencilFormat

= D3DFMT_D24S8; // depth format

d3dpp.Flags

= 0;

d3dpp.FullScreen_RefreshRateInHz

= D3DPRESENT_RATE_DEFAULT;

d3dpp.PresentationInterval

= D3DPRESENT_INTERVAL_IMMEDIATE;

1.4.4 Creating the IDirect3DDevice9 Interface

With the D3DPRESENT_PARAMETERS filled out, we can create the IDirect3DDevice9 object with the following method:

HRESULT IDirect3D9::CreateDevice(

UINT Adapter,

D3DDEVTYPE DeviceType,

HWND hFocusWindow,

DWORD BehaviorFlags,

D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface

);

Adapter—Specifies the physical display adapter that we want the created IDirect3DDevice9 object to represent

DeviceType—Specifies the device type to use (e.g., hardware device (D3DDEVTYPE_HAL) or software device (D3DDEVTYPE_ REF))

hFocusWindow—Handle to the window that the device will be associated with. This is typically the window that the device will draw onto, and for our purposes it is the same handle that we specify for the data member d3dpp.hDeviceWindow of the

D3DPRESENT_PARAMETERS structure.

Direct3D Initialization 51

BehaviorFlags—Specify either D3DCREATE_HARDWARE_ VERTEXPROCESSING or D3DCREATE_SOFTWARE_VERTEXPROCESSING for this parameter

pPresentationParameters—Specifies an initialized

D3DPRESENT_PARAMETERS instance that defines some of the characteristics of the device

ppReturnedDeviceInterface—Returns the created device

Example call:

IDirect3DDevice9* device = 0; hr = d3d9->CreateDevice(

D3DADAPTER_DEFAULT,

// primary adapter

D3DDEVTYPE_HAL,

// device type

hwnd,

// window associated with device

D3DCREATE_HARDWARE_VERTEXPROCESSING, // vertex processing type

&d3dpp,

//

present parameters

&device);

//

returned created device

if( FAILED(hr) )

{

::MessageBox(0, "CreateDevice() - FAILED", 0, 0); return 0;

}

1.5 Sample Application: Initializing Direct3D

For this chapter’s sample, we initialize a Direct3D application and clear the screen to black (see Figure 1.7).

P a r t I I

Figure 1.7: Screen shot of the sample for this chapter

bool InitD3D(
HINSTANCE hInstance, int width, int height, bool windowed,

52 Chapter 1

This sample and all the samples in this book use code from the d3dUtility.h and d3dUtility.cpp files, which can be found on this chapter’s web page on the book’s web site. These files contain functions that implement common tasks that every Direct3D application will need to do, such as creating a window, initializing Direct3D, and entering the application message loop. By wrapping up these common tasks in functions, the samples are more focused on the particular chapter’s topic. In addition, we add useful utility code to these files as we progress through the book.

1.5.1 d3dUtility.h/cpp

Before we get started on this chapter’s sample, let’s spend some time getting familiar with the functions provided by d3dUtility.h/cpp. The d3dUtility.h file looks like this:

//Include the main Direct3DX header file. This will include the

//other Direct3D header files we need.

#include <d3dx9.h>

namespace d3d

{

// [in] Application instance. // [in] Back buffer dimensions. // [in] Windowed (true)or

// full screen (false). D3DDEVTYPE deviceType, // [in] HAL or REF IDirect3DDevice9** device); // [out] The created device.

int EnterMsgLoop(

bool (*ptr_display)(float timeDelta));

LRESULT CALLBACK WndProc(

HWND hwnd,

UINT msg,

WPARAM wParam,

LPARAM lParam);

template<class T> void Release(T t)

{

if( t )

{

t->Release(); t = 0;

}

}

template<class T> void Delete(T t)

{

if( t )

{

delete t;

Direct3D Initialization 53

t = 0;

}

}

}

InitD3D—This function initializes a main application window and implements the Direct3D initialization code discussed in section 1.4. It outputs a pointer to a created IDirect3DDevice9 interface if the function returns successfully. Observe that the parameters allow us to specify the window’s dimensions and whether it should run in windowed mode or full-screen mode. See the sample code for further details on its implementation.

EnterMsgLoop—This function wraps the application message loop. It takes a pointer to a function that is to be the display func-

tion. The display function is the function that implements the sam-

II

rt

ple’s drawing code. The message loop function needs to know the

Pa

display function so that it can call it and display the scene during

 

idle processing:

 

 

 

int d3d::EnterMsgLoop( bool (*ptr_display)(float timeDelta) )

 

{

 

MSG msg;

 

::ZeroMemory(&msg, sizeof(MSG));

 

static float lastTime = (float)timeGetTime();

 

while(msg.message != WM_QUIT)

 

{

 

if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))

 

{

 

::TranslateMessage(&msg);

 

::DispatchMessage(&msg);

 

}

 

else

 

{

 

float currTime = (float)timeGetTime();

 

float timeDelta = (currTime -

 

lastTime)*0.001f;

 

ptr_display(timeDelta); // call display function

 

lastTime = currTime;

 

}

 

}

 

return msg.wParam;

 

}

 

The “time” code is used to calculate the time elapsed between calls

 

to ptr_display, that is, the time between frames.

 

Release—This template function is designed as a convenience function to release COM interfaces and set them to null.