mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Support wireframes for 2D meshes (#12135)
# Objective Wireframes are currently supported for 3D meshes using the `WireframePlugin` in `bevy_pbr`. This PR adds the same functionality for 2D meshes. Closes #5881. ## Solution Since there's no easy way to share material implementations between 2D, 3D, and UI, this is mostly a straight copy and rename from the original plugin into `bevy_sprite`. <img width="1392" alt="image" src="https://github.com/bevyengine/bevy/assets/3961616/7aca156f-448a-4c7e-89b8-0a72c5919769"> --- ## Changelog - Added `Wireframe2dPlugin` and related types to support 2D wireframes. - Added an example to demonstrate how to use 2D wireframes --------- Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
This commit is contained in:
parent
5357e15966
commit
fee824413f
6 changed files with 410 additions and 0 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -562,6 +562,17 @@ description = "Showcases bounding volumes and intersection tests"
|
|||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "wireframe_2d"
|
||||
path = "examples/2d/wireframe_2d.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.wireframe_2d]
|
||||
name = "2D Wireframe"
|
||||
description = "Showcases wireframes for 2d meshes"
|
||||
category = "2D Rendering"
|
||||
wasm = false
|
||||
|
||||
# 3D Rendering
|
||||
[[example]]
|
||||
name = "3d_scene"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
mod color_material;
|
||||
mod material;
|
||||
mod mesh;
|
||||
mod wireframe2d;
|
||||
|
||||
pub use color_material::*;
|
||||
pub use material::*;
|
||||
pub use mesh::*;
|
||||
pub use wireframe2d::*;
|
||||
|
|
231
crates/bevy_sprite/src/mesh2d/wireframe2d.rs
Normal file
231
crates/bevy_sprite/src/mesh2d/wireframe2d.rs
Normal file
|
@ -0,0 +1,231 @@
|
|||
use crate::{Material2d, Material2dKey, Material2dPlugin, Mesh2dHandle};
|
||||
use bevy_app::{Plugin, Startup, Update};
|
||||
use bevy_asset::{load_internal_asset, Asset, Assets, Handle};
|
||||
use bevy_color::{LinearRgba, Srgba};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
|
||||
use bevy_render::{
|
||||
extract_resource::ExtractResource, mesh::MeshVertexBufferLayoutRef, prelude::*,
|
||||
render_resource::*,
|
||||
};
|
||||
|
||||
pub const WIREFRAME_2D_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(6920362697190520314);
|
||||
|
||||
/// A [`Plugin`] that draws wireframes for 2D meshes.
|
||||
///
|
||||
/// Wireframes currently do not work when using webgl or webgpu.
|
||||
/// Supported rendering backends:
|
||||
/// - DX12
|
||||
/// - Vulkan
|
||||
/// - Metal
|
||||
///
|
||||
/// This is a native only feature.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Wireframe2dPlugin;
|
||||
impl Plugin for Wireframe2dPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
load_internal_asset!(
|
||||
app,
|
||||
WIREFRAME_2D_SHADER_HANDLE,
|
||||
"wireframe2d.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
|
||||
app.register_type::<Wireframe2d>()
|
||||
.register_type::<NoWireframe2d>()
|
||||
.register_type::<Wireframe2dConfig>()
|
||||
.register_type::<Wireframe2dColor>()
|
||||
.init_resource::<Wireframe2dConfig>()
|
||||
.add_plugins(Material2dPlugin::<Wireframe2dMaterial>::default())
|
||||
.add_systems(Startup, setup_global_wireframe_material)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
global_color_changed.run_if(resource_changed::<Wireframe2dConfig>),
|
||||
wireframe_color_changed,
|
||||
// Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global
|
||||
// wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed.
|
||||
(apply_wireframe_material, apply_global_wireframe_material).chain(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables wireframe rendering for any entity it is attached to.
|
||||
/// It will ignore the [`Wireframe2dConfig`] global setting.
|
||||
///
|
||||
/// This requires the [`Wireframe2dPlugin`] to be enabled.
|
||||
#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct Wireframe2d;
|
||||
|
||||
/// Sets the color of the [`Wireframe2d`] of the entity it is attached to.
|
||||
/// If this component is present but there's no [`Wireframe2d`] component,
|
||||
/// it will still affect the color of the wireframe when [`Wireframe2dConfig::global`] is set to true.
|
||||
///
|
||||
/// This overrides the [`Wireframe2dConfig::default_color`].
|
||||
#[derive(Component, Debug, Clone, Default, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct Wireframe2dColor {
|
||||
pub color: Srgba,
|
||||
}
|
||||
|
||||
/// Disables wireframe rendering for any entity it is attached to.
|
||||
/// It will ignore the [`Wireframe2dConfig`] global setting.
|
||||
///
|
||||
/// This requires the [`Wireframe2dPlugin`] to be enabled.
|
||||
#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct NoWireframe2d;
|
||||
|
||||
#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct Wireframe2dConfig {
|
||||
/// Whether to show wireframes for all meshes.
|
||||
/// Can be overridden for individual meshes by adding a [`Wireframe2d`] or [`NoWireframe2d`] component.
|
||||
pub global: bool,
|
||||
/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe2d`] component attached to it will have
|
||||
/// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe2d`],
|
||||
/// but no [`Wireframe2dColor`].
|
||||
pub default_color: Srgba,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct GlobalWireframe2dMaterial {
|
||||
// This handle will be reused when the global config is enabled
|
||||
handle: Handle<Wireframe2dMaterial>,
|
||||
}
|
||||
|
||||
fn setup_global_wireframe_material(
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<Wireframe2dMaterial>>,
|
||||
config: Res<Wireframe2dConfig>,
|
||||
) {
|
||||
// Create the handle used for the global material
|
||||
commands.insert_resource(GlobalWireframe2dMaterial {
|
||||
handle: materials.add(Wireframe2dMaterial {
|
||||
color: config.default_color.into(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/// Updates the wireframe material of all entities without a [`Wireframe2dColor`] or without a [`Wireframe2d`] component
|
||||
fn global_color_changed(
|
||||
config: Res<Wireframe2dConfig>,
|
||||
mut materials: ResMut<Assets<Wireframe2dMaterial>>,
|
||||
global_material: Res<GlobalWireframe2dMaterial>,
|
||||
) {
|
||||
if let Some(global_material) = materials.get_mut(&global_material.handle) {
|
||||
global_material.color = config.default_color.into();
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the wireframe material when the color in [`Wireframe2dColor`] changes
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn wireframe_color_changed(
|
||||
mut materials: ResMut<Assets<Wireframe2dMaterial>>,
|
||||
mut colors_changed: Query<
|
||||
(&mut Handle<Wireframe2dMaterial>, &Wireframe2dColor),
|
||||
(With<Wireframe2d>, Changed<Wireframe2dColor>),
|
||||
>,
|
||||
) {
|
||||
for (mut handle, wireframe_color) in &mut colors_changed {
|
||||
*handle = materials.add(Wireframe2dMaterial {
|
||||
color: wireframe_color.color.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies or remove the wireframe material to any mesh with a [`Wireframe2d`] component, and removes it
|
||||
/// for any mesh with a [`NoWireframe2d`] component.
|
||||
fn apply_wireframe_material(
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<Wireframe2dMaterial>>,
|
||||
wireframes: Query<
|
||||
(Entity, Option<&Wireframe2dColor>),
|
||||
(With<Wireframe2d>, Without<Handle<Wireframe2dMaterial>>),
|
||||
>,
|
||||
no_wireframes: Query<Entity, (With<NoWireframe2d>, With<Handle<Wireframe2dMaterial>>)>,
|
||||
mut removed_wireframes: RemovedComponents<Wireframe2d>,
|
||||
global_material: Res<GlobalWireframe2dMaterial>,
|
||||
) {
|
||||
for e in removed_wireframes.read().chain(no_wireframes.iter()) {
|
||||
if let Some(mut commands) = commands.get_entity(e) {
|
||||
commands.remove::<Handle<Wireframe2dMaterial>>();
|
||||
}
|
||||
}
|
||||
|
||||
let mut wireframes_to_spawn = vec![];
|
||||
for (e, wireframe_color) in &wireframes {
|
||||
let material = if let Some(wireframe_color) = wireframe_color {
|
||||
materials.add(Wireframe2dMaterial {
|
||||
color: wireframe_color.color.into(),
|
||||
})
|
||||
} 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);
|
||||
}
|
||||
|
||||
type Wireframe2dFilter = (
|
||||
With<Mesh2dHandle>,
|
||||
Without<Wireframe2d>,
|
||||
Without<NoWireframe2d>,
|
||||
);
|
||||
|
||||
/// Applies or removes a wireframe material on any mesh without a [`Wireframe2d`] or [`NoWireframe2d`] component.
|
||||
fn apply_global_wireframe_material(
|
||||
mut commands: Commands,
|
||||
config: Res<Wireframe2dConfig>,
|
||||
meshes_without_material: Query<
|
||||
Entity,
|
||||
(Wireframe2dFilter, Without<Handle<Wireframe2dMaterial>>),
|
||||
>,
|
||||
meshes_with_global_material: Query<
|
||||
Entity,
|
||||
(Wireframe2dFilter, With<Handle<Wireframe2dMaterial>>),
|
||||
>,
|
||||
global_material: Res<GlobalWireframe2dMaterial>,
|
||||
) {
|
||||
if config.global {
|
||||
let mut material_to_spawn = vec![];
|
||||
for e in &meshes_without_material {
|
||||
// We only add the material handle but not the Wireframe component
|
||||
// This makes it easy to detect which mesh is using the global material and which ones are user specified
|
||||
material_to_spawn.push((e, global_material.handle.clone()));
|
||||
}
|
||||
commands.insert_or_spawn_batch(material_to_spawn);
|
||||
} else {
|
||||
for e in &meshes_with_global_material {
|
||||
commands.entity(e).remove::<Handle<Wireframe2dMaterial>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, AsBindGroup, TypePath, Debug, Clone, Asset)]
|
||||
pub struct Wireframe2dMaterial {
|
||||
#[uniform(0)]
|
||||
pub color: LinearRgba,
|
||||
}
|
||||
|
||||
impl Material2d for Wireframe2dMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
WIREFRAME_2D_SHADER_HANDLE.into()
|
||||
}
|
||||
|
||||
fn depth_bias(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
_layout: &MeshVertexBufferLayoutRef,
|
||||
_key: Material2dKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
descriptor.primitive.polygon_mode = PolygonMode::Line;
|
||||
Ok(())
|
||||
}
|
||||
}
|
11
crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl
Normal file
11
crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl
Normal file
|
@ -0,0 +1,11 @@
|
|||
#import bevy_sprite::mesh2d_vertex_output::VertexOutput
|
||||
|
||||
struct WireframeMaterial {
|
||||
color: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: WireframeMaterial;
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return material.color;
|
||||
}
|
154
examples/2d/wireframe_2d.rs
Normal file
154
examples/2d/wireframe_2d.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
//! Showcases wireframe rendering for 2d meshes.
|
||||
//!
|
||||
//! Wireframes currently do not work when using webgl or webgpu.
|
||||
//! Supported platforms:
|
||||
//! - DX12
|
||||
//! - Vulkan
|
||||
//! - Metal
|
||||
//!
|
||||
//! This is a native only feature.
|
||||
|
||||
use bevy::{
|
||||
color::palettes::basic::{GREEN, RED, WHITE},
|
||||
prelude::*,
|
||||
render::{
|
||||
render_resource::WgpuFeatures,
|
||||
settings::{RenderCreation, WgpuSettings},
|
||||
RenderPlugin,
|
||||
},
|
||||
sprite::{
|
||||
MaterialMesh2dBundle, NoWireframe2d, Wireframe2d, Wireframe2dColor, Wireframe2dConfig,
|
||||
Wireframe2dPlugin,
|
||||
},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.set(RenderPlugin {
|
||||
render_creation: RenderCreation::Automatic(WgpuSettings {
|
||||
// WARN this is a native only feature. It will not work with webgl or webgpu
|
||||
features: WgpuFeatures::POLYGON_MODE_LINE,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}),
|
||||
// You need to add this plugin to enable wireframe rendering
|
||||
Wireframe2dPlugin,
|
||||
))
|
||||
// Wireframes can be configured with this resource. This can be changed at runtime.
|
||||
.insert_resource(Wireframe2dConfig {
|
||||
// The global wireframe config enables drawing of wireframes on every mesh,
|
||||
// except those with `NoWireframe2d`. Meshes with `Wireframe2d` 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 `Wireframe2dColor` component.
|
||||
default_color: WHITE,
|
||||
})
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, update_colors)
|
||||
.run();
|
||||
}
|
||||
|
||||
/// Set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
) {
|
||||
// Triangle: Never renders a wireframe
|
||||
commands.spawn((
|
||||
MaterialMesh2dBundle {
|
||||
mesh: meshes
|
||||
.add(Triangle2d::new(
|
||||
Vec2::new(0.0, 50.0),
|
||||
Vec2::new(-50.0, -50.0),
|
||||
Vec2::new(50.0, -50.0),
|
||||
))
|
||||
.into(),
|
||||
material: materials.add(Color::BLACK),
|
||||
transform: Transform::from_xyz(-150.0, 0.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
NoWireframe2d,
|
||||
));
|
||||
// Rectangle: Follows global wireframe setting
|
||||
commands.spawn(MaterialMesh2dBundle {
|
||||
mesh: meshes.add(Rectangle::new(100.0, 100.0)).into(),
|
||||
material: materials.add(Color::BLACK),
|
||||
transform: Transform::from_xyz(0.0, 0.0, 0.0),
|
||||
..default()
|
||||
});
|
||||
// Circle: Always renders a wireframe
|
||||
commands.spawn((
|
||||
MaterialMesh2dBundle {
|
||||
mesh: meshes.add(Circle::new(50.0)).into(),
|
||||
material: materials.add(Color::BLACK),
|
||||
transform: Transform::from_xyz(150.0, 0.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
Wireframe2d,
|
||||
// This lets you configure the wireframe color of this entity.
|
||||
// If not set, this will use the color in `WireframeConfig`
|
||||
Wireframe2dColor { color: GREEN },
|
||||
));
|
||||
|
||||
// Camera
|
||||
commands.spawn(Camera2dBundle::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 system lets you toggle various wireframe settings
|
||||
fn update_colors(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut config: ResMut<Wireframe2dConfig>,
|
||||
mut wireframe_colors: Query<&mut Wireframe2dColor>,
|
||||
mut text: Query<&mut Text>,
|
||||
) {
|
||||
text.single_mut().sections[0].value = format!(
|
||||
"
|
||||
Controls
|
||||
---------------
|
||||
Z - Toggle global
|
||||
X - Change global color
|
||||
C - Change color of the circle wireframe
|
||||
|
||||
Wireframe2dConfig
|
||||
-------------
|
||||
Global: {}
|
||||
Color: {:?}
|
||||
",
|
||||
config.global, config.default_color,
|
||||
);
|
||||
|
||||
// Toggle showing a wireframe on all meshes
|
||||
if keyboard_input.just_pressed(KeyCode::KeyZ) {
|
||||
config.global = !config.global;
|
||||
}
|
||||
|
||||
// Toggle the global wireframe color
|
||||
if keyboard_input.just_pressed(KeyCode::KeyX) {
|
||||
config.default_color = if config.default_color == WHITE {
|
||||
RED
|
||||
} else {
|
||||
WHITE
|
||||
};
|
||||
}
|
||||
|
||||
// Toggle the color of a wireframe using `Wireframe2dColor` and not the global color
|
||||
if keyboard_input.just_pressed(KeyCode::KeyC) {
|
||||
for mut color in &mut wireframe_colors {
|
||||
color.color = if color.color == GREEN { RED } else { GREEN };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -100,6 +100,7 @@ Example | Description
|
|||
[2D Rotation](../examples/2d/rotation.rs) | Demonstrates rotating entities in 2D with quaternions
|
||||
[2D Shapes](../examples/2d/2d_shapes.rs) | Renders simple 2D primitive shapes like circles and polygons
|
||||
[2D Viewport To World](../examples/2d/2d_viewport_to_world.rs) | Demonstrates how to use the `Camera::viewport_to_world_2d` method
|
||||
[2D Wireframe](../examples/2d/wireframe_2d.rs) | Showcases wireframes for 2d meshes
|
||||
[Custom glTF vertex attribute 2D](../examples/2d/custom_gltf_vertex_attribute.rs) | Renders a glTF mesh in 2D with a custom vertex attribute
|
||||
[Manual Mesh 2D](../examples/2d/mesh2d_manual.rs) | Renders a custom mesh "manually" with "mid-level" renderer apis
|
||||
[Mesh 2D](../examples/2d/mesh2d.rs) | Renders a 2d mesh
|
||||
|
|
Loading…
Reference in a new issue