Skip to content

Commit cef1899

Browse files
IceSentryalice-i-cecile
authored and
Thomas Wilgenbus
committed
use Material for wireframes (bevyengine#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 bevyengine#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 <[email protected]>
1 parent 4351434 commit cef1899

File tree

3 files changed

+132
-222
lines changed

3 files changed

+132
-222
lines changed
Lines changed: 3 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,6 @@
1-
#import bevy_pbr::mesh_bindings mesh
2-
#import bevy_pbr::mesh_functions get_model_matrix, mesh_position_local_to_clip
3-
#import bevy_pbr::morph
4-
5-
#ifdef SKINNED
6-
#import bevy_pbr::skinning
7-
#endif
8-
9-
struct Vertex {
10-
@builtin(instance_index) instance_index: u32,
11-
@location(0) position: vec3<f32>,
12-
#ifdef SKINNED
13-
@location(5) joint_indexes: vec4<u32>,
14-
@location(6) joint_weights: vec4<f32>,
15-
#endif
16-
#ifdef MORPH_TARGETS
17-
@builtin(vertex_index) index: u32,
18-
#endif
19-
};
20-
21-
struct VertexOutput {
22-
@builtin(position) clip_position: vec4<f32>,
23-
};
24-
25-
26-
#ifdef MORPH_TARGETS
27-
fn morph_vertex(vertex_in: Vertex) -> Vertex {
28-
var vertex = vertex_in;
29-
let weight_count = bevy_pbr::morph::layer_count();
30-
for (var i: u32 = 0u; i < weight_count; i ++) {
31-
let weight = bevy_pbr::morph::weight_at(i);
32-
if weight == 0.0 {
33-
continue;
34-
}
35-
vertex.position += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::position_offset, i);
36-
}
37-
return vertex;
38-
}
39-
#endif
40-
41-
@vertex
42-
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
43-
44-
#ifdef MORPH_TARGETS
45-
var vertex = morph_vertex(vertex_no_morph);
46-
#else
47-
var vertex = vertex_no_morph;
48-
#endif
49-
50-
#ifdef SKINNED
51-
let model = bevy_pbr::skinning::skin_model(vertex.joint_indexes, vertex.joint_weights);
52-
#else
53-
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
54-
// See https://github.com/gfx-rs/naga/issues/2416 .
55-
let model = get_model_matrix(vertex_no_morph.instance_index);
56-
#endif
57-
58-
var out: VertexOutput;
59-
out.clip_position = mesh_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
60-
return out;
61-
}
1+
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
622

633
@fragment
64-
fn fragment() -> @location(0) vec4<f32> {
4+
fn fragment(in: MeshVertexOutput) -> @location(0) vec4<f32> {
655
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
66-
}
6+
}

crates/bevy_pbr/src/wireframe.rs

Lines changed: 108 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
1-
use crate::{
2-
DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstance, RenderMeshInstances,
3-
SetMeshBindGroup, SetMeshViewBindGroup,
4-
};
5-
use bevy_app::Plugin;
6-
use bevy_asset::{load_internal_asset, Handle};
7-
use bevy_core_pipeline::core_3d::Opaque3d;
8-
use bevy_derive::{Deref, DerefMut};
9-
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
10-
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
1+
use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin};
2+
use bevy_app::{Plugin, Startup, Update};
3+
use bevy_asset::{load_internal_asset, Asset, Assets, Handle};
4+
use bevy_ecs::prelude::*;
5+
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath, TypeUuid};
116
use bevy_render::{
12-
extract_resource::{ExtractResource, ExtractResourcePlugin},
7+
extract_resource::ExtractResource,
138
mesh::{Mesh, MeshVertexBufferLayout},
14-
render_asset::RenderAssets,
15-
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
9+
prelude::Shader,
1610
render_resource::{
17-
PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline,
18-
SpecializedMeshPipelineError, SpecializedMeshPipelines,
11+
AsBindGroup, PolygonMode, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
1912
},
20-
view::{ExtractedView, Msaa, VisibleEntities},
21-
RenderApp, RenderSet,
2213
};
23-
use bevy_render::{Extract, ExtractSchedule, Render};
24-
use bevy_utils::{tracing::error, EntityHashSet};
2514

2615
pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(192598014480025766);
2716

17+
/// A [`Plugin`] that draws wireframes.
18+
///
19+
/// Wireframes currently do not work when using webgl or webgpu.
20+
/// Supported rendering backends:
21+
/// - DX12
22+
/// - Vulkan
23+
/// - Metal
24+
///
25+
/// This is a native only feature.
2826
#[derive(Debug, Default)]
2927
pub struct WireframePlugin;
3028

