Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/manual_material.rs
6592 views
1
//! A simple 3D scene with light shining over a cube sitting on a plane.
2
3
use bevy::{
4
asset::{AsAssetId, AssetEventSystems},
5
core_pipeline::core_3d::Opaque3d,
6
ecs::system::{
7
lifetimeless::{SRes, SResMut},
8
SystemChangeTick, SystemParamItem,
9
},
10
pbr::{
11
DrawMaterial, EntitiesNeedingSpecialization, EntitySpecializationTicks,
12
MaterialBindGroupAllocator, MaterialBindGroupAllocators, MaterialDrawFunction,
13
MaterialFragmentShader, MaterialProperties, PreparedMaterial, RenderMaterialBindings,
14
RenderMaterialInstance, RenderMaterialInstances, SpecializedMaterialPipelineCache,
15
},
16
platform::collections::hash_map::Entry,
17
prelude::*,
18
render::{
19
erased_render_asset::{ErasedRenderAsset, ErasedRenderAssetPlugin, PrepareAssetError},
20
render_asset::RenderAssets,
21
render_phase::DrawFunctions,
22
render_resource::{
23
binding_types::{sampler, texture_2d},
24
AsBindGroup, BindGroupLayout, BindGroupLayoutEntries, BindingResources,
25
OwnedBindingResource, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
26
TextureSampleType, TextureViewDimension, UnpreparedBindGroup,
27
},
28
renderer::RenderDevice,
29
sync_world::MainEntity,
30
texture::GpuImage,
31
view::ExtractedView,
32
Extract, RenderApp, RenderStartup,
33
},
34
utils::Parallel,
35
};
36
use std::{any::TypeId, sync::Arc};
37
38
const SHADER_ASSET_PATH: &str = "shaders/manual_material.wgsl";
39
40
fn main() {
41
App::new()
42
.add_plugins((DefaultPlugins, ImageMaterialPlugin))
43
.add_systems(Startup, setup)
44
.run();
45
}
46
47
struct ImageMaterialPlugin;
48
49
impl Plugin for ImageMaterialPlugin {
50
fn build(&self, app: &mut App) {
51
app.init_asset::<ImageMaterial>()
52
.add_plugins(ErasedRenderAssetPlugin::<ImageMaterial>::default())
53
.add_systems(
54
PostUpdate,
55
check_entities_needing_specialization.after(AssetEventSystems),
56
)
57
.init_resource::<EntitiesNeedingSpecialization<ImageMaterial>>();
58
59
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
60
return;
61
};
62
63
render_app
64
.add_systems(RenderStartup, init_image_material_resources)
65
.add_systems(
66
ExtractSchedule,
67
(
68
extract_image_materials,
69
extract_image_materials_needing_specialization,
70
),
71
);
72
}
73
}
74
75
fn init_image_material_resources(
76
mut commands: Commands,
77
render_device: Res<RenderDevice>,
78
mut bind_group_allocators: ResMut<MaterialBindGroupAllocators>,
79
) {
80
let bind_group_layout = render_device.create_bind_group_layout(
81
"image_material_layout",
82
&BindGroupLayoutEntries::sequential(
83
ShaderStages::FRAGMENT,
84
(
85
texture_2d(TextureSampleType::Float { filterable: false }),
86
sampler(SamplerBindingType::NonFiltering),
87
),
88
),
89
);
90
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
91
commands.insert_resource(ImageMaterialBindGroupLayout(bind_group_layout.clone()));
92
commands.insert_resource(ImageMaterialBindGroupSampler(sampler));
93
94
bind_group_allocators.insert(
95
TypeId::of::<ImageMaterial>(),
96
MaterialBindGroupAllocator::new(&render_device, None, None, bind_group_layout, None),
97
);
98
}
99
100
#[derive(Resource)]
101
struct ImageMaterialBindGroupLayout(BindGroupLayout);
102
103
#[derive(Resource)]
104
struct ImageMaterialBindGroupSampler(Sampler);
105
106
#[derive(Component)]
107
struct ImageMaterial3d(Handle<ImageMaterial>);
108
109
impl AsAssetId for ImageMaterial3d {
110
type Asset = ImageMaterial;
111
112
fn as_asset_id(&self) -> AssetId<Self::Asset> {
113
self.0.id()
114
}
115
}
116
117
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
118
struct ImageMaterial {
119
image: Handle<Image>,
120
}
121
122
impl ErasedRenderAsset for ImageMaterial {
123
type SourceAsset = ImageMaterial;
124
type ErasedAsset = PreparedMaterial;
125
type Param = (
126
SRes<DrawFunctions<Opaque3d>>,
127
SRes<ImageMaterialBindGroupLayout>,
128
SRes<AssetServer>,
129
SResMut<MaterialBindGroupAllocators>,
130
SResMut<RenderMaterialBindings>,
131
SRes<RenderAssets<GpuImage>>,
132
SRes<ImageMaterialBindGroupSampler>,
133
);
134
135
fn prepare_asset(
136
source_asset: Self::SourceAsset,
137
asset_id: AssetId<Self::SourceAsset>,
138
(
139
opaque_draw_functions,
140
material_layout,
141
asset_server,
142
bind_group_allocators,
143
render_material_bindings,
144
gpu_images,
145
image_material_sampler,
146
): &mut SystemParamItem<Self::Param>,
147
) -> std::result::Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>> {
148
let material_layout = material_layout.0.clone();
149
let draw_function_id = opaque_draw_functions.read().id::<DrawMaterial>();
150
let bind_group_allocator = bind_group_allocators
151
.get_mut(&TypeId::of::<ImageMaterial>())
152
.unwrap();
153
let Some(image) = gpu_images.get(&source_asset.image) else {
154
return Err(PrepareAssetError::RetryNextUpdate(source_asset));
155
};
156
let unprepared = UnpreparedBindGroup {
157
bindings: BindingResources(vec![
158
(
159
0,
160
OwnedBindingResource::TextureView(
161
TextureViewDimension::D2,
162
image.texture_view.clone(),
163
),
164
),
165
(
166
1,
167
OwnedBindingResource::Sampler(
168
SamplerBindingType::NonFiltering,
169
image_material_sampler.0.clone(),
170
),
171
),
172
]),
173
};
174
let binding = match render_material_bindings.entry(asset_id.into()) {
175
Entry::Occupied(mut occupied_entry) => {
176
bind_group_allocator.free(*occupied_entry.get());
177
let new_binding =
178
bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
179
*occupied_entry.get_mut() = new_binding;
180
new_binding
181
}
182
Entry::Vacant(vacant_entry) => *vacant_entry
183
.insert(bind_group_allocator.allocate_unprepared(unprepared, &material_layout)),
184
};
185
186
let mut properties = MaterialProperties {
187
material_layout: Some(material_layout),
188
..Default::default()
189
};
190
properties.add_draw_function(MaterialDrawFunction, draw_function_id);
191
properties.add_shader(MaterialFragmentShader, asset_server.load(SHADER_ASSET_PATH));
192
193
Ok(PreparedMaterial {
194
binding,
195
properties: Arc::new(properties),
196
})
197
}
198
}
199
200
/// set up a simple 3D scene
201
fn setup(
202
mut commands: Commands,
203
mut meshes: ResMut<Assets<Mesh>>,
204
mut materials: ResMut<Assets<ImageMaterial>>,
205
asset_server: Res<AssetServer>,
206
) {
207
// cube
208
commands.spawn((
209
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
210
ImageMaterial3d(materials.add(ImageMaterial {
211
image: asset_server.load("branding/icon.png"),
212
})),
213
Transform::from_xyz(0.0, 0.5, 0.0),
214
));
215
// light
216
commands.spawn((
217
PointLight {
218
shadows_enabled: true,
219
..default()
220
},
221
Transform::from_xyz(4.0, 8.0, 4.0),
222
));
223
// camera
224
commands.spawn((
225
Camera3d::default(),
226
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
227
));
228
}
229
230
fn extract_image_materials(
231
mut material_instances: ResMut<RenderMaterialInstances>,
232
changed_meshes_query: Extract<
233
Query<
234
(Entity, &ViewVisibility, &ImageMaterial3d),
235
Or<(Changed<ViewVisibility>, Changed<ImageMaterial3d>)>,
236
>,
237
>,
238
) {
239
let last_change_tick = material_instances.current_change_tick;
240
241
for (entity, view_visibility, material) in &changed_meshes_query {
242
if view_visibility.get() {
243
material_instances.instances.insert(
244
entity.into(),
245
RenderMaterialInstance {
246
asset_id: material.0.id().untyped(),
247
last_change_tick,
248
},
249
);
250
} else {
251
material_instances
252
.instances
253
.remove(&MainEntity::from(entity));
254
}
255
}
256
}
257
258
fn check_entities_needing_specialization(
259
needs_specialization: Query<
260
Entity,
261
(
262
Or<(
263
Changed<Mesh3d>,
264
AssetChanged<Mesh3d>,
265
Changed<ImageMaterial3d>,
266
AssetChanged<ImageMaterial3d>,
267
)>,
268
With<ImageMaterial3d>,
269
),
270
>,
271
mut par_local: Local<Parallel<Vec<Entity>>>,
272
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<ImageMaterial>>,
273
) {
274
entities_needing_specialization.clear();
275
276
needs_specialization
277
.par_iter()
278
.for_each(|entity| par_local.borrow_local_mut().push(entity));
279
280
par_local.drain_into(&mut entities_needing_specialization);
281
}
282
283
fn extract_image_materials_needing_specialization(
284
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<ImageMaterial>>>,
285
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
286
mut removed_mesh_material_components: Extract<RemovedComponents<ImageMaterial3d>>,
287
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
288
views: Query<&ExtractedView>,
289
ticks: SystemChangeTick,
290
) {
291
// Clean up any despawned entities, we do this first in case the removed material was re-added
292
// the same frame, thus will appear both in the removed components list and have been added to
293
// the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter
294
for entity in removed_mesh_material_components.read() {
295
entity_specialization_ticks.remove(&MainEntity::from(entity));
296
for view in views {
297
if let Some(cache) =
298
specialized_material_pipeline_cache.get_mut(&view.retained_view_entity)
299
{
300
cache.remove(&MainEntity::from(entity));
301
}
302
}
303
}
304
305
for entity in entities_needing_specialization.iter() {
306
// Update the entity's specialization tick with this run's tick
307
entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
308
}
309
}
310
311