Example on how to draw using custom mesh and shader (#1565)

I was looking into "lower level" rendering and I saw no example on how to do that. Yet, I think it's something relevant to show, so I set up a simple example on how to do that. I hope it's welcome.

I'm not confident about the code and a review is definitely nice to have, especially because there are a few things that are not great.
Specifically, I think it would be nice to see how to render with a completely custom set of attributes (position and color, in this case), but I couldn't manage to get it working without normals and uv.

It makes sense if bevy Meshes need these two attributes, but I'm not sure about it.

Co-authored-by: Alessandro Re <ale@ale-re.net>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Alessandro Re 2021-04-12 19:47:12 +00:00
parent 9186c4c8d2
commit 6ce57c85d6
3 changed files with 142 additions and 0 deletions

View file

@ -97,6 +97,10 @@ path = "examples/hello_world.rs"
name = "contributors"
path = "examples/2d/contributors.rs"
[[example]]
name = "mesh"
path = "examples/2d/mesh.rs"
[[example]]
name = "many_sprites"
path = "examples/2d/many_sprites.rs"

137
examples/2d/mesh.rs Normal file
View file

@ -0,0 +1,137 @@
use bevy::{
prelude::*,
render::{
pipeline::{PipelineDescriptor, RenderPipeline},
shader::{ShaderStage, ShaderStages},
},
};
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_startup_system(star.system())
.run();
}
fn star(
mut commands: Commands,
// We will add a new Mesh for the star being created
mut meshes: ResMut<Assets<Mesh>>,
// A pipeline will be added with custom shaders
mut pipelines: ResMut<Assets<PipelineDescriptor>>,
// Access to add new shaders
mut shaders: ResMut<Assets<Shader>>,
) {
// We first create a pipeline, which is the sequence of steps that are
// needed to get to pixels on the screen starting from a description of the
// geometries in the scene. Pipelines have fixed steps, which sometimes can
// be turned off (for instance, depth and stencil tests) and programmable
// steps, the vertex and fragment shaders, that we can customize writing
// shader programs.
let pipeline_handle = pipelines.add(PipelineDescriptor::default_config(ShaderStages {
// Vertex shaders are run once for every vertex in the mesh.
// Each vertex can have attributes associated to it (e.g. position,
// color, texture mapping). The output of a shader is per-vertex.
vertex: shaders.add(Shader::from_glsl(ShaderStage::Vertex, VERTEX_SHADER)),
// Fragment shaders are run for each pixel belonging to a triangle on
// the screen. Their output is per-pixel.
fragment: Some(shaders.add(Shader::from_glsl(ShaderStage::Fragment, FRAGMENT_SHADER))),
}));
// 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(bevy::render::pipeline::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
star.set_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
// And a RGB color attribute as well
let mut v_color = vec![[0.0, 0.0, 0.0]];
v_color.extend_from_slice(&[[1.0, 1.0, 0.0]; 10]);
star.set_attribute("Vertex_Color", v_color);
// 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(bevy::render::mesh::Indices::U32(indices)));
// We can now spawn the entities for the star and the camera
commands.spawn_bundle(MeshBundle {
mesh: meshes.add(star),
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new(
pipeline_handle,
)]),
..Default::default()
});
commands
// And use an orthographic projection
.spawn_bundle(OrthographicCameraBundle::new_2d());
}
const VERTEX_SHADER: &str = r"
#version 450
layout(location = 0) in vec3 Vertex_Position;
layout(location = 1) in vec3 Vertex_Color;
layout(location = 1) out vec3 v_Color;
layout(set = 0, binding = 0) uniform CameraViewProj {
mat4 ViewProj;
};
layout(set = 1, binding = 0) uniform Transform {
mat4 Model;
};
void main() {
v_Color = Vertex_Color;
gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
}
";
const FRAGMENT_SHADER: &str = r"
#version 450
layout(location = 1) in vec3 v_Color;
layout(location = 0) out vec4 o_Target;
void main() {
o_Target = vec4(v_Color, 1.0);
}
";

View file

@ -73,6 +73,7 @@ Example | Main | Description
--- | --- | ---
`contributors` | [`2d/contributors.rs`](./2d/contributors.rs) | Displays each contributor as a bouncy bevy-ball!
`many_sprites` | [`2d/many_sprites.rs`](./2d/many_sprites.rs) | Displays many sprites in a grid arragement! Used for performance testing.
`mesh` | [`2d/mesh.rs`](./2d/mesh.rs) | Renders a custom mesh
`sprite` | [`2d/sprite.rs`](./2d/sprite.rs) | Renders a sprite
`sprite_sheet` | [`2d/sprite_sheet.rs`](./2d/sprite_sheet.rs) | Renders an animated sprite
`text2d` | [`2d/text2d.rs`](./2d/text2d.rs) | Generates text in 2d