| |
|
|
||||
![]() |
||||||
| |
|
|||||
|
2D
Programming in a 3D World: Special Effects: Alpha Blending, Scaling, and Rotation So far I've touched on how to replace DirectDraw with Direct3D 8 functions, but now that we've done so we have exposed some new functionality that is very easy to exploit. One feature is alpha blending, which provides the ability to make graphics partially or fully transparent. Depending upon the graphics format of your textures, you may have up to 256 levels of alpha, as described earlier, ranging anywhere from 0 for fully transparent to 255 for opaque. A value of 128 would provide 50 percent transparency, and experimenting within the 0-255 range provides the opportunity for some very interesting effects. It is now possible to draw graphics and sprites with a see-through effect, suitable for anything from smoke and fire to heads-up-display (HUD)-type graphics to you get the idea.
There are two ways to take advantage of alpha values. First, you can create your source graphics with an alpha component, allowing you to make a single bitmap with varying degrees of transparency throughout the image. Many graphics editors support alpha values, and you'll need to save in a file format that stores this data (.PNG or .TGA, for example). When you load your image into a texture which has an alpha value in its format, the alpha information is maintained. Then, when you use ID3DXSprite::Draw(), the alpha value is used to decide how to blend your image with the background graphics. The second way to take advantage of alpha values is to use the Color parameter of the ID3DXSprite::Draw() function, which "modulates" the color and alpha channels by the value provided. Ordinarily, you would specify 0xFFFFFFFF for this value to get a standard blit. By changing this to 0x80FFFFFF, you will draw your image at 50 percent transparency. It is also possible to change the other bytes to affect the color of your drawn image, making it very easy and fast to draw a single object in a variety of color shades. The next effect is scaling. This is actually available in the DirectX 7 Blt() command by way of the automatic stretching and shrinking operations, but Direct3D 8 does it much better. In DirectX 7 the results were often unexpected on different video hardware and drivers -- some operations were not supported between video memory surfaces, and sometimes the result of a stretch or shrink would be graphically poor. With 3D hardware these effects are accelerated and usually filtered to improve the display quality -- for instance, a stretch of a texture would have algorithms applied that would reduce pixellation and smooth out the resulting image. Next up in the effects list is rotation. This is another effect that was available in DirectX 7, but support was very haphazard. For hardware that didn't support rotation directly, DirectX only emulated 90-degree angle rotations, and so the effect was not really usable in production code. In Direct3D 8, rotation is a standard feature, calculated into the transformation matrix (which we'll discuss in a moment) and so is universally supported. In Listing 4 I've provided an upgraded version of our BltSprite function, BltSpriteEx(), which supports rotation and color modulation values. Finally, for those wishing to go one step further, and maybe even dip your toes into 3D waters, the ID3DXSprite interfaces offer an alternate version of the Draw() function, called DrawTransform(). Instead of taking scaling, rotation, and translation (positioning) information, the DrawTransform() function takes a transformation matrix, which defines the geometrical transformations for every pixel using a 4x4 matrix. Using this you gain access to effects involving the Z-axis, meaning distances closer to or farther from the viewer. A popular effect is perspective, which would give your image the impression of receding into the distance. The use of transformation matrices is far beyond the scope of this article, but for those interested you can start with "About 3-D Transformations" in the DirectX 8 SDK and go from there. Be prepared to dig out your old math textbooks.
For DirectDraw programmers moving to Direct3D 8, I've pointed out that the first question is usually "Where's the blit function?" We've covered that topic, but the second question, almost as common, is "How do I get GDI functions to work?" In DirectX 7, programmers could get the GDI DC (Device context) handle and then use Win32 GDI commands directly on the surface. While GDI is generally slow, in many situations (particularly where execution speed is not a big issue) GDI provides very robust functions that handle a lot of graphic basics, saving programmers from much of the effort of creating routines from scratch. If nothing else, GDI routines could be used during prototype development, to be replaced by custom optimized routines later on. With Direct3D 8, that option is gone. It is no longer possible to "legally" get a DC handle to the primary surface, and so GDI commands to the screen are not possible. There are two possible solutions to this. First, you can create an off-screen bitmap surface with the CreateCompatibleDC/ CreateCompatibleBitmap() combination, do your GDI commands to this bitmap, and then copy the bitmap data to a texture that you can then draw to the screen. Second, you can replace the GDI commands with new custom versions. In graphics, the two most common uses of GDI appear to be drawing rectangles (filled and unfilled) and drawing text. In Listing 5, I've provided a basic rectangle routine that cheats a bit and uses features of the Direct3D 8 Clear() function to implement filled and unfilled rectangles. The documentation indicates the main use of Clear() would be to, as the name implies, clear a surface, but since you can specify the clear location and color, this also makes a good general-purpose rectangle fill, duplicating the features of the DirectX 7 Blt() with the DDBLT_COLORFILL flag. The Clear() function should use hardware acceleration where available, making it a much better choice than any brute-force pixel routine. For applications that are fully 3D, there are better ways of doing this, but for our purposes this should suffice. For text, the D3DX library provides us with the ID3DXFont interface, which works in a similar way to the ID3DXSprite interface. You need to pass the D3DXCreateFont() function a standard GDI Font Handle (HFONT), and in return you get a pointer to an ID3DXFont interface that has one main function, DrawText(). This function mimics the GDI DrawTextEx() very closely, such that the parameters are almost identical. In fact, the DirectX 8 SDK has a documentation error on the return value from this function, and the actual return value is the same as in the GDI function -- the return value is the text height of the drawn text. The visual quality and spacing of text output from this function is excellent, and there are lots of formatting options, but the drawback is speed. Under the hood, the ID3DXFont::DrawText() function actually does what I discussed above, which is to create a GDI-compatible bitmap, draw the text to the bitmap, and then copy the bitmap to textures, and render the textures to the screen. So you get all the sluggishness of the original GDI functions, combined with a lot of overhead -- in the end, this function is up to 6 times slower than the original GDI DrawTextEx() was. The DirectX 8 SDK provides a sample that offers a solution to this -- the "3D Text" sample program defines a class called CD3DFont, which implements a much faster, though less robust, text solution. It uses GDI only at the time of initialization to create a master texture of available characters, which is then used to draw each individual character from the texture, not requiring further calls to GDI. You lose some nice features such as font kerning and formatting options, but you can always customize the CD3DFont routines to meet your needs.
Despite the difficulties that DirectDraw programmers voice when attempting to move to the new Direct3D 8 API, the transition is actually quite painless. The techniques presented here are oriented towards this goal, and as such are poorly suited for being combined with true 3D programming. In particular, 3D programmers must learn to minimize state changes and operations that may cause "pipeline stalls," which significantly affect the ability of 3D hardware to work independently of your program. Yet, as discussed at the very start of this article, true 3D is not beneficial to every gaming situation, and developers of traditional 2D genres need to think very carefully before adding visual glitz that may, in fact, be detrimental to gameplay. Possible next steps beyond what are demonstrated in this article involve taking a more serious step into the Direct3D 8 API, using vertex buffers and the DrawPrimitive() call to draw polygons on the screen. While at first this seems very intimidating to 2D programmers, it is actually not overly complex to rewrite the D3DXSprite interface routines from scratch and thereby get full control (and some improved performance). The last
word likely belongs to hardware. Video hardware, and the differences from
machine to machine, have long been the curse of game programmers. DirectX,
and in particular version 8, have looked to try to level the field and
offer more consistent features and performance across the board. The downside
is the increased minimum hardware requirements. To take advantage of Direct3D
8, the user must have a 3D accelerated video card with at least a DirectX
6-class driver available. Using the techniques listed here in a full product
would likely result in a minimum 8MB video memory requirement as well.
Unlike previous DirectX releases, version 8 does not provide a software
rasterizer, so emulating 3D hardware on older video cards is not an option.
(DirectX 8 does support plug-in software rasterizers, but at the time
of this writing none are announced or expected.) Some software, such as
, allows the user to choose between two versions of the graphics
engine, one using 2D features and the other one enhanced with many of
the 3D-accelerated effects described above. Developers must consider whether
such duplicated effort is really worthwhile, or whether the growing number
of users with 3D hardware is sufficient to concentrate on for new projects.
______________________________________________________
|
||||||||||||||||||||||||||
|
|