1use std::fmt;
4use std::ops::Range;
5
6use bevy::{
7 anti_alias::taa::TemporalAntiAliasing,
8 camera::Hdr,
9 color::palettes::css::{BLACK, WHITE},
10 feathers::{
11 controls::{FeathersNumberInput, HardLimit, NumberInputPrecision, NumberInputValue},
12 display::{label, label_small},
13 theme::UiTheme,
14 FeathersPlugins,
15 },
16 image::{
17 ImageAddressMode, ImageFilterMode, ImageLoaderSettings, ImageSampler,
18 ImageSamplerDescriptor,
19 },
20 input::mouse::MouseWheel,
21 light::Skybox,
22 math::{vec3, vec4},
23 pbr::{
24 DefaultOpaqueRendererMethod, ExtendedMaterial, MaterialExtension,
25 ScreenSpaceAmbientOcclusion, ScreenSpaceReflections,
26 },
27 prelude::*,
28 render::render_resource::{AsBindGroup, ShaderType},
29 shader::ShaderRef,
30 ui_widgets::{radio_self_update, ValueChange},
31};
32
33#[path = "../helpers/radio.rs"]
34mod radio;
35
36#[path = "../helpers/theme.rs"]
37mod theme;
38
39use radio::{feathers_option_buttons, main_ui_node_scene};
40use theme::basic_example_theme;
41
42const SHADER_ASSET_PATH: &str = "shaders/water_material.wesl";
44
45const CAMERA_KEYBOARD_ZOOM_SPEED: f32 = 0.1;
47const CAMERA_KEYBOARD_ORBIT_SPEED: f32 = 0.02;
48const CAMERA_MOUSE_WHEEL_ZOOM_SPEED: f32 = 0.25;
49
50const CAMERA_ZOOM_RANGE: Range<f32> = 2.0..12.0;
52
53#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
55struct Water {
56 #[texture(100)]
60 #[sampler(101)]
61 normals: Handle<Image>,
62
63 #[uniform(102)]
65 settings: WaterSettings,
66}
67
68#[derive(ShaderType, Debug, Clone)]
70struct WaterSettings {
71 octave_vectors: [Vec4; 2],
74 octave_scales: Vec4,
76 octave_strengths: Vec4,
78}
79
80#[derive(Resource)]
82struct AppSettings {
83 ssr_on: SsrOn,
85 displayed_model: DisplayedModel,
87 displayed_base: DisplayedBase,
89 min_perceptual_roughness: Range<f32>,
91 max_perceptual_roughness: Range<f32>,
93 edge_fadeout: Range<f32>,
95}
96
97impl Default for AppSettings {
98 fn default() -> Self {
99 Self {
100 ssr_on: default(),
101 displayed_model: default(),
102 displayed_base: default(),
103 min_perceptual_roughness: 0.0..0.01,
104 max_perceptual_roughness: 0.99..1.0,
105 edge_fadeout: 0.0..0.0,
106 }
107 }
108}
109
110#[derive(Clone, Copy, Deref, PartialEq)]
112pub struct SsrOn(bool);
113
114impl Default for SsrOn {
115 fn default() -> Self {
116 Self(true)
117 }
118}
119
120#[derive(Default, PartialEq, Copy, Clone)]
122enum DisplayedModel {
123 #[default]
125 Cube,
126 FlightHelmet,
128 Capsules,
130}
131
132#[derive(Default, PartialEq, Copy, Clone)]
134enum DisplayedBase {
135 #[default]
137 Water,
138 Metallic,
140 RedPlane,
142}
143
144impl fmt::Display for DisplayedModel {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 let name = match self {
147 DisplayedModel::Cube => "Cube",
148 DisplayedModel::FlightHelmet => "Flight Helmet",
149 DisplayedModel::Capsules => "Capsules",
150 };
151 write!(f, "{}", name)
152 }
153}
154
155impl fmt::Display for DisplayedBase {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 let name = match self {
158 DisplayedBase::Water => "Water",
159 DisplayedBase::Metallic => "Metallic",
160 DisplayedBase::RedPlane => "Red Plane",
161 };
162 write!(f, "{}", name)
163 }
164}
165
166#[derive(Component)]
168struct CubeModel;
169
170#[derive(Component)]
172struct FlightHelmetModel;
173
174#[derive(Component)]
176struct CapsuleModel;
177
178#[derive(Component)]
180struct CapsulesParent;
181
182#[derive(Component)]
184struct MetallicBaseModel;
185
186#[derive(Component)]
188struct RedPlaneBaseModel;
189
190#[derive(Component)]
192struct WaterModel;
193
194#[derive(Component, Clone, Copy, PartialEq, Default)]
198enum AppNumberInput {
199 #[default]
200 MinRoughnessStart,
201 MinRoughnessEnd,
202 MaxRoughnessStart,
203 MaxRoughnessEnd,
204 EdgeFadeoutStart,
205 EdgeFadeoutEnd,
206}
207
208#[derive(bevy::ecs::system::SystemParam)]
209struct ModelQueries<'w, 's> {
210 cube_models: Query<'w, 's, Entity, With<CubeModel>>,
211 flight_helmet_models: Query<'w, 's, Entity, With<FlightHelmetModel>>,
212 capsule_models: Query<'w, 's, Entity, Or<(With<CapsuleModel>, With<CapsulesParent>)>>,
213 metallic_base_models: Query<'w, 's, Entity, With<MetallicBaseModel>>,
214 non_metallic_base_models: Query<'w, 's, Entity, With<RedPlaneBaseModel>>,
215 water_models: Query<'w, 's, Entity, With<WaterModel>>,
216}
217
218fn main() {
219 App::new()
223 .insert_resource(DefaultOpaqueRendererMethod::deferred())
224 .insert_resource(UiTheme(basic_example_theme(Color::WHITE)))
225 .init_resource::<AppSettings>()
226 .add_plugins((
227 DefaultPlugins.set(WindowPlugin {
228 primary_window: Some(Window {
229 title: "Bevy Screen Space Reflections Example".into(),
230 ..default()
231 }),
232 ..default()
233 }),
234 FeathersPlugins,
235 ))
236 .add_plugins(MaterialPlugin::<ExtendedMaterial<StandardMaterial, Water>>::default())
237 .add_systems(Startup, setup)
238 .add_systems(Update, rotate_model)
239 .add_systems(Update, move_camera)
240 .add_observer(handle_value_change_ssr_on)
241 .add_observer(handle_value_change_displayed_model)
242 .add_observer(handle_value_change_displayed_base)
243 .add_observer(radio_self_update)
244 .run();
245}
246
247fn setup(
249 mut commands: Commands,
250 mut meshes: ResMut<Assets<Mesh>>,
251 mut standard_materials: ResMut<Assets<StandardMaterial>>,
252 mut water_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, Water>>>,
253 asset_server: Res<AssetServer>,
254 app_settings: Res<AppSettings>,
255) {
256 spawn_cube(
257 &mut commands,
258 &asset_server,
259 &mut meshes,
260 &mut standard_materials,
261 );
262 spawn_flight_helmet(&mut commands, &asset_server);
263 spawn_capsules(&mut commands, &mut meshes, &mut standard_materials);
264 spawn_metallic_base(&mut commands, &mut meshes, &mut standard_materials);
265 spawn_non_metallic_base(&mut commands, &mut meshes, &mut standard_materials);
266 spawn_water(
267 &mut commands,
268 &asset_server,
269 &mut meshes,
270 &mut water_materials,
271 );
272 spawn_camera(&mut commands, &asset_server, &app_settings);
273 spawn_buttons(&mut commands, &app_settings);
274}
275
276fn spawn_cube(
278 commands: &mut Commands,
279 asset_server: &AssetServer,
280 meshes: &mut Assets<Mesh>,
281 standard_materials: &mut Assets<StandardMaterial>,
282) {
283 commands
284 .spawn((
285 Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
286 MeshMaterial3d(standard_materials.add(StandardMaterial {
287 base_color: Color::from(WHITE),
288 base_color_texture: Some(asset_server.load("branding/icon.png")),
289 ..default()
290 })),
291 Transform::from_xyz(0.0, 0.5, 0.0),
292 ))
293 .insert(CubeModel);
294}
295
296fn spawn_flight_helmet(commands: &mut Commands, asset_server: &AssetServer) {
298 commands.spawn((
299 WorldAssetRoot(
300 asset_server
301 .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
302 ),
303 Transform::from_scale(Vec3::splat(2.5)),
304 FlightHelmetModel,
305 Visibility::Hidden,
306 ));
307}
308
309fn spawn_capsules(
311 commands: &mut Commands,
312 meshes: &mut Assets<Mesh>,
313 standard_materials: &mut Assets<StandardMaterial>,
314) {
315 let capsule_mesh = meshes.add(Capsule3d::new(0.4, 0.5));
316 let parent = commands
317 .spawn((
318 Transform::from_xyz(0.0, 0.5, 0.0),
319 Visibility::Hidden,
320 CapsulesParent,
321 ))
322 .id();
323
324 for i in 0..5 {
325 let roughness = i as f32 * 0.25;
326 let child = commands
327 .spawn((
328 Mesh3d(capsule_mesh.clone()),
329 MeshMaterial3d(standard_materials.add(StandardMaterial {
330 base_color: Color::BLACK,
331 perceptual_roughness: roughness.max(0.08),
332 ..default()
333 })),
334 Transform::from_xyz(i as f32 * 1.1 - (1.1 * 2.0), 0.5, 0.0),
335 CapsuleModel,
336 ))
337 .id();
338 commands.entity(parent).add_child(child);
339 }
340}
341
342fn spawn_metallic_base(
344 commands: &mut Commands,
345 meshes: &mut Assets<Mesh>,
346 standard_materials: &mut Assets<StandardMaterial>,
347) {
348 commands.spawn((
349 Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))),
350 MeshMaterial3d(standard_materials.add(StandardMaterial {
351 base_color: Color::from(bevy::color::palettes::css::DARK_GRAY),
352 metallic: 1.0,
353 perceptual_roughness: 0.3,
354 ..default()
355 })),
356 Transform::from_scale(Vec3::splat(100.0)),
357 MetallicBaseModel,
358 Visibility::Hidden,
359 ));
360}
361
362fn spawn_non_metallic_base(
364 commands: &mut Commands,
365 meshes: &mut Assets<Mesh>,
366 standard_materials: &mut Assets<StandardMaterial>,
367) {
368 commands.spawn((
369 Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))),
370 MeshMaterial3d(standard_materials.add(StandardMaterial {
371 base_color: Color::from(bevy::color::palettes::css::RED),
372 metallic: 0.0,
373 perceptual_roughness: 0.2,
374 ..default()
375 })),
376 Transform::from_scale(Vec3::splat(100.0)),
377 RedPlaneBaseModel,
378 Visibility::Hidden,
379 ));
380}
381
382fn spawn_water(
384 commands: &mut Commands,
385 asset_server: &AssetServer,
386 meshes: &mut Assets<Mesh>,
387 water_materials: &mut Assets<ExtendedMaterial<StandardMaterial, Water>>,
388) {
389 commands.spawn((
390 Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))),
391 MeshMaterial3d(
392 water_materials.add(ExtendedMaterial {
393 base: StandardMaterial {
394 base_color: BLACK.into(),
395 perceptual_roughness: 0.09,
396 ..default()
397 },
398 extension: Water {
399 normals: asset_server
400 .load_builder()
401 .with_settings::<ImageLoaderSettings>(|settings| {
402 settings.is_srgb = false;
403 settings.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
404 address_mode_u: ImageAddressMode::Repeat,
405 address_mode_v: ImageAddressMode::Repeat,
406 mag_filter: ImageFilterMode::Linear,
407 min_filter: ImageFilterMode::Linear,
408 ..default()
409 });
410 })
411 .load("textures/water_normals.png"),
412 settings: WaterSettings {
415 octave_vectors: [
416 vec4(0.080, 0.059, 0.073, -0.062),
417 vec4(0.153, 0.138, -0.149, -0.195),
418 ],
419 octave_scales: vec4(1.0, 2.1, 7.9, 14.9) * 5.0,
420 octave_strengths: vec4(0.16, 0.18, 0.093, 0.044),
421 },
422 },
423 }),
424 ),
425 Transform::from_scale(Vec3::splat(100.0)),
426 WaterModel,
427 ));
428}
429
430fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer, app_settings: &AppSettings) {
432 commands.spawn((
437 Camera3d::default(),
438 Transform::from_translation(vec3(-1.25, 2.25, 4.5)).looking_at(Vec3::ZERO, Vec3::Y),
439 Hdr,
440 Msaa::Off,
441 TemporalAntiAliasing::default(),
442 ScreenSpaceReflections {
443 min_perceptual_roughness: app_settings.min_perceptual_roughness.clone(),
444 max_perceptual_roughness: app_settings.max_perceptual_roughness.clone(),
445 edge_fadeout: app_settings.edge_fadeout.clone(),
446 ..default()
447 },
448 ScreenSpaceAmbientOcclusion::default(),
449 EnvironmentMapLight {
450 diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
451 specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
452 intensity: 5000.0,
453 ..default()
454 },
455 Skybox {
456 image: Some(asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2")),
457 brightness: 5000.0,
458 ..default()
459 },
460 ));
461}
462
463fn spawn_buttons(commands: &mut Commands, app_settings: &AppSettings) {
464 commands.spawn_scene(bsn! {
465 @main_ui_node_scene()
466 Children[
467 @feathers_option_buttons(
468 "SSR",
469 &[
470 (SsrOn(true), "On"),
471 (SsrOn(false), "Off"),
472 ],
473 0,
474 )
475 --
476 @feathers_option_buttons(
477 "Model",
478 &[
479 (DisplayedModel::Cube, "Cube"),
480 (
481 DisplayedModel::FlightHelmet,
482 "Flight Helmet",
483 ),
484 (DisplayedModel::Capsules, "Capsules"),
485 ],
486 0,
487 )
488 --
489 @feathers_option_buttons(
490 "Base",
491 &[
492 (DisplayedBase::Water, "Water"),
493 (DisplayedBase::Metallic, "Metallic"),
494 (DisplayedBase::RedPlane, "Red Plane"),
495 ],
496 0,
497 )
498 --
499 @range_row(
500 "Min Roughness",
501 app_settings.min_perceptual_roughness.start,
502 app_settings.min_perceptual_roughness.end,
503 AppNumberInput::MinRoughnessStart,
504 AppNumberInput::MinRoughnessEnd,
505 )
506 --
507 @range_row(
508 "Max Roughness",
509 app_settings.max_perceptual_roughness.start,
510 app_settings.max_perceptual_roughness.end,
511 AppNumberInput::MaxRoughnessStart,
512 AppNumberInput::MaxRoughnessEnd,
513 )
514 --
515 @range_row(
516 "Edge Fadeout",
517 app_settings.edge_fadeout.start,
518 app_settings.edge_fadeout.end,
519 AppNumberInput::EdgeFadeoutStart,
520 AppNumberInput::EdgeFadeoutEnd,
521 )
522 ]
523 });
524}
525
526fn range_row(
527 title: &str,
528 start_value: f32,
529 end_value: f32,
530 start_number_input: AppNumberInput,
531 end_number_input: AppNumberInput,
532) -> impl Scene {
533 bsn! {
534 Node {
535 align_items: AlignItems::Center,
536 }
537 Children[
538 Node {
539 width: px(150),
540 }
541 Children[
542 @label(title.to_string())
543 ]
544 --
545 @range_controls(
546 start_value,
547 start_number_input
548 )
549 --
550 Node {
551 margin: UiRect::horizontal(px(10)),
552 }
553 Children [
554 @label_small("to".to_string())
555 ]
556 --
557 @range_controls(end_value, end_number_input)
558 ]
559 }
560}
561
562fn range_controls(value: f32, app_number_input: AppNumberInput) -> impl Scene {
563 bsn! {
564 @FeathersNumberInput
565 NumberInputValue::F32(value)
566 app_number_input
567 NumberInputPrecision(3)
568 HardLimit::f32(0.0..=1.0)
569 Node {
570 align_items: AlignItems::Center,
571 }
572 on(handle_value_change_number_input)
573 }
574}
575
576fn rotate_model(
577 mut query: Query<&mut Transform, Or<(With<CubeModel>, With<FlightHelmetModel>)>>,
578 time: Res<Time>,
579) {
580 for mut transform in query.iter_mut() {
581 transform.rotation = Quat::from_rotation_y(time.elapsed_secs());
583 }
584}
585
586fn move_camera(
588 keyboard_input: Res<ButtonInput<KeyCode>>,
589 mut mouse_wheel_reader: MessageReader<MouseWheel>,
590 mut cameras: Query<&mut Transform, With<Camera>>,
591) {
592 let (mut distance_delta, mut theta_delta) = (0.0, 0.0);
593
594 if keyboard_input.pressed(KeyCode::KeyW) {
596 distance_delta -= CAMERA_KEYBOARD_ZOOM_SPEED;
597 }
598 if keyboard_input.pressed(KeyCode::KeyS) {
599 distance_delta += CAMERA_KEYBOARD_ZOOM_SPEED;
600 }
601 if keyboard_input.pressed(KeyCode::KeyA) {
602 theta_delta += CAMERA_KEYBOARD_ORBIT_SPEED;
603 }
604 if keyboard_input.pressed(KeyCode::KeyD) {
605 theta_delta -= CAMERA_KEYBOARD_ORBIT_SPEED;
606 }
607
608 for mouse_wheel in mouse_wheel_reader.read() {
610 distance_delta -= mouse_wheel.y * CAMERA_MOUSE_WHEEL_ZOOM_SPEED;
611 }
612
613 for mut camera_transform in cameras.iter_mut() {
615 let local_z = camera_transform.local_z().as_vec3().normalize_or_zero();
616 if distance_delta != 0.0 {
617 camera_transform.translation = (camera_transform.translation.length() + distance_delta)
618 .clamp(CAMERA_ZOOM_RANGE.start, CAMERA_ZOOM_RANGE.end)
619 * local_z;
620 }
621 if theta_delta != 0.0 {
622 camera_transform
623 .translate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, theta_delta));
624 camera_transform.look_at(Vec3::ZERO, Vec3::Y);
625 }
626 }
627}
628
629fn handle_value_change_ssr_on(
631 event: On<ValueChange<Entity>>,
632 new_value_query: Query<&radio::RadioButtonOptionValue<SsrOn>>,
633 commands: Commands,
634 mut app_settings: ResMut<AppSettings>,
635 cameras: Query<Entity, With<Camera>>,
636) {
637 let Ok(radio::RadioButtonOptionValue(ssr_on)) = new_value_query.get(event.value) else {
638 return;
639 };
640 app_settings.ssr_on = *ssr_on;
641
642 update_views(commands, app_settings, cameras);
643}
644
645fn update_views(
647 mut commands: Commands,
648 app_settings: ResMut<AppSettings>,
649 mut cameras: Query<Entity, With<Camera>>,
650) {
651 for camera in cameras.iter_mut() {
652 if app_settings.ssr_on.0 {
653 commands.entity(camera).insert(ScreenSpaceReflections {
654 min_perceptual_roughness: app_settings.min_perceptual_roughness.clone(),
655 max_perceptual_roughness: app_settings.max_perceptual_roughness.clone(),
656 edge_fadeout: app_settings.edge_fadeout.clone(),
657 ..default()
658 });
659 } else {
660 commands.entity(camera).remove::<ScreenSpaceReflections>();
661 }
662 }
663}
664
665fn handle_value_change_displayed_model(
667 event: On<ValueChange<Entity>>,
668 new_value_query: Query<&radio::RadioButtonOptionValue<DisplayedModel>>,
669 mut app_settings: ResMut<AppSettings>,
670 model_queries: ModelQueries,
671 mut visibilities: Query<&mut Visibility>,
672) {
673 let Ok(radio::RadioButtonOptionValue(displayed_model)) = new_value_query.get(event.value)
674 else {
675 return;
676 };
677 app_settings.displayed_model = *displayed_model;
678
679 for entity in model_queries.cube_models.iter() {
680 if let Ok(mut visibility) = visibilities.get_mut(entity) {
681 *visibility = if app_settings.displayed_model == DisplayedModel::Cube {
682 Visibility::Visible
683 } else {
684 Visibility::Hidden
685 };
686 }
687 }
688 for entity in model_queries.flight_helmet_models.iter() {
689 if let Ok(mut visibility) = visibilities.get_mut(entity) {
690 *visibility = if app_settings.displayed_model == DisplayedModel::FlightHelmet {
691 Visibility::Visible
692 } else {
693 Visibility::Hidden
694 };
695 }
696 }
697 for entity in model_queries.capsule_models.iter() {
698 if let Ok(mut visibility) = visibilities.get_mut(entity) {
699 *visibility = if app_settings.displayed_model == DisplayedModel::Capsules {
700 Visibility::Visible
701 } else {
702 Visibility::Hidden
703 };
704 }
705 }
706}
707
708fn handle_value_change_displayed_base(
710 event: On<ValueChange<Entity>>,
711 new_value_query: Query<&radio::RadioButtonOptionValue<DisplayedBase>>,
712 mut app_settings: ResMut<AppSettings>,
713 model_queries: ModelQueries,
714 mut visibilities: Query<&mut Visibility>,
715) {
716 let Ok(radio::RadioButtonOptionValue(displayed_base)) = new_value_query.get(event.value) else {
717 return;
718 };
719 app_settings.displayed_base = *displayed_base;
720
721 for entity in model_queries.metallic_base_models.iter() {
722 if let Ok(mut visibility) = visibilities.get_mut(entity) {
723 *visibility = if app_settings.displayed_base == DisplayedBase::Metallic {
724 Visibility::Visible
725 } else {
726 Visibility::Hidden
727 };
728 }
729 }
730 for entity in model_queries.non_metallic_base_models.iter() {
731 if let Ok(mut visibility) = visibilities.get_mut(entity) {
732 *visibility = if app_settings.displayed_base == DisplayedBase::RedPlane {
733 Visibility::Visible
734 } else {
735 Visibility::Hidden
736 };
737 }
738 }
739 for entity in model_queries.water_models.iter() {
740 if let Ok(mut visibility) = visibilities.get_mut(entity) {
741 *visibility = if app_settings.displayed_base == DisplayedBase::Water {
742 Visibility::Visible
743 } else {
744 Visibility::Hidden
745 };
746 }
747 }
748}
749
750fn handle_value_change_number_input(
752 value_change: On<ValueChange<f32>>,
753 mut commands: Commands,
754 number_input_q: Query<&AppNumberInput, With<FeathersNumberInput>>,
755 mut app_settings: ResMut<AppSettings>,
756 cameras: Query<Entity, With<Camera>>,
757) {
758 if let Ok(app_number_input) = number_input_q.get(value_change.source) {
759 match app_number_input {
760 AppNumberInput::MinRoughnessStart => {
761 app_settings.min_perceptual_roughness.start = value_change.value;
762 }
763 AppNumberInput::MinRoughnessEnd => {
764 app_settings.min_perceptual_roughness.end = value_change.value;
765 }
766 AppNumberInput::MaxRoughnessStart => {
767 app_settings.max_perceptual_roughness.start = value_change.value;
768 }
769 AppNumberInput::MaxRoughnessEnd => {
770 app_settings.max_perceptual_roughness.end = value_change.value;
771 }
772 AppNumberInput::EdgeFadeoutStart => {
773 app_settings.edge_fadeout.start = value_change.value;
774 }
775 AppNumberInput::EdgeFadeoutEnd => {
776 app_settings.edge_fadeout.end = value_change.value;
777 }
778 }
779
780 commands
781 .entity(value_change.source)
782 .insert(NumberInputValue::F32(value_change.value));
783
784 update_views(commands, app_settings, cameras);
785 }
786}
787
788impl MaterialExtension for Water {
789 fn deferred_fragment_shader() -> ShaderRef {
790 SHADER_ASSET_PATH.into()
791 }
792}