Skip to content

Commit

Permalink
Configurable colors for wireframe (#5303)
Browse files Browse the repository at this point in the history
# 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.~~
  • Loading branch information
IceSentry authored Oct 13, 2023
1 parent a15d152 commit 068e42a
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 42 deletions.
7 changes: 6 additions & 1 deletion crates/bevy_pbr/src/render/wireframe.wgsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
struct WireframeMaterial {
color: vec4<f32>,
};

@group(1) @binding(0)
var<uniform> material: WireframeMaterial;
@fragment
fn fragment(in: MeshVertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
return material.color;
}
85 changes: 73 additions & 12 deletions crates/bevy_pbr/src/wireframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bevy_asset::{load_internal_asset, Asset, Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath, TypeUuid};
use bevy_render::{
color::Color,
extract_resource::ExtractResource,
mesh::{Mesh, MeshVertexBufferLayout},
prelude::Shader,
Expand All @@ -25,7 +26,6 @@ pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(19259
/// This is a native only feature.
#[derive(Debug, Default)]
pub struct WireframePlugin;

impl Plugin for WireframePlugin {
fn build(&self, app: &mut bevy_app::App) {
load_internal_asset!(
Expand All @@ -43,7 +43,12 @@ impl Plugin for WireframePlugin {
.add_systems(Startup, setup_global_wireframe_material)
.add_systems(
Update,
(apply_global_wireframe_material, apply_wireframe_material),
(
global_color_changed.run_if(resource_changed::<WireframeConfig>()),
wireframe_color_changed,
apply_wireframe_material,
apply_global_wireframe_material.run_if(resource_changed::<WireframeConfig>()),
),
);
}
}
Expand All @@ -56,6 +61,17 @@ impl Plugin for WireframePlugin {
#[reflect(Component, Default)]
pub struct Wireframe;

/// Sets the color of the [`Wireframe`] of the entity it is attached to.
/// If this component is present but there's no [`Wireframe`] component,
/// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true.
///
/// This overrides the [`WireframeConfig::default_color`].
#[derive(Component, Debug, Clone, Default, Reflect)]
#[reflect(Component, Default)]
pub struct WireframeColor {
pub color: Color,
}

/// Disables wireframe rendering for any entity it is attached to.
/// It will ignore the [`WireframeConfig`] global setting.
///
Expand All @@ -70,6 +86,10 @@ pub struct WireframeConfig {
/// Whether to show wireframes for all meshes.
/// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component.
pub global: bool,
/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
/// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
/// but no [`WireframeColor`].
pub default_color: Color,
}

#[derive(Resource)]
Expand All @@ -81,19 +101,53 @@ struct GlobalWireframeMaterial {
fn setup_global_wireframe_material(
mut commands: Commands,
mut materials: ResMut<Assets<WireframeMaterial>>,
config: Res<WireframeConfig>,
) {
// Create the handle used for the global material
commands.insert_resource(GlobalWireframeMaterial {
handle: materials.add(WireframeMaterial {}),
handle: materials.add(WireframeMaterial {
color: config.default_color,
}),
});
}

/// Updates the wireframe material of all entities without a [`WireframeColor`] or without a [`Wireframe`] component
fn global_color_changed(
config: Res<WireframeConfig>,
mut materials: ResMut<Assets<WireframeMaterial>>,
global_material: Res<GlobalWireframeMaterial>,
) {
if let Some(global_material) = materials.get_mut(&global_material.handle) {
global_material.color = config.default_color;
}
}

/// Updates the wireframe material when the color in [`WireframeColor`] changes
#[allow(clippy::type_complexity)]
fn wireframe_color_changed(
mut materials: ResMut<Assets<WireframeMaterial>>,
mut colors_changed: Query<
(&mut Handle<WireframeMaterial>, &WireframeColor),
(With<Wireframe>, Changed<WireframeColor>),
>,
) {
for (mut handle, wireframe_color) in &mut colors_changed {
*handle = materials.add(WireframeMaterial {
color: wireframe_color.color,
});
}
}

/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component.
fn apply_wireframe_material(
mut commands: Commands,
mut materials: ResMut<Assets<WireframeMaterial>>,
wireframes: Query<Entity, (With<Wireframe>, Without<Handle<WireframeMaterial>>)>,
wireframes: Query<
(Entity, Option<&WireframeColor>),
(With<Wireframe>, Without<Handle<WireframeMaterial>>),
>,
mut removed_wireframes: RemovedComponents<Wireframe>,
global_material: Res<GlobalWireframeMaterial>,
) {
for e in removed_wireframes.read() {
if let Some(mut commands) = commands.get_entity(e) {
Expand All @@ -102,8 +156,16 @@ fn apply_wireframe_material(
}

let mut wireframes_to_spawn = vec![];
for e in &wireframes {
wireframes_to_spawn.push((e, materials.add(WireframeMaterial {})));
for (e, wireframe_color) in &wireframes {
let material = if let Some(wireframe_color) = wireframe_color {
materials.add(WireframeMaterial {
color: wireframe_color.color,
})
} else {
// If there's no color specified we can use the global material since it's already set to use the default_color
global_material.handle.clone()
};
wireframes_to_spawn.push((e, material));
}
commands.insert_or_spawn_batch(wireframes_to_spawn);
}
Expand All @@ -118,10 +180,6 @@ fn apply_global_wireframe_material(
meshes_with_global_material: Query<Entity, (WireframeFilter, With<Handle<WireframeMaterial>>)>,
global_material: Res<GlobalWireframeMaterial>,
) {
if !config.is_changed() {
return;
}

if config.global {
let mut material_to_spawn = vec![];
for e in &meshes_without_material {
Expand All @@ -130,7 +188,7 @@ fn apply_global_wireframe_material(
material_to_spawn.push((e, global_material.handle.clone()));
}
commands.insert_or_spawn_batch(material_to_spawn);
} else if !config.global {
} else {
for e in &meshes_with_global_material {
commands.entity(e).remove::<Handle<WireframeMaterial>>();
}
Expand All @@ -139,7 +197,10 @@ fn apply_global_wireframe_material(

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

impl Material for WireframeMaterial {
fn fragment_shader() -> ShaderRef {
Expand Down
112 changes: 83 additions & 29 deletions examples/3d/wireframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! This is a native only feature.
use bevy::{
pbr::wireframe::{NoWireframe, Wireframe, WireframeConfig, WireframePlugin},
pbr::wireframe::{NoWireframe, Wireframe, WireframeColor, WireframeConfig, WireframePlugin},
prelude::*,
render::{
render_resource::WgpuFeatures,
Expand All @@ -31,12 +31,18 @@ fn main() {
// You need to add this plugin to enable wireframe rendering
WireframePlugin,
))
.insert_resource(WireframeToggleTimer(Timer::from_seconds(
1.0,
TimerMode::Repeating,
)))
// Wireframes can be configured with this resource. This can be changed at runtime.
.insert_resource(WireframeConfig {
// The global wireframe config enables drawing of wireframes on every mesh,
// except those with `NoWireframe`. Meshes with `Wireframe` will always have a wireframe,
// regardless of the global configuration.
global: true,
// Controls the default color of all wireframes. Used as the default color for global wireframes.
// Can be changed per mesh using the `WireframeColor` component.
default_color: Color::WHITE,
})
.add_systems(Startup, setup)
.add_systems(Update, toggle_global_wireframe_setting)
.add_systems(Update, update_colors)
.run();
}

Expand All @@ -54,14 +60,15 @@ fn setup(
});

// Red cube: Never renders a wireframe
commands
.spawn(PbrBundle {
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::RED.into()),
transform: Transform::from_xyz(-1.0, 0.5, -1.0),
..default()
})
.insert(NoWireframe);
},
NoWireframe,
));
// Orange cube: Follows global wireframe setting
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
Expand All @@ -70,42 +77,89 @@ fn setup(
..default()
});
// Green cube: Always renders a wireframe
commands
.spawn(PbrBundle {
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::GREEN.into()),
transform: Transform::from_xyz(1.0, 0.5, 1.0),
..default()
})
.insert(Wireframe);
},
Wireframe,
// This lets you configure the wireframe color of this entity.
// If not set, this will use the color in `WireframeConfig`
WireframeColor {
color: Color::GREEN,
},
));

// light
commands.spawn(PointLightBundle {
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});

// camera
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}

/// This timer is used to periodically toggle the wireframe rendering.
#[derive(Resource)]
struct WireframeToggleTimer(Timer);
// Text used to show controls
commands.spawn(
TextBundle::from_section("", TextStyle::default()).with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(10.0),
left: Val::Px(10.0),
..default()
}),
);
}

/// Periodically turns the global wireframe setting on and off, to show the differences between
/// [`Wireframe`], [`NoWireframe`], and just a mesh.
fn toggle_global_wireframe_setting(
time: Res<Time>,
mut timer: ResMut<WireframeToggleTimer>,
mut wireframe_config: ResMut<WireframeConfig>,
/// This system let's you toggle various wireframe settings
fn update_colors(
keyboard_input: Res<Input<KeyCode>>,
mut config: ResMut<WireframeConfig>,
mut wireframe_colors: Query<&mut WireframeColor>,
mut text: Query<&mut Text>,
) {
if timer.0.tick(time.delta()).just_finished() {
// The global wireframe config enables drawing of wireframes on every mesh,
// except those with `NoWireframe`. Meshes with `Wireframe` will always have a wireframe,
// regardless of the global configuration.
wireframe_config.global = !wireframe_config.global;
text.single_mut().sections[0].value = format!(
"
Controls
---------------
Z - Toggle global
X - Change global color
C - Change color of the green cube wireframe
WireframeConfig
-------------
Global: {}
Color: {:?}
",
config.global, config.default_color,
);

// Toggle showing a wireframe on all meshes
if keyboard_input.just_pressed(KeyCode::Z) {
config.global = !config.global;
}

// Toggle the global wireframe color
if keyboard_input.just_pressed(KeyCode::X) {
config.default_color = if config.default_color == Color::WHITE {
Color::PINK
} else {
Color::WHITE
};
}

// Toggle the color of a wireframe using WireframeColor and not the global color
if keyboard_input.just_pressed(KeyCode::C) {
for mut color in &mut wireframe_colors {
color.color = if color.color == Color::GREEN {
Color::RED
} else {
Color::GREEN
};
}
}
}

0 comments on commit 068e42a

Please sign in to comment.