Skip to content

Commit 4443213

Browse files
committed
Add colored wireframes
Separate Wireframe from WireframeColor
1 parent ae0cb54 commit 4443213

File tree

3 files changed

+265
-34
lines changed

3 files changed

+265
-34
lines changed

crates/bevy_pbr/src/render/wireframe.wgsl

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ struct Vertex {
99
#endif
1010
};
1111

12+
struct Wireframe {
13+
color: vec4<f32>;
14+
};
15+
1216
[[group(1), binding(0)]]
1317
var<uniform> mesh: Mesh;
18+
[[group(2), binding(0)]]
19+
var<uniform> wireframe: Wireframe;
1420

1521
struct VertexOutput {
1622
[[builtin(position)]] clip_position: vec4<f32>;
@@ -39,5 +45,5 @@ fn vertex(vertex: Vertex) -> VertexOutput {
3945

4046
[[stage(fragment)]]
4147
fn fragment() -> [[location(0)]] vec4<f32> {
42-
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
48+
return wireframe.color;
4349
}

crates/bevy_pbr/src/wireframe.rs

+245-27
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,36 @@
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::{load_internal_asset, Handle, HandleUntyped};
56
use bevy_core_pipeline::Opaque3d;
6-
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
7-
use bevy_reflect::std_traits::ReflectDefault;
7+
use bevy_ecs::{
8+
prelude::*,
9+
query::QueryItem,
10+
reflect::ReflectComponent,
11+
system::{lifetimeless::*, SystemParamItem},
12+
};
13+
use bevy_math::Vec4;
814
use bevy_reflect::{Reflect, TypeUuid};
915
use bevy_render::{
16+
color::Color,
1017
mesh::{Mesh, MeshVertexBufferLayout},
1118
render_asset::RenderAssets,
12-
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
19+
render_component::{ExtractComponent, ExtractComponentPlugin},
20+
render_phase::{
21+
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
22+
SetItemPipeline, TrackedRenderPass,
23+
},
1324
render_resource::{
14-
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline,
15-
SpecializedMeshPipelineError, SpecializedMeshPipelines,
25+
std140::AsStd140, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
26+
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType,
27+
BufferSize, DynamicUniformVec,
1628
},
29+
render_resource::{
30+
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, ShaderStages,
31+
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
32+
},
33+
renderer::{RenderDevice, RenderQueue},
1734
view::{ExtractedView, Msaa, VisibleEntities},
1835
RenderApp, RenderStage,
1936
};
@@ -22,6 +39,7 @@ use bevy_utils::tracing::error;
2239
pub const WIREFRAME_SHADER_HANDLE: HandleUntyped =
2340
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);
2441

42+
/// A [`Plugin`] that draws wireframes.
2543
#[derive(Debug, Default)]
2644
pub struct WireframePlugin;
2745

@@ -34,15 +52,18 @@ impl Plugin for WireframePlugin {
3452
Shader::from_wgsl
3553
);
3654

37-
app.init_resource::<WireframeConfig>();
38-
55+
app.init_resource::<WireframeConfig>()
56+
.add_plugin(ExtractComponentPlugin::<Wireframe>::extract_visible())
57+
.add_plugin(ExtractComponentPlugin::<WireframeColor>::extract_visible());
3958
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
4059
render_app
4160
.add_render_command::<Opaque3d, DrawWireframes>()
61+
.init_resource::<GlobalWireframeMeta>()
4262
.init_resource::<WireframePipeline>()
4363
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
44-
.add_system_to_stage(RenderStage::Extract, extract_wireframes)
4564
.add_system_to_stage(RenderStage::Extract, extract_wireframe_config)
65+
.add_system_to_stage(RenderStage::Prepare, prepare_wireframes)
66+
.add_system_to_stage(RenderStage::Queue, queue_wireframes_bind_group)
4667
.add_system_to_stage(RenderStage::Queue, queue_wireframes);
4768
}
4869
}
@@ -54,31 +75,201 @@ fn extract_wireframe_config(mut commands: Commands, wireframe_config: Res<Wirefr
5475
}
5576
}
5677

