Skip to content

Commit de1f156

Browse files
committed
Add colored wireframes
1 parent 3fcdc5a commit de1f156

File tree

3 files changed

+226
-17
lines changed

3 files changed

+226
-17
lines changed

crates/bevy_pbr/src/render/wireframe.wgsl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ struct Vertex {
55
[[location(0)]] position: vec3<f32>;
66
};
77

8+
struct Wireframe {
9+
color: vec4<f32>;
10+
};
11+
812
[[group(1), binding(0)]]
913
var<uniform> mesh: Mesh;
14+
[[group(2), binding(0)]]
15+
var<uniform> wireframe: Wireframe;
1016

1117
struct VertexOutput {
1218
[[builtin(position)]] clip_position: vec4<f32>;
@@ -24,5 +30,5 @@ fn vertex(vertex: Vertex) -> VertexOutput {
2430

2531
[[stage(fragment)]]
2632
fn fragment() -> [[location(0)]] vec4<f32> {
27-
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
33+
return wireframe.color;
2834
}

crates/bevy_pbr/src/wireframe.rs

Lines changed: 212 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
1-
use crate::MeshPipeline;
2-
use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup};
1+
use crate::{
2+
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup,
3+
};
34
use bevy_app::Plugin;
45
use bevy_asset::{Assets, Handle, HandleUntyped};
56
use bevy_core_pipeline::Opaque3d;
6-
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
7+
use bevy_ecs::{
8+
prelude::*,
9+
reflect::ReflectComponent,
10+
system::{lifetimeless::*, SystemParamItem},
11+
};
12+
use bevy_math::Vec4;
713
use bevy_reflect::{Reflect, TypeUuid};
8-
use bevy_render::render_resource::PolygonMode;
914
use bevy_render::{
15+
color::Color,
1016
mesh::Mesh,
1117
render_asset::RenderAssets,
12-
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
13-
render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines},
18+
render_phase::{
19+
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
20+
SetItemPipeline, TrackedRenderPass,
21+
},
22+
render_resource::{
23+
std140::AsStd140, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
24+
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType,
25+
BufferSize, DynamicUniformVec, PolygonMode, RenderPipelineCache, Shader, ShaderStages,
26+
SpecializedPipeline, SpecializedPipelines,
27+
},
28+
renderer::{RenderDevice, RenderQueue},
1429
view::{ExtractedView, Msaa},
1530
RenderApp, RenderStage,
1631
};
@@ -34,10 +49,13 @@ impl Plugin for WireframePlugin {
3449
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
3550
render_app
3651
.add_render_command::<Opaque3d, DrawWireframes>()
52+
.init_resource::<GlobalWireframeMeta>()
3753
.init_resource::<WireframePipeline>()
3854
.init_resource::<SpecializedPipelines<WireframePipeline>>()
3955
.add_system_to_stage(RenderStage::Extract, extract_wireframes)
4056
.add_system_to_stage(RenderStage::Extract, extract_wireframe_config)
57+
.add_system_to_stage(RenderStage::Prepare, prepare_wireframes)
58+
.add_system_to_stage(RenderStage::Queue, queue_wireframes_bind_group)
4159
.add_system_to_stage(RenderStage::Queue, queue_wireframes);
4260
}
4361
}
@@ -49,31 +67,179 @@ fn extract_wireframe_config(mut commands: Commands, wireframe_config: Res<Wirefr
4967
}
5068
}
5169

52-
fn extract_wireframes(mut commands: Commands, query: Query<Entity, With<Wireframe>>) {
53-
for entity in query.iter() {
54-
commands.get_or_spawn(entity).insert(Wireframe);
70+
fn extract_wireframes(mut commands: Commands, query: Query<(Entity, &Wireframe)>) {
71+
for (entity, wireframe) in query.iter() {
72+
commands.get_or_spawn(entity).insert(wireframe.clone());
5573
}
5674
}
5775

58-
/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled
59-
#[derive(Component, Debug, Clone, Default, Reflect)]
76+
fn prepare_wireframes(
77+
mut commands: Commands,
78+
config: Res<WireframeConfig>,
79+
render_device: Res<RenderDevice>,
80+
render_queue: Res<RenderQueue>,
81+
mut wireframe_meta: ResMut<GlobalWireframeMeta>,
82+
global_query: Query<(Entity, Option<&Wireframe>), (With<Handle<Mesh>>, With<MeshUniform>)>,
83+
wireframe_query: Query<
84+
(Entity, Option<&Wireframe>),
85+
(With<Handle<Mesh>>, With<MeshUniform>, With<Wireframe>),
86+
>,
87+
) {
88+
wireframe_meta.uniforms.clear();
89+
wireframe_meta.uniforms.push(WireframeUniform {
90+
color: config.default_color.as_linear_rgba_f32().into(),
91+
});
92+
93+
let add_wireframe_uniform = |(entity, wireframe): (Entity, Option<&Wireframe>)| {
94+
let custom_color = wireframe.map(|wireframe| wireframe.color);
95+
let uniform_offset = WireframeUniformOffset(if let Some(custom_color) = custom_color {
96+
wireframe_meta.uniforms.push(WireframeUniform {
97+
color: custom_color.as_linear_rgba_f32().into(),
98+
})
99+
} else {
100+
0
101+
});
102+
commands.entity(entity).insert(uniform_offset);
103+
};
104+
105+
if config.global {
106+
global_query.for_each(add_wireframe_uniform);
107+
} else {
108+
wireframe_query.for_each(add_wireframe_uniform);
109+
}
110+
111+
wireframe_meta
112+
.uniforms
113+
.write_buffer(&render_device, &render_queue);
114+
}
115+
116+
/// Internal [`WireframePlugin`] resource.
117+
/// [`GlobalWireframeBindGroup`] stores the [`BindGroup`] of wireframe data that is used on the GPU side.
118+
struct GlobalWireframeBindGroup {
119+
bind_group: BindGroup,
120+
}
121+
122+
fn queue_wireframes_bind_group(
123+
mut commands: Commands,
124+
render_device: Res<RenderDevice>,
125+
meta: Res<GlobalWireframeMeta>,
126+
bind_group: Option<Res<GlobalWireframeBindGroup>>,
127+
) {
128+
if bind_group.is_none() {
129+
commands.insert_resource(GlobalWireframeBindGroup {
130+
bind_group: render_device.create_bind_group(&BindGroupDescriptor {
131+
entries: &[BindGroupEntry {
132+
binding: 0,
133+
resource: meta.uniforms.binding().unwrap(),
134+
}],
135+
label: Some("wireframe_bind_group"),
136+
layout: &meta.bind_group_layout,
137+
}),
138+
});
139+
}
140+
}
141+
142+
/// Controls per-entity wireframe rendering settings (if the [`WireframePlugin`] is enabled)
143+
#[derive(Component, Debug, Clone, Reflect)]
60144
#[reflect(Component)]
61-
pub struct Wireframe;
145+
pub struct Wireframe {
146+
/// The color of this wireframe.
147+
/// For the [`Entity`] that has this [`Wireframe`] component, this color overrides any that was set in [`WireframeConfig::default_color`].
148+
pub color: Color,
149+
}
150+
151+
impl Default for Wireframe {
152+
fn default() -> Self {
153+
Self {
154+
color: Color::WHITE,
155+
}
156+
}
157+
}
62158

63-
#[derive(Debug, Clone, Default)]
159+
/// Configuration for [`WireframePlugin`].
160+
#[derive(Debug, Clone)]
64161
pub struct WireframeConfig {
65-
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered.
162+
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [`Wireframe`] component will be rendered.
66163
pub global: bool,
164+
/// The default color for wireframes. If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`]
165+
/// component attached to it will have wireframes in this color.
166+
pub default_color: Color,
67167
}
68168

69-
pub struct WireframePipeline {
169+
impl Default for WireframeConfig {
170+
fn default() -> Self {
171+
Self {
172+
global: false,
173+
default_color: Color::WHITE,
174+
}
175+
}
176+
}
177+
178+
/// Internal [`WireframePlugin`] component.
179+
/// [`WireframeUniformOffset`] holds the offset of a [`WireframeUniform`] in the [`GlobalWireframeMeta::uniforms`].
180+
#[derive(Component, Copy, Clone, Debug, Default)]
181+
#[repr(transparent)]
182+
struct WireframeUniformOffset(u32);
183+
184+
/// Internal [`WireframePlugin`] state.
185+
/// [`WireframeUniform`] is the GPU representation of a [`Wireframe`].
186+
#[derive(Debug, AsStd140)]
187+
struct WireframeUniform {
188+
color: Vec4,
189+
}
190+
191+
/// Internal [`WireframePlugin`] resource.
192+
/// This is the data required for rendering [`Wireframe`]s.
193+
#[derive(Component)]
194+
struct GlobalWireframeMeta {
195+
uniforms: DynamicUniformVec<WireframeUniform>,
196+
bind_group_layout: BindGroupLayout,
197+
}
198+
199+
impl FromWorld for GlobalWireframeMeta {
200+
fn from_world(world: &mut World) -> Self {
201+
let render_device = world.get_resource::<RenderDevice>().unwrap();
202+
203+
let bind_group_layout =
204+
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
205+
entries: &[BindGroupLayoutEntry {
206+
binding: 0,
207+
visibility: ShaderStages::FRAGMENT,
208+
ty: BindingType::Buffer {
209+
ty: BufferBindingType::Uniform,
210+
has_dynamic_offset: true,
211+
min_binding_size: BufferSize::new(
212+
WireframeUniform::std140_size_static() as u64
213+
),
214+
},
215+
count: None,
216+
}],
217+
label: Some("wireframe_bind_group_layout"),
218+
});
219+
220+
Self {
221+
uniforms: Default::default(),
222+
bind_group_layout,
223+
}
224+
}
225+
}
226+
227+
/// Internal [`WireframePlugin`] resource.
228+
/// [`WireframePipeline`] is the specialized rendering pipeline for wireframes.
229+
struct WireframePipeline {
70230
mesh_pipeline: MeshPipeline,
231+
wireframe_bind_group_layout: BindGroupLayout,
71232
shader: Handle<Shader>,
72233
}
73234
impl FromWorld for WireframePipeline {
74235
fn from_world(render_world: &mut World) -> Self {
75236
WireframePipeline {
76237
mesh_pipeline: render_world.get_resource::<MeshPipeline>().unwrap().clone(),
238+
wireframe_bind_group_layout: render_world
239+
.get_resource::<GlobalWireframeMeta>()
240+
.unwrap()
241+
.bind_group_layout
242+
.clone(),
77243
shader: WIREFRAME_SHADER_HANDLE.typed(),
78244
}
79245
}
@@ -86,6 +252,11 @@ impl SpecializedPipeline for WireframePipeline {
86252
let mut descriptor = self.mesh_pipeline.specialize(key);
87253
descriptor.vertex.shader = self.shader.clone_weak();
88254
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
255+
descriptor
256+
.layout
257+
.as_mut()
258+
.unwrap()
259+
.push(self.wireframe_bind_group_layout.clone());
89260
descriptor.primitive.polygon_mode = PolygonMode::Line;
90261
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
91262
descriptor
@@ -142,9 +313,35 @@ fn queue_wireframes(
142313
}
143314
}
144315

316+
/// Internal [`WireframePlugin`] render command.
317+
/// [`SetWireframeBindGroup`]`<bindgroup index>` binds the [`GlobalWireframeBindGroup`] there.
318+
struct SetWireframeBindGroup<const I: usize>;
319+
impl<const I: usize> EntityRenderCommand for SetWireframeBindGroup<I> {
320+
type Param = (
321+
SRes<GlobalWireframeBindGroup>,
322+
SQuery<Read<WireframeUniformOffset>, With<Handle<Mesh>>>,
323+
);
324+
#[inline]
325+
fn render<'w>(
326+
_view: Entity,
327+
item: Entity,
328+
(global_wireframe_bind_group, view_query): SystemParamItem<'w, '_, Self::Param>,
329+
pass: &mut TrackedRenderPass<'w>,
330+
) -> RenderCommandResult {
331+
let wireframe_uniform_offset = view_query.get(item).unwrap();
332+
pass.set_bind_group(
333+
I,
334+
&global_wireframe_bind_group.into_inner().bind_group,
335+
&[wireframe_uniform_offset.0],
336+
);
337+
RenderCommandResult::Success
338+
}
339+
}
340+
145341
type DrawWireframes = (
146342
SetItemPipeline,
147343
SetMeshViewBindGroup<0>,
148344
SetMeshBindGroup<1>,
345+
SetWireframeBindGroup<2>,
149346
DrawMesh,
150347
);

examples/3d/wireframe.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ fn setup(
2626
) {
2727
// To draw the wireframe on all entities, set this to 'true'
2828
wireframe_config.global = false;
29+
// You can also change the default color of the wireframes, which is used only if `global` is set.
30+
// This is the fallback wireframe color, which is used for any entities that do not have a Wireframe component.
31+
wireframe_config.default_color = Color::AQUAMARINE;
32+
2933
// plane
3034
commands.spawn_bundle(PbrBundle {
3135
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
@@ -41,7 +45,9 @@ fn setup(
4145
..Default::default()
4246
})
4347
// This enables wireframe drawing on this entity
44-
.insert(Wireframe);
48+
.insert(Wireframe {
49+
color: Color::FUCHSIA,
50+
});
4551
// light
4652
commands.spawn_bundle(PointLightBundle {
4753
transform: Transform::from_xyz(4.0, 8.0, 4.0),

0 commit comments

Comments
 (0)