Configurable colors for wireframe (#5303)

# Objective

- Make the wireframe colors configurable at the global level and the
single mesh level
- Based on https://github.com/bevyengine/bevy/pull/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 https://github.com/bevyengine/bevy/pull/3677~~
~~Closes https://github.com/bevyengine/bevy/pull/5301~~

~~https://github.com/bevyengine/bevy/pull/5314 should be merged before
this PR.~~
This commit is contained in:
IceSentry 2023-10-12 20:06:24 -04:00 committed by GitHub
parent a15d152635
commit 068e42a01f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 162 additions and 42 deletions

View File

@ -1,6 +1,11 @@
#import bevy_pbr::mesh_vertex_output MeshVertexOutput #import bevy_pbr::mesh_vertex_output MeshVertexOutput
struct WireframeMaterial {
color: vec4<f32>,
};
@group(1) @binding(0)
var<uniform> material: WireframeMaterial;
@fragment @fragment
fn fragment(in: MeshVertexOutput) -> @location(0) vec4<f32> { fn fragment(in: MeshVertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 1.0); return material.color;
} }

View File

@ -4,6 +4,7 @@ use bevy_asset::{load_internal_asset, Asset, Assets, Handle};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath, TypeUuid}; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath, TypeUuid};
use bevy_render::{ use bevy_render::{
color::Color,
extract_resource::ExtractResource, extract_resource::ExtractResource,
mesh::{Mesh, MeshVertexBufferLayout}, mesh::{Mesh, MeshVertexBufferLayout},
prelude::Shader, prelude::Shader,
@ -25,7 +26,6 @@ pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(19259
/// This is a native only feature. /// This is a native only feature.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct WireframePlugin; pub struct WireframePlugin;
impl Plugin for WireframePlugin { impl Plugin for WireframePlugin {
fn build(&self, app: &mut bevy_app::App) { fn build(&self, app: &mut bevy_app::App) {
load_internal_asset!( load_internal_asset!(
@ -43,7 +43,12 @@ impl Plugin for WireframePlugin {
.add_systems(Startup, setup_global_wireframe_material) .add_systems(Startup, setup_global_wireframe_material)
.add_systems( .add_systems(
Update, 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>()),
),
); );
} }
} }
@ -56,6 +61,17 @@ impl Plugin for WireframePlugin {
#[reflect(Component, Default)] #[reflect(Component, Default)]
pub struct Wireframe; 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. /// Disables wireframe rendering for any entity it is attached to.
/// It will ignore the [`WireframeConfig`] global setting. /// It will ignore the [`WireframeConfig`] global setting.
/// ///
@ -70,6 +86,10 @@ pub struct WireframeConfig {
/// Whether to show wireframes for all meshes. /// Whether to show wireframes for all meshes.
/// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component. /// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component.
pub global: bool, 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)] #[derive(Resource)]
@ -81,19 +101,53 @@ struct GlobalWireframeMaterial {
fn setup_global_wireframe_material( fn setup_global_wireframe_material(
mut commands: Commands, mut commands: Commands,
mut materials: ResMut<Assets<WireframeMaterial>>, mut materials: ResMut<Assets<WireframeMaterial>>,
config: Res<WireframeConfig>,
) { ) {
// Create the handle used for the global material // Create the handle used for the global material
commands.insert_resource(GlobalWireframeMaterial { 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. /// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component.
fn apply_wireframe_material( fn apply_wireframe_material(
mut commands: Commands, mut commands: Commands,
mut materials: ResMut<Assets<WireframeMaterial>>, 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>, mut removed_wireframes: RemovedComponents<Wireframe>,
global_material: Res<GlobalWireframeMaterial>,
) { ) {
for e in removed_wireframes.read() { for e in removed_wireframes.read() {
if let Some(mut commands) = commands.get_entity(e) { if let Some(mut commands) = commands.get_entity(e) {
@ -102,8 +156,16 @@ fn apply_wireframe_material(
} }
let mut wireframes_to_spawn = vec![]; let mut wireframes_to_spawn = vec![];
for e in &wireframes { for (e, wireframe_color) in &wireframes {
wireframes_to_spawn.push((e, materials.add(WireframeMaterial {}))); 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); commands.insert_or_spawn_batch(wireframes_to_spawn);
} }
@ -118,10 +180,6 @@ fn apply_global_wireframe_material(
meshes_with_global_material: Query<Entity, (WireframeFilter, With<Handle<WireframeMaterial>>)>, meshes_with_global_material: Query<Entity, (WireframeFilter, With<Handle<WireframeMaterial>>)>,
global_material: Res<GlobalWireframeMaterial>, global_material: Res<GlobalWireframeMaterial>,
) { ) {
if !config.is_changed() {
return;
}
if config.global { if config.global {
let mut material_to_spawn = vec![]; let mut material_to_spawn = vec![];
for e in &meshes_without_material { for e in &meshes_without_material {
@ -130,7 +188,7 @@ fn apply_global_wireframe_material(
material_to_spawn.push((e, global_material.handle.clone())); material_to_spawn.push((e, global_material.handle.clone()));
} }
commands.insert_or_spawn_batch(material_to_spawn); commands.insert_or_spawn_batch(material_to_spawn);
} else if !config.global { } else {
for e in &meshes_with_global_material { for e in &meshes_with_global_material {
commands.entity(e).remove::<Handle<WireframeMaterial>>(); commands.entity(e).remove::<Handle<WireframeMaterial>>();
} }
@ -139,7 +197,10 @@ fn apply_global_wireframe_material(
#[derive(Default, AsBindGroup, TypeUuid, TypePath, Debug, Clone, Asset)] #[derive(Default, AsBindGroup, TypeUuid, TypePath, Debug, Clone, Asset)]
#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"] #[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"]
struct WireframeMaterial {} pub struct WireframeMaterial {
#[uniform(0)]
pub color: Color,
}
impl Material for WireframeMaterial { impl Material for WireframeMaterial {
fn fragment_shader() -> ShaderRef { fn fragment_shader() -> ShaderRef {

View File

@ -9,7 +9,7 @@
//! This is a native only feature. //! This is a native only feature.
use bevy::{ use bevy::{
pbr::wireframe::{NoWireframe, Wireframe, WireframeConfig, WireframePlugin}, pbr::wireframe::{NoWireframe, Wireframe, WireframeColor, WireframeConfig, WireframePlugin},
prelude::*, prelude::*,
render::{ render::{
render_resource::WgpuFeatures, render_resource::WgpuFeatures,
@ -31,12 +31,18 @@ fn main() {
// You need to add this plugin to enable wireframe rendering // You need to add this plugin to enable wireframe rendering
WireframePlugin, WireframePlugin,
)) ))
.insert_resource(WireframeToggleTimer(Timer::from_seconds( // Wireframes can be configured with this resource. This can be changed at runtime.
1.0, .insert_resource(WireframeConfig {
TimerMode::Repeating, // 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(Startup, setup)
.add_systems(Update, toggle_global_wireframe_setting) .add_systems(Update, update_colors)
.run(); .run();
} }
@ -54,14 +60,15 @@ fn setup(
}); });
// Red cube: Never renders a wireframe // Red cube: Never renders a wireframe
commands commands.spawn((
.spawn(PbrBundle { PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::RED.into()), material: materials.add(Color::RED.into()),
transform: Transform::from_xyz(-1.0, 0.5, -1.0), transform: Transform::from_xyz(-1.0, 0.5, -1.0),
..default() ..default()
}) },
.insert(NoWireframe); NoWireframe,
));
// Orange cube: Follows global wireframe setting // Orange cube: Follows global wireframe setting
commands.spawn(PbrBundle { commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
@ -70,42 +77,89 @@ fn setup(
..default() ..default()
}); });
// Green cube: Always renders a wireframe // Green cube: Always renders a wireframe
commands commands.spawn((
.spawn(PbrBundle { PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::GREEN.into()), material: materials.add(Color::GREEN.into()),
transform: Transform::from_xyz(1.0, 0.5, 1.0), transform: Transform::from_xyz(1.0, 0.5, 1.0),
..default() ..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 // light
commands.spawn(PointLightBundle { commands.spawn(PointLightBundle {
transform: Transform::from_xyz(4.0, 8.0, 4.0), transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default() ..default()
}); });
// camera // camera
commands.spawn(Camera3dBundle { commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default() ..default()
}); });
// 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()
}),
);
} }
/// This timer is used to periodically toggle the wireframe rendering. /// This system let's you toggle various wireframe settings
#[derive(Resource)] fn update_colors(
struct WireframeToggleTimer(Timer); keyboard_input: Res<Input<KeyCode>>,
mut config: ResMut<WireframeConfig>,
/// Periodically turns the global wireframe setting on and off, to show the differences between mut wireframe_colors: Query<&mut WireframeColor>,
/// [`Wireframe`], [`NoWireframe`], and just a mesh. mut text: Query<&mut Text>,
fn toggle_global_wireframe_setting(
time: Res<Time>,
mut timer: ResMut<WireframeToggleTimer>,
mut wireframe_config: ResMut<WireframeConfig>,
) { ) {
if timer.0.tick(time.delta()).just_finished() { text.single_mut().sections[0].value = format!(
// The global wireframe config enables drawing of wireframes on every mesh, "
// except those with `NoWireframe`. Meshes with `Wireframe` will always have a wireframe, Controls
// regardless of the global configuration. ---------------
wireframe_config.global = !wireframe_config.global; 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
};
}
} }
} }