DirectXTutorial.com
The Ultimate DirectX Tutorial
Sign In
Lesson 4: Rendering Frames
Lesson Overview

Once Direct3D is initialized and the swap chain is set up, it's time to start doing some actual rendering. Rendering itself is very easy, but there is a little bit of preparatory work involved. In this lesson we're going to get everything set up to render a blank frame with a specific color, over and over again.

The setup involved is simple. There are two things that need to be done. First, we need to tell the GPU where in memory to create the final image (for us, this is the back buffer). Second, we need to tell the GPU where on the backbuffer it should draw.

Once that's done, we can render, and man is it easy.

Preparing the Render Target

Let's start with where to render to. Now logically you would say, "the backbuffer, duh!" and be done. However, Direct3D doesn't actually know that at this point. It is possible that you do not want to render to the back buffer right away. For example many games render to the surface of a model, then render that model to the back buffer. This technique can produce a variety of effects. If you have played the game Portal you will have seen an example of this. In Portal, the game engine first renders to the portal, then renders a full scene with the portal image included.

Rendering to a Portal, and Rendering to the Back Buffer

Rendering to a Portal, and Rendering to the Back Buffer

When rendering in Direct3D, you must establish the render target. This is a simple COM object that maintains a location in video memory for you to render into. In most cases (including our case) this is the back buffer.

Here's how we do it:

ComPtr<ID3D11RenderTargetView> rendertarget;    // added to the CGame class

// this function initializes and prepares Direct3D for use
void CGame::Initialize()
{
    // Direct3D initialization
    // ...

    // create the swap chain
    // ...

    // get a pointer directly to the back buffer
    ComPtr<ID3D11Texture2D> backbuffer;
    swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), &backbuffer);

    // create a render target pointing to the back buffer
    dev->CreateRenderTargetView(backbuffer.Get(), nullptr, &rendertarget);

}

Whoa. It looks complex, but it's pretty simple.

There are two things being done here. First, we determine the address of the back buffer. Second, we create a render target object that uses the back buffer as the target.

There's some new code here, so let's find out what it all means.

ID3D11RenderTargetView *rendertarget;

This is a COM interface that holds all the information about the render target and lets us manipulate it.

ComPtr<ID3D11Texture2D> *backbuffer;

In 3D rendering, a texture is another name for an image. An ID3D11Texture2D is an object that stores a flat image. Like any COM object, we first define the pointer, and later a function creates the object for us.

swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), &backbuffer);

GetBuffer() is a function finds the back buffer on the swap chain and creates an interface directly pointing to it.

The first parameter is the number of the back buffer we want to obtain. We are only using one back buffer on this swap chain, and it is back buffer #0. Therefore the first parameter will be 0.

The second parameter is not that new to us. __uuidof() tells GetBuffer() what type of COM interface we want to obtain.

The third parameter is the address of our interface ComPtr. GetBuffer() will store our interface here.

dev->CreateRenderTargetView(backbuffer.Get(), nullptr, &rendertarget);

This function creates the render target object. We defined the ComPtr for this object in our CGame class.

The first parameter is a pointer to the texture. We called this 'backbuffer'. We need the actual pointer to the interface, so we use backbuffer.Get().

The second parameter is a struct that describes the render target. We won't need to fill this out, and using nullptr will set everything to defaults.

The third parameter is the address of the render target interface. We called this 'rendertarget', and we'll just put &rendertarget here.

Setting the Render Target

Once we've created the render target, it has to be set as the active render target. This must be done each frame.

Setting the render target is simpler than creating it. It takes a single command.

// this function renders a single frame of 3D graphics
void CGame::Render()
{
    // set our new render target object as the active render target
    devcon->OMSetRenderTargets(1, rendertarget.GetAddressOf(), nullptr);


    // switch the back buffer and the front buffer
    swapchain->Present(1, 0);
}

devcon->OMSetRenderTargets(1, rendertarget.GetAddressOf(), nullptr);

This function sets our previously created render target as the active render target. More precicely, it sets multiple render targets, though we only have one render target for now.

The first parameter is the number of render targets to set. This will usually be 1, but there are cases where it will be more.

The second parameter is a pointer to a list of render target view objects. Again, we only have one, so it's just the address of our render target interface. We get this using the GetAddressOf() function.

GetAddressOf() is a bit different from Get(). It gets a pointer to a pointer, instead of just a pointer.

The third parameter is advanced, and we'll get into it in a later lesson. For now it can be nullptr.

Clearing the Back Buffer

Next we'll create a function that renders a single frame by setting the entire back buffer to a single color. Of course, you can change the color if you want. Here is the code for this function.

// this function renders a single frame of 3D graphics
void CGame::Render()
{
    // set our new render target object as the active render target
    devcon->OMSetRenderTargets(1, rendertarget.GetAddressOf(), nullptr);

    // clear the back buffer to a deep blue
    float color[4] = {0.0f, 0.2f, 0.4f, 1.0f};
    devcon->ClearRenderTargetView(rendertarget.Get(), color);


    // do 3D rendering on the back buffer here

    // switch the back buffer and the front buffer
    swapchain->Present(1, 0);
}

The ClearRenderTargetView() function sets each pixel in a render target to a specific color.

The first parameter is the address of the render target interface. Easy enough.

The second parameter is an array of four float values. The first is red, the second is green, the third is blue, and the fourth is "transparency", though transparency doesn't do anything in this function.

Each value in the float array is a value between 0.0f and 1.0f. 1.0f means full color and 0.0f means no color. By combining float values, we can create any color we want.

The Finished Program

Excellent! We've got Direct3D running and we've got a simple, blank frame rendering into a window. We're making good progress.

Below, I've added in the new code. Take a look over it, and go ahead and run the new program.

[Game.cpp]     [Game.h]     [App.cpp]     

[Download Solution]

Now that we've got a frame rendering, we should get a blue window!

Our Direct3D Program, Now in Color

Our Direct3D Program, Now in Color
Ready...Set...

Once again, let's look at what we've learned and find out if we're ready to move on.

Questions

1. What is a render target? What function creates the render target?
2. Why is it important to be able to specify the render target?

Exercises

1. See if you can get your program to change colors while running. Make it fade from blue to black.
2. Find out what happens if you set the render target during initialization, but not in the Run() function.

Next Lesson: Drawing a Triangle

GO! GO! GO!