ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!
Creating and Using Alpha Channels
by MetalWarrior aka Jeff Smith
Welcome to yet another fabulous (or not) tutorial by me. In this tutorial, I'll explain how to use alpha channels to create some really neat effects. So what's an alpha channel? Well, I assume everyone's familiar with the 32-bit color format: ARGB - or, spelled out, Alpha-Red-Green-Blue. The Alpha part of that is an extra byte set aside for alpha blending. It lets you have a different level of translucency for each and every pixel in a texture. This extra color channel is called the alpha channel. It gives us amazing abilities too... for example, think about a fireball. How good would it look if the entire thing was blended with the same translucency level, say, 50%? Well, it might look pretty good, but it would look a whole lot better if some areas were more opaque while others were more transparent. This kind of effect can only be done with an alpha channel.
Now that you're all jumping up and down for joy, let's look at how to do this. :) There are two parts to this tutorial: Creating a surface with an alpha channel, and loading that surface onto a texture. It might seem odd, but I'm actually using different versions of DirectX for the two parts! Why? Because I can >:) Actually, no, there are good reasons for it. The creating step is done in DirectX7. DX7 is more suited to windowed mode programming, and since we don't need to draw anything to the screen in order to create our alpha surfaces, DX7 makes more sense. Plus, the windowed environment is ideal for editors, which is exactly where you should be creating your alpha surfaces. The creation step is not recommended for on-load in your game. Instead, when your game loads, you should just be loading the surface out of a file. This is the second part of the tutorial, which uses DirectX8. I'm using DX8 because it's easier to convert between formats then in DX7. And I just like it, so there. :)
Alright! You made it through those two annoying intro paragraphs! Grab yourself a reward coke, it's on to the code :)
Part 1: Creating the Alpha Surface
I've created a nice little function here that takes two DirectDraw7 surfaces as parameters. One is the color surface, another is the alpha channel. The alpha channel surface is simply an all-gray bitmap that will be used as the alpha component in the final surface. For simplicities sake (I didn't really want to mess around with palette look-ups, did you? :), both surfaces are 32-bit. If you run your desktop in 32-bit mode, you have nothing to worry about; DirectDraw will create 32-bit surfaces by default. If not, you'll have to make sure they are 32-bit... I'll leave that up to you, as it's beyond the scope of this tutorial.
Here's the function:
Public Function CreateAlpha(ColorSurf As DirectDrawSurface7, _
AlphaSurf As DirectDrawSurface7) As DirectDrawSurface7
Dim SrcDesc As DDSURFACEDESC2
Dim OutputDesc As DDSURFACEDESC2
Dim lockRect As RECT
Dim GetColor() As Byte
Dim GetAlpha() As Byte
Dim ColorB() As Byte
Dim AlphaB() As Byte
Dim FinalB() As Byte
ColorSurf.GetSurfaceDesc SrcDesc
With OutputDesc
.lFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH Or DDSD_PIXELFORMAT
.ddpfPixelFormat.lFlags = DDPF_RGB Or DDPF_ALPHAPIXELS
.ddpfPixelFormat.lRGBBitCount = 32
.ddpfPixelFormat.lRGBAlphaBitMask = &HFF000000
.ddpfPixelFormat.lRBitMask = &HFF0000
.ddpfPixelFormat.lGBitMask = &HFF00&
.ddpfPixelFormat.lBBitMask = &HFF&
.lWidth = SrcDesc.lWidth
.lHeight = SrcDesc.lHeight
.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN Or DDSCAPS_SYSTEMMEMORY
End With
Set CreateAlpha = dd.CreateSurface(OutputDesc)
lockRect.Right = OutputDesc.lWidth
lockRect.Bottom = OutputDesc.lHeight
ColorSurf.Lock lockRect, SrcDesc, DDLOCK_WAIT Or DDLOCK_NOSYSLOCK, 0
ColorSurf.GetLockedArray GetColor
ColorB = GetColor
ColorSurf.Unlock lockRect
AlphaSurf.Lock lockRect, SrcDesc, DDLOCK_WAIT Or DDLOCK_NOSYSLOCK, 0
AlphaSurf.GetLockedArray GetAlpha
AlphaB = GetAlpha
AlphaSurf.Unlock lockRect
CreateAlpha.Lock lockRect, OutputDesc, DDLOCK_WAIT Or DDLOCK_NOSYSLOCK, 0
CreateAlpha.GetLockedArray FinalB
For y = 0 To OutputDesc.lHeight - 1
For x = 0 To (OutputDesc.lWidth - 1) * 4 Step 4
FinalB(x + 3, y) = AlphaB(x, y) 'A alpha
FinalB(x + 0, y) = ColorB(x + 0, y) 'R red
FinalB(x + 1, y) = ColorB(x + 1, y) 'G green
FinalB(x + 2, y) = ColorB(x + 2, y) 'B blue
Next x
Next y
CreateAlpha.Unlock lockRect
End Function
That's one sizable chunk of code, but it's really not difficult to understand. After our variable declarations, and grabbing the surface description from the rgb surface, we create our alpha surface. Notice it's 32-bit, has the DDPF_ALPHAPIXELS flag, and has bitmasks set to be sure DD puts our alpha byte in the right place. After that we grab our color and alpha channel surfaces in the form of byte arrays, and then it's one final lock and loop to set all the values in place. The loop steps through each pixel (notice the Step 4 command, there's 4 bytes per pixel) assigns the correct values to the new surface from the arrays of the source surfaces.
Here's one more function that we'll need:
Public Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _
(Destination As Any, Source As Any, ByVal Length As Long)
Public Function GetImageBytes(ddsRGB As DirectDrawSurface7, _
ddsAlpha As DirectDrawSurface7) As Byte()
Dim ddsImage As DirectDrawSurface7
Dim lockBytes() As Byte
Dim emptyRect As RECT
Dim lockDesc As DDSURFACEDESC2
Dim returnBytes() As Byte
Set ddsImage = CreateAlpha(ddsRGB, ddsAlpha)
ddsImage.Lock emptyRect, lockDesc, DDLOCK_NOSYSLOCK Or DDLOCK_WAIT, 0
ddsImage.GetLockedArray lockBytes
ReDim returnBytes((4 * lockDesc.lWidth * lockDesc.lHeight) - 1)
CopyMemory returnBytes(0), lockBytes(0, 0), UBound(returnBytes) + 1
ddsImage.Unlock emptyRect
GetImageBytes = returnBytes
End Function
All this does is call our CreateAlpha function to make the surface, then grabs the finished surface and puts it into one single-dimension byte array. I included a CopyMemory declaration as well, since there are some different ones out there, and not all of them work to my satisfaction. :)
The point of this is simple: You can easily create an editor that loads up two bitmaps, creates the alpha surface, gets the byte array, and slaps it into a file. We'll be using that same byte array for our texture, in game.
Before we go on, you may want to download Sample Project 1, a simple editor using the above code.
Part 2: Loading and Displaying the Alpha Surface
So now we've got ourselves some fancy alpha surfaces. That's pretty schweet and all, but what good does it do us if we can't load it into texture? That's the next step, and it's all done in one function. Observe:
Public Function LoadAlphaBitmap(dev As Direct3DDevice8, d3dx As D3DX8, _
strFile As String, fmt As CONST_D3DFORMAT) As Direct3DTexture8
Dim imgWidth As Long
Dim imgHeight As Long
Dim imgLength As Long
Dim imgData() As Byte
Dim hf As Integer
Dim surfLoad As Direct3DSurface8
Dim surfDest As Direct3DSurface8
Dim lr As D3DLOCKED_RECT
'open and load the file
hf = FreeFile
Open strFile For Binary Access Read As hf
Get hf, , imgWidth
Get hf, , imgHeight
Get hf, , imgLength
ReDim imgData(imgLength)
Get hf, , imgData
Close hf
'create temporary 32-bit surface
Set surfLoad = dev.CreateImageSurface(imgWidth, imgHeight, D3DFMT_A8R8G8B8)
surfLoad.LockRect lr, ByVal 0, D3DLOCK_NOSYSLOCK
'copy the bytes onto the 32-bit surface
DXCopyMemory ByVal lr.pBits, imgData(0), UBound(imgData) + 1
surfLoad.UnlockRect
'create texture in format specified
Set LoadAlphaBitmap = dev.CreateTexture(imgWidth, imgHeight, 1, 0, fmt, _
D3DPOOL_MANAGED)
'get surface pointer to texture
Set surfDest = LoadAlphaBitmap.GetSurfaceLevel(0)
'convert the surface to the new format
d3dx.LoadSurfaceFromSurface surfDest, ByVal 0, ByVal 0, surfLoad, ByVal 0, _
ByVal 0, D3DX_FILTER_NONE, 0
End Function
This function is designed to load the surfaces saved by the editor in Sample Project 1. It's not hard at all, however, to customize this to your own needs. You can save the byte array however you want in a file. (Compression, anyone?) Anyhow, after it pulls out the byte array, along with the width and height of the image, the function then creates a surface and inserts the data. This is done using CopyMemory (notice DX8's handy built-in declaration). Also notice the Byval in the CopyMemory call... this is because the DX8 LockRect call has handed us an actual pointer in the form of a long. (the pBits variable) If we didn't include the Byval, VB would first make a pointer to the pointer, and then pass that! CopyMemory would try and dump our entire byte array into that one little pBits variable, and of course it would run off the edges and give us a nice crash of game and VB. Memory Access is a lovely thing, ain't it? ;) So all that to say that the Byval is important.
In case you didn't notice, the surface we've just created is 32-bit. There are a few problems associated with this - among them, memory consumption and video card support. Bottom line is, we need to get this alpha surface into a more widely supported format. That's where DX8's spectacular helper library, D3DX, comes into play. We create a new texture, this time in the format we want (it's a parameter of the function). Then we use d3dx.LoadSurfaceFromSurface to convert the 32-bit surface into the texture. It is now ready for use. :)
You may be wondering, how do I draw an alpha surface? The answer: same as always. That's the beauty of alpha channels, in DX8 we already use them. When you specify a colorkey in any of the DX8 creation methods (CreateTextureFromFile, LoadSurfaceFromFile, etc), DX is creating an alpha channel for you, based on the color you give. With this method, we are simply specifying our own alpha channel. So all the initialization and rendering is the exact same. :)
Be sure to download Sample Project 2, which demonstrates the loading and displaying. I hope you've found this tutorial helpful. There are bazgillions of things you can use alpha channels for... smooth particles, smoke, fire, anti-aliasing, and more. Have fun :)
-Metal
PS: if you liked this, have questions, or couldn't get something to work, just post on the board on this website... I'm sure to see it. Thanks :)