| 1 |
|
| 2 |
|
| 3 |
#[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))] |
| 4 |
use bevy::anti_alias::taa::TemporalAntiAliasing; |
| 5 |
|
| 6 |
use 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 |
}; |
| 17 |
use std::f32::consts::PI; |
| 18 |
|
| 19 |
const 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 |
|
| 38 |
fn 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)] |
| 55 |
struct Cubemap { |
| 56 |
is_loaded: bool, |
| 57 |
index: usize, |
| 58 |
image_handle: Handle<Image>, |
| 59 |
} |
| 60 |
|
| 61 |
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { |
| 62 |
|
| 63 |
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 |
|
| 73 |
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 |
|
| 89 |
|
| 90 |
|
| 91 |
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 |
|
| 104 |
const CUBEMAP_SWAP_DELAY: f32 = 3.0; |
| 105 |
|
| 106 |
fn 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 |
|
| 138 |
|
| 139 |
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 |
|
| 148 |
fn 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 |
|
| 158 |
|
| 159 |
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 |
|
| 178 |
fn 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 |
} |