Shadplay 源码拆解:Bevy Material trait、AsBindGroup 着色器数据绑定与插件注册完整指南
Shadplay 源码拆解:Bevy Material trait、AsBindGroup 着色器数据绑定与插件注册完整指南
【免费下载链接】shadplayReal-time wgsl visualisation tooling for educating oneself in the art of shader programming项目地址: https://gitcode.com/gh_mirrors/sh/shadplay
Shadplay 是一个基于 Bevy 的实时 WGSL 着色器可视化工具,帮你零摩擦地学习 Rust 生态中的着色器编程:在外部编辑器里改一行.wgsl,保存即可在窗口里立刻看到效果。本文从源码拆解它的三大核心机制——AsBindGroup数据绑定、Materialtrait 实现与插件注册,讲清楚颜色、纹理、鼠标坐标这三个数据是如何从 Rust 程序一路送到 GPU 着色器里的,读完你就能独立写出自己的 Bevy 自定义材质。
一图看懂数据流:Rust → AsBindGroup → WGSL
在 Shadplay 里,所有传给着色器的数据都走同一条流水线:
Rust 结构体(derive
AsBindGroup)→ Bevy 自动打包成 BindGroup → WGSL 里用@group @binding按编号读取
理解"编号对齐"是理解整个项目的钥匙。看 src/shader_utils/mod.rs 中的 3D 材质结构体:
#[derive(Asset, AsBindGroup, TypePath, Debug, Clone)] pub struct YourShader { #[uniform(100)] pub color: LinearRgba, #[texture(101, dimension = "2d")] #[sampler(102)] pub img: Handle<Image>, }再看它对应的着色器assets/shaders/myshader.wgsl:
@group(3) @binding(100) var<uniform> color: vec4f; @group(3) @binding(101) var texture: texture_2d<f32>; @group(3) @binding(102) var texture_sampler: sampler;三组数字一一对应:
| Rust 侧属性 | WGSL 侧声明 | 数据内容 |
|---|---|---|
#[uniform(100)] | @binding(100) | 材质颜色 |
#[texture(101)] | @binding(101) | 纹理图 |
#[sampler(102)] | @binding(102) | 采样器 |
新手最容易踩的坑:Rust 侧改绑定位,忘了同步改 WGSL 里的@binding编号,结果就是画面错乱或黑屏。@group(3)是 Bevy 为自定义材质预留的固定 group,你不用自己分配。
此外,鼠标坐标MousePos是一个只 derive 了ShaderType的普通结构体,作为字段嵌入YourShader2D的#[uniform(0)],这样每个像素着色器都能读到当前鼠标位置——这正是 Shadplay 支持"鼠标交互着色器"的底层机制。
Material trait 实现:一行代码指定着色器文件
AsBindGroup只解决"数据怎么送","用哪个.wgsl文件渲染"则由Materialtrait 决定。Shadplay 的实现干净到只有一行(src/shader_utils/mod.rs):
impl Material for YourShader { fn fragment_shader() -> ShaderRef { "shaders/myshader.wgsl".into() } }2D 模式则实现Material2d指向shaders/myshader_2d.wgsl(即assets/shaders/myshader_2d.wgsl,也就是打开应用时看到的 ShaderToy 风格默认画面)。按T键切 2D、H键切 3D,切换的就是这两套材质。
插件注册:两行代码接入 Bevy 渲染管线
数据定义好了、着色器选好了,最后一步是让 Bevy 认识这套材质。打开src/plugin.rs,核心是ShadPlayPlugin::build里的两行:
.add_plugins(MaterialPlugin::<YourShader>::default()) .add_plugins(Material2dPlugin::<YourShader2D>::default())Bevy 的MaterialPlugin::<T>是泛型插件,一旦注册,Bevy 会自动为该类型生成渲染通道、处理Assets<YourShader>资源池和 bind group 上传——你不需要写任何底层 GPU 代码。
同一处还注册了ShadplayShaderLibrary(定义在src/shader_utils/common.rs),它用load_internal_asset!宏把src/shader_utils/common.wgsl打包进应用。这份"公共函数库"里有sd_sphere、hextile六边形平铺、fbm_fireball分形噪声等几十个函数,2D 着色器可以直接#import shadplay::shader_utils::common::...使用,这也是 Shadplay 教学价值的体现:让你直接"抄" Bevy 生态的现成工具函数。
最终入口在src/main.rs:App::new()后.add_plugins((DefaultPlugins..., ShadPlayPlugin))一行完成全部装配,普通模式和屏保模式共用同一个插件。
数据回路:鼠标坐标如何实时喂给着色器
前面讲的都是"静态"绑定,Shadplay 最直观的体验是鼠标移动画面跟着变。回路在src/utils.rs的update_mouse_pos系统里:
- 每帧从主窗口读取物理光标坐标;
- 通过
ShadplayWindowDims换算成 0~1 的 UV 坐标; - 直接写入
Assets<YourShader2D>中材质实例的mouse_pos字段。
由于YourShader2Dderive 了AsBindGroup,Bevy 会在下一帧自动把这 8 个字节重新上传到 GPU,着色器里读到的是最新值——整个回路没有你写一行同步代码。
上手自检清单
照着这份清单检查你的自定义材质,基本可以排除 90% 的问题:
- derive 齐全吗?材质结构体需要
Asset + AsBindGroup + TypePath;纯数据子结构体(如MousePos)只需ShaderType。 - 编号对齐吗?逐个核对
#[uniform(n)]/#[texture(n)]/#[sampler(n)]与 WGSL 的@binding(n)。 - trait 实现了吗?
Material或Material2d必须提供fragment_shader()返回正确路径。 - 插件注册了吗?对应类型的
MaterialPlugin/Material2dPlugin出现在add_plugins里。 .wgsl编译通过吗?Shadplay 对保存的文件实时重编译,naga 报错信息会直接标出问题行。
总结
Shadplay 的源码是一份绝佳的 Bevy 自定义材质"活文档":AsBindGroup负责数据按编号对齐,Materialtrait 一行指定着色器,两行插件注册把一切接入渲染管线。关键文件一览——材质与绑定:src/shader_utils/mod.rs,插件装配:src/plugin.rs,入口与屏保:src/main.rs,公共 WGSL 函数库:src/shader_utils/common.wgsl,默认着色器:assets/shaders/myshader_2d.wgsl。把这条"Rust 结构体 → 绑定编号 → WGSL 读取"的主线记牢,你就可以在这个工具里开始写出自己的第一个 WGSL 效果了。
【免费下载链接】shadplayReal-time wgsl visualisation tooling for educating oneself in the art of shader programming项目地址: https://gitcode.com/gh_mirrors/sh/shadplay
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