@@ -41,23 +39,12 @@ impl Plugin for WireframePlugin {
4139
.register_type::<NoWireframe>()
4240
.register_type::<WireframeConfig>()
4341
.init_resource::<WireframeConfig>()
44-
.add_plugins((ExtractResourcePlugin::<WireframeConfig>::default(),));
45-
46-
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
47-
render_app
48-
.add_render_command::<Opaque3d, DrawWireframes>()
49-
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
50-
.init_resource::<Wireframes>()
51-
.init_resource::<NoWireframes>()
52-
.add_systems(ExtractSchedule, extract_wireframes)
53-
.add_systems(Render, queue_wireframes.in_set(RenderSet::QueueMeshes));
54-
}
55-
}
56-
57-
fn finish(&self, app: &mut bevy_app::App) {
58-
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
59-
render_app.init_resource::<WireframePipeline>();
60-
}
42+
.add_plugins(MaterialPlugin::<WireframeMaterial>::default())
43+
.add_systems(Startup, setup_global_wireframe_material)
44+
.add_systems(
45+
Update,
46+
(apply_global_wireframe_material, apply_wireframe_material),
47+
);
6148
}
6249
}
6350

@@ -85,133 +72,103 @@ pub struct WireframeConfig {
8572
pub global: bool,
8673
}
8774

88-
#[derive(Resource, Default, Deref, DerefMut)]
89-
pub struct Wireframes(EntityHashSet<Entity>);
90-
91-
#[derive(Resource, Default, Deref, DerefMut)]
92-
pub struct NoWireframes(EntityHashSet<Entity>);
75+
#[derive(Resource)]
76+
struct GlobalWireframeMaterial {
77+
// This handle will be reused when the global config is enabled
78+
handle: Handle<WireframeMaterial>,
79+
}
9380

