Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

Texture Blending
Author : Jack Hoxley
Written : 28th April 2001
Contact : [Web] [Email]
Download : Graph_12.Zip [128 Kb]


Contents of this lesson:
1. Introduction
2. Multi-Pass Blending
3. Proper Texture Blending
4. Vertex Based Alpha Blending
5. Material Based Alpha Blending
6. Texture Based Alpha Blending


1. Introduction

Welcome to Advanced Texturing part 1, this section of the DirectXGraphics series will cover some of the cleverer features that we can use in DirectX8. First up is texture blending, a very simple, quite fast and excellent tool to be able to use. Texture blending is the process of taking up to 8 textures and combining them based on several arguments and parameters. Several extremely cool effects can be derived from this capability, the main one being the usage of lightmaps.

Currently you should be aware that Direct3D does it's lighting on a per-vertex basis - then blends the results across the rest of the triangle. 90% of the time this is perfectly acceptable, but there are times when visible artifacts appear - and it looks very wrong. For example, take a spot light and a large polygon; as you know, if the vertex isn't within the cone it receives no light, when we have a small cone and a large triangle it is perfectly possible to have the light move to the middle of the triangle and the triangle to receive no light at all; when in real life it should leave a small circle of colour. The inverse can happen as well, if we have a small spotlight move over one corner of our large triangle it can appear to be a very big spotlight - because of the linear interpolation across the triangle. To solve this you could use lightmaps, whilst not always as real-time as you'd like (but they are perfect if you pre-compile the world data) you can get pixel-perfect lighting as a result. And as you're using textures you can define whole new types of lighting - one that gets brighter towards the edge, one that doesn't go in straight lines...

This article will introduce you to the two main types of texture blending, there is a third (new to DirectX8) in the form of pixel shaders, but they are extremely complicated - and involve a simplified version of assembler code.


2. Multi-Pass Blending

Multi-pass texture blending is the easiest way possible, unfortunately it's also the slowest method as well. By setting the source and destination render states you can render a piece of geometry with a texture and then render it again with a different texture and Direct3D will blend it accordingly. BUT as I just hinted at, you need to render the geometry twice - which isn't very frame-rate friendly. If you have a character with 1000 vertices, to blend two textures across the whole model you'd need to render 2000 vertices in total - which can destroy your frame rate if you're not careful.

In order to do multi-pass texture blending you need to set these three render states:

D3DDevice.SetRenderState D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR
D3DDevice.SetRenderState D3DRS_DESTBLEND, D3DBLEND_DESTCOLOR
D3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 1

Just remember to disable blending for any geometry that you dont want blended. The last parameter, specifying what sort of blend you want are described in the next table:

D3DBLEND_ONE
The layer will be blended as (1,1,1,1) - white, everything appears much brighter.
D3DBLEND_ZERO
The layer will be blended as (0,0,0,0) - black.
D3DBLEND_SRCCOLOR
Uses the colour components of the source image
D3DBLEND_INVSRCCOLOR
Invertex the colour components of the source image, 1-sR, 1-sG, 1-sB, 1-sA
D3DBLEND_SRCALPHA
Uses the source alpha component on all channels, (A, A, A, A)
D3DBLEND_INVSRCALPHA
Inverts the source alpha, 1 - A them same as SRCALPHA
D3DBLEND_DESTALPHA
Uses the destination alpha component on all channels, as in SRCALPHA
D3DBLEND_INVDESTALPHA
Inverts the destination alpha then does the same as DESTALPHA
D3DBLEND_DESTCOLOR
Uses the destination colour components
D3DBLEND_INVDESTCOLOR
Inverts the destination colour components
D3DBLEND_SRCALPHASAT
Blend factor is (F, F, F, 1) where F = Min(srcA, 1 - destA)
D3DBLEND_BOTHSRCALPHA
Not supported in DirectX8, do not use
D3DBLEND_BOTHINVSRCALPHA
Inverts the source alpha, then uses the original alpha on the destination; any destination blend parameters are ignored.

The best way of learning how these work is to play around with them, using D3DBLEND_ONE for both parameters creates a
semi alpha-blending type effect, using D3DBLEND_SRCCOLOR and D3DBLEND_DESTCOLOR in the source and dest
entries allows you to use lightmaps, like the picture below:

An example of Multi-pass texture blending when used for lightmapping. The first texture is what we want
displayed on the primative, the second texture is our lightmap - white areas are bright, black areas have
no light. When blended together we get the result in the third box.

To render using multi-pass blending all you need to do is switch on the blending when it's required - then re-render the geometry. This following piece of code is from the Render( ) procedure in the sample code:

D3DDevice.SetTexture 0, LightMapTex
D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLESTRIP, 2, TriStrip3(0), Len(TriStrip3(0))

D3DDevice.SetTexture 0, Texture
D3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 1
D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLESTRIP, 2, TriStrip3(0), Len(TriStrip3(0))
D3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 0

Notice that the first stage does not have blending enabled, when you use blending it blends with whatever pixel colours are already there, so in this case it would blend with the black background before blending with the second layer. Make sure that you turn blending off after you have finished, you may well end up getting other geometry blended by mistake - on top of that blending is much slower than not blending, so the less you use it the faster your game will go.


3. Proper Texture Blending

Proper texture blending is where all textures are blended in one pass, you only need to render the geometry once. It also has it's side effects, You have to query if there is hardware support for more than one texture, The newer and more powerful accelerator cards can handle 8 at a time, older cards only support 1 or 2. The first step then is to decide what support there is for multiple texture stages:

Dim CAPS As D3DCAPS8
D3DDevice.GetDeviceCaps CAPS
Debug.Print "Maximum Texture Stages: " & CAPS.MaxTextureBlendStages

Simple as that really, we'd want to store it as a variable rather than just output it to the immediate window - but you get the idea. Now that we know how many (there will be at least one) we can start playing. You'll have seen the line "D3DDevice.SetTexture 0, TextureName" many times already in this series, the first parameter, 0 in this case, is the texture stage; if there is one texture stage we can only place a texture in entry 0 (any higher and it'll drop out), if we support 8 texture stages we can put textures in slots 0 to 7... Bare in mind that the more you fill up the slower it goes.

Now that we know if the current device supports multiple texture stages (and how many) we can start to do something with them. This looks quite complicated at first, but once you start to get your head around the hierarchy (more on that later) then it becomes quite simple. Here's the multiple-stage texture blending code from the sample code:

If CanDoBlending = True Then
        D3DDevice.SetVertexShader FVF2
        D3DDevice.SetTexture 0, LightMapTexCol
        Call D3DDevice.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE)
        Call D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE)
        Call D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE)
        
        D3DDevice.SetTexture 1, Texture
        Call D3DDevice.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE)
        Call D3DDevice.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE)
        Call D3DDevice.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT)
        
        D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLESTRIP, 2, TriStrip4(0), Len(TriStrip4(0))
    
End If

The above code is executed in the Render() function, whilst it sets up the texture stages each frame it's not required to (we could just configure them during initialisation). When executed, the above code will perform standard colour lightmaping, using the first stage (0) as the lightmap (which areas are light/dark) and the second stage (1) as the actual texture. The final result is shown below:


The main texture is the same as before, but the lightmap is
now a magenta-cyan radial gradient.

One final important thing to note is that we have to use two sets of texture coordinates now, this also requires that we create a new vertex format to obtain this functionality. This is quite simply:

Private Type TLVERTEX2
    X As Single
    Y As Single
    Z As Single
    rhw As Single
    color As Long
    specular As Long
    tu1 As Single
    tv1 As Single
    tu2 As Single
    tv2 As Single
End Type

Const FVF2 = D3DFVF_XYZRHW Or D3DFVF_DIFFUSE Or D3DFVF_SPECULAR Or D3DFVF_TEX2

The actual member names for the coordinates (tu1 or tu2) is not important, they need to be in the correct order and of the correct size, but you could call one "mushroom" and the other "fred", but it's unlikely to help you later on! You could also specify them using other built in types, namely the D3DVECTOR* types (see next example). Finally, note that the FVF constant has D3DFVF_TEX2 instead of _TEX1, fairly logical this one, it just means that we're passing two texture coordinates - you can use _TEX1 - TEX8 here, depending on how many blending stages you require. A revised definition of this vertex format could be:

Private Type TLVERTEX3
    P As D3DVECTOR4
    Color As Long
    Specular As Long
    Tex0 As D3DVECTOR2
    Tex1 As D3DVECTOR2
    Tex2 As D3DVECTOR2
End Type

Const FVF3 = D3DFVF_XYZRHW Or D3DFVF_DIFFUSE Or D3DFVF_SPECULAR Or D3DFVF_TEX3

When you're creating the vertices do so as per usual, but specify the texture coordinates with respect to their texture stage - should you only want 1/2 of the lightmap to light the entire of the texture you can set Tex0 =[0,0]-[0.5,0.5] and Tex1=[0,0]-[1,1]...

Now that we know how to set up the texture stages, we need a little background theory. How does the texture cascade work? what parameters do what? Take the following diagram:

Think of each stage as a logic gate (if you've done electronics before), or a little number generator (like in first school/little-person school). In goes two numbers (Arg1, Arg2) and out comes a new number, what happens in the box is the blending operation. The diagram above shows the process for 4 texture stages, but you can see how it goes onto use all 8 stages (if required). Direct3D supports dozens of different texture stage operations, you should enumerate for their availability (see the object browser in VB for specific flags) before using them, but most of the simple/common blending functions are supported by most hardware your program will encounter. The specific operations are listed in the help file, but the most common (and most useful) are listed below:

D3DTSS_COLOROP - The colour blending operation
D3DTSS_COLORARG0 - The first input argument, not usually used - but required for some functions (lerping for example)
D3DTSS_COLORARG1 - The second colour based argument, what is specified here is used as the first parameter for most operations
D3DTSS_COLORARG2 - The third colour based argument.

D3DTSS_ALPHAOP - The alpha blending operation, same as the colour blending, but only operates on the alpha channel
D3DTSS_ALPHAARG0 - The first input, as with colours, this is only used in special cases.
D3DTSS_ALPHAARG1 - The second input, used in most calculations
D3DTSS_ALPHAARG2 - The third input.

D3DTSS_MAGFILTER - The magnification filter for the texture stage, point, bilinear or anisotropic usually.
D3DTSS_MINFILTER - The minification filter for the current texture stage, same parameters as for magnification.

Above are the main parameters, we now need to specify what the inputs are and how to specify the operation. First up, the operations:

D3DTOP_DISABLE - Disables the current stage, and all those after it. If stage 4 was disabled, 4,5,6,7,8 would not be processed.
D3DTOP_SELECTARG1 - the output is the input in argument 1, unaltered. argument 2 is ignored.
D3DTOP_SELECTARG2 - the same as above, but the output is argument 2
D3DTOP_MODULATE - multiplies the ARG1 and ARG2 values.
D3DTOP_MODULATE2X - Same as modulate, but the final value is multiplied by 2, slightly brightening the image
D3DTOP_MODULATE4X - Same as above, but the final value is multiplied by 4, brightening the final result even more
D3DTOP_LERP - linear interpolation using the ARG1 and ARG2 values by a given value in ARG0 (0.0 to 1.0 based)
D3DTOP_MULTIPLYADD - Adds ARG0 and ARG1 and multiplies by ARG2

These values should be paired with the _COLOROP, _ALPHAOP and similar parameters, setting this values with the ARG* parameters will result in undefined behaviour.

Now that we have a few operations to play with we need to know how to specify values for the arguments. Again, a short list of the most useful and most common argument parameters:

D3DTA_CURRENT - this uses the result from the last operation in the current, in the first stage (0) this is taken as being D3DTA_DIFFUSE
D3DTA_DIFFUSE - This is the current texels colour after lighting (if used) and gouraud shading.
D3DTA_SPECULAR - similiar to the _DIFFUSE argument, but specifies the interpolated specular value
D3DTA_TEXTURE - the colour for the pixel in the stages texture, if no texture is specified [1,1,1,1] (white) is returned.

Done. We now have all the information required, except one thing - the interface call to specify these values. This is one that you've already seen:

SetTextureStageState(Stage As Long, _
                                      Type As CONST_D3DTEXTURESTAGESTATETYPE, _
                                      Value As Long)

Any of the D3DTSS_ constants go in the second parameter (such as D3DTSS_COLOROP), followed by an appropriate value for the second parameter, if the second parameter is one of the operations specifiers (_COLOROP or _ALPHAOP) then the third parameter needs to be a D3DTOP_ constant. If the second parameter is an ARG* (D3DTSS_COLORARG1 for example) then you need to specify one of the D3DTA_ constants in the third parameter. Obviously the first parameter is the stage that you're currently altering. A brief list of examples for texture blending would be:

Color Light Mapping

D3DDevice.SetTexture 0, LightMapTexture
        Call D3DDevice.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE)
        Call D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE)
        Call D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE)
        
        D3DDevice.SetTexture 1, Texture
        Call D3DDevice.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE)
        Call D3DDevice.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TEXTURE)
        Call D3DDevice.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT)

