从零开始bevy渲染器设计-01-具体案例
从零开始渲染器-01. 分析bevy引擎的具体案例。确认几何体,材质与纹理,光照,摄像机等对象。
代码
bevy examples下关于shapes的案例如下:
#[cfg(not(target_arch = "wasm32"))]
use bevy::{
input::common_conditions::input_just_pressed,
sprite_render::{Wireframe2dConfig, Wireframe2dPlugin},
};
use bevy::{input::common_conditions::input_toggle_active, prelude::*};
fn main() {
let mut app = App::new();
app.add_plugins((
DefaultPlugins,
#[cfg(not(target_arch = "wasm32"))]
Wireframe2dPlugin::default(),
))
.add_systems(Startup, setup);
#[cfg(not(target_arch = "wasm32"))]
app.add_systems(
Update,
toggle_wireframe.run_if(input_just_pressed(KeyCode::Space)),
);
app.add_systems(
Update,
rotate.run_if(input_toggle_active(false, KeyCode::KeyR)),
);
app.run();
}
const X_EXTENT: f32 = 1000.;
const Y_EXTENT: f32 = 150.;
const THICKNESS: f32 = 5.0;
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2d);
let shapes = [
meshes.add(Circle::new(50.0)),
meshes.add(CircularSector::new(50.0, 1.0)),
meshes.add(CircularSegment::new(50.0, 1.25)),
meshes.add(Ellipse::new(25.0, 50.0)),
meshes.add(Annulus::new(25.0, 50.0)),
meshes.add(Capsule2d::new(25.0, 50.0)),
meshes.add(Rhombus::new(75.0, 100.0)),
meshes.add(Rectangle::new(50.0, 100.0)),
meshes.add(RegularPolygon::new(50.0, 6)),
meshes.add(Triangle2d::new(
Vec2::Y * 50.0,
Vec2::new(-50.0, -50.0),
Vec2::new(50.0, -50.0),
)),
meshes.add(Segment2d::new(
Vec2::new(-50.0, 50.0),
Vec2::new(50.0, -50.0),
)),
meshes.add(Polyline2d::new(vec![
Vec2::new(-50.0, 50.0),
Vec2::new(0.0, -50.0),
Vec2::new(50.0, 50.0),
])),
];
let num_shapes = shapes.len();
for (i, shape) in shapes.into_iter().enumerate() {
// Distribute colors evenly across the rainbow.
let color = Color::hsl(360. * i as f32 / num_shapes as f32, 0.95, 0.7);
commands.spawn((
Mesh2d(shape),
MeshMaterial2d(materials.add(color)),
Transform::from_xyz(
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
Y_EXTENT / 2.,
0.0,
),
));
}
let rings = [
meshes.add(Circle::new(50.0).to_ring(THICKNESS)),
// this visually produces an arc segment but this is not technically accurate
meshes.add(Ring::new(
CircularSector::new(50.0, 1.0),
CircularSector::new(45.0, 1.0),
)),
meshes.add(CircularSegment::new(50.0, 1.25).to_ring(THICKNESS)),
meshes.add({
// This is an approximation; Ellipse does not implement Inset as concentric ellipses do not have parallel curves
let outer = Ellipse::new(25.0, 50.0);
let mut inner = outer;
inner.half_size -= Vec2::splat(THICKNESS);
Ring::new(outer, inner)
}),
// this is equivalent to the Annulus::new(25.0, 50.0) above
meshes.add(Ring::new(Circle::new(50.0), Circle::new(25.0))),
meshes.add(Capsule2d::new(25.0, 50.0).to_ring(THICKNESS)),
meshes.add(Rhombus::new(75.0, 100.0).to_ring(THICKNESS)),
meshes.add(Rectangle::new(50.0, 100.0).to_ring(THICKNESS)),
meshes.add(RegularPolygon::new(50.0, 6).to_ring(THICKNESS)),
meshes.add(
Triangle2d::new(
Vec2::Y * 50.0,
Vec2::new(-50.0, -50.0),
Vec2::new(50.0, -50.0),
)
.to_ring(THICKNESS),
),
];
// Allow for 2 empty spaces
let num_rings = rings.len() + 2;
for (i, shape) in rings.into_iter().enumerate() {
// Distribute colors evenly across the rainbow.
let color = Color::hsl(360. * i as f32 / num_rings as f32, 0.95, 0.7);
commands.spawn((
Mesh2d(shape),
MeshMaterial2d(materials.add(color)),
Transform::from_xyz(
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
-X_EXTENT / 2. + i as f32 / (num_rings - 1) as f32 * X_EXTENT,
-Y_EXTENT / 2.,
0.0,
),
));
}
let mut text = "Press 'R' to pause/resume rotation".to_string();
#[cfg(not(target_arch = "wasm32"))]
text.push_str("\nPress 'Space' to toggle wireframes");
commands.spawn((
Text::new(text),
Node {
position_type: PositionType::Absolute,
top: px(12),
left: px(12),
..default()
},
));
}
#[cfg(not(target_arch = "wasm32"))]
fn toggle_wireframe(mut wireframe_config: ResMut<Wireframe2dConfig>) {
wireframe_config.global = !wireframe_config.global;
}
fn rotate(mut query: Query<&mut Transform, With<Mesh2d>>, time: Res<Time>) {
for mut transform in &mut query {
transform.rotate_z(time.delta_secs() / 2.0);
}
}
在这个案例中出现的重要对象如下:
- Mesh2d
- Mesh
- ColorMaterial
- MeshMaterial2d Mesh2d是Mesh的Handle包装,它在bevy_mesh中被定义。Mesh是一类资源,它表示在内存中的几何体数据。 ColorMaterial是一类资源,它表示一个材质,在bevy_sprite_render中被定义。MeshMaterial2d是一个具体材质的handle包装。它在bevy_sprite_render中被定义。在此上下文中,它表示ColorMaterial的Handle包装。
ColorMaterial
ColorMaterial由ColorMaterialPlugin引入。代码如下:
#[derive(Default)]
pub struct ColorMaterialPlugin;
impl Plugin for ColorMaterialPlugin {
fn build(&self, app: &mut App) {
embedded_asset!(app, "color_material.wgsl");
app.add_plugins(Material2dPlugin::<ColorMaterial>::default())
.register_asset_reflect::<ColorMaterial>();
// Initialize the default material handle.
app.world_mut()
.resource_mut::<Assets<ColorMaterial>>()
.insert(
&Handle::<ColorMaterial>::default(),
ColorMaterial {
color: Color::srgb(1.0, 0.0, 1.0),
..Default::default()
},
)
.unwrap();
}
}
Material2dPlugin的实现如下:
impl<M: Material2d> Plugin for Material2dPlugin<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
fn build(&self, app: &mut App) {
app.init_asset::<M>()
.init_resource::<EntitiesNeedingSpecialization<M>>()
.register_type::<MeshMaterial2d<M>>()
.add_plugins(RenderAssetPlugin::<PreparedMaterial2d<M>, GpuImage>::default())
.add_systems(
PostUpdate,
check_entities_needing_specialization::<M>.after(AssetEventSystems),
);
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_gpu_resource::<SpecializedMaterial2dPipelineCache<M>>()
.add_render_command::<Opaque2d, DrawMaterial2d<M>>()
.add_render_command::<AlphaMask2d, DrawMaterial2d<M>>()
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
.init_resource::<RenderMaterial2dInstances<M>>()
.init_gpu_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
.init_resource::<PendingMeshMaterial2dQueues>()
.allow_ambiguous_resource::<PendingMeshMaterial2dQueues>()
.add_systems(
RenderStartup,
init_material_2d_pipeline::<M>.after(init_mesh_2d_pipeline),
)
.add_systems(
ExtractSchedule,
(
extract_entities_needs_specialization::<M>
.in_set(DirtySpecializationSystems::CheckForChanges),
extract_entities_that_need_specializations_removed::<M>
.in_set(DirtySpecializationSystems::CheckForRemovals),
extract_mesh_materials_2d::<M>,
),
)
.add_systems(
Render,
(
specialize_material2d_meshes::<M>
.in_set(RenderSystems::Specialize)
.after(prepare_assets::<PreparedMaterial2d<M>>)
.after(prepare_assets::<RenderMesh>)
.after(prepare_pending_mesh_material2d_queues),
queue_material2d_meshes::<M>
.in_set(RenderSystems::QueueMeshes)
.after(prepare_assets::<PreparedMaterial2d<M>>),
),
);
}
}
}
在RenderStartup阶段生成Material2dPipeline。 在ExtractSchedule阶段更新需要重新生成的主世界实体,提取主世界的数据。 在Render阶段生成pipeline,同时生成RenderPhase 那么RenderPhase如何使用?