DirectXTutorial.com
The Ultimate DirectX Tutorial
Lesson 1:  Getting Started with Direct3D
Log In
Lesson Overview

First of all, I officially welcome you to Direct3D.  I would like to teach you both the basics and the advanced topics of 3D programming.  Whether you want to build you own engine, borrow one and modify it, or just buy one and use it, it is important that you understand the concepts underlying it.

Just as important as the underlying concepts of a game engine, are the concepts underlying DirectX itself.  This lesson will cover the basics of Direct3D and what you need to know to you punch out your first DirectX program.  We will then build a basic Direct3D program inside of a window.

Note:  For beginning programmers, starting Direct3D can be a daunting task.  But never fear, it's no more difficult than creating a window, and, like the window, it only needs to be done once.

COM

And COM is what?

COM stands for Component Object Model.  COM is a method of creating very advanced objects that, well, they act a lot like Legos actually.

Legos, as you know, can be stuck together to create more advanced shapes.  No single Lego actually cares about any other Lego in the set.  They are all compatible with each other, and all you have to do is stick them together to get them to work.  If you want to change pieces, all you have to do is unplug one piece and put another in its place.

And so it is with COM.  COM objects are actually C++ classes or groups of classes from which you can call functions and achieve certain aims.  No class requires another to operate, and they don't really need to work together to get things done together, but you can plug them in or unplug them as you desire without changing the rest of the program also.

For example, say you had a game distributed broadly and you wanted to upgrade it.  Well, instead of keeping track of and shipping a new copy to every single user who ever bought your game, all you have to do is say "Upgrade!  Right Here!".  They download the updated COM object, and the new object plugs right in to your program without further hassle.  Nice, huh?

I won't get too detailed into COM, because it is far too complex for what we need.  It's job is to get all the complex stuff out of the way so that you have an easy time.  And if that's its job, what would be the purpose of learning all that complex material?

So why COM?  Well, DirectX is actually a series of COM objects, one of which is Direct3D.  Direct3D is a COM object that has other COM objects inside it.  Ultimately, it contains everything you need to run 2D and 3D graphics using software, hardware, or whateverware.

So because Direct3D is already stored in classes, don't be surprised when you see Direct3D functions being called like this:

device->CreateRenderTargetView()

device->Release()

We use the indirect member access operator to access the functions CreateRenderTargetView() and Release() from a Direct3D class.  We'll get more into this when we see how it is applied in practice.  I'm going to try to avoid unneeded theory from here on out.

Even though COM's job is to hide all the complexity, there are four things you need to know about it.

1. A COM object is a class or set of classes controlled by an interface.  An interface is a set of functions that, well, controls a COM object.  In the example above, "device" is a COM object, and the functions control it.

2. Each type of COM object has a unique ID.  For example, the Direct3D object has its own ID, and the DirectSound object has its own ID.  Sometimes you need to use this ID in the code.

3. When done using a COM object, you must always call the function Release().  This will tell the object to free its memory and close its threads.

4. COM objects are easy to identify, because they typically start with an 'I', such as 'ID3D10Device'.

Don't worry about the details of these four points.  We'll see how they are all applied in a moment.

For now let's go on to the next topic:

Under the Direct3D Hood

Direct3D is a very complicated API, with many COM objects and many other parts which are never seen or directly interacted with.  In order to keep from being lost in all the code, it's important to understand a few basic components that exist as part of Direct3D, and some that work with it from the outside.


The Swap Chain

A graphics adapter contains in its memory a pointer to a buffer of pixels that contains the image currently being displayed on the screen.  When you need to render something, such as a 3D model or image, the graphics adapter updates this array and sends the information to the monitor to display.  The monitor then redraws the screen from top to bottom, adding in the new part rendered.

However, there is a slight problem with this in that the monitor does not refresh as fast as needed for real-time rendering.  Most refresh rates range from 60 Hz (60 fps) to about 100 Hz.  If another model were rendered to the graphics adapter while the monitor was refreshing, the image displayed would be cut in two, the top half containing the old image and the bottom half containing the new.  This effect is called tearing.

