热重载
写着色器最痛的事情是什么?改一行代码、重新编译、重启应用、等待加载……然后发现颜色不对,再来一遍。Bevy 的资产热重载(hot reload)可以终结这个循环。
启用热重载
Bevy 的 DefaultPlugins 包含 AssetPlugin,默认就开启了文件监视。只要你的着色器文件在 assets/ 目录下,运行时修改 .wgsl 文件,Bevy 会自动检测变更、重新编译着色器、更新所有使用它的材质——不需要重启应用。
验证一下:
cargo run -p ch36-shaders --example listing-36-01在运行中的窗口旁边打开 code/ch36-shaders/assets/shaders/uniform_color.wgsl,把返回颜色从绿色改成红色:
@fragment
fn fragment(mesh: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0); // 改成红色
}保存文件。窗口里的方块几乎瞬间变红——没有重新编译 Rust 代码,没有重启进程。
热重载的原理
AssetPlugin 内部启动了一个文件系统监视器(使用 notify crate)。当 assets/ 目录下的文件发生变更时:
- 文件监视器检测到
.wgsl文件被修改 - Bevy 的资产系统重新加载文件内容
Shader资产的from_wgsl被重新调用,通过naga_oil预处理器重新编译 WGSL- 所有引用这个
Shader的材质管线被标记为"需要更新" - 渲染管线在下一帧重建,新着色器生效
整个过程对你的 Rust 代码完全透明——Assets<Shader> 内部处理了一切。
限制
热重载有几个需要注意的点:
只改 WGSL 文件有效。 如果你修改了 Rust 侧的材质结构体(加字段、改类型),仍然需要重新编译。热重载只覆盖着色器代码的变更。
Shader defs 的变更不会触发热重载。 如果你在 specialize 方法里动态注入 shader def,变更 shader def 的逻辑在 Rust 代码里——需要重新编译。但已有的 shader def 分支在热重载时会正确重新编译。
嵌入资产不支持热重载。 如果你用 embedded_asset! 把着色器编译进二进制文件,文件系统监视器看不到它——因为运行时根本没有外部文件。热重载只适用于从磁盘加载的资产路径("shaders/foo.wgsl".into() 这种形式)。
错误处理。 如果你保存了一个有语法错误的 WGSL 文件,Bevy 会在日志中打印编译错误,但不会崩溃——材质会保持上一个正确版本的着色器继续运行。修复错误、保存文件,着色器会自动恢复。
工作流建议
开发阶段的最佳实践:
- 用
cargo run启动应用,窗口不要关 - 用你喜欢的编辑器打开
assets/shaders/*.wgsl - 改着色器、保存、看效果——秒级反馈
- 满意后再
cargo check确认 Rust 侧没有编译问题
这个循环比"改-编译-跑"快一个数量级。当你的着色器越来越复杂——多 pass、多 shader def 分支——热重载的时间节省会更加明显。