1use 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, VertexFormat, VertexState,
27 VertexStepMode,
28 },
29 sync_component::{SyncComponent, SyncComponentPlugin},
30 sync_world::{MainEntityHashMap, RenderEntity},
31 view::{ExtractedView, RenderVisibleEntities},
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};
40use std::f32::consts::PI;
41
42fn main() {
43 App::new()
44 .add_plugins((DefaultPlugins, ColoredMesh2dPlugin))
45 .add_systems(Startup, star)
46 .run();
47}
48
49fn star(
50 mut commands: Commands,
51 mut meshes: ResMut<Assets<Mesh>>,
53) {
54 let mut star = Mesh::new(
62 PrimitiveTopology::TriangleList,
63 RenderAssetUsages::RENDER_WORLD,
64 );
65
66 let mut v_pos = vec![[0.0, 0.0, 0.0]];
79 for i in 0..10 {
80 let a = i as f32 * PI / 5.0;
82 let r = (1 - i % 2) as f32 * 100.0 + 100.0;
84 v_pos.push([r * ops::sin(a), r * ops::cos(a), 0.0]);
86 }
87 star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
89 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 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 commands.spawn((
115 ColoredMesh2d,
117 Mesh2d(meshes.add(star)),
119 ));
120
121 commands.spawn(Camera2d);
122}
123
124#[derive(Component, Default)]
126pub struct ColoredMesh2d;
127
128impl SyncComponent for ColoredMesh2d {
129 type Target = Self;
130}
131
132#[derive(Resource)]
134pub struct ColoredMesh2dPipeline {
135 mesh2d_pipeline: Mesh2dPipeline,
137 shader: Handle<Shader>,
139}
140
141fn 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 shader: colored_mesh2d_shader.0.clone(),
150 });
151}
152
153impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
155 type Key = Mesh2dPipelineKey;
156
157 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
158 let formats = vec![
161 VertexFormat::Float32x3,
163 VertexFormat::Uint32,
165 ];
166
167 let vertex_layout =
168 VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);
169
170 let format = key.target_format();
171
172 RenderPipelineDescriptor {
173 vertex: VertexState {
174 shader: self.shader.clone(),
176 buffers: vec![vertex_layout],
178 ..default()
179 },
180 fragment: Some(FragmentState {
181 shader: self.shader.clone(),
183 targets: vec![Some(ColorTargetState {
184 format,
185 blend: Some(BlendState::ALPHA_BLENDING),
186 write_mask: ColorWrites::ALL,
187 })],
188 ..default()
189 }),
190 layout: vec![
192 self.mesh2d_pipeline.view_layout.clone(),
194 self.mesh2d_pipeline.mesh_layout.clone(),
196 ],
197 primitive: PrimitiveState {
198 cull_mode: Some(Face::Back),
199 topology: key.primitive_topology(),
200 strip_index_format: key.strip_index_format(),
201 ..default()
202 },
203 depth_stencil: Some(DepthStencilState {
204 format: CORE_2D_DEPTH_FORMAT,
205 depth_write_enabled: Some(false),
206 depth_compare: Some(CompareFunction::GreaterEqual),
207 stencil: StencilState {
208 front: StencilFaceState::IGNORE,
209 back: StencilFaceState::IGNORE,
210 read_mask: 0,
211 write_mask: 0,
212 },
213 bias: DepthBiasState {
214 constant: 0,
215 slope_scale: 0.0,
216 clamp: 0.0,
217 },
218 }),
219 multisample: MultisampleState {
220 count: key.msaa_samples(),
221 mask: !0,
222 alpha_to_coverage_enabled: false,
223 },
224 label: Some("colored_mesh2d_pipeline".into()),
225 ..default()
226 }
227 }
228}
229
230type DrawColoredMesh2d = (
232 SetItemPipeline,
234 SetMesh2dViewBindGroup<0>,
236 SetMesh2dBindGroup<1>,
238 DrawMesh2d,
240);
241
242const COLORED_MESH2D_SHADER: &str = r"
245// Import the standard 2d mesh uniforms and set their bind groups
246#import bevy_sprite::mesh2d_functions
247
248// The structure of the vertex buffer is as specified in `specialize()`
249struct Vertex {
250 @builtin(instance_index) instance_index: u32,
251 @location(0) position: vec3<f32>,
252 @location(1) color: u32,
253};
254
255struct VertexOutput {
256 // The vertex shader must set the on-screen position of the vertex
257 @builtin(position) clip_position: vec4<f32>,
258 // We pass the vertex color to the fragment shader in location 0
259 @location(0) color: vec4<f32>,
260};
261
262/// Entry point for the vertex shader
263@vertex
264fn vertex(vertex: Vertex) -> VertexOutput {
265 var out: VertexOutput;
266 // Project the world position of the mesh into screen position
267 let model = mesh2d_functions::get_world_from_local(vertex.instance_index);
268 out.clip_position = mesh2d_functions::mesh2d_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
269 // Unpack the `u32` from the vertex buffer into the `vec4<f32>` used by the fragment shader
270 out.color = vec4<f32>((vec4<u32>(vertex.color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
271 return out;
272}
273
274// The input of the fragment shader must correspond to the output of the vertex shader for all `location`s
275struct FragmentInput {
276 // The color is interpolated between vertices by default
277 @location(0) color: vec4<f32>,
278};
279
280/// Entry point for the fragment shader
281@fragment
282fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
283 return in.color;
284}
285";
286
287pub struct ColoredMesh2dPlugin;
289
290#[derive(Resource)]
293struct ColoredMesh2dShader(Handle<Shader>);
294
295#[derive(Resource, Deref, DerefMut, Default)]
297pub struct RenderColoredMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);
298
299impl Plugin for ColoredMesh2dPlugin {
300 fn build(&self, app: &mut App) {
301 let mut shaders = app.world_mut().resource_mut::<Assets<Shader>>();
303 let shader = shaders.add(Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()));
306
307 app.add_plugins(SyncComponentPlugin::<ColoredMesh2d>::default());
308
309 app.get_sub_app_mut(RenderApp)
311 .unwrap()
312 .insert_resource(ColoredMesh2dShader(shader))
313 .add_render_command::<Transparent2d, DrawColoredMesh2d>()
314 .init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
315 .init_resource::<RenderColoredMesh2dInstances>()
316 .add_systems(
317 RenderStartup,
318 init_colored_mesh_2d_pipeline.after(init_mesh_2d_pipeline),
319 )
320 .add_systems(
321 ExtractSchedule,
322 extract_colored_mesh2d.after(extract_mesh2d),
323 )
324 .add_systems(
325 Render,
326 queue_colored_mesh2d.in_set(RenderSystems::QueueMeshes),
327 );
328 }
329}
330
331pub fn extract_colored_mesh2d(
333 mut commands: Commands,
334 mut previous_len: Local<usize>,
335 query: Extract<
338 Query<
339 (
340 Entity,
341 RenderEntity,
342 &ViewVisibility,
343 &GlobalTransform,
344 &Mesh2d,
345 ),
346 With<ColoredMesh2d>,
347 >,
348 >,
349 mut render_mesh_instances: ResMut<RenderColoredMesh2dInstances>,
350) {
351 let mut values = Vec::with_capacity(*previous_len);
352 for (entity, render_entity, view_visibility, transform, handle) in &query {
353 if !view_visibility.get() {
354 continue;
355 }
356
357 let transforms = Mesh2dTransforms {
358 world_from_local: transform.affine().into(),
359 flags: MeshFlags::empty().bits(),
360 };
361
362 values.push((render_entity, ColoredMesh2d));
363 render_mesh_instances.insert(
364 entity.into(),
365 RenderMesh2dInstance {
366 mesh_asset_id: handle.0.id(),
367 transforms,
368 material_bind_group_id: Material2dBindGroupId::default(),
369 automatic_batching: false,
370 tag: 0,
371 },
372 );
373 }
374 *previous_len = values.len();
375 commands.try_insert_batch(values);
376}
377
378pub fn queue_colored_mesh2d(
380 transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
381 colored_mesh2d_pipeline: Res<ColoredMesh2dPipeline>,
382 mut pipelines: ResMut<SpecializedRenderPipelines<ColoredMesh2dPipeline>>,
383 pipeline_cache: Res<PipelineCache>,
384 render_meshes: Res<RenderAssets<RenderMesh>>,
385 render_mesh_instances: Res<RenderColoredMesh2dInstances>,
386 mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
387 views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,
388) {
389 if render_mesh_instances.is_empty() {
390 return;
391 }
392 for (visible_entities, view, msaa) in &views {
394 let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
395 else {
396 continue;
397 };
398
399 let draw_colored_mesh2d = transparent_draw_functions.read().id::<DrawColoredMesh2d>();
400
401 let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
402 | Mesh2dPipelineKey::from_target_format(view.target_format);
403
404 let Some(visible_entities) = visible_entities.get::<Mesh2d>() else {
406 continue;
407 };
408 for (render_entity, visible_entity) in visible_entities.iter_visible() {
409 if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) {
410 let mesh2d_handle = mesh_instance.mesh_asset_id;
411 let mesh2d_transforms = &mesh_instance.transforms;
412 let mut mesh2d_key = mesh_key;
414 let Some(mesh) = render_meshes.get(mesh2d_handle) else {
415 continue;
416 };
417 mesh2d_key |= Mesh2dPipelineKey::from_primitive_topology_and_strip_index(
418 mesh.primitive_topology(),
419 mesh.index_format(),
420 );
421
422 let pipeline_id =
423 pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);
424
425 let mesh_z = mesh2d_transforms.world_from_local.translation.z;
426 transparent_phase.add_retained(Transparent2d {
427 entity: (*render_entity, *visible_entity),
428 draw_function: draw_colored_mesh2d,
429 pipeline: pipeline_id,
430 sort_key: FloatOrd(mesh_z),
433 batch_range: 0..1,
435 extra_index: PhaseItemExtraIndex::None,
436 extracted_index: usize::MAX,
437 indexed: mesh.indexed(),
438 });
439 }
440 }
441 }
442}