To avoid this, DirectX implements a feature called swapping.

Instead of rendering new images directly to the monitor, Direct3D draws your images onto a secondary buffer of pixels, known as a back buffer.  The front buffer would be the buffer currently being displayed.  You draw all your images onto the back buffer, and when you are done, Direct3D will update the front buffer with the contents of the back buffer, discarding the old image.

However, doing this can still cause tearing, because the image transfer can still occur while the monitor is refreshing (the GPU is faster than the monitor).

In order to avoid this (and to make the whole thing go much faster), DirectX uses a pointer for each buffer (both front and back) and simply switches their values.  The back buffer then becomes the front buffer (and vice versa), and no tearing occurs.

Image 1.1 - Addresses Swap Instantly

Image 1.1 - Addresses Swap Instantly

Of course, we could make our game have better performance by adding additional back buffers, like this.

Image 1.2 - Multiple Back Buffers Gets Better Performance

Image 1.2 - Multiple Back Buffers Gets Better Performance

How can adding additional back buffers get better performance?  Well, let's say that every once in a while you finish rendering the back buffer and are ready to swap, but the screen hasn't yet finished drawing the contents of the front buffer.  Swapping now would cause more tearing.  So what happens instead is your program stops and waits for the screen to finish.  You could, of course, be spending this valuable time preparing the next image, and having multiple back buffers allows your program to do just that.

This setup called a swap chain, as it is a chain of buffers, swapping positions each time a new frame is rendered.


DirectX Graphics Infrastructure (DXGI)

The DirectX Graphics Infrastructure is a component that lies at the base of all the most recent versions of Direct3D.  Its job is to handle fundamental tasks such as displaying images on the screen, managing the swap chain, and finding out what resolutions the monitor and video card can handle.

DXGI is not actually a part of Direct3D.  It underlies it and other graphics components, and it acts as an interface between Direct3D and the hardware.

Image 1.3 - DirectX Graphics Infrastructure

Image 1.3 - DirectX Graphics Infrastructure

There are ways to deal with DXGI directly, but we will not get into these.  All that's important is that you are aware this component exists, and that it is not directly a part of Direct3D.


The Rendering Pipeline

If there is one thing you will hear about a lot in Direct3D, it is the rendering pipeline.  This is because the rendering pipeline is where everything happens.

The rendering pipeline is the process which produces a rendered 3D image on the screen.  It works like an assembly-line, one step after another.  It consists of steps which are executed on the video card's GPU.

Image 1.4 - The Rendering Pipeline

Image 1.4 - The Rendering Pipeline

The Input-Assembler Stage is the first step in the pipeline.  It's responsibility is to collect information from system memory and the CPU about the 3D models you wish to render.  It then compiles them and gets them ready for rendering.

The programmable shader stages are a series of steps that convert that information into an image.   They are run one model at a time, resulting in a separate image for each model.  A shader is a program that is executed on the GPU.  This means that all rendering occurs on the graphics hardware, rather than the CPU.  We'll cover these stages in detail in Lesson 5.

The Output-Merger Stage is the final step in the pipeline.  It's job is to combine the individual model images into one whole image, and to place that image correctly on the back buffer, so it can appear on the screen.


Render Targets

Sometimes you don't want to render to the back buffer.  Sometimes it is necessary to render a 3D image, and draw that image on the surface of another object.  If you've played the game Portal, you've seen an example of this.

Image 1.5 - Rendering to a Portal, and Rendering to the Back Buffer

Image 1.5 - Rendering to a Portal, and Rendering to the Back Buffer

Direct3D contains the concept of a render target.  This is an address that tells Direct3D where to draw the models.  In Portal, there are actually two rendering cycles.  The first renders a full 3D image and draws it on the surface of the wall.  The second then renders the actual scene, in which you can see the first image.

For Direct3D to work, it needs to know the render target.  For us, this will be the back buffer.  We'll talk about how to tell Direct3D this in a moment.

Initializing Direct3D