94-
fn extract_wireframes(
95-
mut wireframes: ResMut<Wireframes>,
96-
mut no_wireframes: ResMut<NoWireframes>,
97-
wireframe_query: Extract<Query<Entity, With<Wireframe>>>,
98-
no_wireframe_query: Extract<Query<Entity, With<NoWireframe>>>,
81+
fn setup_global_wireframe_material(
82+
mut commands: Commands,
83+
mut materials: ResMut<Assets<WireframeMaterial>>,
9984
) {
100-
wireframes.clear();
101-
wireframes.extend(wireframe_query.iter());
102-
no_wireframes.clear();
103-
no_wireframes.extend(no_wireframe_query.iter());
85+
// Create the handle used for the global material
86+
commands.insert_resource(GlobalWireframeMaterial {
87+
handle: materials.add(WireframeMaterial {}),
88+
});
10489
}
10590

106-
#[derive(Resource, Clone)]
107-
pub struct WireframePipeline {
108-
mesh_pipeline: MeshPipeline,
109-
shader: Handle<Shader>,
91+
/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component.
92+
fn apply_wireframe_material(
93+
mut commands: Commands,
94+
mut materials: ResMut<Assets<WireframeMaterial>>,
95+
wireframes: Query<Entity, (With<Wireframe>, Without<Handle<WireframeMaterial>>)>,
96+
mut removed_wireframes: RemovedComponents<Wireframe>,
97+
) {
98+
for e in removed_wireframes.read() {
99+
if let Some(mut commands) = commands.get_entity(e) {
100+
commands.remove::<Handle<WireframeMaterial>>();
101+
}
102+
}
103+
104+
let mut wireframes_to_spawn = vec![];
105+
for e in &wireframes {
106+
wireframes_to_spawn.push((e, materials.add(WireframeMaterial {})));
107+
}
108+
commands.insert_or_spawn_batch(wireframes_to_spawn);
110109
}
111-
impl FromWorld for WireframePipeline {
112-
fn from_world(render_world: &mut World) -> Self {
113-
WireframePipeline {
114-
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
115-
shader: WIREFRAME_SHADER_HANDLE,
110+
111+
/// Applies or removes a wireframe material on any mesh without a [`Wireframe`] component.
112+
#[allow(clippy::type_complexity)]
113+
fn apply_global_wireframe_material(
114+
mut commands: Commands,
115+
config: Res<WireframeConfig>,
116+
meshes_without_material: Query<
117+
Entity,
118+
(
119+
With<Handle<Mesh>>,
120+
Without<Wireframe>,
121+
Without<NoWireframe>,
122+
Without<Handle<WireframeMaterial>>,
123+
),
124+
>,
125+
meshes_with_global_material: Query<
126+
Entity,
127+
(
128+
With<Handle<Mesh>>,
129+
Without<Wireframe>,
130+
Without<NoWireframe>,
131+
With<Handle<WireframeMaterial>>,
132+
),
133+
>,
134+
global_material: Res<GlobalWireframeMaterial>,
135+
) {
136+
if !config.is_changed() {
137+
return;
138+
}
139+
140+
if config.global {
141+
let mut material_to_spawn = vec![];
142+
for e in &meshes_without_material {
143+
// We only add the material handle but not the Wireframe component
144+
// This makes it easy to detect which mesh is using the global material and which ones are user specified
145+
material_to_spawn.push((e, global_material.handle.clone()));
146+
}
147+
commands.insert_or_spawn_batch(material_to_spawn);
148+
} else if !config.global {
149+
for e in &meshes_with_global_material {
150+
commands.entity(e).remove::<Handle<WireframeMaterial>>();
116151
}
117152
}
118153
}
119154

120-
impl SpecializedMeshPipeline for WireframePipeline {
121-
type Key = MeshPipelineKey;
155+
#[derive(Default, AsBindGroup, TypeUuid, TypePath, Debug, Clone, Asset)]
156+
#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"]
157+
struct WireframeMaterial {}
158+
159+
impl Material for WireframeMaterial {
160+
fn fragment_shader() -> ShaderRef {
161+
WIREFRAME_SHADER_HANDLE.into()
162+
}
122163

123164
fn specialize(
124-
&self,
125-
key: Self::Key,
126-
layout: &MeshVertexBufferLayout,
127-
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
128-
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
129-
descriptor.vertex.shader = self.shader.clone_weak();
130-
descriptor
131-
.vertex
132-
.shader_defs
133-
.push("MESH_BINDGROUP_1".into());
134-
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
165+
_pipeline: &MaterialPipeline<Self>,
166+
descriptor: &mut RenderPipelineDescriptor,
167+
_layout: &MeshVertexBufferLayout,
168+
_key: MaterialPipelineKey<Self>,
169+
) -> Result<(), SpecializedMeshPipelineError> {
135170
descriptor.primitive.polygon_mode = PolygonMode::Line;
136171
descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0;
137-
Ok(descriptor)
138-
}
139-
}
140-
141-
#[allow(clippy::too_many_arguments)]
142-
fn queue_wireframes(
143-
opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>,
144-
render_meshes: Res<RenderAssets<Mesh>>,
145-
render_mesh_instances: Res<RenderMeshInstances>,
146-
wireframes: Res<Wireframes>,
147-
no_wireframes: Res<NoWireframes>,
148-
wireframe_config: Res<WireframeConfig>,
149-
wireframe_pipeline: Res<WireframePipeline>,
150-
mut pipelines: ResMut<SpecializedMeshPipelines<WireframePipeline>>,
151-
pipeline_cache: Res<PipelineCache>,
152-
msaa: Res<Msaa>,
153-
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
154-
) {
155-
let draw_custom = opaque_3d_draw_functions.read().id::<DrawWireframes>();
156-
let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples());
157-
for (view, visible_entities, mut opaque_phase) in &mut views {
158-
let rangefinder = view.rangefinder3d();
159-
160-
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
161-
let add_render_phase = |phase_item: (Entity, &RenderMeshInstance)| {
162-
let (entity, mesh_instance) = phase_item;
163-
164-
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
165-
return;
166-
};
167-
let mut key =
168-
view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
169-
if mesh.morph_targets.is_some() {
170-
key |= MeshPipelineKey::MORPH_TARGETS;
171-
}
172-
let pipeline_id =
173-
pipelines.specialize(&pipeline_cache, &wireframe_pipeline, key, &mesh.layout);
174-
let pipeline_id = match pipeline_id {
175-
Ok(id) => id,
176-
Err(err) => {
177-
error!("{}", err);
178-
return;
179-
}
180-
};
181-
opaque_phase.add(Opaque3d {
182-
entity,
183-
pipeline: pipeline_id,
184-
draw_function: draw_custom,
185-
distance: rangefinder
186-
.distance_translation(&mesh_instance.transforms.transform.translation),
187-
batch_range: 0..1,
188-
dynamic_offset: None,
189-
});
190-
};
191-
192-
visible_entities
193-
.entities
194-
.iter()
195-
.filter_map(|visible_entity| {
196-
if no_wireframes.get(visible_entity).is_some() {
197-
return None;
198-
}
199-
200-
if wireframe_config.global || wireframes.get(visible_entity).is_some() {
201-
render_mesh_instances
202-
.get(visible_entity)
203-
.map(|mesh_instance| (*visible_entity, mesh_instance))
204-
} else {
205-
None
206-
}
207-
})
208-
.for_each(add_render_phase);
172+
Ok(())
209173
}
210174
}
211-
212-
type DrawWireframes = (
213-
SetItemPipeline,
214-
SetMeshViewBindGroup<0>,
215-
SetMeshBindGroup<1>,
216-
DrawMesh,
217-
);

0 commit comments

Comments
 (0)