Blending is the action of combining in some way the colour of a pixel from the stage being drawn (the source) with the colour of a pixel already in the framebuffer (the destination). Blending can be used to make transparent polygons or to blend stages on the same surface together.
Remember that when a surface is drawn each stage is applied in turn. This means that if the second stage uses blending, it will blend with the pixels rendered by the first stage. Also, if you want a surface to be transparent then all the stages must be transparent.
Blending is controlled the blendFunc stage attribute. There are two versions of blendFunc, the simple and advanced.
Note that an alternative method for achieving transparency using alphaFunc is discussed below in Rendering Grates.
There are three main types of blends. These can be specified by using the simple (one parameter) version of blendFunc.
This blend is achieved by adding the colour of the source pixel to the colour of the destination pixel. Adding a colour value to another will always make it brighter unless one of them is black, in which case the result will be unchanged.
Examples of additive blending are the Railgun trail, torches and light flares.
To achieve an additive blend use 'blendFunc add'.
This blend is achieved by multiplying the colour of the source pixel by the colour of the destination pixel. Multiplying two colour values will always make the result darker unless one of them is pure white, in which case the result will be unchanged. Multiplicative blending gives a similar effect to coloured glass.
Examples of multiplicative blending are lightmaps (explained below).
To achieve a multiplicative blend use 'blendFunc blend'.
This blend is achieved by calculating the weighted average of the source and destination pixel colours, where the weight is the alpha value of the source texture map.
Um, lets try that again. A .tga file can have an extra part to it called an alpha channel. This is greyscale image the same size as the rest of the image. The alpha channel tells Quake3 how transparent parts of the RGB image are. Where the alpha channel is white (a value of 1) the corresponding pixel will completely opaque. Where the alpha channel is black (a value of 0) the corresponding pixel will be completely transparent. Where the alpha channel is medium grey (a value 0f 0.5) the corresponding pixel will be half transparent. Your picture editing software will typically allow you to add an alpha channel to an image. The alpha channel can be thought of as a template which controls which bits of the texture map are cut out.
Examples of alpha blending are rocket smoke, explosions and the chat bubble.
To achieve an alpha blend use 'blendFunc filter'.
What follows is a rather technical explanation of how the two parameter blendFunc and its blending modes work. You don't need to know this to use blendFunc, but you might find it useful when trying to generate more complicated effects.
An RGBA value is four floating point values in the range 0-1 which represent the red, green, blue and alpha components of the pixel. The alpha component is a special value that is generally used as the 'transparency' of the pixel. I'll use dot notation to represent individual components, so src.R is the red component of the RGBA value src. A complete RGBA value is represented as 4 comma separated values (red, green, blue, alpha respectively) in brackets e.g. (.2, .4, .6, 1). RGBA values can be multiplied together by multiplying the individual components.
(1, 0.5, 0.2, 0) * (0.5, 0.5, 1, 0.5) = (1*0.5, 0.5*0.5, 0.2*1, 0*0.5) = (0.5, 0.25, 0.2, 0)
The blend operation acts upon two RGBA pixel values at a time, the source and destination (src and dest), to make the final pixel value (finalValue) which is copied into the framebuffer. The source value is the pixel from the stage that is being rendered. The destination is the pixel already in the framebuffer. For each pixel the srcFunc and destFunc generate RGBA values which are used to blend the src and dest pixels together.
The final pixel value is calculated using the formula below:
finalValue = src*srcFunc + dest*destFunc
The srcFunc and destFunc values given to the two parameter blendFunc are strings representing a function. They are given with the function they represent below:
GL_ONE = (1, 1, 1, 1) GL_ZERO = (0, 0, 0, 0) GL_DST_COLOR = (dest.R, dest.G, dest.B, dest.A) GL_SRC_COLOR = (src.R, src.G, src.B, src.A) GL_ONE_MINUS_DST_COLOR = (1-dest.R, 1-dest.G, 1-dest.B, 1-dest.A) GL_ONE_MINUS_SRC_COLOR = (1-src.R, 1-src.G, 1-src.B, 1-src.A) GL_SRC_ALPHA = (src.A, src.A, src.A, src.A) GL_ONE_MINUS_SRC_ALPHA = (1-src.A, 1-src.A, 1-src.A, 1-src.A) GL_DST_ALPHA = (dst.A, dst.A, dst.A, dst.A) GL_ONE_MINUS_DST_ALPHA = (1-dst.A, 1-dst.A, 1-dst.A, 1-dst.A)
Notes:
blendFunc add is equivalent to blendFunc GL_ONE GL_ONE.
blendFunc blend is equivalent to blendFunc GL_DST_COLOR GL_ZERO and blendFunc GL_ZERO GL_SRC_COLOR.
blendFunc filter is equivalent to blendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA.
Lightmaps are blended into a texture map using a multiplicative blend. In a lightmap white represents fullbright and black complete shadow. A mild shadow would be grey and a blue tinged light shadow a light blue. To blend the lightmap and texture map together the lightmap value must be multiplied by the texture map value. So, where the lightmap is white the texture map is rendered unaffected. Where the lightmap is grey (in shadow) the texture map is rendered darkened. Where the lightmap is black the texture map is rendered black.
|
|
| After lightmap rendering | After lightmap/texture map blending |
Firstly, the lightmap is rendered to the framebuffer. Next, the texture map is rendered with blendFunc blend.
Rendering the lightmap after the texture map is logically the same because it uses multiplicative blending (x*y = y*x).
You might have noticed this clever grate effect in the q3tourney2 map:
Parts of the texture are rendered completely transparent to create 'holes' in the texture. This effect is achieved by using a texture with an alpha channel. In the examples I'll assume textures/grate.tga is a grate texture with an appropriate alpha channel. If you want to experiment, the texture used in the screenshot is textures/base_floor/pjgrate2.tga.
|
|
| The RGB channels | The alpha channel |
The grate effect is quite simple to achieve if lightmaps are ignored. Consider this shader:
textures/grate
{
cull none
{
map textures/grate.tga
alphaFunc GT0
}
}
First of all culling is turned off. Normally culling throws away polygons that cannot be seen, including when they are seen from the back. Because our grate texture will be seen from both sides culling must be disabled. The texture map is rendered with the alphaFunc set so that only parts of the texture map with an alpha greater than zero are rendered.
Rendering a grate with a lightmap is slightly more complicated. Consider this shader which fails to render the grate properly:
textures/grate
{
cull none
{
map textures/grate.tga
alphaFunc GT0
}
{
map $lightmap
blendFunc blend
}
}
This is a standard lightmap plus texture map with an added alphaFunc. Now imagine this being rendered. Lets say we're at a pixel which is part of a hole in the grate. The grate texture is not drawn for this pixel because the alphaFunc discards it. The lightmap is rendered, however, because it has no way of knowing that this pixel is a hole in the grate. The result is that the lightmap affects the surfaces through the holes.
To make a properly lightmapped grate, we must use the z-buffer to tell the lightmap where the holes are.
textures/grate
{
cull none
{
map textures/grate.tga
alphaFunc GT0
depthWrite
}
{
map $lightmap
blendFunc blend
depthFunc equal // added this
}
}
This shader is same as the previous one except with depthWrite and depthFunc attributes added. So what do they do? depthWrite forces the grate texture map to write to the z-buffer. Normally, the last stage writes to the z-buffer. Because the alphaFunc discards z-buffer info if the condition fails, the holes in the grate are not written to the z-buffer. Next the lightmap is rendered. The clever bit here is that depthFunc is used to make sure that pixels are only rendered if their depth is equal to the depth in the z-buffer. Because the holes in the grate are not written to the z-buffer, the lightmap is not drawn over them.
This method can also be viewed as making a 'stencil' with the grate texture in the z-buffer, then only drawing the lightmap where ther stencil is.
nopicmip in Grate ShadersIf a grate texture contains lots of small holes, such as the example texture shown above, the surface attribute surfaceParm nopicmip should be used. This is because some people with limited graphics card memory set the console variable r_picmip to 3 (forcing every texture to use its lowest resolution mipmap) in order to speed up rendering. If a grate texture has small holes they may be lost when the texture is shrunk, possibly putting the player at a disadvantage. Using nopicmip prevents this from occuring.
It is also recommended that a texture 256x256 or smaller is used by grate surfaces. This is because any texture larger than this is likely to be shrunk even if nopicmip is set due to the limitations of some graphics hardware.
This is a basic portal shader:
textures/maj/majportal
{
surfaceParm noLightMap
portal
{
}
}
First of all, this portal doesn't use a lightmap (although it can have one), so there's a noLightMap surfaceParm. The portal surface attribute tells Quake3 that the surface is a portal. Now, the portal doesn't really need a texture map but at least one has to be added for reasons I'll explain below. If you don't know what a z-buffer is or just don't care (you don't need to know this, it's just useful info), skip the next paragraph.
The polygons drawn inside the portal update the z-buffer as normal. The problem is that if a polygon is drawn after and behind the portal it might obscure polygons seen through the portal. To stop this, the flat surface of the portal must be rendered to the z-buffer so that any polygons later drawn behind are discarded. This is done by having an empty stage definition, which effectively tells Quake3 to just render the surface into the z-buffer.
A more complicated shader:
textures/maj/majportal
{
portal
{
map textures/base_wall/metalblack02.tga
depthWrite
blendFunc blend
}
{
map $lightmap
depthFunc equal
blendFunc blend
}
}
This shader applies a dirty finish and a lightmap to the portal surface. The main attributes to note are the depthWrite and depthFunc attributes. I'm not quite sure why they must be present, but putting them in does seem to prevent some small 'hall of mirrors' effects. The first stage must have a depthWrite attribute and every subsequent stage a depthFunc equal attribute. The dirty finish is achieved by using a very dark texture and blending it in such a way that it slightly darkens the portal. Apart from this the shader is very similar to a normal wall shader.
An even fancier effect can be achieved with this shader:
textures/maj/majportal
{
surfaceParm noLightmap
portal
{
map textures/sfx/portalfog.tga
blendFunc filter
alphaGen portal 256
rgbGen identityLighting
}
}
This applies a 'fog' to the portal in a similar way to the effect on the portal in q3dm7. It uses a special alphaGen to make a fog texture appear on the portal and fade away as the player nears it. Note the rgbGen identityLighting which makes the stage fullbright.
Lights in Quake3 usually have a special texture map overlaid on them, called a glow map. Glow maps are used to give the same effect as q1's fullbright pixels. They are used to make lights glow fullbright even in darkness, but leave the surrounding light fitting dark.
textures/base_light/ceil1_38_10k
{
qer_editorimage textures/base_light/ceil1_38.tga
surfaceParm noMarks
q3map_surfaceLight 10000
q3map_lightImage textures/base_light/deil1_38.blend.tga
light 1
{
map $lightmap
rgbGen identity
}
{
map textures/base_light/ceil1_38.tga
blendFunc blend
}
{
map textures/base_light/ceil1_38.blend.tga
blendFunc add
}
}
The q3map_surfacelight parameter that tells qrad to make this surface emit light (although this is not essential). The surface is then generated from three stages. The first two stages are the standard texture+lightmap combo. The last stage is the glow map which uses an additive blend. This means that the glow texture will always be bright even when the lightmap is dark.
A Quake3 sky is made from two components: a 'skybox' and a cloud layer. These components can be used together or individually.
A sky shader should always contain several surface attributes. A sky shader without the skybox or cloud layers would look like this:
{
surfaceParm nolightmap
surfaceParm noimpact
surfaceParm sky
q3map_sun ? ? ? ? ? ? // optional
q3map_surfaceLight ? // optional
qer_editorimage textures/mysky.tga
}
The three surfaceParms make the sky surface behave like a sky. The optional q3maps add extra behaviour in the form of a sun and some surface lighting.
A skybox simulates a sky by drawing a cube around the player with texture maps on the inside faces. The cube is drawn so that the player is always at the center of the cube, and the rest of the level is drawn in front of the cube faces. The skybox shows through surfaces with a surfaceParm sky. For a more detailed explanation see the skyParms attribute.
Note: the Quake2 sky was a skybox.
{
surfaceParm nolightmap
surfaceParm noimpact
surfaceParm sky
qer_editorimage textures/mysky.tga
skyParms env/space - -
}
This shader is the same as the basic sky shader except for the new skyParms attribute.
A cloud layer simulates a sky by texturing a sort of squashed sphere around the player in a similar way to a skybox. The difference is that the parts of the sphere close to the ground plane are not drawn, giving a HOM effect if they are visible. Also, the apparent height of the cloud layer is adjustable.
Note: the Quake1 sky was a cloud layer.
This shader adds a scrolling cloud layer to the basic sky. The 512 in the skyParms attribute sets the height of the cloud layer.
{
surfaceParm nolightmap
surfaceParm noimpact
surfaceParm sky
qer_editorimage textures/mysky.tga
skyParms - 512 -
{
map textures/skies/inteldimclouds.tga
tcMod scroll 0.15 0.15
}
}
This shader adds an extra, transparent layer of clouds. It does this by using an additive (GL_ONE GL_ONE) blend. Note that if more than one stage is used the first map must have a depthWrite attribute.
{
surfaceParm nolightmap
surfaceParm noimpact
surfaceParm sky
qer_editorimage textures/mysky.tga
skyParms - 512 -
{
map textures/skies/inteldimclouds.tga
tcMod scroll 0.15 0.15
depthWrite
}
{
map textures/skies/intelredclouds.tga
tcMod scroll 0.05 0.05
blendFunc add
}
}
To combine cloud layers and skyboxes just make sure all the cloud layers are partly transparent. For example:
{
surfaceParm nolightmap
surfaceParm noimpact
surfaceParm sky
q3map_sun ? ? ? ? ? ? // optional
q3map_surfaceLight ? // optional
qer_editorimage textures/mysky.tga
skyParms env/space 512 -
{
map textures/skies/intelredclouds.tga
tcMod scroll 0.05 0.05
blendFunc add
}
}
Liquid surfaces must have certain surface attributes. Example:
textures/liquids/lavahell
{
surfaceParm trans // required
surfaceParm noimpact // required
surfaceParm lava // required, but can be water or slime as well
surfaceParm nolightmap // optional
q3map_surfaceLight 600 // optional
cull disable // required
tessSize 128 // optional, should only be used with deformVertexes
deformVertexes wave 100 sin 3 2 .1 0.1 // optional
{
map textures/liquids/lavahell.tga
tcMod turb 0 .2 0 .1 // optional
}
}