Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/skybox.rs
9345 views
1
//! Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats
2
3
#[cfg(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
// directional 'sun' light
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
// camera
73
commands.spawn((
74
Camera3d::default(),
75
Msaa::Off,
76
#[cfg(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: skybox_handle.clone(),
83
brightness: 1000.0,
84
..default()
85
},
86
));
87
88
// ambient light
89
// NOTE: The ambient light is used to scale how bright the environment map is so with a bright
90
// environment map, use an appropriate color and brightness to match
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
// Skip swapping to the same texture. Useful for when ktx2, zstd, or compressed texture support
138
// is missing
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 image = images.get_mut(&cubemap.image_handle).unwrap();
157
// NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture,
158
// so they appear as one texture. The following code reconfigures the texture as necessary.
159
if image.texture_descriptor.array_layer_count() == 1 {
160
image
161
.reinterpret_stacked_2d_as_array(image.height() / image.width())
162
.expect("asset should be 2d texture and height will always be evenly divisible with the given layers");
163
image.texture_view_descriptor = Some(TextureViewDescriptor {
164
dimension: Some(TextureViewDimension::Cube),
165
..default()
166
});
167
}
168
169
for mut skybox in &mut skyboxes {
170
skybox.image = cubemap.image_handle.clone();
171
}
172
173
cubemap.is_loaded = true;
174
}
175
}
176
177
fn animate_light_direction(
178
time: Res<Time>,
179
mut query: Query<&mut Transform, With<DirectionalLight>>,
180
) {
181
for mut transform in &mut query {
182
transform.rotate_y(time.delta_secs() * 0.5);
183
}
184
}
185
186