We won't be making a "Hello World!" application for Direct3D.  We will assume that Direct3D is not a language on its own (which it isn't).  Instead, we will start by filling our window with blue.  Before we do this, we must initialize Direct3D.

However, before we initialize Direct3D in the first place, we need to add a few things to the top of our program.  Let's take a look at these and see what they are.

// include the basic windows header files and the Direct3D header files
#include <windows.h>
#include <windowsx.h>
#include <d3d10.h>
#include <d3dx10.h>


// include the Direct3D Library file
#pragma comment (lib, "d3d10.lib")
#pragma comment (lib, "d3dx10.lib")


// global declarations
ID3D10Device* device;    // the pointer to our Direct3D device interface
ID3D10RenderTargetView* rtv;    // the pointer to the render target view
IDXGISwapChain* swapchain;    // the pointer to the swap chain class

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

#include <d3d10.h>
#include <d3dx10.h>

This includes the Direct3D 10 header files.  These files consist of various declarations to the actual methods contained in the Direct3D library.

#pragma comment (lib, "d3d10.lib")
#pragma comment (lib, "d3dx10.lib")

This includes the Direct3D 10 library files.  The #pragma comment directive places a certain piece of information in your project's object file.  With our first paramter, lib, we indicate that we want to add a library file to the project.  We then specify which file, "d3d10.lib" and "d3dx10.lib".

ID3D10Device* device;

This variable is a pointer to what could be considered the core of Direct3D.  It is known as a device.  What this line means is that we will create a class called an ID3D10Device using COM.  When COM makes this class, we will ignore it, and access it only indirectly using this pointer.  We will cover how the class is created in a moment.

A device is a class that holds all the information pertaining to the graphics drivers, the video card, and everything else having to do with the hardware side of graphics.  It's responsible for letting your program work with the video card, allocating memory for graphics data, and more.

ID3D10RenderTargetView* rtv;

In Direct3D, a view is an object which indicates what part of an image the graphics hardware is able to work with.  This variable is a pointer to an interface that shows where to render to.  We will be rendering to the window we created earlier.  We'll cover how this is done in a moment.

Just like with the device, we will create a pointer to the interface now, and create the interface itself later.

IDXGISwapChain* swapchain;

As we covered before, the swap chain is the series of buffers which take turns being rendered on.  This variable is a pointer to such a chain.

Notice that this object does not belong to Direct3D, but is actually part of the DXGI, underlying Direct3D.


The initD3D() Function

The first step to actually coding Direct3D is to create the interfaces and initialize them.  This is done using a set of functions and structs.

Let's take a look at this function here, which we've called initD3D(), then go over its parts.  I didn't bother to bold the new parts, because the entire thing is new.

// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    DXGI_SWAP_CHAIN_DESC scd;    // create a struct to hold various swap chain information

    ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));    // clear out the struct for use

    scd.BufferCount = 1;    // create two buffers, one for the front, one for the back
    scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;    // use 32-bit color
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;    // tell how the chain is to be used
    scd.OutputWindow = hWnd;    // set the window to be used by Direct3D
    scd.SampleDesc.Count = 1;    // set the level of multi-sampling
    scd.SampleDesc.Quality = 0;    // set the quality of multi-sampling
    scd.Windowed = TRUE;    // set to windowed or full-screen mode

    // create a device class and swap chain class using the information in the scd struct
    D3D10CreateDeviceAndSwapChain(NULL,
                                  D3D10_DRIVER_TYPE_HARDWARE,
                                  NULL,
                                  0,
                                  D3D10_SDK_VERSION,
                                  &scd,
                                  &swapchain,
                                  &device);

    // get the address of the back buffer and use it to create the render target
    ID3D10Texture2D* pBackBuffer;
    swapchain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
    device->CreateRenderTargetView(pBackBuffer, NULL, &rtv);
    pBackBuffer->Release();

    // set the render target as the back buffer
    device->OMSetRenderTargets(1, &rtv, NULL);

    D3D10_VIEWPORT viewport;    // create a struct to hold the viewport data

    ZeroMemory(&viewport, sizeof(D3D10_VIEWPORT));    // clear out the struct for use

    viewport.TopLeftX = 0;    // set the left to 0
    viewport.TopLeftY = 0;    // set the top to 0
    viewport.Width = 800;    // set the width to the window's width
    viewport.Height = 600;    // set the height to the window's height

    device->RSSetViewports(1, &viewport);    // set the viewport
}