Monochrome Light Mapping

'This example assumes the lighting data is in greyscale form in the textures alpha channel.
' Set the light map texture as the current texture.
Call D3DDevice.SetTexture(0, texLightMap)

' Set the color operation.
Call D3DDevice.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1)

' Set argument 1 to the color operation.
Call D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG1, _
                            D3DTA_TEXTURE Or D3DTA_ALPHAREPLICATE)

Specular Light Mapping

' Set the base texture.
Call D3DDevice.SetTexture(0, texBaseTexture)
 
' Set the base texture operation and args.
Call D3DDevice.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE)
Call D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE)
Call D3DDevice.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE)
 
' Set the specular light map.
Call D3DDevice.SetTexture(1, texSpecLightMap)
 
' Set the specular light map operation and args.
Call D3DDevice.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_ADD)
Call D3DDevice.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE)
Call D3DDevice.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT)
 
' Set the RGB light map.
Call D3DDevice.SetTexture(2, texLightMap)
 
' Set the RGB light map operation and args
Call D3DDevice.SetTextureStageState(2, D3DTSS_COLOROP, D3DTOP_MODULATE)
Call D3DDevice.SetTextureStageState(2, D3DTSS_COLORARG1, D3DTA_TEXTURE)
Call D3DDevice.SetTextureStageState(2, D3DTSS_COLORARG2, D3DTA_CURRENT)

 


4. Vertex Based Alpha Blending

Alpha blending, what is it? why does everyone love it so much? Alpha blending is the process of blending a source colour and a destination colour based on an alpha value, this alpha value can either be specified externally, or be part of the actual pixel colour - currently you've probably only seen RGB notation, now we use ARGB or RGBA; where the A value is our alpha value. Changing this alpha value we can achieve translucency effects with geometry and textures - enabling you to replicate glass, water, smoke and any number of special effects. Traditionally the process of alpha blending has been far too slow to be done in real-time, the calculations required are very simple, but multiply it by the number of pixels that need to be blended and you'll very quickly see why it's slow. Direct3D allows us to use the hardware to do the blending, therefore making alpha blending an almost effortless process (don't overkill on it though).

The fastest method of using alpha blending is to do it on a per-vertex basis and let Direct3D interpolate the values across a triangle. This isn't incredibly accurate, and only works for vertices that you manually light yourself. To embed an alpha value in the vertex colour we simply change the current RRGGBB hex value to be an AARRGGBB hex value - simple as that. For example:

'vertex 0
TriStrip(0) = CreateTLVertex(10, 10, 0, 1, &H80FFFFFF, 0, 0, 0)

'vertex 1
TriStrip(1) = CreateTLVertex(210, 10, 0, 1, &H80FFFFFF, 0, 1, 0)

'vertex 2
TriStrip(2) = CreateTLVertex(10, 210, 0, 1, &H80FFFFFF, 0, 0, 1)

'vertex 3
TriStrip(3) = CreateTLVertex(210, 210, 0, 1, &H80FFFFFF, 0, 1, 1)

notice that the first part of the hex value is '80' - in decimal notation that's 128, or 0.5 alpha - indicating that we blend this vertex's colour 50:50 with whatever is behind it, if we changed the hex value to be EE (238 in decimal) we would get an almost opaque result - you may just see a bit of the background appearing through the triangle, alternatively change the value to be 19 (50 in decimal) and you'd get an almost transparent result - you'd only just see the geometry over the background. As mentioned, the alpha operation blends the current stage with whatever is behind it, therefore, when rendering, make sure everything that is behind the geometry has been rendered before rendering a semi-transparent object - unwanted artifacts will result otherwise.