57-
fn extract_wireframes(mut commands: Commands, query: Query<Entity, With<Wireframe>>) {
58-
for entity in query.iter() {
59-
commands.get_or_spawn(entity).insert(Wireframe);
78+
#[allow(clippy::type_complexity)]
79+
fn prepare_wireframes(
80+
mut commands: Commands,
81+
config: Res<WireframeConfig>,
82+
render_device: Res<RenderDevice>,
83+
render_queue: Res<RenderQueue>,
84+
mut wireframe_meta: ResMut<GlobalWireframeMeta>,
85+
global_query: Query<(Entity, Option<&WireframeColor>), (With<Handle<Mesh>>, With<MeshUniform>)>,
86+
wireframe_query: Query<
87+
(Entity, Option<&WireframeColor>),
88+
(With<Handle<Mesh>>, With<MeshUniform>, With<Wireframe>),
89+
>,
90+
) {
91+
wireframe_meta.uniforms.clear();
92+
wireframe_meta.uniforms.push(WireframeUniform {
93+
color: config.default_color.as_linear_rgba_f32().into(),
94+
});
95+
96+
let add_wireframe_uniform = |(entity, wireframe_color): (Entity, Option<&WireframeColor>)| {
97+
let override_color = wireframe_color.map(|wireframe_color| wireframe_color.0);
98+
let uniform_offset = WireframeUniformOffset(if let Some(override_color) = override_color {
99+
wireframe_meta.uniforms.push(WireframeUniform {
100+
color: override_color.as_linear_rgba_f32().into(),
101+
})
102+
} else {
103+
0
104+
});
105+
commands.entity(entity).insert(uniform_offset);
106+
};
107+
108+
if config.on_all_meshes {
109+
global_query.for_each(add_wireframe_uniform);
110+
} else {
111+
wireframe_query.for_each(add_wireframe_uniform);
60112
}
113+
114+
wireframe_meta
115+
.uniforms
116+
.write_buffer(&render_device, &render_queue);
117+
}
118+
119+
/// Stores the [`BindGroup`] of wireframe data that is used on the GPU side.
120+
///
121+
/// Internal [`WireframePlugin`] resource.
122+
struct GlobalWireframeBindGroup {
123+
bind_group: BindGroup,
61124
}
62125

63-
/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled
64-
#[derive(Component, Debug, Clone, Default, Reflect)]
65-
#[reflect(Component, Default)]
126+
fn queue_wireframes_bind_group(
127+
mut commands: Commands,
128+
render_device: Res<RenderDevice>,
129+
meta: Res<GlobalWireframeMeta>,
130+
bind_group: Option<Res<GlobalWireframeBindGroup>>,
131+
) {
132+
if bind_group.is_none() {
133+
commands.insert_resource(GlobalWireframeBindGroup {
134+
bind_group: render_device.create_bind_group(&BindGroupDescriptor {
135+
entries: &[BindGroupEntry {
136+
binding: 0,
137+
resource: meta.uniforms.binding().unwrap(),
138+
}],
139+
label: Some("wireframe_bind_group"),
140+
layout: &meta.bind_group_layout,
141+
}),
142+
});
143+
}
144+
}
145+
146+
/// Toggles wireframe rendering for any entity it is attached to.
147+
///
148+
/// This requires the [`WireframePlugin`] to be enabled.
149+
#[derive(Component, Debug, Default, Copy, Clone, Reflect)]
150+
#[reflect(Component)]
66151
pub struct Wireframe;
67152

68-
#[derive(Debug, Clone, Default)]
153+
impl ExtractComponent for Wireframe {
154+
type Query = &'static Wireframe;
155+
156+
type Filter = ();
157+
158+
#[inline]
159+
fn extract_component(item: QueryItem<Self::Query>) -> Self {
160+
*item
161+
}
162+
}
163+
164+
/// Sets the color of the [`Wireframe`] of the entity it is attached to.
165+
///
166+
/// This overrides the [`WireframeConfig::default_color`].
167+
#[derive(Component, Debug, Default, Copy, Clone, Reflect)]
168+
#[reflect(Component)]
169+
pub struct WireframeColor(pub Color);
170+
171+
impl ExtractComponent for WireframeColor {
172+
type Query = &'static WireframeColor;
173+
174+
type Filter = ();
175+
176+
#[inline]
177+
fn extract_component(item: QueryItem<Self::Query>) -> Self {
178+
*item
179+
}
180+
}
181+
182+
/// Configuration resource for [`WireframePlugin`].
183+
#[derive(Debug, Clone)]
69184
pub struct WireframeConfig {
70-
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered.
71-
pub global: bool,
185+
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [`Wireframe`] component will be rendered.
186+
pub on_all_meshes: bool,
187+
/// The default color for wireframes.
188+
///
189+
/// If [`Self::on_all_meshes`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
190+
/// wireframes in this color. Otherwise, this will be the fallback color for any [`Wireframe`]s that does have a `None`
191+
/// [`Wireframe::color`].
192+
pub default_color: Color,
193+
}
194+
195+
impl Default for WireframeConfig {
196+
fn default() -> Self {
197+
Self {
198+
on_all_meshes: false,
199+
default_color: Color::WHITE,
200+
}
201+
}
202+
}
203+
204+
/// Holds the offset of a [`WireframeUniform`] in the [`GlobalWireframeMeta::uniforms`].
205+
///
206+
/// Internal [`WireframePlugin`] component.
207+
#[derive(Component, Copy, Clone, Debug, Default)]
208+
#[repr(transparent)]
209+
struct WireframeUniformOffset(u32);
210+
211+
/// [`WireframeUniform`] is the GPU representation of a [`Wireframe`].
212+
///
213+
/// Internal [`WireframePlugin`] state.
214+
#[derive(Debug, AsStd140)]
215+
struct WireframeUniform {
216+
color: Vec4,
217+
}
218+
219+
/// The data required for rendering [`Wireframe`]s.
220+
///
221+
/// Internal [`WireframePlugin`] resource.
222+
#[derive(Component)]
223+
struct GlobalWireframeMeta {
224+
uniforms: DynamicUniformVec<WireframeUniform>,
225+
bind_group_layout: BindGroupLayout,
226+
}
227+
228+
impl FromWorld for GlobalWireframeMeta {
229+
fn from_world(world: &mut World) -> Self {
230+
let render_device = world.get_resource::<RenderDevice>().unwrap();
231+
232+
let bind_group_layout =
233+
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
234+
entries: &[BindGroupLayoutEntry {
235+
binding: 0,
236+
visibility: ShaderStages::FRAGMENT,
237+
ty: BindingType::Buffer {
238+
ty: BufferBindingType::Uniform,
239+
has_dynamic_offset: true,
240+
min_binding_size: BufferSize::new(
241+
WireframeUniform::std140_size_static() as u64
242+
),
243+
},
244+
count: None,
245+
}],
246+
label: Some("wireframe_bind_group_layout"),
247+
});
248+
249+
Self {
250+
uniforms: Default::default(),
251+
bind_group_layout,
252+
}
253+
}
72254
}
73255