This function is best described if broken down into three main parts:

1. First, we create a COM object representing Direct3D.
2. Second, we set Direct3D's render target as the back buffer.
3. Third, we tell Direct3D where on the back buffer it should draw.


Creating the COM Object for Direct3D

If the comments in the code are good enough for you, excellent.  Otherwise, I've described each of these commands below.

DXGI_SWAP_CHAIN_DESC scd;

This is where the fun begins.  Here is the first line of DirectX code you will run.  It is a struct that contains information about how Direct3D will operate.  More specifically, it contains all the properties of the swap chain.  We won't cover all the values of the struct, but we'll cover all the mandatory ones and cover new members as they come up throughout the tutorial.

We're going to abbreviate swap chain description and call the struct 'scd'.

ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

We use ZeroMemory() to quickly initialize the entire scd struct to NULL.  That way we don't have to go through every member of the struct and initialize them individually.

scd.BufferCount = 1;

This member contains the number of back buffers to use.  We'll only be using one back buffer and one front buffer.  Therefore, we'll set this value to 1.  We could use more, but 1 will probably be enough for anything we'll do.

scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

This next member we will use to set the format of the colors.  On the front and back buffers, each pixel is stored by color.  This value determines what format that data is stored in.  For our example, we've used DXGI_FORMAT_R8G8B8A8_UNORM.

The R8G8B8A8 indicates that there is 1 byte (8 bits) for each color channel: red, green, blue and alpha.  Naturally, red, green and blue are the primary colors of light.  Alpha is an additional value which can be used to set the transparency of the pixel, so it shows a little of what is behind it.  We'll get into this more later.

The UNORM part at the end means that when we refer to colors, we want to refer to them as a value between 0.0 and 1.0, where 0.0 is no color and 1.0 is full color.  We want to do this because this is the way the video card stores color data, so it's the most efficient.

We'll get more into colors and how they are stored in Lesson 3.

scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

These things just keep getting longer and longer!  This member tells how we intend to use our swap chain.  Render target ouput means that this swap chain is intended to be used as the render target.

scd.OutputWindow = hWnd;

This value sets the handle to the window Direct3D should use.  We'll just use the same hWnd we've always been using.

scd.SampleDesc.Count = 1;

This member is advanced and a bit beyond this lesson, but I'll describe what it does so you're not in mystery.  It is used to store the number of multisamples used by Direct3D.

Multisampling is where Direct3D takes pixel and looks at the other pixels around it.  It then uses averages to determine what the final color of the pixel should be.

Image 1.6 - Multisampling 

Image 1.6 - Multisampling

The effect is shown in the image above.  On the left, you can see the line has created a stair-like effect.  On the right, the image is a little bit smoothed because Direct3D looked around each pixel and blended them together.

This value sets the number of times Direct3D will do this.  We will put 1 in here, which tells Direct3D to only look at the pixel itself.  This will look ugly for now, but we'll fix it a bit later when we've covered more of Direct3D.

scd.Windowed = TRUE;

When we run Direct3D in a window like we are now, this value is set to TRUE.  Otherwise, it is set to FALSE.

Note:  There are other changes to make when an application goes full-screen.  Changing this one value will not make your application properly full-screen unfortunately.  Patience until the next lesson.

D3D10CreateDeviceAndSwapChain()

This is a big function, but in actual fact, it is quite simple.  Most of the parameters will probably stay the same in every game you write.

This function does two things.  First, it creates a graphics device interface.  What this means is a new class will be created from which we will handle all the graphics we need.  Second, it creates a swap chain using the properties given in the scd struct.

Here's the function prototype:

