# How to Apply GLSL Shaders for Visual Styles Using CesiumJS PostProcessStage

> Learn how to apply GLSL shaders for visual styles in CesiumJS using PostProcessStage. Control effects like CRT, noir, and thermal imaging with animated transitions.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: how-to-guide
- Published: 2026-09-09

---

**The gods-eye-view repository applies GLSL shaders for visual styles by encapsulating fragment shader code in CesiumJS PostProcessStage objects, where an intensity uniform controls activation and 500ms animated transitions enable smooth cross-fading between effects like retro CRT, noir, and thermal imaging.**

The implementation creates a modular post-processing pipeline that separates shader definitions from runtime management. By leveraging CesiumJS PostProcessStage APIs, the application stacks multiple visual effects while maintaining performance through selective stage enabling based on uniform thresholds.

## Architecture of the Post-Processing System

The system architecture follows a three-step pipeline implemented across [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js) and the `src/styles/` directory. According to the gods-eye-view source code, each visual style is defined as a standalone shader module, instantiated as a Cesium PostProcessStage, and driven by animated intensity values that control GPU execution and visual blending.

## Step 1: Defining GLSL Shader Modules

Each visual style resides in `src/styles/*.js` as a module exporting a configuration object. This object contains the **fragment shader** source code as a string and a **uniforms** descriptor that declares custom parameters and default values.