74-
pub struct WireframePipeline {
256+
/// [`WireframePipeline`] is the specialized rendering pipeline for wireframes.
257+
///
258+
/// Internal [`WireframePlugin`] resource.
259+
struct WireframePipeline {
75260
mesh_pipeline: MeshPipeline,
261+
wireframe_bind_group_layout: BindGroupLayout,
76262
shader: Handle<Shader>,
77263
}
78264
impl FromWorld for WireframePipeline {
79265
fn from_world(render_world: &mut World) -> Self {
80266
WireframePipeline {
81267
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
268+
wireframe_bind_group_layout: render_world
269+
.get_resource::<GlobalWireframeMeta>()
270+
.unwrap()
271+
.bind_group_layout
272+
.clone(),
82273
shader: WIREFRAME_SHADER_HANDLE.typed(),
83274
}
84275
}
@@ -95,6 +286,11 @@ impl SpecializedMeshPipeline for WireframePipeline {
95286
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
96287
descriptor.vertex.shader = self.shader.clone_weak();
97288
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
289+
descriptor
290+
.layout
291+
.as_mut()
292+
.unwrap()
293+
.push(self.wireframe_bind_group_layout.clone());
98294
descriptor.primitive.polygon_mode = PolygonMode::Line;
99295
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
100296
Ok(descriptor)
@@ -153,13 +349,8 @@ fn queue_wireframes(
153349
}
154350
};
155351

156-
if wireframe_config.global {
157-
let query = material_meshes.p0();
158-
visible_entities
159-
.entities
160-
.iter()
161-
.filter_map(|visible_entity| query.get(*visible_entity).ok())
162-
.for_each(add_render_phase);
352+
if wireframe_config.on_all_meshes {
353+
material_meshes.p0().iter().for_each(add_render_phase);
163354
} else {
164355
let query = material_meshes.p1();
165356
visible_entities
@@ -171,9 +362,36 @@ fn queue_wireframes(
171362
}
172363
}
173364

365+
/// [`SetWireframeBindGroup`]`<bindgroup index>` binds the [`GlobalWireframeBindGroup`] there.
366+
///
367+
/// Internal [`WireframePlugin`] render command.
368+
struct SetWireframeBindGroup<const I: usize>;
369+
impl<const I: usize> EntityRenderCommand for SetWireframeBindGroup<I> {
370+
type Param = (
371+
SRes<GlobalWireframeBindGroup>,
372+
SQuery<Read<WireframeUniformOffset>, With<Handle<Mesh>>>,
373+
);
374+
#[inline]
375+
fn render<'w>(
376+
_view: Entity,
377+
item: Entity,
378+
(global_wireframe_bind_group, view_query): SystemParamItem<'w, '_, Self::Param>,
379+
pass: &mut TrackedRenderPass<'w>,
380+
) -> RenderCommandResult {
381+
let wireframe_uniform_offset = view_query.get(item).unwrap();
382+
pass.set_bind_group(
383+
I,
384+
&global_wireframe_bind_group.into_inner().bind_group,
385+
&[wireframe_uniform_offset.0],
386+
);
387+
RenderCommandResult::Success
388+
}
389+
}
390+
174391
type DrawWireframes = (
175392
SetItemPipeline,
176393
SetMeshViewBindGroup<0>,
177394
SetMeshBindGroup<1>,
395+
SetWireframeBindGroup<2>,
178396
DrawMesh,
179397
);

0 commit comments

Comments
 (0)