Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Effects Pass

Live Demo: PSX Retro Effects

The EffectsPass is a configurable post-processing pass with 38 shader parameters. It covers distortions, color grading, raymarched overlays, retro effects, and pattern overlays. Effects can be combined and animated for music visualizers, stylized games, and creative applications.

Overview

The EffectsPass is a render graph node that runs the rendered scene through a fullscreen shader. The shader reads from the input slot, applies whatever effects are enabled in the uniform block, and writes to the output slot. Defaults are "input" and "output".

Setup

Create the shared state handle, then add the pass to the render graph.

#![allow(unused)]
fn main() {
use nightshade::render::wgpu::passes::postprocess::effects::*;

fn configure_render_graph(
    &mut self,
    graph: &mut RenderGraph<World>,
    device: &wgpu::Device,
    surface_format: wgpu::TextureFormat,
    resources: RenderResources,
) {
    let effects_state = create_effects_state();
    self.effects_state = Some(effects_state.clone());

    let effects_pass = EffectsPass::new(device, surface_format, effects_state);
    graph.add_pass(Box::new(effects_pass));
}
}

Modifying effects

The state handle lets you mutate effect parameters from anywhere in the application.

#![allow(unused)]
fn main() {
fn run_systems(&mut self, world: &mut World) {
    if let Some(state_handle) = &self.effects_state {
        if let Ok(mut state) = state_handle.write() {
            state.uniforms.chromatic_aberration = 0.02;
            state.uniforms.vignette = 0.3;
            state.enabled = true;
            state.animate_hue = false;
        }
    }
}
}

Effect parameters

Distortion effects

#![allow(unused)]
fn main() {
// Chromatic aberration: RGB channel separation (0.0-0.1 typical)
uniforms.chromatic_aberration = 0.02;

// Wave distortion: sinusoidal screen warping
uniforms.wave_distortion = 0.5;

// Glitch intensity: digital glitch artifacts
uniforms.glitch_intensity = 0.3;

// VHS distortion: analog tape wobble and noise
uniforms.vhs_distortion = 0.4;

// Heat distortion: rising heat shimmer effect
uniforms.heat_distortion = 0.2;

// Screen shake: camera shake offset
uniforms.screen_shake = 0.1;

// Warp speed: hyperspace stretch effect
uniforms.warp_speed = 0.5;
}

Color effects

#![allow(unused)]
fn main() {
// Hue rotation: shift all colors around the wheel (0.0-1.0)
uniforms.hue_rotation = 0.5;

// Saturation: color intensity (0.0=grayscale, 1.0=normal, 2.0=oversaturated)
uniforms.saturation = 1.0;

// Color shift: global color offset
uniforms.color_shift = 0.1;

// Invert: color inversion (0.0=normal, 1.0=inverted)
uniforms.invert = 1.0;

// Color posterize: reduce color depth (0.0=off, higher=fewer colors)
uniforms.color_posterize = 4.0;

// Color cycle speed: rate of automatic color animation
uniforms.color_cycle_speed = 1.0;
}

Color grading

Color grading applies a preset LUT-style transform.

#![allow(unused)]
fn main() {
uniforms.color_grade_mode = ColorGradeMode::Cyberpunk as f32;
}

Available modes.

ModeValueDescription
None0No color grading
Cyberpunk1Teal and magenta, high contrast
Sunset2Warm orange and purple tones
Grayscale3Black and white
Sepia4Vintage brown tones
Matrix5Green tinted, digital look
HotMetal6Heat map colors

Geometric effects

#![allow(unused)]
fn main() {
// Kaleidoscope: mirror segments (0=off, 6-12 typical)
uniforms.kaleidoscope_segments = 6.0;

// Mirror mode: horizontal/vertical mirroring
uniforms.mirror_mode = 1.0;

// Zoom pulse: rhythmic zoom in/out
uniforms.zoom_pulse = 0.5;

// Radial blur: motion blur from center
uniforms.radial_blur = 0.2;

// Pixelate: reduce resolution (0=off, higher=larger pixels)
uniforms.pixelate = 8.0;
}

Raymarched overlays

Raymarched 3D effects blend over the scene at a configurable opacity.

#![allow(unused)]
fn main() {
uniforms.raymarch_mode = RaymarchMode::Tunnel as f32;
uniforms.raymarch_blend = 0.5; // 0.0-1.0 blend with scene
uniforms.tunnel_speed = 1.0;   // Animation speed
uniforms.fractal_iterations = 4.0;
}

Available raymarch modes.

ModeValueDescription
Off0No raymarching
Tunnel1Infinite tunnel flythrough
Fractal22D fractal pattern
Mandelbulb33D mandelbulb fractal
PlasmaVortex4Swirling plasma effect
Geometric5Repeating geometric shapes

Retro effects

#![allow(unused)]
fn main() {
// CRT scanlines: horizontal line overlay
uniforms.crt_scanlines = 0.5;

// Film grain: random noise overlay
uniforms.film_grain = 0.1;

// ASCII mode: convert to ASCII art characters
uniforms.ascii_mode = 1.0;

// Digital rain: Matrix-style falling characters
uniforms.digital_rain = 0.5;
}

Glow and light effects

#![allow(unused)]
fn main() {
// Vignette: darken screen edges (0.0-1.0)
uniforms.vignette = 0.3;

// Glow intensity: bloom-like glow
uniforms.glow_intensity = 0.5;

// Lens flare: bright light artifacts
uniforms.lens_flare = 0.3;

// Edge glow: outline bright edges
uniforms.edge_glow = 0.2;

// Strobe: flashing white overlay
uniforms.strobe = 0.5;
}

Plasma and patterns

#![allow(unused)]
fn main() {
// Plasma intensity: colorful plasma overlay
uniforms.plasma_intensity = 0.5;

// Pulse rings: expanding circular rings
uniforms.pulse_rings = 0.3;

// Speed lines: motion/action lines
uniforms.speed_lines = 0.5;
}

Image processing

#![allow(unused)]
fn main() {
// Sharpen: edge enhancement (0.0-1.0)
uniforms.sharpen = 0.5;

// Feedback amount: recursive frame blending
uniforms.feedback_amount = 0.3;
}

Combining effects

Effects layer freely. A few cohesive looks.

#![allow(unused)]
fn main() {
// Cyberpunk aesthetic
state.uniforms.chromatic_aberration = 0.015;
state.uniforms.crt_scanlines = 0.2;
state.uniforms.vignette = 0.4;
state.uniforms.color_grade_mode = ColorGradeMode::Cyberpunk as f32;
state.uniforms.glow_intensity = 0.3;

// VHS tape look
state.uniforms.vhs_distortion = 0.4;
state.uniforms.crt_scanlines = 0.3;
state.uniforms.film_grain = 0.15;
state.uniforms.chromatic_aberration = 0.01;
state.uniforms.saturation = 0.8;

// Psychedelic visualizer
state.uniforms.kaleidoscope_segments = 8.0;
state.uniforms.plasma_intensity = 0.3;
state.uniforms.hue_rotation = time * 0.1;
state.uniforms.wave_distortion = 0.2;
}

Music-reactive effects

Wire AudioAnalyzer outputs straight into effect uniforms.

#![allow(unused)]
fn main() {
fn run_systems(&mut self, world: &mut World) {
    self.analyzer.analyze_at_time(self.time);

    if let Some(state_handle) = &self.effects_state {
        if let Ok(mut state) = state_handle.write() {
            state.uniforms.chromatic_aberration =
                self.analyzer.smoothed_bass * 0.05;

            state.uniforms.glitch_intensity =
                self.analyzer.snare_decay * 0.5;

            state.uniforms.zoom_pulse =
                self.analyzer.kick_decay * 0.3;

            state.uniforms.hue_rotation =
                self.time * self.analyzer.intensity * 0.2;

            if self.analyzer.is_dropping {
                state.uniforms.screen_shake =
                    self.analyzer.drop_intensity * 0.1;
            } else {
                state.uniforms.screen_shake *= 0.9;
            }

            if self.analyzer.is_breakdown {
                state.uniforms.raymarch_mode = RaymarchMode::Tunnel as f32;
                state.uniforms.raymarch_blend =
                    self.analyzer.breakdown_intensity * 0.5;
            } else {
                state.uniforms.raymarch_blend *= 0.95;
            }
        }
    }
}
}

Custom input and output slots

The default slots are "input" and "output". Override them at construction.

#![allow(unused)]
fn main() {
let effects_pass = EffectsPass::with_slots(
    device,
    surface_format,
    effects_state,
    "post_bloom",    // Input slot name
    "final_output"   // Output slot name
);
}

Disabling the pass

The enabled flag bypasses the shader. When disabled, the pass performs a simple blit with no effects applied.

#![allow(unused)]
fn main() {
if let Ok(mut state) = state_handle.write() {
    state.enabled = false;
}
}

Auto-animate hue

animate_hue rotates the hue uniform continuously from elapsed time.

#![allow(unused)]
fn main() {
if let Ok(mut state) = state_handle.write() {
    state.animate_hue = true;
}
}

Complete example

use nightshade::prelude::*;
use nightshade::render::wgpu::passes::postprocess::effects::*;

struct VisualDemo {
    effects_state: Option<EffectsStateHandle>,
    analyzer: AudioAnalyzer,
    time: f32,
}

