Bevy Pixel Art Shader
Overview
A material extension for Bevy that renders 3D models with a pixel art aesthetic. Integrates with Bevy's full PBR lighting pipeline, then post-processes through toon quantization, CIELAB palette matching, and screen-space Bayer dithering. Includes a depth-aware compositor that correctly resolves occlusion between the low-res pixel art layer and the full-res scene.

Version compatibility
| Crate version | Bevy version |
|---|---|
| 0.2.x | 0.18.x |
| 0.1.x | 0.18.x |
Features
- Full PBR integration: Builds on
StandardMaterialviaMaterialExtension— all scene lights, shadows, and IBL work out of the box. - Depth-aware compositor: Post-process render node compares reversed-Z depth from both cameras, displaying whichever layer is closer. Replaces the old UI ImageNode overlay.
- Toon quantization: Configurable band count and softness for hard or smooth luminance banding.
- CIELAB palette matching: Nearest-neighbor color quantization in perceptually uniform CIELAB space. Ships with a 64-color default palette (PICO-8 32 + DB32-inspired 32).
- Screen-space Bayer dithering: 4x4 ordered dither aligned to screen pixels — no surface distortion when objects move.
- Holdout material: Invisible occluder (
HoldoutMaterial) that writes depth but outputs fully transparent color. Use on duplicated geometry in the low-res layer to occlude pixel art entities behind full-res scene geometry. - Edge detection compatible: Prepass writes
alpha=1.0for pixel art andalpha=0.0for holdout, enabling selective outline rendering viabevy_edge_detection_outline. - Debug stages: Cycle through pipeline stages (PBR only → +Toon → +Palette → +Dither) to inspect each step.
Architecture
Low-res Camera3d (e.g. 320×180, RenderLayers 1)
├── PixelArtMaterial entities (3D models)
├── HoldoutMaterial entities (occluders)
├── EdgeDetection + DepthPrepass + NormalPrepass
├── LowResPixelArtCamera marker
└── Output: color texture + depth prepass texture
Full-res Camera3d (window resolution, RenderLayers 0)
├── Standard PBR entities (terrain, comparison objects)
├── PixelArtCompositor (post-process node)
│ ├── Reads: full-res color + full-res depth
│ ├── Reads: low-res color + low-res depth
│ └── Per-pixel reversed-Z depth comparison → closer layer wins
└── Output: composited result
Usage
use *;
use RenderTarget;
use RenderLayers;
use ImageSampler;
use TextureFormat;
use ;
use ;
Shader Parameters
| Parameter | Default | Description |
|---|---|---|
base_tint |
(1, 1, 1, 1) |
Base tint color (linear RGBA), replaces model's base_color |
toon_bands |
10.0 |
Number of toon shading bands |
toon_softness |
0.0 |
Softness of band transitions (0 = hard pixel art edges) |
toon_shadow_floor |
0.1 |
Minimum brightness in shadow areas |
dither_density |
1.0 |
Screen-space dither scale (1.0 = 1 Bayer cell per pixel) |
palette_count |
64 |
Number of active palette colors (0 = disable quantization) |
palette_strength |
0.25 |
Blend strength toward palette (0 = off, 1 = full) |
dither_strength |
0.3 |
Bayer dither strength (0 = off, 1 = full) |
debug_stage |
0 |
Pipeline stage to visualize (0=full, 1=PBR, 2=+Toon, 3=+Palette, 4=+Dither) |
Compositor Parameters
| Parameter | Default | Description |
|---|---|---|
depth_bias |
0.01 |
Depth comparison tolerance, scaled proportionally by depth. Compensates for precision mismatch between low-res and full-res depth buffers. |
Default Palette
64 colors: PICO-8 base (16) + PICO-8 extended (16) + DB32-inspired extras (32 earth tones, skin, sky, foliage, metal shades). Use default_pixel_art_palette() or supply your own [Vec4; 64] array.
Run the example
Example controls
- Left-drag: orbit camera
- Right-drag: pan camera
- Scroll: zoom
- EGUI panel: adjust all shader parameters, toggle edge detection, switch between Sobel/Roberts Cross operators, tune depth bias
Dependencies
[]
= "0.2"