Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/mesh2d_manual.rs
9359 views
1
//! This example shows how to manually render 2d items using "mid level render apis" with a custom
2
//! pipeline for 2d meshes.
3
//! It doesn't use the [`Material2d`] abstraction, but changes the vertex buffer to include vertex color.
4
//! Check out the "mesh2d" example for simpler / higher level 2d meshes.
5
//!
6
//! [`Material2d`]: bevy::sprite::Material2d
7
8
use bevy::{
9
asset::RenderAssetUsages,
10
color::palettes::basic::YELLOW,
11
core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
12
math::{ops, FloatOrd},
13
mesh::{Indices, MeshVertexAttribute, VertexBufferLayout},
14
prelude::*,
15
render::{
16
mesh::RenderMesh,
17
render_asset::RenderAssets,
18
render_phase::{
19
AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline,
20
ViewSortedRenderPhases,
21
},
22
render_resource::{
23
BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
24
DepthStencilState, Face, FragmentState, MultisampleState, PipelineCache,
25
PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, SpecializedRenderPipeline,
26
SpecializedRenderPipelines, StencilFaceState, StencilState, TextureFormat,
27
VertexFormat, VertexState, VertexStepMode,
28
},
29
sync_component::{SyncComponent, SyncComponentPlugin},
30
sync_world::{MainEntityHashMap, RenderEntity},
31
view::{ExtractedView, RenderVisibleEntities, ViewTarget},
32
Extract, Render, RenderApp, RenderStartup, RenderSystems,
33
},
34
sprite_render::{
35
extract_mesh2d, init_mesh_2d_pipeline, DrawMesh2d, Material2dBindGroupId, Mesh2dPipeline,
36
Mesh2dPipelineKey, Mesh2dTransforms, MeshFlags, RenderMesh2dInstance, SetMesh2dBindGroup,
37
SetMesh2dViewBindGroup,
38
},
39
};
40
use std::f32::consts::PI;
41
42
fn main() {
43
App::new()
44
.add_plugins((DefaultPlugins, ColoredMesh2dPlugin))
45
.add_systems(Startup, star)
46
.run();
47
}
48
49
fn star(
50
mut commands: Commands,
51
// We will add a new Mesh for the star being created
52
mut meshes: ResMut<Assets<Mesh>>,
53
) {
54
// Let's define the mesh for the object we want to draw: a nice star.
55
// We will specify here what kind of topology is used to define the mesh,
56
// that is, how triangles are built from the vertices. We will use a
57
// triangle list, meaning that each vertex of the triangle has to be
58
// specified. We set `RenderAssetUsages::RENDER_WORLD`, meaning this mesh
59
// will not be accessible in future frames from the `meshes` resource, in
60
// order to save on memory once it has been uploaded to the GPU.
61
let mut star = Mesh::new(
62
PrimitiveTopology::TriangleList,
63
RenderAssetUsages::RENDER_WORLD,
64
);
65
66
// Vertices need to have a position attribute. We will use the following
67
// vertices (I hope you can spot the star in the schema).
68
//
69
// 1
70
//
71
// 10 2
72
// 9 0 3
73
// 8 4
74
// 6
75
// 7 5
76
//
77
// These vertices are specified in 3D space.
78
let mut v_pos = vec![[0.0, 0.0, 0.0]];
79
for i in 0..10 {
80
// The angle between each vertex is 1/10 of a full rotation.
81
let a = i as f32 * PI / 5.0;
82
// The radius of inner vertices (even indices) is 100. For outer vertices (odd indices) it's 200.
83
let r = (1 - i % 2) as f32 * 100.0 + 100.0;
84
// Add the vertex position.
85
v_pos.push([r * ops::sin(a), r * ops::cos(a), 0.0]);
86
}
87
// Set the position attribute
88
star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
89
// And a RGB color attribute as well. A built-in `Mesh::ATTRIBUTE_COLOR` exists, but we
90
// use a custom vertex attribute here for demonstration purposes.
91
let mut v_color: Vec<u32> = vec![LinearRgba::BLACK.as_u32()];
92
v_color.extend_from_slice(&[LinearRgba::from(YELLOW).as_u32(); 10]);
93
star.insert_attribute(
94
MeshVertexAttribute::new("Vertex_Color", 1, VertexFormat::Uint32),
95
v_color,
96
);
97
98
// Now, we specify the indices of the vertex that are going to compose the
99
// triangles in our star. Vertices in triangles have to be specified in CCW
100
// winding (that will be the front face, colored). Since we are using
101
// triangle list, we will specify each triangle as 3 vertices
102
// First triangle: 0, 2, 1
103
// Second triangle: 0, 3, 2
104
// Third triangle: 0, 4, 3
105
// etc
106
// Last triangle: 0, 1, 10
107
let mut indices = vec![0, 1, 10];
108
for i in 2..=10 {
109
indices.extend_from_slice(&[0, i, i - 1]);
110
}
111
star.insert_indices(Indices::U32(indices));
112
113
// We can now spawn the entities for the star and the camera
114
commands.spawn((
115
// We use a marker component to identify the custom colored meshes
116
ColoredMesh2d,
117
// The `Handle<Mesh>` needs to be wrapped in a `Mesh2d` for 2D rendering
118
Mesh2d(meshes.add(star)),
119
));
120
121
commands.spawn(Camera2d);
122
}
123
124
/// A marker component for colored 2d meshes
125
#[derive(Component, Default)]
126
pub struct ColoredMesh2d;
127
128
impl SyncComponent for ColoredMesh2d {
129
type Out = Self;
130
}
131
132
/// Custom pipeline for 2d meshes with vertex colors
133
#[derive(Resource)]
134
pub struct ColoredMesh2dPipeline {
135
/// This pipeline wraps the standard [`Mesh2dPipeline`]
136
mesh2d_pipeline: Mesh2dPipeline,
137
/// The shader asset handle.
138
shader: Handle<Shader>,
139
}
140
141
fn init_colored_mesh_2d_pipeline(
142
mut commands: Commands,
143
mesh2d_pipeline: Res<Mesh2dPipeline>,
144
colored_mesh2d_shader: Res<ColoredMesh2dShader>,
145
) {
146
commands.insert_resource(ColoredMesh2dPipeline {
147
mesh2d_pipeline: mesh2d_pipeline.clone(),
148
// Clone the shader from the shader resource we inserted in the plugin.
149
shader: colored_mesh2d_shader.0.clone(),
150
});
151
}
152
153
// We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline`
154
impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
155
type Key = Mesh2dPipelineKey;
156
157
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
158
// Customize how to store the meshes' vertex attributes in the vertex buffer
159
// Our meshes only have position and color
160
let formats = vec![
161
// Position
162
VertexFormat::Float32x3,
163
// Color
164
VertexFormat::Uint32,
165
];
166
167
let vertex_layout =
168
VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);
169
170
let format = match key.contains(Mesh2dPipelineKey::HDR) {
171
true => ViewTarget::TEXTURE_FORMAT_HDR,
172
false => TextureFormat::bevy_default(),
173
};
174
175
RenderPipelineDescriptor {
176
vertex: VertexState {
177
// Use our custom shader
178
shader: self.shader.clone(),
179
// Use our custom vertex buffer
180
buffers: vec![vertex_layout],
181
..default()
182
},
183
fragment: Some(FragmentState {
184
// Use our custom shader
185
shader: self.shader.clone(),
186
targets: vec![Some(ColorTargetState {
187
format,
188
blend: Some(BlendState::ALPHA_BLENDING),
189
write_mask: ColorWrites::ALL,
190
})],
191
..default()
192
}),
193
// Use the two standard uniforms for 2d meshes
194
layout: vec![
195
// Bind group 0 is the view uniform
196
self.mesh2d_pipeline.view_layout.clone(),
197
// Bind group 1 is the mesh uniform
198
self.mesh2d_pipeline.mesh_layout.clone(),
199
],
200
primitive: PrimitiveState {
201
cull_mode: Some(Face::Back),
202
topology: key.primitive_topology(),
203
..default()
204
},
205
depth_stencil: Some(DepthStencilState {
206
format: CORE_2D_DEPTH_FORMAT,
207
depth_write_enabled: false,
208
depth_compare: CompareFunction::GreaterEqual,
209
stencil: StencilState {
210
front: StencilFaceState::IGNORE,
211
back: StencilFaceState::IGNORE,
212
read_mask: 0,
213
write_mask: 0,
214
},
215
bias: DepthBiasState {
216
constant: 0,
217
slope_scale: 0.0,
218
clamp: 0.0,
219
},
220
}),
221
multisample: MultisampleState {
222
count: key.msaa_samples(),
223
mask: !0,
224
alpha_to_coverage_enabled: false,
225
},
226
label: Some("colored_mesh2d_pipeline".into()),
227
..default()
228
}
229
}
230
}
231
232
// This specifies how to render a colored 2d mesh
233
type DrawColoredMesh2d = (
234
// Set the pipeline
235
SetItemPipeline,
236
// Set the view uniform as bind group 0
237
SetMesh2dViewBindGroup<0>,
238
// Set the mesh uniform as bind group 1
239
SetMesh2dBindGroup<1>,
240
// Draw the mesh
241
DrawMesh2d,
242
);
243
244
// The custom shader can be inline like here, included from another file at build time
245
// using `include_str!()`, or loaded like any other asset with `asset_server.load()`.
246
const COLORED_MESH2D_SHADER: &str = r"
247
// Import the standard 2d mesh uniforms and set their bind groups
248
#import bevy_sprite::mesh2d_functions
249
250
// The structure of the vertex buffer is as specified in `specialize()`
251
struct Vertex {
252
@builtin(instance_index) instance_index: u32,
253
@location(0) position: vec3<f32>,
254
@location(1) color: u32,
255
};
256
257
struct VertexOutput {
258
// The vertex shader must set the on-screen position of the vertex
259
@builtin(position) clip_position: vec4<f32>,
260
// We pass the vertex color to the fragment shader in location 0
261
@location(0) color: vec4<f32>,
262
};
263
264
/// Entry point for the vertex shader
265
@vertex
266
fn vertex(vertex: Vertex) -> VertexOutput {
267
var out: VertexOutput;
268
// Project the world position of the mesh into screen position
269
let model = mesh2d_functions::get_world_from_local(vertex.instance_index);
270
out.clip_position = mesh2d_functions::mesh2d_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
271
// Unpack the `u32` from the vertex buffer into the `vec4<f32>` used by the fragment shader
272
out.color = vec4<f32>((vec4<u32>(vertex.color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
273
return out;
274
}
275
276
// The input of the fragment shader must correspond to the output of the vertex shader for all `location`s
277
struct FragmentInput {
278
// The color is interpolated between vertices by default
279
@location(0) color: vec4<f32>,
280
};
281
282
/// Entry point for the fragment shader
283
@fragment
284
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
285
return in.color;
286
}
287
";
288
289
/// Plugin that renders [`ColoredMesh2d`]s
290
pub struct ColoredMesh2dPlugin;
291
292
/// A resource holding the shader asset handle for the pipeline to take. There are many ways to get
293
/// the shader into the pipeline - this is just one option.
294
#[derive(Resource)]
295
struct ColoredMesh2dShader(Handle<Shader>);
296
297
/// Our custom pipeline needs its own instance storage
298
#[derive(Resource, Deref, DerefMut, Default)]
299
pub struct RenderColoredMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);
300
301
impl Plugin for ColoredMesh2dPlugin {
302
fn build(&self, app: &mut App) {
303
// Load our custom shader
304
let mut shaders = app.world_mut().resource_mut::<Assets<Shader>>();
305
// Here, we construct and add the shader asset manually. There are many ways to load this
306
// shader, including `embedded_asset`/`load_embedded_asset`.
307
let shader = shaders.add(Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()));
308
309
app.add_plugins(SyncComponentPlugin::<ColoredMesh2d>::default());
310
311
// Register our custom draw function, and add our render systems
312
app.get_sub_app_mut(RenderApp)
313
.unwrap()
314
.insert_resource(ColoredMesh2dShader(shader))
315
.add_render_command::<Transparent2d, DrawColoredMesh2d>()
316
.init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
317
.init_resource::<RenderColoredMesh2dInstances>()
318
.add_systems(
319
RenderStartup,
320
init_colored_mesh_2d_pipeline.after(init_mesh_2d_pipeline),
321
)
322
.add_systems(
323
ExtractSchedule,
324
extract_colored_mesh2d.after(extract_mesh2d),
325
)
326
.add_systems(
327
Render,
328
queue_colored_mesh2d.in_set(RenderSystems::QueueMeshes),
329
);
330
}
331
}
332
333
/// Extract the [`ColoredMesh2d`] marker component into the render app
334
pub fn extract_colored_mesh2d(
335
mut commands: Commands,
336
mut previous_len: Local<usize>,
337
// When extracting, you must use `Extract` to mark the `SystemParam`s
338
// which should be taken from the main world.
339
query: Extract<
340
Query<
341
(
342
Entity,
343
RenderEntity,
344
&ViewVisibility,
345
&GlobalTransform,
346
&Mesh2d,
347
),
348
With<ColoredMesh2d>,
349
>,
350
>,
351
mut render_mesh_instances: ResMut<RenderColoredMesh2dInstances>,
352
) {
353
let mut values = Vec::with_capacity(*previous_len);
354
for (entity, render_entity, view_visibility, transform, handle) in &query {
355
if !view_visibility.get() {
356
continue;
357
}
358
359
let transforms = Mesh2dTransforms {
360
world_from_local: transform.affine().into(),
361
flags: MeshFlags::empty().bits(),
362
};
363
364
values.push((render_entity, ColoredMesh2d));
365
render_mesh_instances.insert(
366
entity.into(),
367
RenderMesh2dInstance {
368
mesh_asset_id: handle.0.id(),
369
transforms,
370
material_bind_group_id: Material2dBindGroupId::default(),
371
automatic_batching: false,
372
tag: 0,
373
},
374
);
375
}
376
*previous_len = values.len();
377
commands.try_insert_batch(values);
378
}
379
380
/// Queue the 2d meshes marked with [`ColoredMesh2d`] using our custom pipeline and draw function
381
pub fn queue_colored_mesh2d(
382
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
383
colored_mesh2d_pipeline: Res<ColoredMesh2dPipeline>,
384
mut pipelines: ResMut<SpecializedRenderPipelines<ColoredMesh2dPipeline>>,
385
pipeline_cache: Res<PipelineCache>,
386
render_meshes: Res<RenderAssets<RenderMesh>>,
387
render_mesh_instances: Res<RenderColoredMesh2dInstances>,
388
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
389
views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,
390
) {
391
if render_mesh_instances.is_empty() {
392
return;
393
}
394
// Iterate each view (a camera is a view)
395
for (visible_entities, view, msaa) in &views {
396
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
397
else {
398
continue;
399
};
400
401
let draw_colored_mesh2d = transparent_draw_functions.read().id::<DrawColoredMesh2d>();
402
403
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
404
| Mesh2dPipelineKey::from_hdr(view.hdr);
405
406
// Queue all entities visible to that view
407
for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
408
if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) {
409
let mesh2d_handle = mesh_instance.mesh_asset_id;
410
let mesh2d_transforms = &mesh_instance.transforms;
411
// Get our specialized pipeline
412
let mut mesh2d_key = mesh_key;
413
let Some(mesh) = render_meshes.get(mesh2d_handle) else {
414
continue;
415
};
416
mesh2d_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology());
417
418
let pipeline_id =
419
pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);
420
421
let mesh_z = mesh2d_transforms.world_from_local.translation.z;
422
transparent_phase.add(Transparent2d {
423
entity: (*render_entity, *visible_entity),
424
draw_function: draw_colored_mesh2d,
425
pipeline: pipeline_id,
426
// The 2d render items are sorted according to their z value before rendering,
427
// in order to get correct transparency
428
sort_key: FloatOrd(mesh_z),
429
// This material is not batched
430
batch_range: 0..1,
431
extra_index: PhaseItemExtraIndex::None,
432
extracted_index: usize::MAX,
433
indexed: mesh.indexed(),
434
});
435
}
436
}
437
}
438
}
439
440