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 system with 38 shader parameters for visual effects. It includes distortions, color grading, raymarched overlays, retro effects, and more.

Overview

The EffectsPass operates as a render graph node that processes the rendered scene through a fullscreen shader. Effects can be combined and animated for music visualizers, stylized games, or creative applications.

Setup

Create and configure the effects state, then add the pass to your 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,
) {
    // Create shared state handle
    let effects_state = create_effects_state();
    self.effects_state = Some(effects_state.clone());

    // Create and add the pass
    let effects_pass = EffectsPass::new(device, surface_format, effects_state);
    graph.add_pass(Box::new(effects_pass));
}
}

Modifying Effects

Access the state handle to modify effect parameters:

#![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() {
            // Modify uniforms
            state.uniforms.chromatic_aberration = 0.02;
            state.uniforms.vignette = 0.3;

            // Enable/disable the entire pass
            state.enabled = true;

            // Auto-animate hue rotation
            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

Apply preset color grades:

#![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

Blend raymarched 3D effects over the scene:

#![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 can be layered for complex 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

Combine with AudioAnalyzer for reactive visuals:

#![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() {
            // Chromatic aberration on bass
            state.uniforms.chromatic_aberration =
                self.analyzer.smoothed_bass * 0.05;

            // Glitch on snare hits
            state.uniforms.glitch_intensity =
                self.analyzer.snare_decay * 0.5;

            // Zoom pulse on kick
            state.uniforms.zoom_pulse =
                self.analyzer.kick_decay * 0.3;

            // Color cycling based on energy
            state.uniforms.hue_rotation =
                self.time * self.analyzer.intensity * 0.2;

            // Screen shake on drops
            if self.analyzer.is_dropping {
                state.uniforms.screen_shake =
                    self.analyzer.drop_intensity * 0.1;
            } else {
                state.uniforms.screen_shake *= 0.9;
            }

            // Switch to tunnel during breakdown
            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/Output Slots

By default, the EffectsPass reads from "input" and writes to "output". Configure custom slots:

#![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

Temporarily bypass all effects:

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

When disabled, the pass performs a simple blit operation with no effects applied.

Auto-Animate Hue

Enable automatic hue rotation:

#![allow(unused)]
fn main() {
if let Ok(mut state) = state_handle.write() {
    state.animate_hue = true; // Continuously rotate hue based on time
}
}

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;

        // Analyze audio
        self.analyzer.analyze_at_time(self.time);

        // Update effects based on audio
        if let Some(state_handle) = &self.effects_state {
            if let Ok(mut state) = state_handle.write() {
                // Base effects
                state.uniforms.vignette = 0.3;
                state.uniforms.crt_scanlines = 0.15;

                // Audio-reactive
                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;

                // Structure-based
                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...

        // Create effects pass
        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