Add 2d meshes and materials (#3460)
# Objective
The current 2d rendering is specialized to render sprites, we need a generic way to render 2d items, using meshes and materials like we have for 3d.
## Solution
I cloned a good part of `bevy_pbr` into `bevy_sprite/src/mesh2d`, removed lighting and pbr itself, adapted it to 2d rendering, added a `ColorMaterial`, and modified the sprite rendering to break batches around 2d meshes.
~~The PR is a bit crude; I tried to change as little as I could in both the parts copied from 3d and the current sprite rendering to make reviewing easier. In the future, I expect we could make the sprite rendering a normal 2d material, cleanly integrated with the rest.~~ _edit: see <https://github.com/bevyengine/bevy/pull/3460#issuecomment-1003605194>_
## Remaining work
- ~~don't require mesh normals~~ _out of scope_
- ~~add an example~~ _done_
- support 2d meshes & materials in the UI?
- bikeshed names (I didn't think hard about naming, please check if it's fine)
## Remaining questions
- ~~should we add a depth buffer to 2d now that there are 2d meshes?~~ _let's revisit that when we have an opaque render phase_
- ~~should we add MSAA support to the sprites, or remove it from the 2d meshes?~~ _I added MSAA to sprites since it's really needed for 2d meshes_
- ~~how to customize vertex attributes?~~ _#3120_
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-01-08 01:29:08 +00:00
|
|
|
use bevy::{
|
|
|
|
core::FloatOrd,
|
|
|
|
core_pipeline::Transparent2d,
|
|
|
|
prelude::*,
|
|
|
|
reflect::TypeUuid,
|
|
|
|
render::{
|
|
|
|
mesh::Indices,
|
|
|
|
render_asset::RenderAssets,
|
|
|
|
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
|
|
|
render_resource::{
|
|
|
|
BlendState, ColorTargetState, ColorWrites, Face, FragmentState, FrontFace,
|
|
|
|
MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineCache,
|
|
|
|
RenderPipelineDescriptor, SpecializedPipeline, SpecializedPipelines, TextureFormat,
|
|
|
|
VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode,
|
|
|
|
},
|
|
|
|
texture::BevyDefault,
|
|
|
|
view::VisibleEntities,
|
|
|
|
RenderApp, RenderStage,
|
|
|
|
},
|
|
|
|
sprite::{
|
|
|
|
DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform,
|
|
|
|
SetMesh2dBindGroup, SetMesh2dViewBindGroup,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/// This example shows how to manually render 2d items using "mid level render apis" with a custom pipeline for 2d meshes
|
|
|
|
/// It doesn't use the [`Material2d`] abstraction, but changes the vertex buffer to include vertex color
|
|
|
|
/// Check out the "mesh2d" example for simpler / higher level 2d meshes
|
|
|
|
fn main() {
|
|
|
|
App::new()
|
|
|
|
.add_plugins(DefaultPlugins)
|
|
|
|
.add_plugin(ColoredMesh2dPlugin)
|
|
|
|
.add_startup_system(star)
|
|
|
|
.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn star(
|
|
|
|
mut commands: Commands,
|
|
|
|
// We will add a new Mesh for the star being created
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
) {
|
|
|
|
// Let's define the mesh for the object we want to draw: a nice star.
|
|
|
|
// We will specify here what kind of topology is used to define the mesh,
|
|
|
|
// that is, how triangles are built from the vertices. We will use a
|
|
|
|
// triangle list, meaning that each vertex of the triangle has to be
|
|
|
|
// specified.
|
|
|
|
let mut star = Mesh::new(PrimitiveTopology::TriangleList);
|
|
|
|
|
|
|
|
// Vertices need to have a position attribute. We will use the following
|
|
|
|
// vertices (I hope you can spot the star in the schema).
|
|
|
|
//
|
|
|
|
// 1
|
|
|
|
//
|
|
|
|
// 10 2
|
|
|
|
// 9 0 3
|
|
|
|
// 8 4
|
|
|
|
// 6
|
|
|
|
// 7 5
|
|
|
|
//
|
|
|
|
// These vertices are specificed in 3D space.
|
|
|
|
let mut v_pos = vec![[0.0, 0.0, 0.0]];
|
|
|
|
for i in 0..10 {
|
|
|
|
// Angle of each vertex is 1/10 of TAU, plus PI/2 for positioning vertex 0
|
|
|
|
let a = std::f32::consts::FRAC_PI_2 - i as f32 * std::f32::consts::TAU / 10.0;
|
|
|
|
// Radius of internal vertices (2, 4, 6, 8, 10) is 100, it's 200 for external
|
|
|
|
let r = (1 - i % 2) as f32 * 100.0 + 100.0;
|
|
|
|
// Add the vertex coordinates
|
|
|
|
v_pos.push([r * a.cos(), r * a.sin(), 0.0]);
|
|
|
|
}
|
|
|
|
// Set the position attribute
|
Mesh vertex buffer layouts (#3959)
This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes:
* Reworks the `Mesh` type to use the newly added `VertexAttribute` internally
* `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat`
* `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting"
* Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently.
* Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key).
* Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed<T>` keys and `PreHashMap<K, V>` (which uses `Hashed<T>` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR.
* Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key. For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool!
To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now:
```rust
impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> RenderPipelineDescriptor {
let mut vertex_attributes = vec![
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
];
let mut shader_defs = Vec::new();
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
}
let vertex_buffer_layout = layout
.get_layout(&vertex_attributes)
.expect("Mesh is missing a vertex attribute");
```
Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`.
This is still a draft because I still need to:
* Add more docs
* Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it.
* Consider breaking out the PreHash / hashbrown changes into a separate PR.
* Add an example illustrating this change
* Verify that the "mesh-specialized pipeline de-duplication code" works properly
Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap.
Alternative to #3120
Fixes #3030
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-02-23 23:21:13 +00:00
|
|
|
star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
|
Add 2d meshes and materials (#3460)
# Objective
The current 2d rendering is specialized to render sprites, we need a generic way to render 2d items, using meshes and materials like we have for 3d.
## Solution
I cloned a good part of `bevy_pbr` into `bevy_sprite/src/mesh2d`, removed lighting and pbr itself, adapted it to 2d rendering, added a `ColorMaterial`, and modified the sprite rendering to break batches around 2d meshes.
~~The PR is a bit crude; I tried to change as little as I could in both the parts copied from 3d and the current sprite rendering to make reviewing easier. In the future, I expect we could make the sprite rendering a normal 2d material, cleanly integrated with the rest.~~ _edit: see <https://github.com/bevyengine/bevy/pull/3460#issuecomment-1003605194>_
## Remaining work
- ~~don't require mesh normals~~ _out of scope_
- ~~add an example~~ _done_
- support 2d meshes & materials in the UI?
- bikeshed names (I didn't think hard about naming, please check if it's fine)
## Remaining questions
- ~~should we add a depth buffer to 2d now that there are 2d meshes?~~ _let's revisit that when we have an opaque render phase_
- ~~should we add MSAA support to the sprites, or remove it from the 2d meshes?~~ _I added MSAA to sprites since it's really needed for 2d meshes_
- ~~how to customize vertex attributes?~~ _#3120_
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-01-08 01:29:08 +00:00
|
|
|
// And a RGB color attribute as well
|
|
|
|
let mut v_color = vec![[0.0, 0.0, 0.0, 1.0]];
|
|
|
|
v_color.extend_from_slice(&[[1.0, 1.0, 0.0, 1.0]; 10]);
|
Mesh vertex buffer layouts (#3959)
This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes:
* Reworks the `Mesh` type to use the newly added `VertexAttribute` internally
* `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat`
* `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting"
* Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently.
* Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key).
* Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed<T>` keys and `PreHashMap<K, V>` (which uses `Hashed<T>` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR.
* Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key. For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool!
To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now:
```rust
impl SpecializedMeshPipeline for MeshPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> RenderPipelineDescriptor {
let mut vertex_attributes = vec![
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
];
let mut shader_defs = Vec::new();
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push(String::from("VERTEX_TANGENTS"));
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
}
let vertex_buffer_layout = layout
.get_layout(&vertex_attributes)
.expect("Mesh is missing a vertex attribute");
```
Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`.
This is still a draft because I still need to:
* Add more docs
* Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it.
* Consider breaking out the PreHash / hashbrown changes into a separate PR.
* Add an example illustrating this change
* Verify that the "mesh-specialized pipeline de-duplication code" works properly
Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap.
Alternative to #3120
Fixes #3030
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-02-23 23:21:13 +00:00
|
|
|
star.insert_attribute(Mesh::ATTRIBUTE_COLOR, v_color);
|
Add 2d meshes and materials (#3460)
# Objective
The current 2d rendering is specialized to render sprites, we need a generic way to render 2d items, using meshes and materials like we have for 3d.
## Solution
I cloned a good part of `bevy_pbr` into `bevy_sprite/src/mesh2d`, removed lighting and pbr itself, adapted it to 2d rendering, added a `ColorMaterial`, and modified the sprite rendering to break batches around 2d meshes.
~~The PR is a bit crude; I tried to change as little as I could in both the parts copied from 3d and the current sprite rendering to make reviewing easier. In the future, I expect we could make the sprite rendering a normal 2d material, cleanly integrated with the rest.~~ _edit: see <https://github.com/bevyengine/bevy/pull/3460#issuecomment-1003605194>_
## Remaining work
- ~~don't require mesh normals~~ _out of scope_
- ~~add an example~~ _done_
- support 2d meshes & materials in the UI?
- bikeshed names (I didn't think hard about naming, please check if it's fine)
## Remaining questions
- ~~should we add a depth buffer to 2d now that there are 2d meshes?~~ _let's revisit that when we have an opaque render phase_
- ~~should we add MSAA support to the sprites, or remove it from the 2d meshes?~~ _I added MSAA to sprites since it's really needed for 2d meshes_
- ~~how to customize vertex attributes?~~ _#3120_
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-01-08 01:29:08 +00:00
|
|
|
|
|
|
|
// Now, we specify the indices of the vertex that are going to compose the
|
|
|
|
// triangles in our star. Vertices in triangles have to be specified in CCW
|
|
|
|
// winding (that will be the front face, colored). Since we are using
|
|
|
|
// triangle list, we will specify each triangle as 3 vertices
|
|
|
|
// First triangle: 0, 2, 1
|
|
|
|
// Second triangle: 0, 3, 2
|
|
|
|
// Third triangle: 0, 4, 3
|
|
|
|
// etc
|
|
|
|
// Last triangle: 0, 1, 10
|
|
|
|
let mut indices = vec![0, 1, 10];
|
|
|
|
for i in 2..=10 {
|
|
|
|
indices.extend_from_slice(&[0, i, i - 1]);
|
|
|
|
}
|
|
|
|
star.set_indices(Some(Indices::U32(indices)));
|
|
|
|
|
|
|
|
// We can now spawn the entities for the star and the camera
|
|
|
|
commands.spawn_bundle((
|
|
|
|
// We use a marker component to identify the custom colored meshes
|
|
|
|
ColoredMesh2d::default(),
|
|
|
|
// The `Handle<Mesh>` needs to be wrapped in a `Mesh2dHandle` to use 2d rendering instead of 3d
|
|
|
|
Mesh2dHandle(meshes.add(star)),
|
|
|
|
// These other components are needed for 2d meshes to be rendered
|
|
|
|
Transform::default(),
|
|
|
|
GlobalTransform::default(),
|
|
|
|
Visibility::default(),
|
|
|
|
ComputedVisibility::default(),
|
|
|
|
));
|
|
|
|
commands
|
|
|
|
// And use an orthographic projection
|
|
|
|
.spawn_bundle(OrthographicCameraBundle::new_2d());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A marker component for colored 2d meshes
|
|
|
|
#[derive(Component, Default)]
|
|
|
|
pub struct ColoredMesh2d;
|
|
|
|
|
|
|
|
/// Custom pipeline for 2d meshes with vertex colors
|
|
|
|
pub struct ColoredMesh2dPipeline {
|
|
|
|
/// this pipeline wraps the standard [`Mesh2dPipeline`]
|
|
|
|
mesh2d_pipeline: Mesh2dPipeline,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromWorld for ColoredMesh2dPipeline {
|
|
|
|
fn from_world(world: &mut World) -> Self {
|
|
|
|
Self {
|
|
|
|
mesh2d_pipeline: Mesh2dPipeline::from_world(world),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline`
|
|
|
|
impl SpecializedPipeline for ColoredMesh2dPipeline {
|
|
|
|
type Key = Mesh2dPipelineKey;
|
|
|
|
|
|
|
|
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
|
|
|
// Customize how to store the meshes' vertex attributes in the vertex buffer
|
|
|
|
// Our meshes only have position and color
|
|
|
|
let vertex_attributes = vec![
|
|
|
|
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
|
|
|
|
VertexAttribute {
|
|
|
|
format: VertexFormat::Float32x3,
|
|
|
|
// this offset is the size of the color attribute, which is stored first
|
|
|
|
offset: 16,
|
|
|
|
// position is available at location 0 in the shader
|
|
|
|
shader_location: 0,
|
|
|
|
},
|
|
|
|
// Color
|
|
|
|
VertexAttribute {
|
|
|
|
format: VertexFormat::Float32x4,
|
|
|
|
offset: 0,
|
|
|
|
shader_location: 1,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
// This is the sum of the size of position and color attributes (12 + 16 = 28)
|
|
|
|
let vertex_array_stride = 28;
|
|
|
|
|
|
|
|
RenderPipelineDescriptor {
|
|
|
|
vertex: VertexState {
|
|
|
|
// Use our custom shader
|
|
|
|
shader: COLORED_MESH2D_SHADER_HANDLE.typed::<Shader>(),
|
|
|
|
entry_point: "vertex".into(),
|
|
|
|
shader_defs: Vec::new(),
|
|
|
|
// Use our custom vertex buffer
|
|
|
|
buffers: vec![VertexBufferLayout {
|
|
|
|
array_stride: vertex_array_stride,
|
|
|
|
step_mode: VertexStepMode::Vertex,
|
|
|
|
attributes: vertex_attributes,
|
|
|
|
}],
|
|
|
|
},
|
|
|
|
fragment: Some(FragmentState {
|
|
|
|
// Use our custom shader
|
|
|
|
shader: COLORED_MESH2D_SHADER_HANDLE.typed::<Shader>(),
|
|
|
|
shader_defs: Vec::new(),
|
|
|
|
entry_point: "fragment".into(),
|
|
|
|
targets: vec![ColorTargetState {
|
|
|
|
format: TextureFormat::bevy_default(),
|
|
|
|
blend: Some(BlendState::ALPHA_BLENDING),
|
|
|
|
write_mask: ColorWrites::ALL,
|
|
|
|
}],
|
|
|
|
}),
|
|
|
|
// Use the two standard uniforms for 2d meshes
|
|
|
|
layout: Some(vec![
|
|
|
|
// Bind group 0 is the view uniform
|
|
|
|
self.mesh2d_pipeline.view_layout.clone(),
|
|
|
|
// Bind group 1 is the mesh uniform
|
|
|
|
self.mesh2d_pipeline.mesh_layout.clone(),
|
|
|
|
]),
|
|
|
|
primitive: PrimitiveState {
|
|
|
|
front_face: FrontFace::Ccw,
|
|
|
|
cull_mode: Some(Face::Back),
|
|
|
|
unclipped_depth: false,
|
|
|
|
polygon_mode: PolygonMode::Fill,
|
|
|
|
conservative: false,
|
|
|
|
topology: key.primitive_topology(),
|
|
|
|
strip_index_format: None,
|
|
|
|
},
|
|
|
|
depth_stencil: None,
|
|
|
|
multisample: MultisampleState {
|
|
|
|
count: key.msaa_samples(),
|
|
|
|
mask: !0,
|
|
|
|
alpha_to_coverage_enabled: false,
|
|
|
|
},
|
|
|
|
label: Some("colored_mesh2d_pipeline".into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This specifies how to render a colored 2d mesh
|
|
|
|
type DrawColoredMesh2d = (
|
|
|
|
// Set the pipeline
|
|
|
|
SetItemPipeline,
|
|
|
|
// Set the view uniform as bind group 0
|
|
|
|
SetMesh2dViewBindGroup<0>,
|
|
|
|
// Set the mesh uniform as bind group 1
|
|
|
|
SetMesh2dBindGroup<1>,
|
|
|
|
// Draw the mesh
|
|
|
|
DrawMesh2d,
|
|
|
|
);
|
|
|
|
|
|
|
|
// The custom shader can be inline like here, included from another file at build time
|
|
|
|
// using `include_str!()`, or loaded like any other asset with `asset_server.load()`.
|
|
|
|
const COLORED_MESH2D_SHADER: &str = r"
|
|
|
|
// Import the standard 2d mesh uniforms and set their bind groups
|
|
|
|
#import bevy_sprite::mesh2d_view_bind_group
|
|
|
|
[[group(0), binding(0)]]
|
|
|
|
var<uniform> view: View;
|
|
|
|
#import bevy_sprite::mesh2d_struct
|
|
|
|
[[group(1), binding(0)]]
|
|
|
|
var<uniform> mesh: Mesh2d;
|
|
|
|
|
|
|
|
// The structure of the vertex buffer is as specified in `specialize()`
|
|
|
|
struct Vertex {
|
|
|
|
[[location(0)]] position: vec3<f32>;
|
|
|
|
[[location(1)]] color: vec4<f32>;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct VertexOutput {
|
|
|
|
// The vertex shader must set the on-screen position of the vertex
|
|
|
|
[[builtin(position)]] clip_position: vec4<f32>;
|
|
|
|
// We pass the vertex color to the framgent shader in location 0
|
|
|
|
[[location(0)]] color: vec4<f32>;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Entry point for the vertex shader
|
|
|
|
[[stage(vertex)]]
|
|
|
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
|
|
|
var out: VertexOutput;
|
|
|
|
// Project the world position of the mesh into screen position
|
|
|
|
out.clip_position = view.view_proj * mesh.model * vec4<f32>(vertex.position, 1.0);
|
|
|
|
out.color = vertex.color;
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The input of the fragment shader must correspond to the output of the vertex shader for all `location`s
|
|
|
|
struct FragmentInput {
|
|
|
|
// The color is interpolated between vertices by default
|
|
|
|
[[location(0)]] color: vec4<f32>;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Entry point for the fragment shader
|
|
|
|
[[stage(fragment)]]
|
|
|
|
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
|
|
|
return in.color;
|
|
|
|
}
|
|
|
|
";
|
|
|
|
|
|
|
|
/// Plugin that renders [`ColoredMesh2d`]s
|
|
|
|
pub struct ColoredMesh2dPlugin;
|
|
|
|
|
|
|
|
/// Handle to the custom shader with a unique random ID
|
|
|
|
pub const COLORED_MESH2D_SHADER_HANDLE: HandleUntyped =
|
|
|
|
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 13828845428412094821);
|
|
|
|
|
|
|
|
impl Plugin for ColoredMesh2dPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
// Load our custom shader
|
|
|
|
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap();
|
|
|
|
shaders.set_untracked(
|
|
|
|
COLORED_MESH2D_SHADER_HANDLE,
|
|
|
|
Shader::from_wgsl(COLORED_MESH2D_SHADER),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Register our custom draw function and pipeline, and add our render systems
|
|
|
|
let render_app = app.get_sub_app_mut(RenderApp).unwrap();
|
|
|
|
render_app
|
|
|
|
.add_render_command::<Transparent2d, DrawColoredMesh2d>()
|
|
|
|
.init_resource::<ColoredMesh2dPipeline>()
|
|
|
|
.init_resource::<SpecializedPipelines<ColoredMesh2dPipeline>>()
|
|
|
|
.add_system_to_stage(RenderStage::Extract, extract_colored_mesh2d)
|
|
|
|
.add_system_to_stage(RenderStage::Queue, queue_colored_mesh2d);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Extract the [`ColoredMesh2d`] marker component into the render app
|
|
|
|
pub fn extract_colored_mesh2d(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut previous_len: Local<usize>,
|
|
|
|
query: Query<(Entity, &ComputedVisibility), With<ColoredMesh2d>>,
|
|
|
|
) {
|
|
|
|
let mut values = Vec::with_capacity(*previous_len);
|
|
|
|
for (entity, computed_visibility) in query.iter() {
|
|
|
|
if !computed_visibility.is_visible {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
values.push((entity, (ColoredMesh2d,)));
|
|
|
|
}
|
|
|
|
*previous_len = values.len();
|
|
|
|
commands.insert_or_spawn_batch(values);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Queue the 2d meshes marked with [`ColoredMesh2d`] using our custom pipeline and draw function
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
pub fn queue_colored_mesh2d(
|
|
|
|
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
|
|
|
|
colored_mesh2d_pipeline: Res<ColoredMesh2dPipeline>,
|
|
|
|
mut pipelines: ResMut<SpecializedPipelines<ColoredMesh2dPipeline>>,
|
|
|
|
mut pipeline_cache: ResMut<RenderPipelineCache>,
|
|
|
|
msaa: Res<Msaa>,
|
|
|
|
render_meshes: Res<RenderAssets<Mesh>>,
|
|
|
|
colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dUniform), With<ColoredMesh2d>>,
|
|
|
|
mut views: Query<(&VisibleEntities, &mut RenderPhase<Transparent2d>)>,
|
|
|
|
) {
|
|
|
|
if colored_mesh2d.is_empty() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Iterate each view (a camera is a view)
|
|
|
|
for (visible_entities, mut transparent_phase) in views.iter_mut() {
|
|
|
|
let draw_colored_mesh2d = transparent_draw_functions
|
|
|
|
.read()
|
|
|
|
.get_id::<DrawColoredMesh2d>()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples);
|
|
|
|
|
|
|
|
// Queue all entities visible to that view
|
|
|
|
for visible_entity in &visible_entities.entities {
|
|
|
|
if let Ok((mesh2d_handle, mesh2d_uniform)) = colored_mesh2d.get(*visible_entity) {
|
|
|
|
// Get our specialized pipeline
|
|
|
|
let mut mesh2d_key = mesh_key;
|
|
|
|
if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) {
|
|
|
|
mesh2d_key |=
|
|
|
|
Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
|
|
|
}
|
|
|
|
|
|
|
|
let pipeline_id =
|
|
|
|
pipelines.specialize(&mut pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);
|
|
|
|
|
|
|
|
let mesh_z = mesh2d_uniform.transform.w_axis.z;
|
|
|
|
transparent_phase.add(Transparent2d {
|
|
|
|
entity: *visible_entity,
|
|
|
|
draw_function: draw_colored_mesh2d,
|
|
|
|
pipeline: pipeline_id,
|
|
|
|
// The 2d render items are sorted according to their z value before rendering,
|
|
|
|
// in order to get correct transparency
|
|
|
|
sort_key: FloatOrd(mesh_z),
|
|
|
|
// This material is not batched
|
|
|
|
batch_range: None,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|