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)
|