In [`src/styles/retro.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/styles/retro.js), the retro CRT effect is defined with custom uniforms for pixelation, distortion, and instability:

```javascript
export const retroShader = {
  fragmentShader: `
    uniform sampler2D colorTexture;
    uniform float intensity;
    uniform float pixelation;
    uniform float distortion;
    uniform float instability;
    uniform float time;
    in vec2 v_textureCoordinates;
    out vec4 fragColor;
    
    void main() {
      vec2 uv = v_textureCoordinates;
      // CRT-specific GLSL logic using uniforms
      fragColor = texture(colorTexture, uv) * intensity;
    }
  `,
  uniforms: {
    pixelation: 4.0,
    distortion: 0.2,
    instability: 0.1
  }
};

```

The **uniforms** field specifies default values for shader parameters, while the presence of `uniform float time` triggers the animation loop initialization when the stage is created.

## Step 2: Creating PostProcessStage Objects

The `_initStages()` method in [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js) instantiates a `Cesium.PostProcessStage` for every style imported into the `STYLES` constant. This method builds the bridge between static shader definitions and Cesium's rendering pipeline.

### The _initStages() Implementation

The method iterates over the style map, constructs stage objects with merged uniform configurations, and initially disables them to prevent unnecessary GPU computation:

```javascript
_initStages() {
  this.stages = {};
  
  Object.entries(STYLES).forEach(([styleName, styleDef]) => {
    const uniforms = {
      intensity: 0.0,
      ...styleDef.uniforms
    };
    
    // Add time uniform if shader references it
    if (styleDef.fragmentShader.includes('uniform float time')) {
      uniforms.time = 0.0;
    }
    
    const stage = new Cesium.PostProcessStage({
      name: styleName,
      fragmentShader: styleDef.fragmentShader,
      uniforms: uniforms
    });
    
    stage.enabled = false; // Disabled until intensity > 0
    this.stages[styleName] = stage;
    this.viewer.scene.postProcessStages.add(stage);
  });
}

```

The **intensity** uniform serves as the master control for the effect's visibility. Stages remain disabled (`stage.enabled = false`) when intensity is zero, ensuring zero GPU overhead for inactive styles.

## Step 3: Driving Shader Intensity and Transitions

Runtime control flows through the `_setStageIntensity()` method in [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js), which updates uniform values and manages the render cycle. When a user selects a visual style or toggles the cockpit-vision overlay, this method orchestrates the transition.

### Uniform Updates and Stage Activation

The method applies a threshold check to determine stage visibility, preventing floating-point precision issues from keeping shaders active at negligible intensities:

```javascript
_setStageIntensity(stage, value) {
  stage.uniforms.intensity = value;
  
  // Enable stage only when perceptibly visible
  stage.enabled = value > 0.001;
  
  governorRequestRender('style-stage');
}

```

### Time-Based Animation and Cross-Fading

For shaders utilizing temporal effects, the system manages an animation loop that updates the **time** uniform per frame. The UI animates intensity values over `TRANSITION_DURATION_MS = 500` milliseconds to achieve smooth cross-fades between styles:

```javascript
// Animation loop for time-dependent shaders
if (stage.uniforms.time !== undefined && stage.enabled) {
  this._animationActive = true;
  this._lastFrameTime = performance.now();
}

// Transition logic (simplified)
const currentIntensity = startIntensity + 
  (targetIntensity - startIntensity) * (elapsedTime / TRANSITION_DURATION_MS);

```

The animation system ensures that outgoing styles fade out while incoming styles fade in, creating cinematic transitions without frame drops.

## Stacking Multiple Visual Styles

Because each style maintains its own PostProcessStage in the `postProcessStages` collection, the system supports **compositing multiple effects** simultaneously. For example, combining retro CRT scanlines with thermal color grading requires activating both stages with non-zero intensity values.

Performance optimization relies on the early-exit logic: stages with intensity approximately zero remain disabled and incur no GPU cost. The [`src/scopeMask.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/scopeMask.js) file provides additional intensity helpers that ensure safe stacking, while [`src/bloom.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/bloom.js) contributes bloom-related intensity calculations that feed into the style pipeline.

## Summary

- **Shader modules** in `src/styles/*.js` export GLSL fragment shaders and uniform metadata for each visual style.
- **`_initStages()`** in [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js) creates Cesium PostProcessStage objects with disabled initial states and merged uniform maps.
- **Intensity uniforms** control stage activation via `_setStageIntensity()`, using a 0.001 threshold to toggle `stage.enabled`.
- **Time-based shaders** trigger per-frame animation loops when the stage is active and the GLSL source contains `uniform float time`.
- **Cross-fade transitions** animate intensity over 500 milliseconds (`TRANSITION_DURATION_MS`) for smooth visual style switching.
- **Stage stacking** allows multiple post-process effects to render simultaneously, with disabled stages (intensity ≈ 0) consuming zero GPU resources.

## Frequently Asked Questions

### How do I add a custom GLSL shader to the gods-eye-view application?

Create a new file in `src/styles/` that exports an object containing a `fragmentShader` string and a `uniforms` object with default values. Import this module into [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js), add it to the `STYLES` constant map, and the `_initStages()` method will automatically instantiate a PostProcessStage for your shader. Ensure your GLSL code declares `uniform float intensity` to integrate with the existing intensity control system.

### Why are PostProcessStage objects initially disabled in the code?

Stages are created with `stage.enabled = false` in `_initStages()` to prevent GPU computation on inactive styles. The `_setStageIntensity()` method enables stages only when `intensity > 0.001`, ensuring that zero-intensity effects consume no rendering resources. This optimization is critical for maintaining frame rates when multiple style modules are defined but inactive.

### How does the application handle smooth transitions between visual styles?

The UI layer animates the `intensity` uniform over `TRANSITION_DURATION_MS = 500` milliseconds using a requestAnimationFrame loop. As the outgoing style's intensity decreases from 1.0 to 0.0, the incoming style's intensity increases from 0.0 to 1.0, creating a cross-fade effect. The `_setStageIntensity()` method triggers `governorRequestRender()` to ensure Cesium re-renders the scene each frame during the transition.

### Can multiple visual effects be active simultaneously?

Yes. Each visual style exists as a separate PostProcessStage in the scene's `postProcessStages` collection. You can activate multiple styles by setting their intensity values independently—allowing combinations such as retro CRT distortion overlaid with thermal color grading. The system handles compositing automatically through Cesium's sequential post-processing pipeline, where each enabled stage receives the output of the previous stage as its input texture.