From 6b34e81f00db8c525479714e909bd5255eaa3f2e Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 15 Jul 2022 22:37:05 +0000 Subject: [PATCH] add a 3d lines example (#5319) # Objective - Showcase how to use a `Material` and `Mesh` to spawn 3d lines ![image](https://user-images.githubusercontent.com/8348954/179034236-ebc07f90-3eb5-46cc-8fc1-be7e6bf983fb.png) ## Solution - Add an example using a simple `Material` and `Mesh` definition to draw a 3d line - Shows how to use `LineList` and `LineStrip` in combination with a specialized `Material` ## Notes This isn't just a primitive shape because it needs a special Material, but I think it's a good showcase of the power of the `Material` and `AsBindGroup` abstractions. All of this is easy to figure out when you know these options are a thing, but I think they are hard to discover which is why I think this should be an example and not shipped with bevy. Co-authored-by: Charles --- Cargo.toml | 20 ++++- assets/shaders/line_material.wgsl | 11 +++ examples/3d/lines.rs | 141 ++++++++++++++++++++++++++++++ examples/README.md | 1 + 4 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 assets/shaders/line_material.wgsl create mode 100644 examples/3d/lines.rs diff --git a/Cargo.toml b/Cargo.toml index d8b358a9d4..ba1947661a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,15 @@ repository = "https://github.com/bevyengine/bevy" [workspace] exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"] -members = ["crates/*", "examples/ios", "tools/ci", "tools/spancmp", "tools/build-example-pages", "tools/build-wasm-example", "errors"] +members = [ + "crates/*", + "examples/ios", + "tools/ci", + "tools/spancmp", + "tools/build-example-pages", + "tools/build-wasm-example", + "errors", +] [features] default = [ @@ -281,6 +289,16 @@ description = "Illustrates various lighting options in a simple scene" category = "3D Rendering" wasm = true +[[example]] +name = "lines" +path = "examples/3d/lines.rs" + +[package.metadata.example.lines] +name = "Lines" +description = "Create a custom material to draw 3d lines" +category = "3D Rendering" +wasm = true + [[example]] name = "spotlight" path = "examples/3d/spotlight.rs" diff --git a/assets/shaders/line_material.wgsl b/assets/shaders/line_material.wgsl new file mode 100644 index 0000000000..02f278f60f --- /dev/null +++ b/assets/shaders/line_material.wgsl @@ -0,0 +1,11 @@ +struct LineMaterial { + color: vec4; +}; + +[[group(1), binding(0)]] +var material: LineMaterial; + +[[stage(fragment)]] +fn fragment() -> [[location(0)]] vec4 { + return material.color; +} \ No newline at end of file diff --git a/examples/3d/lines.rs b/examples/3d/lines.rs new file mode 100644 index 0000000000..9e0da236bd --- /dev/null +++ b/examples/3d/lines.rs @@ -0,0 +1,141 @@ +//! Create a custom material to draw basic lines in 3D + +use bevy::{ + pbr::{MaterialPipeline, MaterialPipelineKey}, + prelude::*, + reflect::TypeUuid, + render::{ + mesh::{MeshVertexBufferLayout, PrimitiveTopology}, + render_resource::{ + AsBindGroup, PolygonMode, RenderPipelineDescriptor, ShaderRef, + SpecializedMeshPipelineError, + }, + }, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(MaterialPlugin::::default()) + .add_startup_system(setup) + .run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Spawn a list of lines with start and end points for each lines + commands.spawn().insert_bundle(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(LineList { + lines: vec![ + (Vec3::ZERO, Vec3::new(1.0, 1.0, 0.0)), + (Vec3::new(1.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0)), + ], + })), + transform: Transform::from_xyz(-1.5, 0.0, 0.0), + material: materials.add(LineMaterial { + color: Color::GREEN, + }), + ..default() + }); + + // Spawn a line strip that goes from point to point + commands.spawn().insert_bundle(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(LineStrip { + points: vec![ + Vec3::ZERO, + Vec3::new(1.0, 1.0, 0.0), + Vec3::new(1.0, 0.0, 0.0), + ], + })), + transform: Transform::from_xyz(0.5, 0.0, 0.0), + material: materials.add(LineMaterial { color: Color::BLUE }), + ..default() + }); + + // camera + commands.spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} + +#[derive(Default, AsBindGroup, TypeUuid, Debug, Clone)] +#[uuid = "050ce6ac-080a-4d8c-b6b5-b5bab7560d8f"] +struct LineMaterial { + #[uniform(0)] + color: Color, +} + +impl Material for LineMaterial { + fn fragment_shader() -> ShaderRef { + "shaders/line_material.wgsl".into() + } + + fn specialize( + _pipeline: &MaterialPipeline, + descriptor: &mut RenderPipelineDescriptor, + _layout: &MeshVertexBufferLayout, + _key: MaterialPipelineKey, + ) -> Result<(), SpecializedMeshPipelineError> { + // This is the important part to tell bevy to render this material as a line between vertices + descriptor.primitive.polygon_mode = PolygonMode::Line; + Ok(()) + } +} + +/// A list of lines with a start and end position +#[derive(Debug, Clone)] +pub struct LineList { + pub lines: Vec<(Vec3, Vec3)>, +} + +impl From for Mesh { + fn from(line: LineList) -> Self { + let mut vertices = vec![]; + let mut normals = vec![]; + for (start, end) in line.lines { + vertices.push(start.to_array()); + vertices.push(end.to_array()); + normals.push(Vec3::ZERO.to_array()); + normals.push(Vec3::ZERO.to_array()); + } + + // This tells wgpu that the positions are list of lines + // where every pair is a start and end point + let mut mesh = Mesh::new(PrimitiveTopology::LineList); + + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + // Normals are currently required by bevy, but they aren't used by the [`LineMaterial`] + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh + } +} + +/// A list of points that will have a line drawn between each consecutive points +#[derive(Debug, Clone)] +pub struct LineStrip { + pub points: Vec, +} + +impl From for Mesh { + fn from(line: LineStrip) -> Self { + let mut vertices = vec![]; + let mut normals = vec![]; + for pos in line.points { + vertices.push(pos.to_array()); + normals.push(Vec3::ZERO.to_array()); + } + + // This tells wgpu that the positions are a list of points + // where a line will be drawn between each consecutive point + let mut mesh = Mesh::new(PrimitiveTopology::LineStrip); + + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + // Normals are currently required by bevy, but they aren't used by the [`LineMaterial`] + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh + } +} diff --git a/examples/README.md b/examples/README.md index 6838f39373..6fa1f9acda 100644 --- a/examples/README.md +++ b/examples/README.md @@ -107,6 +107,7 @@ Example | Description [3D Scene](../examples/3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting [3D Shapes](../examples/3d/shapes.rs) | A scene showcasing the built-in 3D shapes [Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene +[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines [Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene [MSAA](../examples/3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges [Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications)