This document describes the fog rendering system used in Tribes 2 (based on the V12/Torque engine circa 2001). It provides enough detail to reimplement the system in another renderer such as Three.js.
**Important Note**: The version of Torque used by Tribes 2 does **NOT** use `fogDensity` or the later `VolumetricFog` object. Instead, it uses a combination of distance-based "haze" and height-based "fog volumes."
The Tribes 2 fog system has two independent components that are combined:
1.**Haze**: Distance-based fog with a quadratic falloff curve. Applies uniformly regardless of height.
2.**Fog Volumes**: Up to three height-bounded fog layers that add additional fog based on how much of the ray from camera to object passes through each volume.
The final fog value is the sum of haze and fog volume contributions, clamped to [0, 1].
---
## Mission File Parameters
These parameters are defined on the `Sky` object in `.mis` files:
Note that the `fogVolumeColor` values above use 0-255 range instead of the expected 0-1 range for `TypeColorF`. Many community maps have similar issues, sometimes with garbage alpha values from uninitialized memory. Since per-volume colors are unused by default (see [`$specialFog`](#the-specialfog-console-variable)), this has no visual effect.
---
## Haze (Distance-Based Fog)
Haze is a simple distance-based fog with a **quadratic** falloff curve. It creates a smooth transition from clear visibility to full fog.
### Algorithm
```cpp
// From sceneState.h:247-256
inline F32 SceneState::getHaze(F32 dist)
{
// No fog if distance is less than fogDistance
if (dist <= mFogDistance)
return 0;
// Full fog beyond visibleDistance
if (dist > mVisibleDistance)
return 1.0;
// Quadratic falloff between fogDistance and visibleDistance
**IMPORTANT**: Notice that haze uses `mFogDistance` and `mVisibleDistance` (global parameters), while fog bands use their own per-volume `visibleDistance` in the factor calculation.
When implementing in Three.js or other engines:
1.**Haze** must use global `fogDistance` and `visibleDistance`
2.**Fog volumes** use their own per-volume `visibleDistance` for factor calculation
3. These are ADDED together, not blended
A common mistake is to adjust the haze near/far values based on which fog volume the camera is in. This is incorrect - the haze always uses global parameters regardless of camera height.
---
## Fog Application by Object Type
Different object types apply fog differently:
### 1. Terrain
Terrain uses a pre-computed **fog texture** (64x64 RGBA). Each vertex samples this texture to get its fog value.
x = (getVisibleDistanceMod() - dist) * mInvVisibleDistance;
y = (z - mHeightOffset) * mInvHeightRange;
}
```
### 2. Shapes (TSShapeInstance)
Shapes use **textured fogging** - a single uniform fog value for the entire shape, computed at the shape's center.
From the GarageGames forum:
> "TS instances use textured fogging. Textured fogging calculates a single fog value based on the TS object's location, and uses that for the whole thing."
For a quick approximation, you could use Three.js's built-in fog, but note it won't match exactly:
```javascript
// This is NOT an exact match but gives similar visual results
scene.fog = new THREE.Fog(fogColor, fogDistance, visibleDistance);
```
However, Three.js uses **linear** interpolation between near and far, while Tribes 2 uses **quadratic**. For accurate results, you need a custom shader.
### JavaScript Helper Functions
```javascript
// Haze calculation (distance-based fog)
function getHaze(dist, fogDistance, visibleDistance) {
The Sky object renders "bans" (fog bands) directly onto the sky to create a smooth fog-to-sky gradient at the horizon. This is controlled by the `noRenderBans` property (default: false).
#### How Sky Fog Bans Work
When the camera is NOT inside a fog volume, the engine renders fog bands as geometry overlaid on the skybox:
```cpp
// sky.cc - Key constants
#define HORIZON 0.0f // Base height of lower fog band
#define OFFSET_HEIGHT 60.0f // Height of fog band above horizon (world units)
```
The fog bands are positioned relative to the skybox geometry:
The fog band geometry spans from height 0 (`HORIZON`) to height 60 (`OFFSET_HEIGHT`) at the skybox distance. This creates a narrow fog gradient just above the terrain horizon.
The `(1-t)²` curve approximates the visual result of Torque's linear vertex interpolation when rendered through perspective projection on curved fog band geometry.
#### Visible Distance Culling
Objects at or beyond `visibleDistance` are not rendered in Tribes 2 (see `sceneGraph.cc` where the far plane is set to `getVisibleDistanceMod()`). For Three.js implementations, discard fragments at `dist >= fogFar` in the fog shader to prevent fully-fogged geometry from appearing as silhouettes against the sky gradient:
```glsl
// In fog fragment shader
if (dist >= fogFar) {
discard;
}
```
This ensures a seamless transition from fogged terrain to the sky's fog gradient.
### Storm Fog
The engine supports dynamic fog changes via the `stormFog` scripting method, which fades fog layers in and out over time. The `percentage` field on fog volumes is modified during storms.
The engine has a global `smVisibleDistanceMod` (range 0.5 to 1.0, default 1.0) that acts as a quality setting for draw distance. It affects fog calculation in two ways:
1.**Distance scaling**: Both `fogDistance` and `visibleDistance` are multiplied by `smVisibleDistanceMod`, reducing effective visibility on lower quality settings.
2.**Fog volume factor**: The fog factor formula uses this value:
**Important for Three.js implementation**: Since this is a quality preference (not a ratio of visibility distances), use `visFactor = 1.0` for full quality:
```glsl
float factor = (1.0 / volVisDist) * percentage;
```
A common mistake is computing `visFactor = globalVisDist / volumeVisDist` - this is incorrect. The Torque code passes `smVisibleDistanceMod` directly to `SceneState`, not a computed ratio.
### Object Culling
Objects are culled if `getHazeAndFog()` returns 1.0 for their bounding box. The function `isBoxFogVisible()` is used for this check.
---
## The `$specialFog` Console Variable
The engine contains two separate implementations for building the fog texture, controlled by the static variable `SceneGraph::useSpecial` which is exposed as the `$specialFog` console variable.
This is the standard fog texture builder. It computes fog values based on distance and height, but **uses only the global `mFogColor`** for the entire texture:
The volume colors are extracted via `getFogs()` which pulls from the `FogBand` structures that were populated from `mFogVolumes[i].color` during `setupFog()`.
### Why Per-Volume Colors Don't Work in Tribes 2
1.**Default is disabled**: `SceneGraph::useSpecial` initializes to `false`
2.**Scripts never enable it**: A search of `GameData-base` shows no scripts set `$specialFog = true`
3.**Result**: The special fog path is never taken, so per-volume colors have no effect
### Enabling Per-Volume Colors
To enable per-volume fog colors, you would need to run in the game console:
```
$specialFog = true;
```
This was likely a feature that was implemented but never shipped as the default, possibly due to performance concerns or visual issues.
---
## References
-`tribes2-engine/sceneGraph/sceneState.cc` - Core fog calculation functions
-`tribes2-engine/sceneGraph/sceneGraph.cc` - Fog texture building
-`tribes2-engine/terrain/sky.cc` - Sky object and fog volume configuration
-`tribes2-engine/terrain/terrRender.cc` - Terrain fog application
- GarageGames Forum Discussion on Fog Rendering
- The Game Programmer's Guide to Torque, Chapter 8.4.5 (Fog)