impl Default for VisualDemo {
    fn default() -> Self {
        Self {
            effects_state: None,
            analyzer: AudioAnalyzer::new(),
            time: 0.0,
        }
    }
}

impl State for VisualDemo {
    fn initialize(&mut self, world: &mut World) {
        let camera = spawn_camera(world, Vec3::new(0.0, 5.0, 10.0), "Camera".to_string());
        world.resources.active_camera = Some(camera);
        spawn_sun(world);

        spawn_cube_at(world, Vec3::new(0.0, 1.0, 0.0));
        spawn_sphere_at(world, Vec3::new(3.0, 1.0, 0.0));

        // Decode audio to raw samples (requires symphonia or similar crate)
        // self.analyzer.load_samples(samples, sample_rate);
    }

    fn run_systems(&mut self, world: &mut World) {
        let dt = world.resources.window.timing.delta_time;
        self.time += dt;

        self.analyzer.analyze_at_time(self.time);

        if let Some(state_handle) = &self.effects_state {
            if let Ok(mut state) = state_handle.write() {
                state.uniforms.vignette = 0.3;
                state.uniforms.crt_scanlines = 0.15;

                state.uniforms.chromatic_aberration =
                    self.analyzer.smoothed_bass * 0.04;
                state.uniforms.glow_intensity =
                    self.analyzer.intensity * 0.5;
                state.uniforms.zoom_pulse =
                    self.analyzer.kick_decay * 0.2;

                if self.analyzer.is_dropping {
                    state.uniforms.color_grade_mode =
                        ColorGradeMode::Cyberpunk as f32;
                    state.uniforms.strobe =
                        self.analyzer.drop_intensity * 0.3;
                } else if self.analyzer.is_breakdown {
                    state.uniforms.color_grade_mode =
                        ColorGradeMode::Grayscale as f32;
                } else {
                    state.uniforms.color_grade_mode =
                        ColorGradeMode::None as f32;
                    state.uniforms.strobe = 0.0;
                }
            }
        }
    }

    fn configure_render_graph(
        &mut self,
        graph: &mut RenderGraph<World>,
        device: &wgpu::Device,
        surface_format: wgpu::TextureFormat,
        resources: RenderResources,
    ) {
        // Add standard passes first...

        let effects_state = create_effects_state();
        self.effects_state = Some(effects_state.clone());

        let effects_pass = EffectsPass::new(device, surface_format, effects_state);
        graph.add_pass(Box::new(effects_pass));
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    nightshade::launch(VisualDemo::default())
}

Parameter reference

ParameterRangeDefaultDescription
time0.0+0.0Elapsed time (auto-updated)
chromatic_aberration0.0-0.10.0RGB channel offset
wave_distortion0.0-1.00.0Sinusoidal screen warp
color_shift0.0-1.00.0Global color offset
kaleidoscope_segments0-160.0Mirror segment count
crt_scanlines0.0-1.00.0Scanline intensity
vignette0.0-1.00.0Edge darkening
plasma_intensity0.0-1.00.0Plasma overlay strength
glitch_intensity0.0-1.00.0Digital glitch amount
mirror_mode0.0-1.00.0Screen mirroring
invert0.0-1.00.0Color inversion
hue_rotation0.0-1.00.0Hue shift amount
raymarch_mode0-50.0Raymarch effect type
raymarch_blend0.0-1.00.0Raymarch overlay blend
film_grain0.0-1.00.0Noise grain intensity
sharpen0.0-1.00.0Edge sharpening
pixelate0-640.0Pixel size (0=off)
color_posterize0-160.0Color quantization
radial_blur0.0-1.00.0Center blur amount
tunnel_speed0.0-5.01.0Tunnel animation speed
fractal_iterations1-84.0Fractal detail level
glow_intensity0.0-1.00.0Bloom-like glow
screen_shake0.0-0.50.0Camera shake offset
zoom_pulse0.0-1.00.0Rhythmic zoom amount
speed_lines0.0-1.00.0Motion line intensity
color_grade_mode0-60.0Color grading preset
vhs_distortion0.0-1.00.0VHS tape wobble
lens_flare0.0-1.00.0Light flare intensity
edge_glow0.0-1.00.0Edge highlight amount
saturation0.0-2.01.0Color saturation
warp_speed0.0-1.00.0Hyperspace stretch
pulse_rings0.0-1.00.0Expanding ring effect
heat_distortion0.0-1.00.0Heat shimmer amount
digital_rain0.0-1.00.0Matrix rain effect
strobe0.0-1.00.0Flash intensity
color_cycle_speed0.0-5.01.0Auto color animation rate
feedback_amount0.0-1.00.0Frame feedback blend
ascii_mode0.0-1.00.0ASCII art conversion