Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/pccm.rs
9308 views
1
//! Demonstrates parallax-corrected cubemap reflections.
2
3
use core::f32;
4
5
use bevy::{
6
camera::Hdr,
7
camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
8
light::ParallaxCorrection,
9
prelude::*,
10
};
11
12
use crate::widgets::{WidgetClickEvent, WidgetClickSender};
13
14
#[path = "../helpers/widgets.rs"]
15
mod widgets;
16
17
/// A marker component for the inner rotating reflective cube.
18
#[derive(Clone, Component)]
19
struct InnerCube;
20
21
/// The brightness of the cubemap.
22
///
23
/// Since the cubemap image was baked in Blender, which uses a different
24
/// exposure setting than that of Bevy, we need this factor in order to make the
25
/// exposure of the baked image match ours.
26
const ENVIRONMENT_MAP_INTENSITY: f32 = 100.0;
27
28
const OUTER_CUBE_URL: &str =
29
"https://github.com/bevyengine/bevy_asset_files/raw/main/pccm_example/outer_cube.glb#Scene0";
30
const ENV_DIFFUSE_URL: &str =
31
"https://github.com/bevyengine/bevy_asset_files/raw/main/pccm_example/env_diffuse.ktx2";
32
const ENV_SPECULAR_URL: &str =
33
"https://github.com/bevyengine/bevy_asset_files/raw/main/pccm_example/env_specular.ktx2";
34
35
/// The current value of user-customizable settings for this demo.
36
#[derive(Resource, Default)]
37
struct AppStatus {
38
/// Whether parallax correction is enabled.
39
pccm_enabled: PccmEnableStatus,
40
}
41
42
/// Whether parallax correction is enabled.
43
#[derive(Clone, Copy, PartialEq, Default)]
44
enum PccmEnableStatus {
45
/// Parallax correction is enabled.
46
#[default]
47
Enabled,
48
/// Parallax correction is disabled.
49
Disabled,
50
}
51
52
/// The example entry point.
53
fn main() {
54
App::new()
55
.add_plugins((
56
DefaultPlugins.set(WindowPlugin {
57
primary_window: Some(Window {
58
title: "Bevy Parallax-Corrected Cubemaps Example".into(),
59
..default()
60
}),
61
..default()
62
}),
63
FreeCameraPlugin,
64
))
65
.init_resource::<AppStatus>()
66
.add_message::<WidgetClickEvent<PccmEnableStatus>>()
67
.add_systems(Startup, setup)
68
.add_systems(Update, widgets::handle_ui_interactions::<PccmEnableStatus>)
69
.add_systems(
70
Update,
71
(handle_pccm_enable_change, update_radio_buttons)
72
.after(widgets::handle_ui_interactions::<PccmEnableStatus>),
73
)
74
.run();
75
}
76
77
/// Creates the initial scene.
78
fn setup(
79
mut commands: Commands,
80
asset_server: Res<AssetServer>,
81
mut meshes: ResMut<Assets<Mesh>>,
82
mut materials: ResMut<Assets<StandardMaterial>>,
83
) {
84
// Spawn the glTF scene.
85
commands.spawn(SceneRoot(asset_server.load(OUTER_CUBE_URL)));
86
87
spawn_camera(&mut commands);
88
spawn_inner_cube(&mut commands, &mut meshes, &mut materials);
89
spawn_reflection_probe(&mut commands, &asset_server);
90
spawn_buttons(&mut commands);
91
}
92
93
/// Spawns the camera.
94
fn spawn_camera(commands: &mut Commands) {
95
commands.spawn((
96
Camera3d::default(),
97
FreeCamera::default(),
98
Transform::from_xyz(0.0, 0.0, 4.0).looking_at(Vec3::new(0.0, -2.5, 0.0), Dir3::Y),
99
Hdr,
100
));
101
}
102
103
/// Spawns the inner reflective cube in the scene.
104
fn spawn_inner_cube(
105
commands: &mut Commands,
106
meshes: &mut Assets<Mesh>,
107
materials: &mut Assets<StandardMaterial>,
108
) {
109
let cube_mesh = meshes.add(
110
Cuboid {
111
half_size: Vec3::new(5.0, 1.0, 2.0),
112
}
113
.mesh()
114
.build()
115
.with_duplicated_vertices()
116
.with_computed_flat_normals(),
117
);
118
let cube_material = materials.add(StandardMaterial {
119
base_color: Color::WHITE,
120
metallic: 1.0,
121
reflectance: 1.0,
122
perceptual_roughness: 0.0,
123
..default()
124
});
125
126
commands.spawn((
127
Mesh3d(cube_mesh),
128
MeshMaterial3d(cube_material),
129
Transform::from_xyz(0.0, -4.0, -2.5),
130
InnerCube,
131
));
132
}
133
134
/// Spawns the reflection probe (i.e. cubemap reflection) in the center of the scene.
135
fn spawn_reflection_probe(commands: &mut Commands, asset_server: &AssetServer) {
136
let diffuse_map = asset_server.load(ENV_DIFFUSE_URL);
137
let specular_map = asset_server.load(ENV_SPECULAR_URL);
138
commands.spawn((
139
LightProbe::default(),
140
EnvironmentMapLight {
141
diffuse_map,
142
specular_map,
143
intensity: ENVIRONMENT_MAP_INTENSITY,
144
..default()
145
},
146
// HACK: slightly larger than 10.0 to avoid z-fighting from the outer cube
147
// faces being partially inside and partially outside the light probe influence
148
// volume. We should have a smooth falloff probe transition option at some point.
149
Transform::from_scale(Vec3::splat(10.01)),
150
));
151
}
152
153
/// Spawns the buttons at the bottom of the screen.
154
fn spawn_buttons(commands: &mut Commands) {
155
commands.spawn((
156
widgets::main_ui_node(),
157
children![widgets::option_buttons(
158
"Parallax Correction",
159
&[
160
(PccmEnableStatus::Enabled, "On"),
161
(PccmEnableStatus::Disabled, "Off"),
162
],
163
)],
164
));
165
}
166
167
/// Handles a change to the parallax correction setting UI.
168
fn handle_pccm_enable_change(
169
mut commands: Commands,
170
light_probe_query: Query<Entity, With<LightProbe>>,
171
mut app_status: ResMut<AppStatus>,
172
mut messages: MessageReader<WidgetClickEvent<PccmEnableStatus>>,
173
) {
174
let Some(light_probe_entity) = light_probe_query.iter().next() else {
175
return;
176
};
177
178
for message in messages.read() {
179
// The UI message contains the `PccmEnableStatus` value that the user
180
// selected.
181
app_status.pccm_enabled = **message;
182
183
// Add the appropriate variant of the `ParallaxCorrection` component.
184
match **message {
185
PccmEnableStatus::Enabled => {
186
commands
187
.entity(light_probe_entity)
188
.insert(ParallaxCorrection::Auto);
189
}
190
PccmEnableStatus::Disabled => {
191
commands
192
.entity(light_probe_entity)
193
.insert(ParallaxCorrection::None);
194
}
195
}
196
}
197
}
198
199
/// Updates the state of the UI at the bottom of the screen to reflect the
200
/// current application settings.
201
fn update_radio_buttons(
202
mut widgets_query: Query<(
203
Entity,
204
Option<&mut BackgroundColor>,
205
Has<Text>,
206
&WidgetClickSender<PccmEnableStatus>,
207
)>,
208
app_status: Res<AppStatus>,
209
mut text_ui_writer: TextUiWriter,
210
) {
211
for (entity, maybe_bg_color, has_text, sender) in &mut widgets_query {
212
// The `sender` value contains the `PccmEnableStatus` that the user
213
// selected.
214
let selected = app_status.pccm_enabled == **sender;
215
216
if let Some(mut bg_color) = maybe_bg_color {
217
widgets::update_ui_radio_button(&mut bg_color, selected);
218
}
219
if has_text {
220
widgets::update_ui_radio_button_text(entity, &mut text_ui_writer, selected);
221
}
222
}
223
}
224
225