1#[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
4use bevy::anti_alias::taa::TemporalAntiAliasing;
5
6use bevy::{
7 camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
8 image::CompressedImageFormats,
9 light::Skybox,
10 pbr::ScreenSpaceAmbientOcclusion,
11 prelude::*,
12 render::{
13 render_resource::{TextureViewDescriptor, TextureViewDimension},
14 renderer::RenderDevice,
15 },
16};
17use std::f32::consts::PI;
18
19const CUBEMAPS: &[(&str, CompressedImageFormats)] = &[
20 (
21 "textures/Ryfjallet_cubemap.png",
22 CompressedImageFormats::NONE,
23 ),
24 (
25 "textures/Ryfjallet_cubemap_astc4x4.ktx2",
26 CompressedImageFormats::ASTC_LDR,
27 ),
28 (
29 "textures/Ryfjallet_cubemap_bc7.ktx2",
30 CompressedImageFormats::BC,
31 ),
32 (
33 "textures/Ryfjallet_cubemap_etc2.ktx2",
34 CompressedImageFormats::ETC2,
35 ),
36];
37
38fn main() {
39 App::new()
40 .add_plugins(DefaultPlugins)
41 .add_plugins(FreeCameraPlugin)
42 .add_systems(Startup, setup)
43 .add_systems(
44 Update,
45 (
46 cycle_cubemap_asset,
47 asset_loaded.after(cycle_cubemap_asset),
48 animate_light_direction,
49 ),
50 )
51 .run();
52}
53
54#[derive(Resource)]
55struct Cubemap {
56 is_loaded: bool,
57 index: usize,
58 image_handle: Handle<Image>,
59}
60
61fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
62 commands.spawn((
64 DirectionalLight {
65 illuminance: 32000.0,
66 ..default()
67 },
68 Transform::from_xyz(0.0, 2.0, 0.0).with_rotation(Quat::from_rotation_x(-PI / 4.)),
69 ));
70
71 let skybox_handle = asset_server.load(CUBEMAPS[0].0);
72 commands.spawn((
74 Camera3d::default(),
75 Msaa::Off,
76 #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
77 TemporalAntiAliasing::default(),
78 ScreenSpaceAmbientOcclusion::default(),
79 Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
80 FreeCamera::default(),
81 Skybox {
82 image: Some(skybox_handle.clone()),
83 brightness: 1000.0,
84 ..default()
85 },
86 ));
87
88 commands.insert_resource(GlobalAmbientLight {
92 color: Color::srgb_u8(210, 220, 240),
93 brightness: 1.0,
94 ..default()
95 });
96
97 commands.insert_resource(Cubemap {
98 is_loaded: false,
99 index: 0,
100 image_handle: skybox_handle,
101 });
102}
103
104const CUBEMAP_SWAP_DELAY: f32 = 3.0;
105
106fn cycle_cubemap_asset(
107 time: Res<Time>,
108 mut next_swap: Local<f32>,
109 mut cubemap: ResMut<Cubemap>,
110 asset_server: Res<AssetServer>,
111 render_device: Res<RenderDevice>,
112) {
113 let now = time.elapsed_secs();
114 if *next_swap == 0.0 {
115 *next_swap = now + CUBEMAP_SWAP_DELAY;
116 return;
117 } else if now < *next_swap {
118 return;
119 }
120 *next_swap += CUBEMAP_SWAP_DELAY;
121
122 let supported_compressed_formats =
123 CompressedImageFormats::from_features(render_device.features());
124
125 let mut new_index = cubemap.index;
126 for _ in 0..CUBEMAPS.len() {
127 new_index = (new_index + 1) % CUBEMAPS.len();
128 if supported_compressed_formats.contains(CUBEMAPS[new_index].1) {
129 break;
130 }
131 info!(
132 "Skipping format which is not supported by current hardware: {:?}",
133 CUBEMAPS[new_index]
134 );
135 }
136
137 if new_index == cubemap.index {
140 return;
141 }
142
143 cubemap.index = new_index;
144 cubemap.image_handle = asset_server.load(CUBEMAPS[cubemap.index].0);
145 cubemap.is_loaded = false;
146}
147
148fn asset_loaded(
149 asset_server: Res<AssetServer>,
150 mut images: ResMut<Assets<Image>>,
151 mut cubemap: ResMut<Cubemap>,
152 mut skyboxes: Query<&mut Skybox>,
153) {
154 if !cubemap.is_loaded && asset_server.load_state(&cubemap.image_handle).is_loaded() {
155 info!("Swapping to {}...", CUBEMAPS[cubemap.index].0);
156 let mut image = images.get_mut(&cubemap.image_handle).unwrap();
157 if image.texture_descriptor.array_layer_count() == 1 {
160 let layers = image.height() / image.width();
161 image
162 .reinterpret_stacked_2d_as_array(layers)
163 .expect("asset should be 2d texture and height will always be evenly divisible with the given layers");
164 image.texture_view_descriptor = Some(TextureViewDescriptor {
165 dimension: Some(TextureViewDimension::Cube),
166 ..default()
167 });
168 }
169
170 for mut skybox in &mut skyboxes {
171 skybox.image = Some(cubemap.image_handle.clone());
172 }
173
174 cubemap.is_loaded = true;
175 }
176}
177
178fn animate_light_direction(
179 time: Res<Time>,
180 mut query: Query<&mut Transform, With<DirectionalLight>>,
181) {
182 for mut transform in &mut query {
183 transform.rotate_y(time.delta_secs() * 0.5);
184 }
185}