# How Fog Settings and Lighting Component Interactions Shape Atmosphere in Pyrite64

> Discover how Pyrite64 fog settings and lighting interactions create atmospheric depth using GLSL blending. Learn how distance-based fog modifies fragment colors for enhanced realism.

- Repository: [Max Bebök/pyrite64](https://github.com/hailtododongo/pyrite64)
- Tags: deep-dive
- Published: 2026-02-19

---

**Pyrite64 combines per-layer fog configuration with directional and ambient lighting through a unified GLSL blending pipeline, where distance-based fog interpolation modifies lit fragment colors before final compositing to create atmospheric depth.**

The Pyrite64 rendering engine implements Nintendo 64-style graphics through sophisticated fog settings and lighting component interactions. This open-source project manages atmospheric effects via C++ scene serialization and real-time GLSL shader blending. Understanding how these systems converge in the rendering pipeline reveals how Pyrite64 achieves its characteristic low-poly aesthetic with realistic depth cues.

## Fog Configuration Architecture

### Layer-Level Fog Properties

In [`src/project/scene/scene.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.cpp), fog settings are stored as layer properties that control distance-based color blending. The system supports three color modes: material color (0), constant color (1), and custom color (2). Each layer defines `fogMin` and `fogMax` distances that establish the linear interpolation range for atmospheric density.

Key properties include:

- **layer.fog**: Boolean toggle enabling fog for the entire layer, stored in [`src/project/scene/scene.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.cpp)
- **layer.fogColorMode**: Integer selecting the color source (0=material, 1=constant, 2=custom), stored in [`src/project/scene/scene.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.cpp)
- **layer.fogColor**: RGBA vector for custom fog color when mode equals 2, stored in [`src/project/scene/scene.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.cpp)
- **layer.fogMin / fogMax**: Float values defining the linear interpolation range for fog density, stored in [`src/project/scene/scene.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.cpp)
- **layer.fogMode**: Byte encoding the active mode during binary export, written in [`src/build/sceneBuilder.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/build/sceneBuilder.cpp)

### Binary Export Pipeline

The [`src/build/sceneBuilder.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/build/sceneBuilder.cpp) file handles serialization of fog data into binary scene files. During export, the builder writes the RGBA fog color, distance thresholds, and mode byte:

```cpp
// src/build/sceneBuilder.cpp
uint8_t fogMode = layer.fog.value ? layer.fogColorMode.value : 0;
ctx.fileScene.writeRGBA(layer.fogColor.value);
ctx.fileScene.write<float>(layer.fogMin.value);
ctx.fileScene.write<float>(layer.fogMax.value);
ctx.fileScene.write<uint8_t>(fogMode);

```

## Lighting Component Implementation

### Per-Draw Uniform Population

Lighting data flows through [`src/renderer/n64Mesh.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/renderer/n64Mesh.cpp), where the `drawPart()` method populates uniform buffers with scene lighting information. The system supports one ambient light and up to two directional lights:

```cpp
// src/renderer/n64Mesh.cpp
float clip = uniforms.mat.lightDir[0].w;
const auto &lights = scene->getLights();
int lightIdx = 0;
for (auto &light : lights) {
    if (light.type == 0) {
        uniforms.mat.ambientColor = light.color;
    } else if (lightIdx < 2) {
        uniforms.mat.lightDir[lightIdx] = glm::vec4(light.dir, 0.0f);
        uniforms.mat.lightColor[lightIdx] = light.color;
        ++lightIdx;
    }
}
uniforms.mat.lightDir[0].w = clip;

```

### Shader Uniform Blocks

The GLSL shaders access lighting through the `UniformObject` block defined in `src/shader/ubo.glsl`. This standardized layout ensures consistent data access across vertex and fragment stages, containing `lightDir[2]`, `lightColor[2]`, and `ambientColor` vectors.

## Shader Integration: Where Fog Meets Lighting

### The Blender Pipeline Architecture

Fog settings and lighting component interactions converge in `src/shader/n64.frag.glsl` within the `blender_fetch()` function. While full fog implementation remains pending, the architecture clearly defines the integration point:

```glsl
// src/shader/n64.frag.glsl
else if (val == BLENDER_CLR_FOG) return colorCC;
...
vec4 colorFog = vec4(1.0, 0.0, 0.0, 1.0); // placeholder @TODO

```

When active, the shader will replace the placeholder with linear interpolation between `colorFog` and the lit fragment color based on view-space depth relative to the `fogMin` and `fogMax` values supplied by the binary scene file.

### Atmospheric Depth Cues

The interaction creates realistic atmospheric perspective through three mechanisms:

- **Distance-based color wash**: Fragments beyond `fogMin` begin blending toward `fogColor`, reaching full replacement at `fogMax`
- **Lighting preservation**: Fog blends with already-lit surfaces, maintaining directional light gradients and ambient occlusion
- **Transparency consistency**: Because fog participates in the same gamma-corrected blending pipeline as lighting, translucent materials receive consistent atmospheric treatment

## Editor Workflow for Atmospheric Configuration

The [`src/editor/pages/parts/layerInspector.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/editor/pages/parts/layerInspector.cpp) file provides the ImGui interface for configuring these interactions:

```cpp
// src/editor/pages/parts/layerInspector.cpp
ImTable::addProp("Fog", layer.fog);
if (layer.fog.value) {
    std::vector<ImTable::ComboEntry> fogColorModes{
        {"Material colour", 0},
        {"Constant colour", 1},
        {"Custom colour",   2}
    };
    ImTable::addVecComboBox("Fog‑Mode", fogColorModes, layer.fogColorMode.value);
    if (layer.fogColorMode.value == 2) {
        ImTable::addColor("Fog Color", layer.fogColor.value);
    }
    ImTable::addProp("Fog Min", layer.fogMin);
    ImTable::addProp("Fog Max", layer.fogMax);
}

```

## Summary

- Pyrite64 stores **fog settings** as layer properties in [`src/project/scene/scene.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.cpp), supporting material-based, constant, or custom color modes with configurable distance ranges
- The **scene builder** serializes fog data to binary format in [`src/build/sceneBuilder.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/build/sceneBuilder.cpp), writing color values, min/max distances, and mode bytes
- **Lighting components** are populated per-draw-call in [`src/renderer/n64Mesh.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/renderer/n64Mesh.cpp), supporting one ambient and two directional lights passed via uniform buffers defined in `src/shader/ubo.glsl`
- The **fragment shader** in `src/shader/n64.frag.glsl` defines the integration point where fog will blend with lit fragments through the `blender_fetch()` function, though full implementation remains pending
- Together, these systems create atmospheric depth by applying distance-based color interpolation to already-lit surfaces, preserving lighting gradients while adding environmental haze

## Frequently Asked Questions

### How does Pyrite64 determine which fog color mode to use?

The fog color mode is stored as an integer in the layer properties (`layer.fogColorMode`) and serialized as a byte in the binary scene file. Mode 0 uses the material's inherent color, mode 1 uses a constant predefined color, and mode 2 allows artists to specify a custom RGBA value through the editor interface in [`src/editor/pages/parts/layerInspector.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/editor/pages/parts/layerInspector.cpp).

### What is the maximum number of lights supported per object?

Pyrite64 supports one ambient light and up to two directional lights per draw call. This limitation aligns with Nintendo 64 hardware constraints and is enforced in [`src/renderer/n64Mesh.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/renderer/n64Mesh.cpp) where the uniform buffer population loop explicitly checks `if (lightIdx < 2)` before assigning directional light data.

### Where does the actual fog blending calculation occur?

While the architecture is prepared for fog blending in `src/shader/n64.frag.glsl` within the `blender_fetch()` function, the actual implementation remains a TODO placeholder. The current code defines `vec4 colorFog = vec4(1.0, 0.0, 0.0, 1.0)` as a placeholder, indicating that future development will implement the linear interpolation between lit fragments and fog color based on view-space depth.

### How do fog settings interact with transparent materials?

Fog settings blend with fragment colors before the final two-stage blending operation in the shader pipeline. Because both fog and lighting participate in the same gamma-corrected blending system defined in `src/shader/n64.frag.glsl`, translucent materials receive consistent atmospheric treatment. The final alpha value is forced to the linearized new color's alpha, ensuring that fog-induced transparency modifications remain physically consistent with the rest of the rendering pipeline.