use Material
for wireframes (#5314)
# Objective - Use the `Material` abstraction for the Wireframes - Right now this doesn't have many benefits other than simplifying some of the rendering code - We can reuse the default vertex shader and avoid rendering inconsistencies - The goal is to have a material with a color on each mesh so this approach will make it easier to implement - Originally done in https://github.com/bevyengine/bevy/pull/5303 but I decided to split the Material part to it's own PR and then adding per-entity colors and globally configurable colors will be a much simpler diff. ## Solution - Use the new `Material` abstraction for the Wireframes ## Notes It's possible this isn't ideal since this adds a `Handle<WireframeMaterial>` to all the meshes compared to the original approach that didn't need anything. I didn't notice any performance impact on my machine. This might be a surprising usage of `Material` at first, because intuitively you only have one material per mesh, but the way it's implemented you can have as many different types of materials as you want on a mesh. ## Migration Guide `WireframePipeline` was removed. If you were using it directly, please create an issue explaining your use case. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
ca873e767f
commit
e05a9f9315
@ -1,66 +1,6 @@
|
||||
#import bevy_pbr::mesh_bindings mesh
|
||||
#import bevy_pbr::mesh_functions get_model_matrix, mesh_position_local_to_clip
|
||||
#import bevy_pbr::morph
|
||||
|
||||
#ifdef SKINNED
|
||||
#import bevy_pbr::skinning
|
||||
#endif
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
@location(0) position: vec3<f32>,
|
||||
#ifdef SKINNED
|
||||
@location(5) joint_indexes: vec4<u32>,
|
||||
@location(6) joint_weights: vec4<f32>,
|
||||
#endif
|
||||
#ifdef MORPH_TARGETS
|
||||
@builtin(vertex_index) index: u32,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
|
||||
#ifdef MORPH_TARGETS
|
||||
fn morph_vertex(vertex_in: Vertex) -> Vertex {
|
||||
var vertex = vertex_in;
|
||||
let weight_count = bevy_pbr::morph::layer_count();
|
||||
for (var i: u32 = 0u; i < weight_count; i ++) {
|
||||
let weight = bevy_pbr::morph::weight_at(i);
|
||||
if weight == 0.0 {
|
||||
continue;
|
||||
}
|
||||
vertex.position += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::position_offset, i);
|
||||
}
|
||||
return vertex;
|
||||
}
|
||||
#endif
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
||||
|
||||
#ifdef MORPH_TARGETS
|
||||
var vertex = morph_vertex(vertex_no_morph);
|
||||
#else
|
||||
var vertex = vertex_no_morph;
|
||||
#endif
|
||||
|
||||
#ifdef SKINNED
|
||||
let model = bevy_pbr::skinning::skin_model(vertex.joint_indexes, vertex.joint_weights);
|
||||
#else
|
||||
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
||||
// See https://github.com/gfx-rs/naga/issues/2416 .
|
||||
let model = get_model_matrix(vertex_no_morph.instance_index);
|
||||
#endif
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = mesh_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
|
||||
return out;
|
||||
}
|
||||
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
|
||||
|
||||
@fragment
|
||||
fn fragment() -> @location(0) vec4<f32> {
|
||||
fn fragment(in: MeshVertexOutput) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
}
|
@ -1,30 +1,28 @@
|
||||
use crate::{
|
||||
DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstance, RenderMeshInstances,
|
||||
SetMeshBindGroup, SetMeshViewBindGroup,
|
||||
};
|
||||
use bevy_app::Plugin;
|
||||
use bevy_asset::{load_internal_asset, Handle};
|
||||
use bevy_core_pipeline::core_3d::Opaque3d;
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin};
|
||||
use bevy_app::{Plugin, Startup, Update};
|
||||
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::{
|
||||
extract_resource::{ExtractResource, ExtractResourcePlugin},
|
||||
extract_resource::ExtractResource,
|
||||
mesh::{Mesh, MeshVertexBufferLayout},
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
prelude::Shader,
|
||||
render_resource::{
|
||||
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline,
|
||||
SpecializedMeshPipelineError, SpecializedMeshPipelines,
|
||||
AsBindGroup, PolygonMode, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
|
||||
},
|
||||
view::{ExtractedView, Msaa, VisibleEntities},
|
||||
RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_render::{Extract, ExtractSchedule, Render};
|
||||
use bevy_utils::{tracing::error, EntityHashSet};
|
||||
|
||||
pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(192598014480025766);
|
||||
|
||||
/// A [`Plugin`] that draws wireframes.
|
||||
///
|
||||
/// 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 WireframePlugin;
|
||||
|
||||
@ -41,23 +39,12 @@ impl Plugin for WireframePlugin {
|
||||
.register_type::<NoWireframe>()
|
||||
.register_type::<WireframeConfig>()
|
||||
.init_resource::<WireframeConfig>()
|
||||
.add_plugins((ExtractResourcePlugin::<WireframeConfig>::default(),));
|
||||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.add_render_command::<Opaque3d, DrawWireframes>()
|
||||
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
|
||||
.init_resource::<Wireframes>()
|
||||
.init_resource::<NoWireframes>()
|
||||
.add_systems(ExtractSchedule, extract_wireframes)
|
||||
.add_systems(Render, queue_wireframes.in_set(RenderSet::QueueMeshes));
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<WireframePipeline>();
|
||||
}
|
||||
.add_plugins(MaterialPlugin::<WireframeMaterial>::default())
|
||||
.add_systems(Startup, setup_global_wireframe_material)
|
||||
.add_systems(
|
||||
Update,
|
||||
(apply_global_wireframe_material, apply_wireframe_material),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,133 +72,103 @@ pub struct WireframeConfig {
|
||||
pub global: bool,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Deref, DerefMut)]
|
||||
pub struct Wireframes(EntityHashSet<Entity>);
|
||||
#[derive(Resource)]
|
||||
struct GlobalWireframeMaterial {
|
||||
// This handle will be reused when the global config is enabled
|
||||
handle: Handle<WireframeMaterial>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Deref, DerefMut)]
|
||||
pub struct NoWireframes(EntityHashSet<Entity>);
|
||||
|
||||
fn extract_wireframes(
|
||||
mut wireframes: ResMut<Wireframes>,
|
||||
mut no_wireframes: ResMut<NoWireframes>,
|
||||
wireframe_query: Extract<Query<Entity, With<Wireframe>>>,
|
||||
no_wireframe_query: Extract<Query<Entity, With<NoWireframe>>>,
|
||||
fn setup_global_wireframe_material(
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<WireframeMaterial>>,
|
||||
) {
|
||||
wireframes.clear();
|
||||
wireframes.extend(wireframe_query.iter());
|
||||
no_wireframes.clear();
|
||||
no_wireframes.extend(no_wireframe_query.iter());
|
||||
// Create the handle used for the global material
|
||||
commands.insert_resource(GlobalWireframeMaterial {
|
||||
handle: materials.add(WireframeMaterial {}),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct WireframePipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
shader: Handle<Shader>,
|
||||
/// 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>>)>,
|
||||
mut removed_wireframes: RemovedComponents<Wireframe>,
|
||||
) {
|
||||
for e in removed_wireframes.read() {
|
||||
if let Some(mut commands) = commands.get_entity(e) {
|
||||
commands.remove::<Handle<WireframeMaterial>>();
|
||||
}
|
||||
}
|
||||
|
||||
let mut wireframes_to_spawn = vec![];
|
||||
for e in &wireframes {
|
||||
wireframes_to_spawn.push((e, materials.add(WireframeMaterial {})));
|
||||
}
|
||||
commands.insert_or_spawn_batch(wireframes_to_spawn);
|
||||
}
|
||||
impl FromWorld for WireframePipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
WireframePipeline {
|
||||
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
|
||||
shader: WIREFRAME_SHADER_HANDLE,
|
||||
|
||||
/// Applies or removes a wireframe material on any mesh without a [`Wireframe`] component.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn apply_global_wireframe_material(
|
||||
mut commands: Commands,
|
||||
config: Res<WireframeConfig>,
|
||||
meshes_without_material: Query<
|
||||
Entity,
|
||||
(
|
||||
With<Handle<Mesh>>,
|
||||
Without<Wireframe>,
|
||||
Without<NoWireframe>,
|
||||
Without<Handle<WireframeMaterial>>,
|
||||
),
|
||||
>,
|
||||
meshes_with_global_material: Query<
|
||||
Entity,
|
||||
(
|
||||
With<Handle<Mesh>>,
|
||||
Without<Wireframe>,
|
||||
Without<NoWireframe>,
|
||||
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 {
|
||||
// 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 if !config.global {
|
||||
for e in &meshes_with_global_material {
|
||||
commands.entity(e).remove::<Handle<WireframeMaterial>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for WireframePipeline {
|
||||
type Key = MeshPipelineKey;
|
||||
#[derive(Default, AsBindGroup, TypeUuid, TypePath, Debug, Clone, Asset)]
|
||||
#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"]
|
||||
struct WireframeMaterial {}
|
||||
|
||||
impl Material for WireframeMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
WIREFRAME_SHADER_HANDLE.into()
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
key: Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
|
||||
descriptor.vertex.shader = self.shader.clone_weak();
|
||||
descriptor
|
||||
.vertex
|
||||
.shader_defs
|
||||
.push("MESH_BINDGROUP_1".into());
|
||||
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
|
||||
_pipeline: &MaterialPipeline<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
_layout: &MeshVertexBufferLayout,
|
||||
_key: MaterialPipelineKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
descriptor.primitive.polygon_mode = PolygonMode::Line;
|
||||
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
|
||||
Ok(descriptor)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn queue_wireframes(
|
||||
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
render_mesh_instances: Res<RenderMeshInstances>,
|
||||
wireframes: Res<Wireframes>,
|
||||
no_wireframes: Res<NoWireframes>,
|
||||
wireframe_config: Res<WireframeConfig>,
|
||||
wireframe_pipeline: Res<WireframePipeline>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<WireframePipeline>>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
msaa: Res<Msaa>,
|
||||
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
|
||||
) {
|
||||
let draw_custom = opaque_3d_draw_functions.read().id::<DrawWireframes>();
|
||||
let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples());
|
||||
for (view, visible_entities, mut opaque_phase) in &mut views {
|
||||
let rangefinder = view.rangefinder3d();
|
||||
|
||||
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
|
||||
let add_render_phase = |phase_item: (Entity, &RenderMeshInstance)| {
|
||||
let (entity, mesh_instance) = phase_item;
|
||||
|
||||
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
|
||||
return;
|
||||
};
|
||||
let mut key =
|
||||
view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
if mesh.morph_targets.is_some() {
|
||||
key |= MeshPipelineKey::MORPH_TARGETS;
|
||||
}
|
||||
let pipeline_id =
|
||||
pipelines.specialize(&pipeline_cache, &wireframe_pipeline, key, &mesh.layout);
|
||||
let pipeline_id = match pipeline_id {
|
||||
Ok(id) => id,
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
opaque_phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline: pipeline_id,
|
||||
draw_function: draw_custom,
|
||||
distance: rangefinder
|
||||
.distance_translation(&mesh_instance.transforms.transform.translation),
|
||||
batch_range: 0..1,
|
||||
dynamic_offset: None,
|
||||
});
|
||||
};
|
||||
|
||||
visible_entities
|
||||
.entities
|
||||
.iter()
|
||||
.filter_map(|visible_entity| {
|
||||
if no_wireframes.get(visible_entity).is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if wireframe_config.global || wireframes.get(visible_entity).is_some() {
|
||||
render_mesh_instances
|
||||
.get(visible_entity)
|
||||
.map(|mesh_instance| (*visible_entity, mesh_instance))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.for_each(add_render_phase);
|
||||
}
|
||||
}
|
||||
|
||||
type DrawWireframes = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
DrawMesh,
|
||||
);
|
||||
|
@ -1,21 +1,34 @@
|
||||
//! Showcases wireframe rendering.
|
||||
//!
|
||||
//! Wireframes currently do not work when using webgl or webgpu.
|
||||
//! Supported platforms:
|
||||
//! - DX12
|
||||
//! - Vulkan
|
||||
//! - Metal
|
||||
//!
|
||||
//! This is a native only feature.
|
||||
|
||||
use bevy::{
|
||||
pbr::wireframe::{NoWireframe, Wireframe, WireframeConfig, WireframePlugin},
|
||||
prelude::*,
|
||||
render::{render_resource::WgpuFeatures, settings::WgpuSettings, RenderPlugin},
|
||||
render::{
|
||||
render_resource::WgpuFeatures,
|
||||
settings::{RenderCreation, WgpuSettings},
|
||||
RenderPlugin,
|
||||
},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.set(RenderPlugin {
|
||||
render_creation: WgpuSettings {
|
||||
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()
|
||||
}
|
||||
.into(),
|
||||
}),
|
||||
}),
|
||||
// You need to add this plugin to enable wireframe rendering
|
||||
WireframePlugin,
|
||||
))
|
||||
.insert_resource(WireframeToggleTimer(Timer::from_seconds(
|
||||
@ -36,7 +49,7 @@ fn setup(
|
||||
// plane
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Plane::from_size(5.0))),
|
||||
material: materials.add(Color::rgb(0.3, 0.3, 0.5).into()),
|
||||
material: materials.add(Color::BLUE.into()),
|
||||
..default()
|
||||
});
|
||||
|
||||
@ -44,7 +57,7 @@ fn setup(
|
||||
commands
|
||||
.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.1, 0.1).into()),
|
||||
material: materials.add(Color::RED.into()),
|
||||
transform: Transform::from_xyz(-1.0, 0.5, -1.0),
|
||||
..default()
|
||||
})
|
||||
@ -52,7 +65,7 @@ fn setup(
|
||||
// Orange cube: Follows global wireframe setting
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.8, 0.1).into()),
|
||||
material: materials.add(Color::ORANGE.into()),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
@ -60,7 +73,7 @@ fn setup(
|
||||
commands
|
||||
.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.1, 0.8, 0.1).into()),
|
||||
material: materials.add(Color::GREEN.into()),
|
||||
transform: Transform::from_xyz(1.0, 0.5, 1.0),
|
||||
..default()
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user