Unofficial Quake3 Shader Docs

prev :: index :: next

3 Examples

3.1 Blending/Transparency

WTF Is Blending?

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.

Simple blendFunc

There are three main types of blends. These can be specified by using the simple (one parameter) version of blendFunc.

Additive Blend

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'.

Multiplicative Blend

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'.

Alpha 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'.

Advanced blendFunc

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:

Lightmaps

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

3.2 Rendering Grates

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 RGB channels The alpha channel

Rendering Without a Lightmap

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 With a Lightmap

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.

Using nopicmip in Grate Shaders

If 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.

3.3 Portals

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.

3.4 Lights

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.

3.5 Sky

A Quake3 sky is made from two components: a 'skybox' and a cloud layer. These components can be used together or individually.

Prerequisites

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.

Skybox

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.

Cloud Layer

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

Combining Skyboxes And Cloud Layers

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

3.6 Liquids

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