|Using Direct3D for 2D Graphics|
|Written by Markus Ewald|
|Saturday, September 02 2006 18:48|
Sometimes, we're required to render graphics in two dimensions instead of the nowadays usual three. In a 3D game or application, this is not as easy as it sounds because just blitting stuff to the screen requires a dynamic texture and might even stall the GPU's rendering queue, causing performance problems.
This article will show you how to render 2D overlays in a 3D scene taking full advantage of 3D acceleration. First, let's see why we want to go through all the hazzle instead of just using a set of blitting routines to render onto a direct draw surface or texture.
This article isn't finished!
I am going to assume you know how to set up Direct3D, create a device and use it to at least clear the screen. Even if you're new to this, you can use the code accompanying the article or some of the tutorials available on the net. A basic understanding of C++ classes and STL containers is also recommended.
In the progression of this article, we will build a class named
Preparing for 2D
Let's start with something simple. We're going to draw some lines, using a
To render in 2D, we have to use (pre-)transformed vertices. A vertex marks the corner of a polygon or the beginning/ending of a line, pretransformed meaning that it will not be modified by the GPU to account for the camera's position in 3D space. This is the structure for this kind of vertex in Direct3D:
When using such vertices to draw lines and other 2D primitives, there's an interesting fact that we should pay attention to: An even screen coordinate will not hit a pixel's center, but lie exactly between two pixels. If you draw with such coordinates, small rounding errors will decide which texel (pixel on a texture) of the bitmap is chosen for a pixel, generating lots of slightly dislocated pixels for any sprite or bitmap we draw. To combat this, all we have to do is offset the screen coordinates by 0.5 pixels to the upper left.
As you probably know, graphics cards can render large amounts of polygons, but to eliminate the call-overhead and processing cost, these polygons have to be sent to the graphics card in larger batches. In order to obtain good rendering performance, we'll need to build such batches from the vertices generated for the 2D primitives that are drawn.
Usually, when we render two-dimensional stuff, the depth buffer needs to be disabled, lighting calculations avoided and culling of of backfaces usually is of no relevance to us. In Direct3D, these features are disabled by setting various RenderStates accordingly.
Let's start with a small class that batches vertices for lines and sends them to the Direct3D device for rendering, using a dynamic vertex buffer:
As you can see, the
When you call render, it will lock the vertex buffer (usually using
Next, we're going to add the capability to render rectangles with our
Sounds more complicated than it is. We'll extend the
Whenever a drawing method of the
A working implementation of the
Using bitmaps in the context of 3D accelerators means using textures, which have
the habit of being forced into sizes that are a power of 2. But we'll take care of
that later. To support rectangle rendering in our lines-only
Doing hundreds of texture switches, one for each bitmap you're going to draw, would certainly kill rendering performance, so we'd like to eliminate texture switches. We also want to be able to render bitmaps of arbitrary sizes, maybe for drawing text to the screen, maybe just for convenience. There's a good working solution for all this, althought it's a quite complicated one.
All bitmaps that are rendered have to be copied into large internal cache textures by