Version 2.0!
Features
Tutorials
Files
Glossary
Projects
Contact
Links
Message Board
Extras
LuckyCam
Old News
Sign Guestbook
View Guestbook
VB Horoscope
VB Photo Album
.
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 :)