Mesh2d 与 ColorMaterial:不用画的道具
《渡口夜话》还缺一轮满月。小棠提笔要画,老雷拦下:“一个正圆还要劳驾画师?”确实不用——圆、环、三角这类几何形状,引擎可以现场“铸”出来,根本不需要图片文件。
干这活的是另一套渲染管线。Sprite 本质上是“一张矩形的图”;而 Mesh(网格——由顶点和三角形拼成的几何形状)想是什么形状就是什么形状。3D 世界里所有模型都是 Mesh(第 21 章的正题),2D 里它同样能用——拍扁了用,像桌上的剪纸。
一只 2D 网格实体由两个组件拼成,各管一半:
Mesh2d——形状。内里是Handle<Mesh>:网格也是资产,住Assets<Mesh>货架;MeshMaterial2d<ColorMaterial>——皮相。Material(材质——“这个表面怎么着色”的完整说明书)也是资产,2D 的标配材质是ColorMaterial:一块颜色,可选再贴一张图。
形状从哪来?第 12 章几何课上的那批图元(Circle、Annulus、Rectangle、Triangle2d……)在这儿兑现第二重身份:它们都能 From 成 Mesh,所以 meshes.add(Circle::new(64.0)) 一句话就把“数学上的圆”铸成“可渲染的圆盘”。月亮、光晕、远山、江面、星斗,全场布景一张图片不用:
// 圆月:形状铸成 Mesh、颜色调成 ColorMaterial,两张提货单各管各的
commands.spawn((
Mesh2d(meshes.add(Circle::new(64.0))),
MeshMaterial2d(materials.add(Color::srgb(0.93, 0.91, 0.82))),
Transform::from_xyz(320.0, 170.0, 1.0),
));
// 光晕:两圈半透明的同心圆环
commands.spawn((
Mesh2d(meshes.add(Annulus::new(64.0, 92.0))),
MeshMaterial2d(materials.add(Color::srgba(0.93, 0.91, 0.82, 0.06))),
Transform::from_xyz(320.0, 170.0, 0.9),
));
commands.spawn((
Mesh2d(meshes.add(Annulus::new(92.0, 126.0))),
MeshMaterial2d(materials.add(Color::srgba(0.93, 0.91, 0.82, 0.03))),
Transform::from_xyz(320.0, 170.0, 0.8),
));Listing 15-11(节选一):月亮与光晕——形状铸成 Mesh、颜色调成 ColorMaterial,两张提货单各管各的(examples/listing-15-11.rs)
// 远山两座、江面一条——全是现铸的几何形状
commands.spawn((
Mesh2d(meshes.add(Triangle2d::new(
Vec2::new(-280.0, -95.0),
Vec2::new(280.0, -95.0),
Vec2::new(-30.0, 95.0),
))),
MeshMaterial2d(materials.add(Color::srgb(0.07, 0.10, 0.16))),
Transform::from_xyz(-300.0, -50.0, 0.5),
));
commands.spawn((
Mesh2d(meshes.add(Triangle2d::new(
Vec2::new(-220.0, -70.0),
Vec2::new(220.0, -70.0),
Vec2::new(40.0, 70.0),
))),
MeshMaterial2d(materials.add(Color::srgb(0.05, 0.08, 0.13))),
Transform::from_xyz(260.0, -75.0, 0.45),
));
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(1280.0, 220.0))),
MeshMaterial2d(materials.add(Color::srgb(0.09, 0.16, 0.24))),
Transform::from_xyz(0.0, -250.0, 0.6),
));
// 星斗:一份网格、一份材质,挂十四份提货单——上一章的省钱诀窍
let star_mesh = meshes.add(RegularPolygon::new(4.0, 4));
let star_material = materials.add(Color::srgba(0.92, 0.94, 1.00, 0.85));
for i in 0..14 {
let x = ((i * 173) % 1200) as f32 - 600.0;
let y = ((i * 97) % 230) as f32 + 90.0;
commands.spawn((
Mesh2d(star_mesh.clone()),
MeshMaterial2d(star_material.clone()),
Transform::from_xyz(x, y, 0.3),
));
}Listing 15-11(节选二):远山、江面与十四颗星——星星共用一份网格一份材质,只是提货单多发了几张(examples/listing-15-11.rs)
两处细节值得圈出来。其一,materials.add(Color::srgb(...)) 能直接收颜色,因为 ColorMaterial 实现了 From<Color>——这是便捷写法,全写开是 ColorMaterial { color, ..default() }。其二,十四颗星共用同一份网格与材质,循环里只克隆提货单——第 14 章“clone 只加计数不复制货”的省钱诀窍,在渲染资产上同样成立,而且渲染器看到同料同形的实体还能批量处理。
忘了上料
小棠铸伴月星盘时漏了一步——只给了形状,没给材质。猜猜会发生什么?按第 3 章的直觉,Mesh2d 是组件,挂上就该生效;可渲染管线的答案是:
// 西天的伴月盘——只铸了形状,忘了配材质:画面上不会有任何东西
commands.spawn((
Mesh2d(meshes.add(Circle::new(36.0))),
Transform::from_xyz(-320.0, 190.0, 1.0),
));
// 旁边这块挂的是"默认材质":洋红是渲染器的缺料警告色
commands.spawn((
Mesh2d(meshes.add(Circle::new(36.0))),
MeshMaterial2d::<ColorMaterial>::default(),
Transform::from_xyz(-470.0, 190.0, 1.0),
));Listing 15-11(节选三):一只忘了配材质的 Mesh2d,与一只挂着默认材质的(examples/listing-15-11.rs)
cargo run -p ch15-sprites --example listing-15-11老雷:伴月盘呢?西天怎么空了一块?
小棠:形状铸了,料忘了配……旁边那块洋红的挂了默认料,等于贴了张催料单。
Figure 15-13:右上的月亮一切正常;左上洋红的圆挂了默认材质,它右边那只“裸网格”干脆没画出来
没有材质的 Mesh2d 什么也不画——不报错、不警告,安静地隐身。这跟 Sprite 的脾气不同:Sprite 一个组件包圆了形状与皮相,而网格渲染管线认的是“形状 + 材质”这对组合,缺谁都不上场。“我的圆怎么不见了”属于 2D 新手高频问题,答案十有八九是忘了 MeshMaterial2d。
旁边那块洋红是另一课:MeshMaterial2d::<ColorMaterial>::default() 给的是默认材质句柄,Bevy 把它预设成了刺眼的洋红色(srgb(1.0, 0.0, 1.0))——游戏行业的传统信号色,意思是“这儿缺正经材质”。日后在哪儿见到洋红,第一反应都该是:有人忘了上料。
ColorMaterial 还能贴图
ColorMaterial 一共四个字段:color 之外还有 texture: Option<Handle<Image>>(贴一张图上去,颜色继续当染色乘上来——相当于 Sprite 的 image + color 组合)、alpha_mode(半透明怎么混:Blend 默认、Opaque 不透明、Mask 镂空)、uv_transform(贴图坐标的仿射变换,能滚动、缩放贴图)。也就是说,一张贴了图的矩形网格能干 Sprite 的大部分活——那什么时候用谁?
Sprite | Mesh2d + ColorMaterial | |
|---|---|---|
| 形状 | 永远是矩形 | 任意几何形状 |
| 图集/九宫格/翻面 | 全套内建 | 无,自己动 uv |
| 批量渲染 | 引擎深度优化,万级无压力 | 同网格同材质可合批 |
| 自定义着色 | 不行 | 换自定义 Material 即可(第 36 章) |
经验法则:带图的矩形用 Sprite,几何形状或要上特效的用 Mesh2d。第 20 章的打砖块两边都会用到。3D 那边的 Mesh3d + MeshMaterial3d<StandardMaterial> 是同一个模式的立体版——第 13 章结尾你已经瞥见过,第 21 章正式开讲。