Skip to content

Commit 2d63f54

Browse files
IceSentryItsDoot
authored andcommitted
add a 3d lines example (bevyengine#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 <IceSentry@users.noreply.github.com>
1 parent eb64eef commit 2d63f54

File tree

4 files changed

+172
-1
lines changed

4 files changed

+172
-1
lines changed

Cargo.toml

+19-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@ repository = "https://github.com/bevyengine/bevy"
1313

1414
[workspace]
1515
exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"]
16-
members = ["crates/*", "examples/ios", "tools/ci", "tools/spancmp", "tools/build-example-pages", "tools/build-wasm-example", "errors"]
16+
members = [
17+
"crates/*",
18+
"examples/ios",
19+
"tools/ci",
20+
"tools/spancmp",
21+
"tools/build-example-pages",
22+
"tools/build-wasm-example",
23+
"errors",
24+
]
1725

1826
[features]
1927
default = [
@@ -281,6 +289,16 @@ description = "Illustrates various lighting options in a simple scene"
281289
category = "3D Rendering"
282290
wasm = true
283291

292+
[[example]]
293+
name = "lines"
294+
path = "examples/3d/lines.rs"
295+
296+
[package.metadata.example.lines]
297+
name = "Lines"
298+
description = "Create a custom material to draw 3d lines"
299+
category = "3D Rendering"
300+
wasm = true
301+
284302
[[example]]
285303
name = "spotlight"
286304
path = "examples/3d/spotlight.rs"

assets/shaders/line_material.wgsl

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
struct LineMaterial {
2+
color: vec4<f32>;
3+
};
4+
5+
[[group(1), binding(0)]]
6+
var<uniform> material: LineMaterial;
7+
8+
[[stage(fragment)]]
9+
fn fragment() -> [[location(0)]] vec4<f32> {
10+
return material.color;
11+
}

examples/3d/lines.rs

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
//! Create a custom material to draw basic lines in 3D
2+
3+
use bevy::{
4+
pbr::{MaterialPipeline, MaterialPipelineKey},
5+
prelude::*,
6+
reflect::TypeUuid,
7+
render::{
8+
mesh::{MeshVertexBufferLayout, PrimitiveTopology},
9+
render_resource::{
10+
AsBindGroup, PolygonMode, RenderPipelineDescriptor, ShaderRef,
11+
SpecializedMeshPipelineError,
12+
},
13+
},
14+
};
15+
16+
fn main() {
17+
App::new()
18+
.add_plugins(DefaultPlugins)
19+
.add_plugin(MaterialPlugin::<LineMaterial>::default())
20+
.add_startup_system(setup)
21+
.run();
22+
}
23+
24+
fn setup(
25+
mut commands: Commands,
26+
mut meshes: ResMut<Assets<Mesh>>,
27+
mut materials: ResMut<Assets<LineMaterial>>,
28+
) {
29+
// Spawn a list of lines with start and end points for each lines
30+
commands.spawn().insert_bundle(MaterialMeshBundle {
31+
mesh: meshes.add(Mesh::from(LineList {
32+
lines: vec![
33+
(Vec3::ZERO, Vec3::new(1.0, 1.0, 0.0)),
34+
(Vec3::new(1.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
35+
],
36+
})),
37+
transform: Transform::from_xyz(-1.5, 0.0, 0.0),
38+
material: materials.add(LineMaterial {
39+
color: Color::GREEN,
40+
}),
41+
..default()
42+
});
43+
44+
// Spawn a line strip that goes from point to point
45+
commands.spawn().insert_bundle(MaterialMeshBundle {
46+
mesh: meshes.add(Mesh::from(LineStrip {
47+
points: vec![
48+
Vec3::ZERO,
49+
Vec3::new(1.0, 1.0, 0.0),
50+
Vec3::new(1.0, 0.0, 0.0),
51+
],
52+
})),
53+
transform: Transform::from_xyz(0.5, 0.0, 0.0),
54+
material: materials.add(LineMaterial { color: Color::BLUE }),
55+
..default()
56+
});
57+
58+
// camera
59+
commands.spawn_bundle(Camera3dBundle {
60+
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
61+
..default()
62+
});
63+
}
64+
65+
#[derive(Default, AsBindGroup, TypeUuid, Debug, Clone)]
66+
#[uuid = "050ce6ac-080a-4d8c-b6b5-b5bab7560d8f"]
67+
struct LineMaterial {
68+
#[uniform(0)]
69+
color: Color,
70+
}
71+
72+
impl Material for LineMaterial {
73+
fn fragment_shader() -> ShaderRef {
74+
"shaders/line_material.wgsl".into()
75+
}
76+
77+
fn specialize(
78+
_pipeline: &MaterialPipeline<Self>,
79+
descriptor: &mut RenderPipelineDescriptor,
80+
_layout: &MeshVertexBufferLayout,
81+
_key: MaterialPipelineKey<Self>,
82+
) -> Result<(), SpecializedMeshPipelineError> {
83+
// This is the important part to tell bevy to render this material as a line between vertices
84+
descriptor.primitive.polygon_mode = PolygonMode::Line;
85+
Ok(())
86+
}
87+
}
88+
89+
/// A list of lines with a start and end position
90+
#[derive(Debug, Clone)]
91+
pub struct LineList {
92+
pub lines: Vec<(Vec3, Vec3)>,
93+
}
94+
95+
impl From<LineList> for Mesh {
96+
fn from(line: LineList) -> Self {
97+
let mut vertices = vec![];
98+
let mut normals = vec![];
99+
for (start, end) in line.lines {
100+
vertices.push(start.to_array());
101+
vertices.push(end.to_array());
102+
normals.push(Vec3::ZERO.to_array());
103+
normals.push(Vec3::ZERO.to_array());
104+
}
105+
106+
// This tells wgpu that the positions are list of lines
107+
// where every pair is a start and end point
108+
let mut mesh = Mesh::new(PrimitiveTopology::LineList);
109+
110+
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices);
111+
// Normals are currently required by bevy, but they aren't used by the [`LineMaterial`]
112+
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
113+
mesh
114+
}
115+
}
116+
117+
/// A list of points that will have a line drawn between each consecutive points
118+
#[derive(Debug, Clone)]
119+
pub struct LineStrip {
120+
pub points: Vec<Vec3>,
121+
}
122+
123+
impl From<LineStrip> for Mesh {
124+
fn from(line: LineStrip) -> Self {
125+
let mut vertices = vec![];
126+
let mut normals = vec![];
127+
for pos in line.points {
128+
vertices.push(pos.to_array());
129+
normals.push(Vec3::ZERO.to_array());
130+
}
131+
132+
// This tells wgpu that the positions are a list of points
133+
// where a line will be drawn between each consecutive point
134+
let mut mesh = Mesh::new(PrimitiveTopology::LineStrip);
135+
136+
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices);
137+
// Normals are currently required by bevy, but they aren't used by the [`LineMaterial`]
138+
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
139+
mesh
140+
}
141+
}

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Example | Description
107107
[3D Scene](../examples/3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting
108108
[3D Shapes](../examples/3d/shapes.rs) | A scene showcasing the built-in 3D shapes
109109
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
110+
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
110111
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene
111112
[MSAA](../examples/3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges
112113
[Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications)

0 commit comments

Comments
 (0)