1use bevy::{
9 asset::weak_handle,
10 color::palettes::basic::YELLOW,
11 core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
12 math::{ops, FloatOrd},
13 prelude::*,
14 render::{
15 mesh::{Indices, MeshVertexAttribute, RenderMesh},
16 render_asset::{RenderAssetUsages, RenderAssets},
17 render_phase::{
18 AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline,
19 ViewSortedRenderPhases,
20 },
21 render_resource::{
22 BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
23 DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PipelineCache,
24 PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor,
25 SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState, StencilState,
26 TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode,
27 },
28 sync_component::SyncComponentPlugin,
29 sync_world::{MainEntityHashMap, RenderEntity},
30 view::{ExtractedView, RenderVisibleEntities, ViewTarget},
31 Extract, Render, RenderApp, RenderSet,
32 },
33 sprite::{
34 extract_mesh2d, DrawMesh2d, Material2dBindGroupId, Mesh2dPipeline, Mesh2dPipelineKey,
35 Mesh2dTransforms, MeshFlags, RenderMesh2dInstance, SetMesh2dBindGroup,
36 SetMesh2dViewBindGroup,
37 },
38};
39use std::f32::consts::PI;
40
41fn main() {
42 App::new()
43 .add_plugins((DefaultPlugins, ColoredMesh2dPlugin))
44 .add_systems(Startup, star)
45 .run();
46}
47
48fn star(
49 mut commands: Commands,
50 mut meshes: ResMut<Assets<Mesh>>,
52) {
53 let mut star = Mesh::new(
61 PrimitiveTopology::TriangleList,
62 RenderAssetUsages::RENDER_WORLD,
63 );
64
65 let mut v_pos = vec![[0.0, 0.0, 0.0]];
78 for i in 0..10 {
79 let a = i as f32 * PI / 5.0;
81 let r = (1 - i % 2) as f32 * 100.0 + 100.0;
83 v_pos.push([r * ops::sin(a), r * ops::cos(a), 0.0]);
85 }
86 star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
88 let mut v_color: Vec<u32> = vec![LinearRgba::BLACK.as_u32()];
91 v_color.extend_from_slice(&[LinearRgba::from(YELLOW).as_u32(); 10]);
92 star.insert_attribute(
93 MeshVertexAttribute::new("Vertex_Color", 1, VertexFormat::Uint32),
94 v_color,
95 );
96
97 let mut indices = vec![0, 1, 10];
107 for i in 2..=10 {
108 indices.extend_from_slice(&[0, i, i - 1]);
109 }
110 star.insert_indices(Indices::U32(indices));
111
112 commands.spawn((
114 ColoredMesh2d,
116 Mesh2d(meshes.add(star)),
118 ));
119
120 commands.spawn(Camera2d);
121}
122
123#[derive(Component, Default)]
125pub struct ColoredMesh2d;
126
127#[derive(Resource)]
129pub struct ColoredMesh2dPipeline {
130 mesh2d_pipeline: Mesh2dPipeline,
132}
133
134impl FromWorld for ColoredMesh2dPipeline {
135 fn from_world(world: &mut World) -> Self {
136 Self {
137 mesh2d_pipeline: Mesh2dPipeline::from_world(world),
138 }
139 }
140}
141
142impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
144 type Key = Mesh2dPipelineKey;
145
146 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
147 let formats = vec![
150 VertexFormat::Float32x3,
152 VertexFormat::Uint32,
154 ];
155
156 let vertex_layout =
157 VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);
158
159 let format = match key.contains(Mesh2dPipelineKey::HDR) {
160 true => ViewTarget::TEXTURE_FORMAT_HDR,
161 false => TextureFormat::bevy_default(),
162 };
163
164 RenderPipelineDescriptor {
165 vertex: VertexState {
166 shader: COLORED_MESH2D_SHADER_HANDLE,
168 entry_point: "vertex".into(),
169 shader_defs: vec![],
170 buffers: vec![vertex_layout],
172 },
173 fragment: Some(FragmentState {
174 shader: COLORED_MESH2D_SHADER_HANDLE,
176 shader_defs: vec![],
177 entry_point: "fragment".into(),
178 targets: vec![Some(ColorTargetState {
179 format,
180 blend: Some(BlendState::ALPHA_BLENDING),
181 write_mask: ColorWrites::ALL,
182 })],
183 }),
184 layout: vec![
186 self.mesh2d_pipeline.view_layout.clone(),
188 self.mesh2d_pipeline.mesh_layout.clone(),
190 ],
191 push_constant_ranges: vec![],
192 primitive: PrimitiveState {
193 front_face: FrontFace::Ccw,
194 cull_mode: Some(Face::Back),
195 unclipped_depth: false,
196 polygon_mode: PolygonMode::Fill,
197 conservative: false,
198 topology: key.primitive_topology(),
199 strip_index_format: None,
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 zero_initialize_workgroup_memory: false,
224 }
225 }
226}
227
228type DrawColoredMesh2d = (
230 SetItemPipeline,
232 SetMesh2dViewBindGroup<0>,
234 SetMesh2dBindGroup<1>,
236 DrawMesh2d,
238);
239
240const COLORED_MESH2D_SHADER: &str = r"
243// Import the standard 2d mesh uniforms and set their bind groups
244#import bevy_sprite::mesh2d_functions
245
246// The structure of the vertex buffer is as specified in `specialize()`
247struct Vertex {
248 @builtin(instance_index) instance_index: u32,
249 @location(0) position: vec3<f32>,
250 @location(1) color: u32,
251};
252
253struct 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
262fn 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
273struct 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
280fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
281 return in.color;
282}
283";
284
285pub struct ColoredMesh2dPlugin;
287
288pub const COLORED_MESH2D_SHADER_HANDLE: Handle<Shader> =
290 weak_handle!("f48b148f-7373-4638-9900-392b3b3ccc66");
291
292#[derive(Resource, Deref, DerefMut, Default)]
294pub struct RenderColoredMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);
295
296impl Plugin for ColoredMesh2dPlugin {
297 fn build(&self, app: &mut App) {
298 let mut shaders = app.world_mut().resource_mut::<Assets<Shader>>();
300 shaders.insert(
301 &COLORED_MESH2D_SHADER_HANDLE,
302 Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()),
303 );
304 app.add_plugins(SyncComponentPlugin::<ColoredMesh2d>::default());
305
306 app.get_sub_app_mut(RenderApp)
308 .unwrap()
309 .add_render_command::<Transparent2d, DrawColoredMesh2d>()
310 .init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
311 .init_resource::<RenderColoredMesh2dInstances>()
312 .add_systems(
313 ExtractSchedule,
314 extract_colored_mesh2d.after(extract_mesh2d),
315 )
316 .add_systems(Render, queue_colored_mesh2d.in_set(RenderSet::QueueMeshes));
317 }
318
319 fn finish(&self, app: &mut App) {
320 app.get_sub_app_mut(RenderApp)
322 .unwrap()
323 .init_resource::<ColoredMesh2dPipeline>();
324 }
325}
326
327pub fn extract_colored_mesh2d(
329 mut commands: Commands,
330 mut previous_len: Local<usize>,
331 query: Extract<
334 Query<
335 (
336 Entity,
337 RenderEntity,
338 &ViewVisibility,
339 &GlobalTransform,
340 &Mesh2d,
341 ),
342 With<ColoredMesh2d>,
343 >,
344 >,
345 mut render_mesh_instances: ResMut<RenderColoredMesh2dInstances>,
346) {
347 let mut values = Vec::with_capacity(*previous_len);
348 for (entity, render_entity, view_visibility, transform, handle) in &query {
349 if !view_visibility.get() {
350 continue;
351 }
352
353 let transforms = Mesh2dTransforms {
354 world_from_local: (&transform.affine()).into(),
355 flags: MeshFlags::empty().bits(),
356 };
357
358 values.push((render_entity, ColoredMesh2d));
359 render_mesh_instances.insert(
360 entity.into(),
361 RenderMesh2dInstance {
362 mesh_asset_id: handle.0.id(),
363 transforms,
364 material_bind_group_id: Material2dBindGroupId::default(),
365 automatic_batching: false,
366 tag: 0,
367 },
368 );
369 }
370 *previous_len = values.len();
371 commands.try_insert_batch(values);
372}
373
374pub fn queue_colored_mesh2d(
376 transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
377 colored_mesh2d_pipeline: Res<ColoredMesh2dPipeline>,
378 mut pipelines: ResMut<SpecializedRenderPipelines<ColoredMesh2dPipeline>>,
379 pipeline_cache: Res<PipelineCache>,
380 render_meshes: Res<RenderAssets<RenderMesh>>,
381 render_mesh_instances: Res<RenderColoredMesh2dInstances>,
382 mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
383 views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,
384) {
385 if render_mesh_instances.is_empty() {
386 return;
387 }
388 for (visible_entities, view, msaa) in &views {
390 let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
391 else {
392 continue;
393 };
394
395 let draw_colored_mesh2d = transparent_draw_functions.read().id::<DrawColoredMesh2d>();
396
397 let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
398 | Mesh2dPipelineKey::from_hdr(view.hdr);
399
400 for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
402 if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) {
403 let mesh2d_handle = mesh_instance.mesh_asset_id;
404 let mesh2d_transforms = &mesh_instance.transforms;
405 let mut mesh2d_key = mesh_key;
407 let Some(mesh) = render_meshes.get(mesh2d_handle) else {
408 continue;
409 };
410 mesh2d_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology());
411
412 let pipeline_id =
413 pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);
414
415 let mesh_z = mesh2d_transforms.world_from_local.translation.z;
416 transparent_phase.add(Transparent2d {
417 entity: (*render_entity, *visible_entity),
418 draw_function: draw_colored_mesh2d,
419 pipeline: pipeline_id,
420 sort_key: FloatOrd(mesh_z),
423 batch_range: 0..1,
425 extra_index: PhaseItemExtraIndex::None,
426 extracted_index: usize::MAX,
427 indexed: mesh.indexed(),
428 });
429 }
430 }
431 }
432}