To enable vertex based alpha blending you need to use the texture cascade (see previous section), however it's not dependant on having multiple stages available - we're only going to be changing the first one. Here is the code required to setup per-vertex alpha blending:

Call D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE)
Call D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE)
Call D3DDevice.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_CURRENT)

If you apply your knowledge from section 3 you'll understand how this works, we tell it to modulate the alpha values based on the diffuse colour (in both arguments), because it's an alpha blend it will automatically use whatever value is behind it. If this is the only form of texture blending being used then you can omit the ARG* lines and just specify the ALPHAOP, but if there are other parameters being used you may wish to make sure that the correct arguments are still in place.


5. Material Based Alpha Blending

This is a nice simple one this, but useful to know about. The first (per-vertex) technique only work with manually lit vertices - where you can specify the colour. So what happens when you're letting Direct3D do the lighting for you? You use the material properties! You have no where near the same amount of control (no interpolation across a triangle), but it's useful for making an entire model/buffer a certain colour or opacity (the alpha value is measured as opacity - best seen in paint packages), it's also excellent for motion blur (should you like that sort of thing). Here's how we configure our material, apply it to the device, and then make sure Direct3D uses the alpha component:

D3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 1
D3DDevice.SetRenderState D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR
D3DDevice.SetRenderState D3DRS_SRCBLEND, D3DBLEND_SRCALPHA

Dim mat as D3DMATERIAL8
mat.Col.A=AlphaVal 'on a 0.0 to 1.0 scale
mat.Col.R = 1: mat.Col.G = 1: mat.Col.B = 1 'material color doesn't affect texture or lighting colour
mat.Ambient = mat.Col
mat.Diffuse = mat.Col

D3DDevice.SetMaterial mat

'//Render geometry here!

D3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 0

Not too complicated really, just make sure that you disable alpha blending after you're finished (otherwise other objects will get alpha-blended as well).


6. Texture Based Alpha Blending

Now we're cooking! This is the best type of alpha blending available to you, consequently it's not the fastest either. As we mentioned for per-vertex based alpha blending, each vertex uses the ARGB component, so what if we could specify a texture that had an A component for every pixel in it - we would get amazing amounts of control over the rendering. Should you want, you could draw your name in alpha values and have it rendered so that only your name was semi-transparent over everything. You could also use the alpha-channel to create a softening/anti-aliasing type effect around the edges of text/geometry - the possibilities are endless (well, probably a few 1000 possibilities!).

The first stage to this effect is to make a texture that contains an alpha channel; if you have the DirectX8 SDK then you can use the DXTex tool to combine multiple files (one as the RGB image, the other as the Alpha channel) - you can use some paint packages to the same effect, buy you'll have to experiment or read the help files. The texture supplied with the example code was made using the DXTex tool, so has an extension of dds.

The code to render geometry using the alpha-channel in the texture looks like this, note that we have to tell Direct3D we want to use the alpha channel.

D3DDevice.SetVertexShader FVF
D3DDevice.SetTexture 0, TexWithA
D3DDevice.SetRenderState D3DRS_SRCBLEND, D3DBLEND_SRCALPHA
D3DDevice.SetRenderState D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA
D3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 1
D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLESTRIP, 2, MouseBox(0), Len(MouseBox(0))
D3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 0

Not too horrible is it! The end result, including the texture blending examples will look like this:

The strange 8-like figure over the two left hand squares is the primitive rendered using the embedded alpha channel method. Notice that there is no practical way of generating such a shape using pure geometry, transparent texture and/or vertex/material based alpha blending.



Well, you've now completed another tutorial on the rocky road to Direct3D success - with the techniques discussed here you should be perfectly capable of rendering some cutting edge special effects - many of the weapon effects, explosions, magic effects etc.. are rendered using a combination of these techniques (amongst other things). You can download the working source code from the top of the page, or from the downloads page; and when your done you can move onto Lesson 13 : Advanced Texturing Part 2 (Texture pooling)

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster