Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/shader_advanced/specialized_mesh_pipeline.rs
6849 views
1
//! Demonstrates how to define and use specialized mesh pipeline
2
//!
3
//! This example shows how to use the built-in [`SpecializedMeshPipeline`]
4
//! functionality with a custom [`RenderCommand`] to allow custom mesh rendering with
5
//! more flexibility than the material api.
6
//!
7
//! [`SpecializedMeshPipeline`] let's you customize the entire pipeline used when rendering a mesh.
8
9
use bevy::{
10
asset::RenderAssetUsages,
11
camera::visibility::{self, VisibilityClass},
12
core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT},
13
ecs::component::Tick,
14
math::{vec3, vec4},
15
mesh::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology},
16
pbr::{
17
DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, RenderMeshInstances,
18
SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewEmptyBindGroup,
19
},
20
prelude::*,
21
render::{
22
batching::gpu_preprocessing::GpuPreprocessingSupport,
23
extract_component::{ExtractComponent, ExtractComponentPlugin},
24
mesh::{allocator::MeshAllocator, RenderMesh},
25
render_asset::RenderAssets,
26
render_phase::{
27
AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, SetItemPipeline,
28
ViewBinnedRenderPhases,
29
},
30
render_resource::{
31
ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, Face, FragmentState,
32
FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState,
33
RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError,
34
SpecializedMeshPipelines, TextureFormat, VertexState,
35
},
36
view::{ExtractedView, RenderVisibleEntities, ViewTarget},
37
Render, RenderApp, RenderStartup, RenderSystems,
38
},
39
};
40
41
const SHADER_ASSET_PATH: &str = "shaders/specialized_mesh_pipeline.wgsl";
42
43
fn main() {
44
App::new()
45
.add_plugins(DefaultPlugins)
46
.add_plugins(CustomRenderedMeshPipelinePlugin)
47
.add_systems(Startup, setup)
48
.run();
49
}
50
51
/// Spawns the objects in the scene.
52
fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
53
// Build a custom triangle mesh with colors
54
// We define a custom mesh because the examples only uses a limited
55
// set of vertex attributes for simplicity
56
let mesh = Mesh::new(
57
PrimitiveTopology::TriangleList,
58
RenderAssetUsages::default(),
59
)
60
.with_inserted_indices(Indices::U32(vec![0, 1, 2]))
61
.with_inserted_attribute(
62
Mesh::ATTRIBUTE_POSITION,
63
vec![
64
vec3(-0.5, -0.5, 0.0),
65
vec3(0.5, -0.5, 0.0),
66
vec3(0.0, 0.25, 0.0),
67
],
68
)
69
.with_inserted_attribute(
70
Mesh::ATTRIBUTE_COLOR,
71
vec![
72
vec4(1.0, 0.0, 0.0, 1.0),
73
vec4(0.0, 1.0, 0.0, 1.0),
74
vec4(0.0, 0.0, 1.0, 1.0),
75
],
76
);
77
78
// spawn 3 triangles to show that batching works
79
for (x, y) in [-0.5, 0.0, 0.5].into_iter().zip([-0.25, 0.5, -0.25]) {
80
// Spawn an entity with all the required components for it to be rendered with our custom pipeline
81
commands.spawn((
82
// We use a marker component to identify the mesh that will be rendered
83
// with our specialized pipeline
84
CustomRenderedEntity,
85
// We need to add the mesh handle to the entity
86
Mesh3d(meshes.add(mesh.clone())),
87
Transform::from_xyz(x, y, 0.0),
88
));
89
}
90
91
// Spawn the camera.
92
commands.spawn((
93
Camera3d::default(),
94
// Move the camera back a bit to see all the triangles
95
Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
96
));
97
}
98
99
// When writing custom rendering code it's generally recommended to use a plugin.
100
// The main reason for this is that it gives you access to the finish() hook
101
// which is called after rendering resources are initialized.
102
struct CustomRenderedMeshPipelinePlugin;
103
impl Plugin for CustomRenderedMeshPipelinePlugin {
104
fn build(&self, app: &mut App) {
105
app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default());
106
107
// We make sure to add these to the render app, not the main app.
108
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
109
return;
110
};
111
render_app
112
// This is needed to tell bevy about your custom pipeline
113
.init_resource::<SpecializedMeshPipelines<CustomMeshPipeline>>()
114
// We need to use a custom draw command so we need to register it
115
.add_render_command::<Opaque3d, DrawSpecializedPipelineCommands>()
116
.add_systems(RenderStartup, init_custom_mesh_pipeline)
117
.add_systems(
118
Render,
119
queue_custom_mesh_pipeline.in_set(RenderSystems::Queue),
120
);
121
}
122
}
123
124
/// A marker component that represents an entity that is to be rendered using
125
/// our specialized pipeline.
126
///
127
/// Note the [`ExtractComponent`] trait implementation: this is necessary to
128
/// tell Bevy that this object should be pulled into the render world. Also note
129
/// the `on_add` hook, which is needed to tell Bevy's `check_visibility` system
130
/// that entities with this component need to be examined for visibility.
131
#[derive(Clone, Component, ExtractComponent)]
132
#[require(VisibilityClass)]
133
#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]
134
struct CustomRenderedEntity;
135
136
/// The custom draw commands that Bevy executes for each entity we enqueue into
137
/// the render phase.
138
type DrawSpecializedPipelineCommands = (
139
// Set the pipeline
140
SetItemPipeline,
141
// Set the view uniform at bind group 0
142
SetMeshViewBindGroup<0>,
143
// Set an empty material bind group at bind group 1
144
SetMeshViewEmptyBindGroup<1>,
145
// Set the mesh uniform at bind group 2
146
SetMeshBindGroup<2>,
147
// Draw the mesh
148
DrawMesh,
149
);
150
151
// This contains the state needed to specialize a mesh pipeline
152
#[derive(Resource)]
153
struct CustomMeshPipeline {
154
/// The base mesh pipeline defined by bevy
155
///
156
/// This isn't required, but if you want to use a bevy `Mesh` it's easier when you
157
/// have access to the base `MeshPipeline` that bevy already defines
158
mesh_pipeline: MeshPipeline,
159
/// Stores the shader used for this pipeline directly on the pipeline.
160
/// This isn't required, it's only done like this for simplicity.
161
shader_handle: Handle<Shader>,
162
}
163
164
fn init_custom_mesh_pipeline(
165
mut commands: Commands,
166
asset_server: Res<AssetServer>,
167
mesh_pipeline: Res<MeshPipeline>,
168
) {
169
// Load the shader
170
let shader_handle: Handle<Shader> = asset_server.load(SHADER_ASSET_PATH);
171
commands.insert_resource(CustomMeshPipeline {
172
mesh_pipeline: mesh_pipeline.clone(),
173
shader_handle,
174
});
175
}
176
177
impl SpecializedMeshPipeline for CustomMeshPipeline {
178
/// Pipeline use keys to determine how to specialize it.
179
/// The key is also used by the pipeline cache to determine if
180
/// it needs to create a new pipeline or not
181
///
182
/// In this example we just use the base `MeshPipelineKey` defined by bevy, but this could be anything.
183
/// For example, if you want to make a pipeline with a procedural shader you could add the Handle<Shader> to the key.
184
type Key = MeshPipelineKey;
185
186
fn specialize(
187
&self,
188
mesh_key: Self::Key,
189
layout: &MeshVertexBufferLayoutRef,
190
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
191
// Define the vertex attributes based on a standard bevy [`Mesh`]
192
let mut vertex_attributes = Vec::new();
193
if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
194
// Make sure this matches the shader location
195
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
196
}
197
if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
198
// Make sure this matches the shader location
199
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(1));
200
}
201
// This will automatically generate the correct `VertexBufferLayout` based on the vertex attributes
202
let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
203
204
let view_layout = self
205
.mesh_pipeline
206
.get_view_layout(MeshPipelineViewLayoutKey::from(mesh_key));
207
208
Ok(RenderPipelineDescriptor {
209
label: Some("Specialized Mesh Pipeline".into()),
210
layout: vec![
211
view_layout.main_layout.clone(),
212
view_layout.empty_layout.clone(),
213
self.mesh_pipeline.mesh_layouts.model_only.clone(),
214
],
215
vertex: VertexState {
216
shader: self.shader_handle.clone(),
217
// Customize how to store the meshes' vertex attributes in the vertex buffer
218
buffers: vec![vertex_buffer_layout],
219
..default()
220
},
221
fragment: Some(FragmentState {
222
shader: self.shader_handle.clone(),
223
targets: vec![Some(ColorTargetState {
224
// This isn't required, but bevy supports HDR and non-HDR rendering
225
// so it's generally recommended to specialize the pipeline for that
226
format: if mesh_key.contains(MeshPipelineKey::HDR) {
227
ViewTarget::TEXTURE_FORMAT_HDR
228
} else {
229
TextureFormat::bevy_default()
230
},
231
// For this example we only use opaque meshes,
232
// but if you wanted to use alpha blending you would need to set it here
233
blend: None,
234
write_mask: ColorWrites::ALL,
235
})],
236
..default()
237
}),
238
primitive: PrimitiveState {
239
topology: mesh_key.primitive_topology(),
240
front_face: FrontFace::Ccw,
241
cull_mode: Some(Face::Back),
242
polygon_mode: PolygonMode::Fill,
243
..default()
244
},
245
// Note that if your view has no depth buffer this will need to be
246
// changed.
247
depth_stencil: Some(DepthStencilState {
248
format: CORE_3D_DEPTH_FORMAT,
249
depth_write_enabled: true,
250
depth_compare: CompareFunction::GreaterEqual,
251
stencil: default(),
252
bias: default(),
253
}),
254
// It's generally recommended to specialize your pipeline for MSAA,
255
// but it's not always possible
256
multisample: MultisampleState {
257
count: mesh_key.msaa_samples(),
258
..default()
259
},
260
..default()
261
})
262
}
263
}
264
265
/// A render-world system that enqueues the entity with custom rendering into
266
/// the opaque render phases of each view.
267
fn queue_custom_mesh_pipeline(
268
pipeline_cache: Res<PipelineCache>,
269
custom_mesh_pipeline: Res<CustomMeshPipeline>,
270
(mut opaque_render_phases, opaque_draw_functions): (
271
ResMut<ViewBinnedRenderPhases<Opaque3d>>,
272
Res<DrawFunctions<Opaque3d>>,
273
),
274
mut specialized_mesh_pipelines: ResMut<SpecializedMeshPipelines<CustomMeshPipeline>>,
275
views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,
276
(render_meshes, render_mesh_instances): (
277
Res<RenderAssets<RenderMesh>>,
278
Res<RenderMeshInstances>,
279
),
280
mut change_tick: Local<Tick>,
281
mesh_allocator: Res<MeshAllocator>,
282
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
283
) {
284
// Get the id for our custom draw function
285
let draw_function = opaque_draw_functions
286
.read()
287
.id::<DrawSpecializedPipelineCommands>();
288
289
// Render phases are per-view, so we need to iterate over all views so that
290
// the entity appears in them. (In this example, we have only one view, but
291
// it's good practice to loop over all views anyway.)
292
for (view_visible_entities, view, msaa) in views.iter() {
293
let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
294
continue;
295
};
296
297
// Create the key based on the view. In this case we only care about MSAA and HDR
298
let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
299
| MeshPipelineKey::from_hdr(view.hdr);
300
301
// Find all the custom rendered entities that are visible from this
302
// view.
303
for &(render_entity, visible_entity) in
304
view_visible_entities.get::<CustomRenderedEntity>().iter()
305
{
306
// Get the mesh instance
307
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(visible_entity)
308
else {
309
continue;
310
};
311
312
// Get the mesh data
313
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
314
continue;
315
};
316
317
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
318
319
// Specialize the key for the current mesh entity
320
// For this example we only specialize based on the mesh topology
321
// but you could have more complex keys and that's where you'd need to create those keys
322
let mut mesh_key = view_key;
323
mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());
324
325
// Finally, we can specialize the pipeline based on the key
326
let pipeline_id = specialized_mesh_pipelines
327
.specialize(
328
&pipeline_cache,
329
&custom_mesh_pipeline,
330
mesh_key,
331
&mesh.layout,
332
)
333
// This should never happen with this example, but if your pipeline
334
// specialization can fail you need to handle the error here
335
.expect("Failed to specialize mesh pipeline");
336
337
// Bump the change tick so that Bevy is forced to rebuild the bin.
338
let next_change_tick = change_tick.get() + 1;
339
change_tick.set(next_change_tick);
340
341
// Add the mesh with our specialized pipeline
342
opaque_phase.add(
343
Opaque3dBatchSetKey {
344
draw_function,
345
pipeline: pipeline_id,
346
material_bind_group_index: None,
347
vertex_slab: vertex_slab.unwrap_or_default(),
348
index_slab,
349
lightmap_slab: None,
350
},
351
// For this example we can use the mesh asset id as the bin key,
352
// but you can use any asset_id as a key
353
Opaque3dBinKey {
354
asset_id: mesh_instance.mesh_asset_id.into(),
355
},
356
(render_entity, visible_entity),
357
mesh_instance.current_uniform_index,
358
// This example supports batching and multi draw indirect,
359
// but if your pipeline doesn't support it you can use
360
// `BinnedRenderPhaseType::UnbatchableMesh`
361
BinnedRenderPhaseType::mesh(
362
mesh_instance.should_batch(),
363
&gpu_preprocessing_support,
364
),
365
*change_tick,
366
);
367
}
368
}
369
}
370
371