Bevy Toon Shader 实现方案
Concept: Cartoon Shading (Cel Shading) in Bevy 0.18 Scope: Custom Material implementation for ARPG character rendering Related: bevy-rendering, stylized-realism-game-art, bevy
Problem Statement
Existing toon shader crates (bevy_toon_material, bevy_toon_shader, bevy_shader_mtoon) max out at Bevy 0.15. For Bevy 0.18 projects (e.g., GoldMiner, ARPG), a custom Material implementation is required.
Core Algorithm
1. Diffuse Banding
The key visual signature of toon shading is quantized light response:
let n_dot_l = dot(light_direction, normal);
var light_intensity = 0.0;
if band_count > 0 {
// Hard bands: e.g., band_count=3 gives 0, 1/3, 2/3, 1.0
let x = round(n_dot_l * f32(band_count));
light_intensity = x / f32(band_count);
} else {
// Smooth toon with tight threshold
light_intensity = smoothstep(0.0, 0.01, n_dot_l);
}2. Specular Highlight
Blinn-Phong with sharp step function for cartoon look:
let half_vec = normalize(light_direction + view_direction);
let n_dot_h = dot(normal, half_vec);
let spec_intensity = pow(n_dot_h * light_intensity, glossiness * glossiness);
let spec_smooth = smoothstep(0.005, 0.01, spec_intensity);3. Rim Lighting
View-dependent edge glow, common in anime-style rendering:
let rim_dot = 1.0 - dot(view_direction, normal);
var rim_intensity = rim_dot * pow(n_dot_l, rim_threshold);
rim_intensity = smoothstep(rim_amount - 0.01, rim_amount + 0.01, rim_intensity);4. Final Composite
return base_color * (ambient + diffuse + specular + rim);Bevy 0.18 Implementation
Material Struct
#[derive(Asset, AsBindGroup, TypePath, Debug, Clone)]
pub struct ToonMaterial {
#[uniform(0)] pub base_color: LinearRgba,
#[uniform(0)] pub light_direction: Vec3,
#[uniform(0)] pub light_color: LinearRgba,
#[uniform(0)] pub camera_position: Vec3,
#[uniform(0)] pub ambient_color: LinearRgba,
#[uniform(0)] pub rim_amount: f32,
#[uniform(0)] pub rim_color: LinearRgba,
#[uniform(0)] pub rim_threshold: f32,
#[uniform(0)] pub band_count: u32,
#[texture(1)] #[sampler(2)] pub base_texture: Option<Handle<Image>>,
}Key API Points
MaterialPlugin::<ToonMaterial>::default()registers the materialMeshMaterial3d<ToonMaterial>component applies it to meshesVertexOutputprovidesworld_normal,world_position,uv#{MATERIAL_BIND_GROUP}macro expands to correct group indexAsBindGroupderive auto-generates bind group layout
Per-Frame Update System
Light direction, light color, and camera position must be synced from scene entities to material uniforms every frame. This is done via ResMut<Assets<ToonMaterial>> iteration.
Design Decisions
| Decision | Rationale |
|---|---|
Custom Material over MaterialExtension | Full control over fragment shader; StandardMaterial PBR is irrelevant for stylized look |
| Single directional light | Toon shading typically uses a single “key light” for readability; multiple lights create visual noise |
| No shadow map integration | Shadow receiving in toon requires special handling (hard shadow bands); deferred for future iteration |
| Global light/camera in per-material uniform | Simplest implementation; optimizable later via global uniform buffer |
ARPG Integration Notes
Visual Separation Strategy
Toon-shaded characters naturally separate from PBR environments. Three-layer approach:
- Character layer: ToonMaterial, band_count=3~5, warm rim
- Environment layer: StandardMaterial with PBR
- Skill VFX layer: Separate additive/custom shaders with bloom
Per-Character Tuning
Each character has independent Handle<ToonMaterial> allowing:
- Different
base_colortints - Different
band_count(hero=3, boss=2 for harder look) - Different
rim_color(ally=blue rim, enemy=red rim)
Performance Considerations
update_toon_materialsiterates allAssets<ToonMaterial>per frame- With 100+ materials, consider moving light/camera to global uniform
MaterialPluginhandles batching automatically
mosaic 美术研究(2026-07-29 / 2026-08-10)
docs/research/2026-07-29-arpg-art-style-deep-dive.md(2026-07-29):有效方向是可读性分层(角色从环境 lift),不是「画得更细」。Bevy 0.19 cel 走官方 extended_material post-lighting 量化;不要移植钉死 0.15 的 bevy_toon_material。mosaic 当时 sim 是 entity-per-tile,presentation 是单 mesh 挤出——per-tile 描边外推不成立。
docs/research/2026-08-10-stylized-toon-terrain-rendering.md(2026-08-10):风格化地形渲染与角色 toon 不是同一套预算。Torchlight 2/3 对照:TL2 不是 cel/toon 合同;TL3 是风格化 UE4 PBR,不是 toon。不要把 TL 画风写成 SideDawn 锁。
SideDawn 产品名是等距 3D toon ARPG,但今日 preview 没有角色 toon 管线。见 sidedawn、arpg-character-proportions。
References
- Raw research notes: bevy-toon-shader-research.md
- Roy Stan’s Unity Toon Shader Tutorial: https://roystan.net/articles/toon-shader/
- bevy_toon_material implementation: https://github.com/AndrewDanial/bevy_toon_material
- Bevy Material API docs: https://docs.rs/bevy/latest/bevy/pbr/trait.Material.html