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.
| Mode | Value | Description |
|---|---|---|
None | 0 | No color grading |
Cyberpunk | 1 | Teal and magenta, high contrast |
Sunset | 2 | Warm orange and purple tones |
Grayscale | 3 | Black and white |
Sepia | 4 | Vintage brown tones |
Matrix | 5 | Green tinted, digital look |
HotMetal | 6 | Heat 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.
| Mode | Value | Description |
|---|---|---|
Off | 0 | No raymarching |
Tunnel | 1 | Infinite tunnel flythrough |
Fractal | 2 | 2D fractal pattern |
Mandelbulb | 3 | 3D mandelbulb fractal |
PlasmaVortex | 4 | Swirling plasma effect |
Geometric | 5 | Repeating 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
| Parameter | Range | Default | Description |
|---|---|---|---|
time | 0.0+ | 0.0 | Elapsed time (auto-updated) |
chromatic_aberration | 0.0-0.1 | 0.0 | RGB channel offset |
wave_distortion | 0.0-1.0 | 0.0 | Sinusoidal screen warp |
color_shift | 0.0-1.0 | 0.0 | Global color offset |
kaleidoscope_segments | 0-16 | 0.0 | Mirror segment count |
crt_scanlines | 0.0-1.0 | 0.0 | Scanline intensity |
vignette | 0.0-1.0 | 0.0 | Edge darkening |
plasma_intensity | 0.0-1.0 | 0.0 | Plasma overlay strength |
glitch_intensity | 0.0-1.0 | 0.0 | Digital glitch amount |
mirror_mode | 0.0-1.0 | 0.0 | Screen mirroring |
invert | 0.0-1.0 | 0.0 | Color inversion |
hue_rotation | 0.0-1.0 | 0.0 | Hue shift amount |
raymarch_mode | 0-5 | 0.0 | Raymarch effect type |
raymarch_blend | 0.0-1.0 | 0.0 | Raymarch overlay blend |
film_grain | 0.0-1.0 | 0.0 | Noise grain intensity |
sharpen | 0.0-1.0 | 0.0 | Edge sharpening |
pixelate | 0-64 | 0.0 | Pixel size (0=off) |
color_posterize | 0-16 | 0.0 | Color quantization |
radial_blur | 0.0-1.0 | 0.0 | Center blur amount |
tunnel_speed | 0.0-5.0 | 1.0 | Tunnel animation speed |
fractal_iterations | 1-8 | 4.0 | Fractal detail level |
glow_intensity | 0.0-1.0 | 0.0 | Bloom-like glow |
screen_shake | 0.0-0.5 | 0.0 | Camera shake offset |
zoom_pulse | 0.0-1.0 | 0.0 | Rhythmic zoom amount |
speed_lines | 0.0-1.0 | 0.0 | Motion line intensity |
color_grade_mode | 0-6 | 0.0 | Color grading preset |
vhs_distortion | 0.0-1.0 | 0.0 | VHS tape wobble |
lens_flare | 0.0-1.0 | 0.0 | Light flare intensity |
edge_glow | 0.0-1.0 | 0.0 | Edge highlight amount |
saturation | 0.0-2.0 | 1.0 | Color saturation |
warp_speed | 0.0-1.0 | 0.0 | Hyperspace stretch |
pulse_rings | 0.0-1.0 | 0.0 | Expanding ring effect |
heat_distortion | 0.0-1.0 | 0.0 | Heat shimmer amount |
digital_rain | 0.0-1.0 | 0.0 | Matrix rain effect |
strobe | 0.0-1.0 | 0.0 | Flash intensity |
color_cycle_speed | 0.0-5.0 | 1.0 | Auto color animation rate |
feedback_amount | 0.0-1.0 | 0.0 | Frame feedback blend |
ascii_mode | 0.0-1.0 | 0.0 | ASCII art conversion |