The decal system is responsible for rendering bullet holes, blood splatter, explosion marks, and other flat textures applied over over BSP surfaces (decals cannot appear on objects). Decal tags describe the appearance and lifetime of these effects.
Dynamic decals are created from effects, such as explosions and projectile impacts. It is recommended to give the decal a maximum lifetime to avoid poor framerates. The game supports up to 2048 decal "slots". However it may also reach a limit at fewer decals when it can no longer allocate decal vertices. This is extremely unlikely unless there are many decals with a large radius being created, but the console will log:
### WUT? decals: failed to allocate vertices (locked=37, permanent=4)
This means there are 37 "locked" decals (unexpired). When these decals reach the end of their lifetime, they will be destroyed and their vertices released back to the pool. Rough testing shows that the pool is likely 16*2048 vertices, with each decal able to use as many vertices from the pool as needed to conform to the BSP.
Chain decals are when multiple decals are created at the same time. A decal tag can specify another decal which should be generated at the same location. It's useful for composite effects, especially when mixing blending modes. For example, you might want to have a plasma impact generate both a short-lived add glowing decal and a long-lived multiply scorch mark. You could spawn more decals from effects to achieve a similar result, but doing it by chain decals means it's easier to re-use across different effects and allows you to share geometry.
When a decal needs to be created the game goes through a process to generate geometry for it. In most cases decals are created against flat surfaces so are built as simple 4-sided quads. However, if the decal needs to cover an area which is not flat, the game may need to conform the decal to the underlying BSP geometry (depending on type) which may result in dozens of faces and hundreds of vertices.
You can observe decal mesh generation using
debug_decals 1. Original corner vertices of decal meshes stand off from their background with a small margin to avoid Z-fighting, controlled by
rasterizer_zoffset, while additional vertices generated from the conformation process are not necessarily z-offset. Decals can wrap onto
+sky faces, but not onto or past breakable surfaces even after the surface is broken.
Decals only render when their origin point is in a visible cluster. If the decal wraps into another cluster and the origin cluster goes off-screen, the decal will abruptly disappear.
Displays red numbers over each dynamic and permanent decal in the environment. The mesh of the most recently created decal (initially a permanent decal if one exists, then any new dynamic one) will also be highlighted so you can see how it conforms around the BSP.
White points indicate the original 4 corners of the decal, while red ones were added during the conformation to the BSP. The meaning of the number is not known definitely, but seems to be the number of vertices needed if we assume each projected decal region must be converted into quads.
Toggles the display of yellow bounding spheres around each permanent decal in the environment.
Destroys all dynamic and permanent decals. You shouldn't use this to optimize your map's framerate because it doesn't preserve decorative environmental decals. Instead, set reasonable lifetimes for your dynamic decals and limit the number of decals created by custom effects.
Toggles the display and creation of both permanent and dynamic decals. While
Controls how far away from surfaces new decals are generated, e.g. for projectile impacts. Defaults to
Controls how the decal geometry is generated.
Determines what stage of rendering the decal will be drawn in.
|next decal in chain|
The next decal to generate at this location. Do not form a circular chain of decals.
Sets the world units scale for 16px of decal. For example, a value of
Sets lower and upper bounds for how visible the decal is. Defaults to fully visible (
|color lower bounds|
A lower bound for color that will be multiplied with the decal. Defaults to white. The color will be selected at random between this and the upper bound.
|color upper bounds|
An upper bound for color that will be multiplied with the decal. Defaults to white.
|animation loop frame|
Controls how long the decal will exist for.
Controls how long it takes for the decal to fade out at the end of its lifetime. This does not extend the lifetime, but rather the decal begins fading out this many seconds before it will expire.
|framebuffer blend function|
Sets how this decal will be blended into its background. Examples are:
The texture to use for the decal.
|maximum sprite extent|
Thanks to the following individuals for their research or contributions to this topic:
- Conscars (Testing functions and globals, tag fields, decal geometry)
- Ifafudafi (Explaining chain decals and radius scaling)
- Kavawuvi (Invader tag definitions)
- MosesOfEgypt (Tag structure research)