Skip to content

Commit 068e42a

Browse files
authored
Configurable colors for wireframe (#5303)
# Objective - Make the wireframe colors configurable at the global level and the single mesh level - Based on #5314 This video shows what happens when playing with various settings from the example https://github.com/bevyengine/bevy/assets/8348954/1ee9aee0-fab7-4da8-bc5d-8d0562bb34e6 ## Solution - Add a `color` field to the `WireframeMaterial` - Use a `WireframeColor` component to configure the color per entity - Add a `default_color` field to `WireframeConfig` for global wireframes or wireframes with no specified color. ## Notes - Most of the docs and the general idea for `WireframeColor` came from [UberLambda](https://github.com/UberLambda) in #3677 but the code ended up completely different so I created a separate branch. ~~I'm not sure how to correctly credit them on this PR.~~ (I re-created the commit but I added them as co-author in the commit message) ~~Closes #3677 ~~Closes #5301 ~~#5314 should be merged before this PR.~~
1 parent a15d152 commit 068e42a

File tree

3 files changed

+162
-42
lines changed

3 files changed

+162
-42
lines changed
+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
2+
struct WireframeMaterial {
3+
color: vec4<f32>,
4+
};
25

6+
@group(1) @binding(0)
7+
var<uniform> material: WireframeMaterial;
38
@fragment
49
fn fragment(in: MeshVertexOutput) -> @location(0) vec4<f32> {
5-
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
10+
return material.color;
611
}

crates/bevy_pbr/src/wireframe.rs

+73-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use bevy_asset::{load_internal_asset, Asset, Assets, Handle};
44
use bevy_ecs::prelude::*;
55
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath, TypeUuid};
66
use bevy_render::{
7+
color::Color,
78
extract_resource::ExtractResource,
89
mesh::{Mesh, MeshVertexBufferLayout},
910
prelude::Shader,
@@ -25,7 +26,6 @@ pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(19259
2526
/// This is a native only feature.
2627
#[derive(Debug, Default)]
2728
pub struct WireframePlugin;
28-
2929
impl Plugin for WireframePlugin {
3030
fn build(&self, app: &mut bevy_app::App) {
3131
load_internal_asset!(
@@ -43,7 +43,12 @@ impl Plugin for WireframePlugin {
4343
.add_systems(Startup, setup_global_wireframe_material)
4444
.add_systems(
4545
Update,
46-
(apply_global_wireframe_material, apply_wireframe_material),
46+
(
47+
global_color_changed.run_if(resource_changed::<WireframeConfig>()),
48+
wireframe_color_changed,
49+
apply_wireframe_material,
50+
apply_global_wireframe_material.run_if(resource_changed::<WireframeConfig>()),
51+
),
4752
);
4853
}
4954
}
@@ -56,6 +61,17 @@ impl Plugin for WireframePlugin {
5661
#[reflect(Component, Default)]
5762
pub struct Wireframe;
5863

64+
/// Sets the color of the [`Wireframe`] of the entity it is attached to.
65+
/// If this component is present but there's no [`Wireframe`] component,
66+
/// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true.
67+
///
68+
/// This overrides the [`WireframeConfig::default_color`].
69+
#[derive(Component, Debug, Clone, Default, Reflect)]
70+
#[reflect(Component, Default)]
71+
pub struct WireframeColor {
72+
pub color: Color,
73+
}
74+
5975
/// Disables wireframe rendering for any entity it is attached to.
6076
/// It will ignore the [`WireframeConfig`] global setting.
6177
///
@@ -70,6 +86,10 @@ pub struct WireframeConfig {
7086
/// Whether to show wireframes for all meshes.
7187
/// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component.
7288
pub global: bool,
89+
/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
90+
/// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
91+
/// but no [`WireframeColor`].
92+
pub default_color: Color,
7393
}
7494

7595
#[derive(Resource)]
@@ -81,19 +101,53 @@ struct GlobalWireframeMaterial {
81101
fn setup_global_wireframe_material(
82102
mut commands: Commands,
83103
mut materials: ResMut<Assets<WireframeMaterial>>,
104+
config: Res<WireframeConfig>,
84105
) {
85106
// Create the handle used for the global material
86107
commands.insert_resource(GlobalWireframeMaterial {
87-
handle: materials.add(WireframeMaterial {}),
108+
handle: materials.add(WireframeMaterial {
109+
color: config.default_color,
110+
}),
88111
});
89112
}
90113

114+
/// Updates the wireframe material of all entities without a [`WireframeColor`] or without a [`Wireframe`] component
115+
fn global_color_changed(
116+
config: Res<WireframeConfig>,
117+
mut materials: ResMut<Assets<WireframeMaterial>>,
118+
global_material: Res<GlobalWireframeMaterial>,
119+
) {
120+
if let Some(global_material) = materials.get_mut(&global_material.handle) {
121+
global_material.color = config.default_color;
122+
}
123+
}
124+
125+
/// Updates the wireframe material when the color in [`WireframeColor`] changes
126+
#[allow(clippy::type_complexity)]
127+
fn wireframe_color_changed(
128+
mut materials: ResMut<Assets<WireframeMaterial>>,
129+
mut colors_changed: Query<
130+
(&mut Handle<WireframeMaterial>, &WireframeColor),
131+
(With<Wireframe>, Changed<WireframeColor>),
132+
>,
133+
) {
134+
for (mut handle, wireframe_color) in &mut colors_changed {
135+
*handle = materials.add(WireframeMaterial {
136+
color: wireframe_color.color,
137+
});
138+
}
139+
}
140+
91141
/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component.
92142
fn apply_wireframe_material(
93143
mut commands: Commands,
94144
mut materials: ResMut<Assets<WireframeMaterial>>,
95-
wireframes: Query<Entity, (With<Wireframe>, Without<Handle<WireframeMaterial>>)>,
145+
wireframes: Query<
146+
(Entity, Option<&WireframeColor>),
147+
(With<Wireframe>, Without<Handle<WireframeMaterial>>),
148+
>,
96149
mut removed_wireframes: RemovedComponents<Wireframe>,
150+
global_material: Res<GlobalWireframeMaterial>,
97151
) {
98152
for e in removed_wireframes.read() {
99153
if let Some(mut commands) = commands.get_entity(e) {
@@ -102,8 +156,16 @@ fn apply_wireframe_material(
102156
}
103157

104158
let mut wireframes_to_spawn = vec![];
105-
for e in &wireframes {
106-
wireframes_to_spawn.push((e, materials.add(WireframeMaterial {})));
159+
for (e, wireframe_color) in &wireframes {
160+
let material = if let Some(wireframe_color) = wireframe_color {
161+
materials.add(WireframeMaterial {
162+
color: wireframe_color.color,
163+
})
164+
} else {
165+
// If there's no color specified we can use the global material since it's already set to use the default_color
166+
global_material.handle.clone()
167+
};
168+
wireframes_to_spawn.push((e, material));
107169
}
108170
commands.insert_or_spawn_batch(wireframes_to_spawn);
109171
}
@@ -118,10 +180,6 @@ fn apply_global_wireframe_material(
118180
meshes_with_global_material: Query<Entity, (WireframeFilter, With<Handle<WireframeMaterial>>)>,
119181
global_material: Res<GlobalWireframeMaterial>,
120182
) {
121-
if !config.is_changed() {
122-
return;
123-
}
124-
125183
if config.global {
126184
let mut material_to_spawn = vec![];
127185
for e in &meshes_without_material {
@@ -130,7 +188,7 @@ fn apply_global_wireframe_material(
130188
material_to_spawn.push((e, global_material.handle.clone()));
131189
}
132190
commands.insert_or_spawn_batch(material_to_spawn);
133-
} else if !config.global {
191+
} else {
134192
for e in &meshes_with_global_material {
135193
commands.entity(e).remove::<Handle<WireframeMaterial>>();
136194
}
@@ -139,7 +197,10 @@ fn apply_global_wireframe_material(
139197

140198
#[derive(Default, AsBindGroup, TypeUuid, TypePath, Debug, Clone, Asset)]
141199
#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"]
142-
struct WireframeMaterial {}
200+
pub struct WireframeMaterial {
201+
#[uniform(0)]
202+
pub color: Color,
203+
}
143204

144205
impl Material for WireframeMaterial {
145206
fn fragment_shader() -> ShaderRef {

examples/3d/wireframe.rs

+83-29
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! This is a native only feature.
1010
1111
use bevy::{
12-
pbr::wireframe::{NoWireframe, Wireframe, WireframeConfig, WireframePlugin},
12+
pbr::wireframe::{NoWireframe, Wireframe, WireframeColor, WireframeConfig, WireframePlugin},
1313
prelude::*,
1414
render::{
1515
render_resource::WgpuFeatures,
@@ -31,12 +31,18 @@ fn main() {
3131
// You need to add this plugin to enable wireframe rendering
3232
WireframePlugin,
3333
))
34-
.insert_resource(WireframeToggleTimer(Timer::from_seconds(
35-
1.0,
36-
TimerMode::Repeating,
37-
)))
34+
// Wireframes can be configured with this resource. This can be changed at runtime.
35+
.insert_resource(WireframeConfig {
36+
// The global wireframe config enables drawing of wireframes on every mesh,
37+
// except those with `NoWireframe`. Meshes with `Wireframe` will always have a wireframe,
38+
// regardless of the global configuration.
39+
global: true,
40+
// Controls the default color of all wireframes. Used as the default color for global wireframes.
41+
// Can be changed per mesh using the `WireframeColor` component.
42+
default_color: Color::WHITE,
43+
})
3844
.add_systems(Startup, setup)
39-
.add_systems(Update, toggle_global_wireframe_setting)
45+
.add_systems(Update, update_colors)
4046
.run();
4147
}
4248

@@ -54,14 +60,15 @@ fn setup(
5460
});
5561

5662
// Red cube: Never renders a wireframe
57-
commands
58-
.spawn(PbrBundle {
63+
commands.spawn((
64+
PbrBundle {
5965
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
6066
material: materials.add(Color::RED.into()),
6167
transform: Transform::from_xyz(-1.0, 0.5, -1.0),
6268
..default()
63-
})
64-
.insert(NoWireframe);
69+
},
70+
NoWireframe,
71+
));
6572
// Orange cube: Follows global wireframe setting
6673
commands.spawn(PbrBundle {
6774
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
@@ -70,42 +77,89 @@ fn setup(
7077
..default()
7178
});
7279
// Green cube: Always renders a wireframe
73-
commands
74-
.spawn(PbrBundle {
80+
commands.spawn((
81+
PbrBundle {
7582
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
7683
material: materials.add(Color::GREEN.into()),
7784
transform: Transform::from_xyz(1.0, 0.5, 1.0),
7885
..default()
79-
})
80-
.insert(Wireframe);
86+
},
87+
Wireframe,
88+
// This lets you configure the wireframe color of this entity.
89+
// If not set, this will use the color in `WireframeConfig`
90+
WireframeColor {
91+
color: Color::GREEN,
92+
},
93+
));
8194

8295
// light
8396
commands.spawn(PointLightBundle {
8497
transform: Transform::from_xyz(4.0, 8.0, 4.0),
8598
..default()
8699
});
100+
87101
// camera
88102
commands.spawn(Camera3dBundle {
89103
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
90104
..default()
91105
});
92-
}
93106

94-
/// This timer is used to periodically toggle the wireframe rendering.
95-
#[derive(Resource)]
96-
struct WireframeToggleTimer(Timer);
107+
// Text used to show controls
108+
commands.spawn(
109+
TextBundle::from_section("", TextStyle::default()).with_style(Style {
110+
position_type: PositionType::Absolute,
111+
top: Val::Px(10.0),
112+
left: Val::Px(10.0),
113+
..default()
114+
}),
115+
);
116+
}
97117

98-
/// Periodically turns the global wireframe setting on and off, to show the differences between
99-
/// [`Wireframe`], [`NoWireframe`], and just a mesh.
100-
fn toggle_global_wireframe_setting(
101-
time: Res<Time>,
102-
mut timer: ResMut<WireframeToggleTimer>,
103-
mut wireframe_config: ResMut<WireframeConfig>,
118+
/// This system let's you toggle various wireframe settings
119+
fn update_colors(
120+
keyboard_input: Res<Input<KeyCode>>,
121+
mut config: ResMut<WireframeConfig>,
122+
mut wireframe_colors: Query<&mut WireframeColor>,
123+
mut text: Query<&mut Text>,
104124
) {
105-
if timer.0.tick(time.delta()).just_finished() {
106-
// The global wireframe config enables drawing of wireframes on every mesh,
107-
// except those with `NoWireframe`. Meshes with `Wireframe` will always have a wireframe,
108-
// regardless of the global configuration.
109-
wireframe_config.global = !wireframe_config.global;
125+
text.single_mut().sections[0].value = format!(
126+
"
127+
Controls
128+
---------------
129+
Z - Toggle global
130+
X - Change global color
131+
C - Change color of the green cube wireframe
132+
133+
WireframeConfig
134+
-------------
135+
Global: {}
136+
Color: {:?}
137+
",
138+
config.global, config.default_color,
139+
);
140+
141+
// Toggle showing a wireframe on all meshes
142+
if keyboard_input.just_pressed(KeyCode::Z) {
143+
config.global = !config.global;
144+
}
145+
146+
// Toggle the global wireframe color
147+
if keyboard_input.just_pressed(KeyCode::X) {
148+
config.default_color = if config.default_color == Color::WHITE {
149+
Color::PINK
150+
} else {
151+
Color::WHITE
152+
};
153+
}
154+
155+
// Toggle the color of a wireframe using WireframeColor and not the global color
156+
if keyboard_input.just_pressed(KeyCode::C) {
157+
for mut color in &mut wireframe_colors {
158+
color.color = if color.color == Color::GREEN {
159+
Color::RED
160+
} else {
161+
Color::GREEN
162+
};
163+
}
110164
}
111165
}

0 commit comments

Comments
 (0)