Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
64 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Introduction
 The Basic Blit
 Final Word

 Printable version
 Discuss this article

The Basic Blit

A new blitter is needed to address these problems. We start by defining a custom vertex type and a corresponding FVF for the vertices of our textured quad (D3DTLVERTEX and D3DFVF_TLVERTEX in the demo).

We can use transformed, lit vertices because we already know the screen coordinates of our sprite. This lets us bypass the usual 3D transformations (world, view, and projection) and lighting calculations in the rendering pipeline [1b]. It's more efficient but it also means more work on our part.

Each vertex corresponds to a texture coordinate (tu, tv), indicating how the texture should be mapped (see Blit in the demo). Texture coordinates range from 0.0 to 1.0, with (0, 0) being the top-left pixel. The system can process values outside this range [1b]. It is possible to specify a source rectangle that's larger than the texture (strange things happen when you do).

Texture coordinates let you use a portion of the texture. It is easy to map a source rectangle (as in ID3DXSprite::Draw) into texture coordinates. This lets the user draw a subsection of the sprite. (The demo does not implement this feature.)

The colour argument should be set to 0xFFFFFFFF (ARGB) for a standard blit. The RGB and alpha channels are modified by this argument: specifying 0x80FFFFFF will draw images at 50% transparency and 0xFF00FF00 gives them a green tint. Different colours can be set for each vertex to create lighting effects.

More realistic lighting can be achieved through the use of light maps. A light map is a texture, or group of textures, that contain lighting information [1d]. Light mapping is a big topic; consult the DirectX documentation for more information.

We can now to draw our sprite (or "textured quad") but we might want to scale or rotate it first.

Transforming the Vertices

Because of the vertex format we've chosen, we cannot use the world matrix to transform our sprite. Fortunately, transforming the sprite is as easy as transforming its vertices: a texture can be mapped onto any primitive regardless of its orientation or position.

Scaling and rotation must be performed with respect to a point, usually the centre of the sprite. We need to translate the sprite such that this point is on the origin, scale and/or rotate it, and then translate it back to its original position. All these operations can be concatenated into one transformation matrix.

The quad has four vertices and there are four rows in a Direct3D matrix. We can make a matrix out of these four vertices and use Direct3D's matrix multiplication functions to transform our vertices. (See TransformVertices in the demo.

Reflection is identical to scaling with a negative number. Another way to reflect sprites is to reverse the texture coordinates (tu, tv). To reflect about the

  1. x-axis, make tu = (1 - tu)
  2. y-axis, make tv = (1 - tv)

For example, reflecting about the x-axis looks like this

Sprite (x, y) Normal (tu, tv) Reflect (1 - tu, tv)
(left, top) (0, 0) (1, 0)
(right, top) (1, 0) (0, 0)
(right, bottom) (1, 1) (0, 1)
(left, bottom) (0, 1) (1, 1)

Preparing to Blit

There are three things we need to set up before we call our blit function (see InitBlit in the demo).

  1. Enable alpha blending. Colour keying (or setting a transparent colour) is done through alpha blending in Direct3D. Alpha blending gives us varying degrees of transparency.
  2. Allow alpha and colour modulation for transparency and tinting effects. The colour argument in Blit will not work without this.
  3. Turn off back face culling so that we can see the back of primitives. This lets us reflect our sprite (to change the direction that it is facing). With culling on (the default), a reflected sprite is not drawn.

Important: set BackBufferWidth and BackBufferHeight (in D3DPRESENT_PARAMETERS) to zero if you are using Windowed mode. Otherwise, your sprites will be rescaled and they will not match their original size.

Performance Tips

The SetTexture command represents a render-state change, something that you want to minimise. You'll improve performance if you can use one SetTexture to draw all identical sprites in a frame. You can, however, get away with calling SetTexture for every bitmap in the frame because the performance hit is not too high. (See Blit in the demo.)

Alternatively, one texture can contain multiple sprites but beware of size restrictions (covered next). You'll need to implement a source rectangle argument for your blit function first (discussed earlier in The Basic Blit).

More "Performance Optimizations" can be found in the DirectX documentation.

Big Bad Textures

It is convenient to keep related sprite images in one bitmap file but we cannot simply load a big bitmap into a texture because of size restrictions. Most video cards place a maximum limit on the size of textures, which can be as low as 256x256 on older cards.

The DirectX documentation also suggests keeping textures small: "the smaller the textures are, the better chance they have of being maintained in the main CPU's secondary cache" [1d].

What we need is a way to break up a (potentially large) bitmap into smaller images to be stored in textures. The easiest way to do this is to load the entire bitmap into a Direct3D surface (which have no size restrictions) and then use CopyRects to copy surface sub-images into textures.

The demo program uses D3DFMT_A1R5G5B5, a 16-bit surface format where five bits are reserved for each colour and one bit is reserved for alpha transparency. Other suitable surface formats are D3DFMT_A8R8G8B8 and D3DFMT_A4R4G4B4. (See CreateSurfaceFromFilein the demo.

You need an alpha component in your surface format for transparency, and an RGB component for the colours to display correctly. The exception to this rule is D3DFMT_A8R3G3B2, which (in my experience) has insufficient RGB values to produce a good range of colours and often results in an incomprehensible grey image.

The same surface format is used for the textures. (You will not be able to copy data between textures and surface otherwise.)

In most video cards, the width and height of a texture must be a power of two. A 20x40 image will be stored in a 32x64 texture. This extra area within our texture must be set to a transparent colour or random colours may surround your sprites.

Newer video cards let you create textures that are not limited to powers of two.

To clear the texture we need to create a blank surface and then copy its content over to the texture. Can't we clear the texture directly, you ask? We can but textures placed in the D3DPOOL_DEFAULT pool cannot be locked for clearing [1g]. Placing our textures in D3DPOOL_MANAGED memory will allow us to lock the texture but the copy method works for both cases so it is more flexible. See CreateTextureFromSurface in the demo.

Finally, we can copy the bitmap image over to the texture.





Next : Final Word