How to Apply GLSL Shaders for Visual Styles Using CesiumJS PostProcessStage
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 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, the retro CRT effect is defined with custom uniforms for pixelation, distortion, and instability:
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 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:
_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, 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:
_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:
// 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 file provides additional intensity helpers that ensure safe stacking, while src/bloom.js contributes bloom-related intensity calculations that feed into the style pipeline.
Summary
- Shader modules in
src/styles/*.jsexport GLSL fragment shaders and uniform metadata for each visual style. _initStages()insrc/ui.jscreates Cesium PostProcessStage objects with disabled initial states and merged uniform maps.- Intensity uniforms control stage activation via
_setStageIntensity(), using a 0.001 threshold to togglestage.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, 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →