Skip to content

Commit f93e37e

Browse files
IceSentryUberLambda
andcommitted
use Material abstraction for wireframe and configurable colors
Co-authored-by: UberLambda <[email protected]>
1 parent df7736c commit f93e37e

File tree

3 files changed

+220
-173
lines changed

3 files changed

+220
-173
lines changed
+5-38
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,11 @@
1-
#import bevy_pbr::mesh_types
2-
#import bevy_pbr::mesh_view_bindings
3-
4-
[[group(1), binding(0)]]
5-
var<uniform> mesh: Mesh;
6-
7-
#ifdef SKINNED
8-
[[group(1), binding(1)]]
9-
var<uniform> joint_matrices: SkinnedMesh;
10-
#import bevy_pbr::skinning
11-
#endif
12-
13-
// NOTE: Bindings must come before functions that use them!
14-
#import bevy_pbr::mesh_functions
15-
16-
struct Vertex {
17-
[[location(0)]] position: vec3<f32>;
18-
#ifdef SKINNED
19-
[[location(4)]] joint_indexes: vec4<u32>;
20-
[[location(5)]] joint_weights: vec4<f32>;
21-
#endif
1+
struct WireframeMaterial {
2+
color: vec4<f32>;
223
};
234

24-
struct VertexOutput {
25-
[[builtin(position)]] clip_position: vec4<f32>;
26-
};
27-
28-
[[stage(vertex)]]
29-
fn vertex(vertex: Vertex) -> VertexOutput {
30-
#ifdef SKINNED
31-
let model = skin_model(vertex.joint_indexes, vertex.joint_weights);
32-
#else
33-
let model = mesh.model;
34-
#endif
35-
36-
var out: VertexOutput;
37-
out.clip_position = mesh_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
38-
return out;
39-
}
5+
[[group(1), binding(0)]]
6+
var<uniform> material: WireframeMaterial;
407

418
[[stage(fragment)]]
429
fn fragment() -> [[location(0)]] vec4<f32> {
43-
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
10+
return material.color;
4411
}

crates/bevy_pbr/src/wireframe.rs

+152-131
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,195 @@
1-
use crate::MeshPipeline;
2-
use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup};
1+
use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin};
32
use bevy_app::Plugin;
4-
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
5-
use bevy_core_pipeline::core_3d::Opaque3d;
6-
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
7-
use bevy_reflect::std_traits::ReflectDefault;
3+
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
4+
use bevy_ecs::prelude::*;
85
use bevy_reflect::{Reflect, TypeUuid};
96
use bevy_render::{
107
extract_resource::{ExtractResource, ExtractResourcePlugin},
118
mesh::{Mesh, MeshVertexBufferLayout},
12-
render_asset::RenderAssets,
13-
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
9+
prelude::{Color, Shader},
1410
render_resource::{
15-
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline,
16-
SpecializedMeshPipelineError, SpecializedMeshPipelines,
11+
AsBindGroup, PolygonMode, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
1712
},
18-
view::{ExtractedView, Msaa, VisibleEntities},
19-
RenderApp, RenderStage,
2013
};
21-
use bevy_utils::tracing::error;
2214

23-
pub const WIREFRAME_SHADER_HANDLE: HandleUntyped =
15+
pub const WIREFRAME_MATERIAL_SHADER_HANDLE: HandleUntyped =
2416
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);
2517

26-
#[derive(Debug, Default)]
27-
pub struct WireframePlugin;
18+
/// Configuration resource for [`WireframePlugin`].
19+
#[derive(Debug, Clone, Default, ExtractResource, Reflect)]
20+
#[reflect(Resource)]
21+
pub struct WireframeConfig {
22+
/// Whether to show wireframes for all meshes.
23+
/// If `false`, only meshes with a [Wireframe] component will be rendered.
24+
pub global: bool,
25+
/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
26+
/// wireframes in this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
27+
/// but no [`WireframeColor`].
28+
pub color: Color,
29+
}
2830

31+
/// Toggles wireframe rendering for any entity it is attached to.
32+
///
33+
/// This requires the [`WireframePlugin`] to be enabled.
34+
#[derive(Component)]
35+
pub struct Wireframe;
36+
37+
/// Sets the color of the [`Wireframe`] of the entity it is attached to.
38+
///
39+
/// This overrides the [`WireframeConfig::default_color`].
40+
#[derive(Component)]
41+
pub struct WireframeColor {
42+
pub color: Color,
43+
}
44+
45+
/// A [`Plugin`] that draws wireframes.
46+
pub struct WireframePlugin;
2947
impl Plugin for WireframePlugin {
3048
fn build(&self, app: &mut bevy_app::App) {
3149
load_internal_asset!(
3250
app,
33-
WIREFRAME_SHADER_HANDLE,
51+
WIREFRAME_MATERIAL_SHADER_HANDLE,
3452
"render/wireframe.wgsl",
3553
Shader::from_wgsl
3654
);
3755

56+
app.add_plugin(MaterialPlugin::<WireframeMaterial>::default());
57+
3858
app.register_type::<WireframeConfig>()
3959
.init_resource::<WireframeConfig>()
4060
.add_plugin(ExtractResourcePlugin::<WireframeConfig>::default());
4161

42-
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
43-
render_app
44-
.add_render_command::<Opaque3d, DrawWireframes>()
45-
.init_resource::<WireframePipeline>()
46-
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
47-
.add_system_to_stage(RenderStage::Extract, extract_wireframes)
48-
.add_system_to_stage(RenderStage::Queue, queue_wireframes);
49-
}
62+
app.add_system(apply_global)
63+
.add_system(apply_material)
64+
.add_system(wireframe_color_changed)
65+
.add_system(global_color_changed);
5066
}
5167
}
5268

53-
fn extract_wireframes(mut commands: Commands, query: Query<Entity, With<Wireframe>>) {
54-
for entity in &query {
55-
commands.get_or_spawn(entity).insert(Wireframe);
69+
/// Apply the wireframe material to any mesh with a `Wireframe` component.
70+
/// Uses `WireframeConfig::color` as a fallback if no `WireframeColor` component is found
71+
#[allow(clippy::type_complexity)]
72+
fn apply_material(
73+
mut commands: Commands,
74+
config: Res<WireframeConfig>,
75+
mut materials: ResMut<Assets<WireframeMaterial>>,
76+
wireframes: Query<
77+
(Entity, Option<&WireframeColor>),
78+
(With<Wireframe>, Without<Handle<WireframeMaterial>>),
79+
>,
80+
) {
81+
for (e, color) in &wireframes {
82+
commands.entity(e).insert(materials.add(WireframeMaterial {
83+
color: if let Some(wireframe_color) = color {
84+
wireframe_color.color
85+
} else {
86+
config.color
87+
},
88+
}));
5689
}
5790
}
5891

59-
/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled
60-
#[derive(Component, Debug, Clone, Default, Reflect)]
61-
#[reflect(Component, Default)]
62-
pub struct Wireframe;
63-
64-
#[derive(Debug, Clone, Default, ExtractResource, Reflect)]
65-
#[reflect(Resource)]
66-
pub struct WireframeConfig {
67-
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered.
68-
pub global: bool,
69-
}
70-
71-
pub struct WireframePipeline {
72-
mesh_pipeline: MeshPipeline,
73-
shader: Handle<Shader>,
74-
}
75-
impl FromWorld for WireframePipeline {
76-
fn from_world(render_world: &mut World) -> Self {
77-
WireframePipeline {
78-
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
79-
shader: WIREFRAME_SHADER_HANDLE.typed(),
80-
}
92+
/// Updates the wireframe material when the color in `WireframeColor` changes
93+
#[allow(clippy::type_complexity)]
94+
fn wireframe_color_changed(
95+
mut materials: ResMut<Assets<WireframeMaterial>>,
96+
mut colors_changed: Query<
97+
(&mut Handle<WireframeMaterial>, &WireframeColor),
98+
(With<Wireframe>, Changed<WireframeColor>),
99+
>,
100+
) {
101+
for (mut handle, wireframe_color) in &mut colors_changed {
102+
*handle = materials.add(WireframeMaterial {
103+
color: wireframe_color.color,
104+
});
81105
}
82106
}
83107

84-
impl SpecializedMeshPipeline for WireframePipeline {
85-
type Key = MeshPipelineKey;
108+
/// Updates the wireframe material of all entities without a specified color or without a `Wireframe` component
109+
fn global_color_changed(
110+
config: Res<WireframeConfig>,
111+
mut materials: ResMut<Assets<WireframeMaterial>>,
112+
mut wireframes: Query<&mut Handle<WireframeMaterial>, Without<WireframeColor>>,
113+
) {
114+
if !config.is_changed() {
115+
return;
116+
}
86117

87-
fn specialize(
88-
&self,
89-
key: Self::Key,
90-
layout: &MeshVertexBufferLayout,
91-
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
92-
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
93-
descriptor.vertex.shader = self.shader.clone_weak();
94-
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
95-
descriptor.primitive.polygon_mode = PolygonMode::Line;
96-
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
97-
Ok(descriptor)
118+
for mut handle in &mut wireframes {
119+
*handle = materials.add(WireframeMaterial {
120+
color: config.color,
121+
});
98122
}
99123
}
100124

101-
#[allow(clippy::too_many_arguments)]
102-
fn queue_wireframes(
103-
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
104-
render_meshes: Res<RenderAssets<Mesh>>,
105-
wireframe_config: Res<WireframeConfig>,
106-
wireframe_pipeline: Res<WireframePipeline>,
107-
mut pipelines: ResMut<SpecializedMeshPipelines<WireframePipeline>>,
108-
mut pipeline_cache: ResMut<PipelineCache>,
109-
msaa: Res<Msaa>,
110-
mut material_meshes: ParamSet<(
111-
Query<(Entity, &Handle<Mesh>, &MeshUniform)>,
112-
Query<(Entity, &Handle<Mesh>, &MeshUniform), With<Wireframe>>,
125+
/// Applies or remove a wireframe material on any mesh without a `Wireframe` component.
126+
#[allow(clippy::type_complexity)]
127+
fn apply_global(
128+
mut commands: Commands,
129+
config: Res<WireframeConfig>,
130+
mut materials: ResMut<Assets<WireframeMaterial>>,
131+
mut q1: ParamSet<(
132+
Query<
133+
Entity,
134+
(
135+
With<Handle<Mesh>>,
136+
Without<Handle<WireframeMaterial>>,
137+
Without<Wireframe>,
138+
),
139+
>,
140+
Query<
141+
Entity,
142+
(
143+
With<Handle<Mesh>>,
144+
With<Handle<WireframeMaterial>>,
145+
Without<Wireframe>,
146+
),
147+
>,
113148
)>,
114-
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
149+
mut is_global_applied: Local<bool>,
115150
) {
116-
let draw_custom = opaque_3d_draw_functions
117-
.read()
118-
.get_id::<DrawWireframes>()
119-
.unwrap();
120-
let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
121-
for (view, visible_entities, mut opaque_phase) in &mut views {
122-
let rangefinder = view.rangefinder3d();
123-
124-
let add_render_phase =
125-
|(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| {
126-
if let Some(mesh) = render_meshes.get(mesh_handle) {
127-
let key = msaa_key
128-
| MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
129-
let pipeline_id = pipelines.specialize(
130-
&mut pipeline_cache,
131-
&wireframe_pipeline,
132-
key,
133-
&mesh.layout,
134-
);
135-
let pipeline_id = match pipeline_id {
136-
Ok(id) => id,
137-
Err(err) => {
138-
error!("{}", err);
139-
return;
140-
}
141-
};
142-
opaque_phase.add(Opaque3d {
143-
entity,
144-
pipeline: pipeline_id,
145-
draw_function: draw_custom,
146-
distance: rangefinder.distance(&mesh_uniform.transform),
147-
});
148-
}
149-
};
150-
151-
if wireframe_config.global {
152-
let query = material_meshes.p0();
153-
visible_entities
154-
.entities
155-
.iter()
156-
.filter_map(|visible_entity| query.get(*visible_entity).ok())
157-
.for_each(add_render_phase);
158-
} else {
159-
let query = material_meshes.p1();
160-
visible_entities
161-
.entities
162-
.iter()
163-
.filter_map(|visible_entity| query.get(*visible_entity).ok())
164-
.for_each(add_render_phase);
151+
if !config.is_changed() {
152+
return;
153+
}
154+
155+
if !*is_global_applied && config.global {
156+
let global_material = materials.add(WireframeMaterial {
157+
color: config.color,
158+
});
159+
160+
for e in &mut q1.p0() {
161+
commands.entity(e).insert(global_material.clone());
165162
}
163+
164+
*is_global_applied = true;
165+
} else if *is_global_applied && !config.global {
166+
for e in &mut q1.p1() {
167+
commands.entity(e).remove::<Handle<WireframeMaterial>>();
168+
}
169+
*is_global_applied = false;
166170
}
167171
}
168172

169-
type DrawWireframes = (
170-
SetItemPipeline,
171-
SetMeshViewBindGroup<0>,
172-
SetMeshBindGroup<1>,
173-
DrawMesh,
174-
);
173+
#[derive(Default, AsBindGroup, TypeUuid, Debug, Clone)]
174+
#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"]
175+
struct WireframeMaterial {
176+
#[uniform(0)]
177+
color: Color,
178+
}
179+
180+
impl Material for WireframeMaterial {
181+
fn fragment_shader() -> ShaderRef {
182+
WIREFRAME_MATERIAL_SHADER_HANDLE.typed().into()
183+
}
184+
185+
fn specialize(
186+
_pipeline: &MaterialPipeline<Self>,
187+
descriptor: &mut RenderPipelineDescriptor,
188+
_layout: &MeshVertexBufferLayout,
189+
_key: MaterialPipelineKey<Self>,
190+
) -> Result<(), SpecializedMeshPipelineError> {
191+
descriptor.primitive.polygon_mode = PolygonMode::Line;
192+
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
193+
Ok(())
194+
}
195+
}

0 commit comments

Comments
 (0)