Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/mesh2d_manual.rs
6592 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::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
/// Custom pipeline for 2d meshes with vertex colors
129
#[derive(Resource)]
130
pub struct ColoredMesh2dPipeline {
131
/// This pipeline wraps the standard [`Mesh2dPipeline`]
132
mesh2d_pipeline: Mesh2dPipeline,
133
/// The shader asset handle.
134
shader: Handle<Shader>,
135
}
136
137
fn init_colored_mesh_2d_pipeline(
138
mut commands: Commands,
139
mesh2d_pipeline: Res<Mesh2dPipeline>,
140
colored_mesh2d_shader: Res<ColoredMesh2dShader>,
141
) {
142
commands.insert_resource(ColoredMesh2dPipeline {
143
mesh2d_pipeline: mesh2d_pipeline.clone(),
144
// Clone the shader from the shader resource we inserted in the plugin.
145
shader: colored_mesh2d_shader.0.clone(),
146
});
147
}
148
149
// We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline`
150
impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
151
type Key = Mesh2dPipelineKey;
152
153
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
154
// Customize how to store the meshes' vertex attributes in the vertex buffer
155
// Our meshes only have position and color
156
let formats = vec![
157
// Position
158
VertexFormat::Float32x3,
159
// Color
160
VertexFormat::Uint32,
161
];
162
163
let vertex_layout =
164
VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);
165
166
let format = match key.contains(Mesh2dPipelineKey::HDR) {
167
true => ViewTarget::TEXTURE_FORMAT_HDR,
168
false => TextureFormat::bevy_default(),
169
};
170
171
RenderPipelineDescriptor {
172
vertex: VertexState {
173
// Use our custom shader
174
shader: self.shader.clone(),
175
// Use our custom vertex buffer
176
buffers: vec![vertex_layout],
177
..default()
178
},
179
fragment: Some(FragmentState {
180
// Use our custom shader
181
shader: self.shader.clone(),
182
targets: vec![Some(ColorTargetState {
183
format,
184
blend: Some(BlendState::ALPHA_BLENDING),
185
write_mask: ColorWrites::ALL,
186
})],
187
..default()
188
}),
189
// Use the two standard uniforms for 2d meshes
190
layout: vec![
191
// Bind group 0 is the view uniform
192
self.mesh2d_pipeline.view_layout.clone(),
193
// Bind group 1 is the mesh uniform
194
self.mesh2d_pipeline.mesh_layout.clone(),
195
],
196
primitive: PrimitiveState {
197
cull_mode: Some(Face::Back),
198
topology: key.primitive_topology(),
199
..default()
200
},
201
depth_stencil: Some(DepthStencilState {
202
format: CORE_2D_DEPTH_FORMAT,
203
depth_write_enabled: false,
204
depth_compare: CompareFunction::GreaterEqual,
205
stencil: StencilState {
206
front: StencilFaceState::IGNORE,
207
back: StencilFaceState::IGNORE,
208
read_mask: 0,
209
write_mask: 0,
210
},
211
bias: DepthBiasState {
212
constant: 0,
213
slope_scale: 0.0,
214
clamp: 0.0,
215
},
216
}),
217
multisample: MultisampleState {
218
count: key.msaa_samples(),
219
mask: !0,
220
alpha_to_coverage_enabled: false,
221
},
222
label: Some("colored_mesh2d_pipeline".into()),
223
..default()
224
}
225
}
226
}
227
228
// This specifies how to render a colored 2d mesh
229
type DrawColoredMesh2d = (
230
// Set the pipeline
231
SetItemPipeline,
232
// Set the view uniform as bind group 0
233
SetMesh2dViewBindGroup<0>,
234
// Set the mesh uniform as bind group 1
235
SetMesh2dBindGroup<1>,
236
// Draw the mesh
237
DrawMesh2d,
238
);
239
240
// The custom shader can be inline like here, included from another file at build time
241
// using `include_str!()`, or loaded like any other asset with `asset_server.load()`.
242
const COLORED_MESH2D_SHADER: &str = r"
243
// Import the standard 2d mesh uniforms and set their bind groups
244
#import bevy_sprite_render::mesh2d_functions
245
246
// The structure of the vertex buffer is as specified in `specialize()`
247
struct Vertex {
248
@builtin(instance_index) instance_index: u32,
249
@location(0) position: vec3<f32>,
250
@location(1) color: u32,
251
};
252
253
struct VertexOutput {
254
// The vertex shader must set the on-screen position of the vertex
255
@builtin(position) clip_position: vec4<f32>,
256
// We pass the vertex color to the fragment shader in location 0
257
@location(0) color: vec4<f32>,
258
};
259
260
/// Entry point for the vertex shader
261
@vertex
262
fn vertex(vertex: Vertex) -> VertexOutput {
263
var out: VertexOutput;
264
// Project the world position of the mesh into screen position
265
let model = mesh2d_functions::get_world_from_local(vertex.instance_index);
266
out.clip_position = mesh2d_functions::mesh2d_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
267
// Unpack the `u32` from the vertex buffer into the `vec4<f32>` used by the fragment shader
268
out.color = vec4<f32>((vec4<u32>(vertex.color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
269
return out;
270
}
271
272
// The input of the fragment shader must correspond to the output of the vertex shader for all `location`s
273
struct FragmentInput {
274
// The color is interpolated between vertices by default
275
@location(0) color: vec4<f32>,
276
};
277
278
/// Entry point for the fragment shader
279
@fragment
280
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
281
return in.color;
282
}
283
";
284
285
/// Plugin that renders [`ColoredMesh2d`]s
286
pub struct ColoredMesh2dPlugin;
287
288
/// A resource holding the shader asset handle for the pipeline to take. There are many ways to get
289
/// the shader into the pipeline - this is just one option.
290
#[derive(Resource)]
291
struct ColoredMesh2dShader(Handle<Shader>);
292
293
/// Our custom pipeline needs its own instance storage
294
#[derive(Resource, Deref, DerefMut, Default)]
295
pub struct RenderColoredMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);
296
297
impl Plugin for ColoredMesh2dPlugin {
298
fn build(&self, app: &mut App) {
299
// Load our custom shader
300
let mut shaders = app.world_mut().resource_mut::<Assets<Shader>>();
301
// Here, we construct and add the shader asset manually. There are many ways to load this
302
// shader, including `embedded_asset`/`load_embedded_asset`.
303
let shader = shaders.add(Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()));
304
305
app.add_plugins(SyncComponentPlugin::<ColoredMesh2d>::default());
306
307
// Register our custom draw function, and add our render systems
308
app.get_sub_app_mut(RenderApp)
309
.unwrap()
310
.insert_resource(ColoredMesh2dShader(shader))
311
.add_render_command::<Transparent2d, DrawColoredMesh2d>()
312
.init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
313
.init_resource::<RenderColoredMesh2dInstances>()
314
.add_systems(
315
RenderStartup,
316
init_colored_mesh_2d_pipeline.after(init_mesh_2d_pipeline),
317
)
318
.add_systems(
319
ExtractSchedule,
320
extract_colored_mesh2d.after(extract_mesh2d),
321
)
322
.add_systems(
323
Render,
324
queue_colored_mesh2d.in_set(RenderSystems::QueueMeshes),
325
);
326
}
327
}
328
329
/// Extract the [`ColoredMesh2d`] marker component into the render app
330
pub fn extract_colored_mesh2d(
331
mut commands: Commands,
332
mut previous_len: Local<usize>,
333
// When extracting, you must use `Extract` to mark the `SystemParam`s
334
// which should be taken from the main world.
335
query: Extract<
336
Query<
337
(
338
Entity,
339
RenderEntity,
340
&ViewVisibility,
341
&GlobalTransform,
342
&Mesh2d,
343
),
344
With<ColoredMesh2d>,
345
>,
346
>,
347
mut render_mesh_instances: ResMut<RenderColoredMesh2dInstances>,
348
) {
349
let mut values = Vec::with_capacity(*previous_len);
350
for (entity, render_entity, view_visibility, transform, handle) in &query {
351
if !view_visibility.get() {
352
continue;
353
}
354
355
let transforms = Mesh2dTransforms {
356
world_from_local: (&transform.affine()).into(),
357
flags: MeshFlags::empty().bits(),
358
};
359
360
values.push((render_entity, ColoredMesh2d));
361
render_mesh_instances.insert(
362
entity.into(),
363
RenderMesh2dInstance {
364
mesh_asset_id: handle.0.id(),
365
transforms,
366
material_bind_group_id: Material2dBindGroupId::default(),
367
automatic_batching: false,
368
tag: 0,
369
},
370
);
371
}
372
*previous_len = values.len();
373
commands.try_insert_batch(values);
374
}
375
376
/// Queue the 2d meshes marked with [`ColoredMesh2d`] using our custom pipeline and draw function
377
pub fn queue_colored_mesh2d(
378
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
379
colored_mesh2d_pipeline: Res<ColoredMesh2dPipeline>,
380
mut pipelines: ResMut<SpecializedRenderPipelines<ColoredMesh2dPipeline>>,
381
pipeline_cache: Res<PipelineCache>,
382
render_meshes: Res<RenderAssets<RenderMesh>>,
383
render_mesh_instances: Res<RenderColoredMesh2dInstances>,
384
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
385
views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,
386
) {
387
if render_mesh_instances.is_empty() {
388
return;
389
}
390
// Iterate each view (a camera is a view)
391
for (visible_entities, view, msaa) in &views {
392
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
393
else {
394
continue;
395
};
396
397
let draw_colored_mesh2d = transparent_draw_functions.read().id::<DrawColoredMesh2d>();
398
399
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
400
| Mesh2dPipelineKey::from_hdr(view.hdr);
401
402
// Queue all entities visible to that view
403
for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
404
if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) {
405
let mesh2d_handle = mesh_instance.mesh_asset_id;
406
let mesh2d_transforms = &mesh_instance.transforms;
407
// Get our specialized pipeline
408
let mut mesh2d_key = mesh_key;
409
let Some(mesh) = render_meshes.get(mesh2d_handle) else {
410
continue;
411
};
412
mesh2d_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology());
413
414
let pipeline_id =
415
pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);
416
417
let mesh_z = mesh2d_transforms.world_from_local.translation.z;
418
transparent_phase.add(Transparent2d {
419
entity: (*render_entity, *visible_entity),
420
draw_function: draw_colored_mesh2d,
421
pipeline: pipeline_id,
422
// The 2d render items are sorted according to their z value before rendering,
423
// in order to get correct transparency
424
sort_key: FloatOrd(mesh_z),
425
// This material is not batched
426
batch_range: 0..1,
427
extra_index: PhaseItemExtraIndex::None,
428
extracted_index: usize::MAX,
429
indexed: mesh.indexed(),
430
});
431
}
432
}
433
}
434
}
435
436