HRESULT D3D10CreateDeviceAndSwapChain(
    IDXGIAdapter* pAdapter,
    D3D10_DRIVER_TYPE DriverType,
    HMODULE Software,
    UINT Flags,
    UINT SDKVersion,
    DXGI_SWAP_CHAIN_DESC* pSwapChainDesc,
    IDXGISwapChain** ppSwapChain,
    ID3D10Device** ppDevice);

Now let's go into the parameters of this function.  They are all simple, so this will go fast.

IDXGIAdapter* pAdapter,

This is a value that indicates what graphics adapter, or video card, Direct3D should use.  We could get detailed here, and try to find the best graphics card available, but we'll let DXGI take care of that for us (because in most cases there's only one).  To tell DXGI that it needs to decide, we put into this parameter the value NULL, indicating the default graphics card.

D3D10_DRIVER_TYPE DriverType,

This parameter is used to determine whether Direct3D should use hardware or software for rendering.  Naturally, we would prefer hardware.  To indicate this, we use the value D3D10_DRIVER_TYPE_HARDWARE.

At this point, not everyone has a DirectX 10 video card.  If you are not currently enjoying one, you will have to use D3D10_DRIVER_TYPE_REFERENCE.  All the DirectX 10 features will work, though it is quite slow.

HMODULE Software,

We won't get into this parameter.  It is used in cases where you wish to bypass the hardware and have everything rendered with your own software.  As this is typically slow, it's definitely not what we want.  We'll just set this value to NULL.

UINT Flags,

In this parameter, we have a number of flag-values which we can use to alter the behavior of Direct3D.  These can be logically ORed together, used by themselves, or you can just put 0 here to use none.  The table below shows a few of the possible values.

[Table 1.1 - Device Creation Flags]

UINT SDKVersion,

This parameter is always the same: D3D10_SDK_VERSION.  Why is this?  Well, it really only matters for compatibility on other machines.  When a later release of DirectX is published, the users will usually have varying levels of DirectX upgrades.  This tells the user's DirectX which version you developed your game for.  The user's DirectX can then look back and properly execute your program without implementing the changes that have occurred since then.  Kinda useful, eh?

In different versions of the SDK, this value returns different numbers.  Note that you should not change this value, as it will cause confusion and is not really needed anyway.  Just use D3D10_SDK_VERSION, and everything will work out all right.

DXGI_SWAP_CHAIN_DESC* pSwapChainDesc,

This is a pointer to the scd struct that we filled out earlier.  We just put '&scd'.

IDXGISwapChain** ppSwapChain,

This is a pointer to a pointer to the swap chain interface (if you can understand that).  Earlier in the program we defined this pointer as 'swapchain', so we will put '&swapchain' in this parameter.

The function will then create the swap chain interface behind the scenes, and fill this pointer with its address.  Easy!  It does all the work for us!

ID3D10Device** ppDevice,

This is a pointer to a pointer to the graphics device interface.  We defined this pointer as 'device', so we will put '&device' in this parameter.

Like the swap chain, this function will create the device, and store the address in our pointer, 'device'.

And that's all there is to the D3D10CreateDeviceAndSwapChain() function!  There are few functions in DirectX that are quite that complex, so there's no need to panic.


Setting the Render Target

The next step to initializing Direct3D is to set the render target.  Remember, this is going to be the back buffer.

Here's the code in the function that does this:

    // ...

    // get the address of the back buffer and use it to create the render target
    ID3D10Texture2D* pBackBuffer;
    swapchain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
    device->CreateRenderTargetView(pBackBuffer, NULL, &rtv);
    pBackBuffer->Release();

    // set the render target as the back buffer
    device->OMSetRenderTargets(1, &rtv, NULL);

    // ...

Let's take up each line and see how it all works.

ID3D10Texture2D* pBackBuffer;

In 3D rendering, a texture is another name for an image.  An ID3D10Texture2D is a COM object which stores a flat texture.  On this line we've created a pointer to a texture object.  In this pointer we're going to store the address of the back buffer.

swapchain->GetBuffer(0, __uuidof(pBackBuffer), (void**)&pBackBuffer);

You may not believe me, but this command is actually simple.  What this GetBuffer() function does is get the address of the back buffer and store it in pBackBuffer.

The first parameter is the number of the back buffer to get.  We only have one back buffer, and it is back buffer #0.  Therefore, the first parameter will be 0.

The second parameter is a number identifying the ID3D10Texture2D COM object.  Each type of COM object has it's own unique ID that is used to get information about it.  To get this ID, we can use the __uuidof operator.  The exact details of how this works are unimportant, but the reason we do it is so that the GetBuffer() function knows what format to provide the data in.

Unless you are at least intermediate with C++, the third parameter will probably make no sense to you.  Basically a void* is a pointer that points to no particular type of variable.  For example, a double* points to a double, whereas an int* points to an int.  Each type of pointer is handled differently, and so it can't convert to another type without causing confusion.  A void* points to any of these, and can be converted without trouble.

In the third parameter, we have a pointer to a void*.  This pointer gets filled with the location of the buffer.  The GetBuffer() function takes care of all the details for you.  All you need to know is that pBackBuffer will contain the location of the back buffer.

device->CreateRenderTargetView(&pBackBuffer, NULL, &rtv);

In this command we will create the render target.  A render target is actually a separate COM object, and to make it requires its own separate function.

The first parameter here is the pointer to the target itself.  In our case, this is the back buffer.  Therefore, we'll put the address of the back buffer in here: &pBackBuffer.

The second parameter is advanced and we won't get into it.  We'll just leave it NULL.

The last parameter is a pointer to the render target object.  In the beginning of the program we created one and named it 'rtv'.  Therefore, this parameter will be '&rtv'.  'rtv' will then contain the address of the render target COM object.

pBackBuffer->Release();

Here, we call the Release() function for the texture object we created, pBackBuffer.  Release is a simple function that most COM objects have.  No parameters, nothing spectacular.  It just cleans everything up, deleting any memory the object may have allocated.

Why?  Well, let's say it would be a bad thing to do otherwise.  Basically, if you create a COM object, but never close it, it will just keep on running in the background of the computer until your next reboot, even after the program itself closes.  Bad.  Especially bad if you have a lot of resources in your game.  Releasing COM objects let's everything off the hook and allows Windows to take back it's memory.

We are releasing pBackBuffer now because we don't need it anymore.  All we needed it for was to create the render target object, and we've done that.

device->OMSetRenderTargets(1, &rtv, NULL);

After creating the render target object, we must set it.  OMSetRenderTargets does just that.  OM stands for output-merger, the last stage of rendering.

The first parameter is the number of render target objects we wish to set.  Putting more than one would just be complicated, and we only have one anyway.  So this value will be 1.

The second parameter is the pointer to the render target object.  We'll put '&rtv' here.

The third parameter is advanced and we'll cover it a little later in the tutorial.  We can just put NULL for now.


Setting the Viewport

That section was a bit more complex than the first.  This third and last section will be a piece of cake:

    // ...

    D3D10_VIEWPORT viewport;    // create a struct to hold the viewport data
    ZeroMemory(&viewport, sizeof(D3D10_VIEWPORT));    // clear out the struct for use

    viewport.TopLeftX = 0;    // set the left to 0
    viewport.TopLeftY = 0;    // set the top to 0
    viewport.Width = 800;    // set the width to the window's width
    viewport.Height = 600;    // set the height to the window's height

    device->RSSetViewports(1, &viewport);    // set the viewport
}

This section sets up a viewport.  This tells Direct3D where in the window it is allowed to draw. 

D3D10_VIEWPORT viewport;

This is a struct that contains the parameters of the viewport.  We would just use a RECT, but there is actually more information in this struct, which we will cover in a later lesson.

ZeroMemory(&viewport, sizeof(D3D10_VIEWPORT));

Just like the last struct we dealt with, we will zero it out before using it.

viewport.TopLeftX = 0;
viewport.TopLeftY = 0;

These two values in the struct set the top-left corner of where we can render.  These will be zeroes, representing the top-left corner of the window.

viewport.Width = 800;
viewport.Height = 600;

These two values represent the width and height of where we can render.  We'll set these to be the same as the size of the window.

device->RSSetViewports(1, &viewport);

Now that we've filled out the viewport struct, we set it using the RSSetViewports() function.

The first parameter tells how many viewports to set.  We could make an array of viewport structs and set more, but we only have one, so we'll put 1 in this parameter.

The second parameter is the address of the viewport struct.


At this point, Direct3D is up and running.  However, if you run this program (and you shouldn't just yet), you will find that absolutely nothing different occurs.  All we have done at this point is prepare for action.  Now let's have some action!

Rendering Frames

Next we'll render an empty frame.  Rendering an empty frame is far, far easier than initializing Direct3D.  It takes a whopping two lines of code to accomplish.

Despite the simplicity, we'll create a new function called render_frame().

In this function we will render a single frame.  The frame will be rather simple, and will consist of a blue background.  Of course, you can change the color if you want.  Here is the code for this function.

// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    device->ClearRenderTargetView(rtv, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));

    // do 3D rendering on the back buffer here

    // display the rendered frame
    swapchain->Present(0, 0);
}

device->ClearRenderTargetView()

This fills a render target buffer with a specific color.  In our case, we are going to fill our back buffer.  It's pretty simple.

There are two parameters.  The first one is the address of the render target object.  We just put 'rtv' here.

The second parameter is the color you want to fill the back buffer with.  To do this, we use a simple struct called D3DXCOLOR.  The four constructor parameters are used to build the color.  The first three are the red, green and blue values, which are mixed together to determine the actual color.  These exist as values between 0.0f (black) and 1.0f (full color).  The fourth parameter is the alpha, and we'll get into this one in a later lesson.  For now just set it to 1.0f.

swapchain->Present()

Next we call the Present() function.  The two parameters, both set to 0, are not going to be used much in this tutorial.

This function is where everything that has been drawn actually appears.  It's job is basically to perform the "swap" in the swap chain, so that the back buffer becomes the front buffer.

Closing Direct3D

This is the last (and easiest) step to creating a Direct3D program.  We will pack it away into its own function called cleanD3D().

We just have three commands in here, with no parameters:

// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    swapchain->Release();    // close and release the swap chain
    rtv->Release();    // close and release the render target view
    device->Release();    // close and release the 3D device
}

Here, we call the Release() function from each of the three interfaces we created, sc, rtv, and device.  We run these at the end of the program before we close down.

Remember, not doing this step will mean that the Direct3D objects will still exist in the background, even after the program has closed.  It's very important to call Release() on every COM object that has the Release() function.

The Finished Program

Wow!  That was quite a start, but things will get easier from here on.

Note:  Remember, if you don't have your DirectX 10 card yet, you'll have to use reference mode, or the program won't run.

Let's take a look at what we just did.  Following is the code we added to our program.  The new parts are now in bold.

[Show Code]

And that's it!  If you run this program you should get something like this:

Image 1.7 - Our First Direct3D Program 

Image 1.7 - Our First Direct3D Program
Test Yourself

Are you ready to go on?

Well, it isn't much yet.  But you have begun the journey into the near-inifinite depths of 3D game programming.  You've created a window and gotten DirectX to put an image (albeit a somewhat dull image) into it.

At the end of each lesson, I'll ask a few questions and give a few exercises.  If you can do these, you'll be ready for the next lesson.

Questions

1. How do you call a COM-based function?  Why do you have to "release" them?
2. What is the rendering pipeline?

Exercises

1. See if you can get your program to change colors while running.  Make it fade from blue to black.
2. Change the values of the viewport and see what "happens".  We'll go over why this is in Lesson 4.

So we haven't got a game running yet, but we have enough concepts to start.  So with that, let's dive right in and start your next step (which is rather short, btw)!

Next Lesson:  Going Fullscreen

GO! GO! GO!