Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/parallax_mapping.rs
6592 views
1
//! A simple 3D scene with a spinning cube with a normal map and depth map to demonstrate parallax mapping.
2
//! Press left mouse button to cycle through different views.
3
4
use std::fmt;
5
6
use bevy::{image::ImageLoaderSettings, math::ops, prelude::*};
7
8
fn main() {
9
App::new()
10
.add_plugins(DefaultPlugins)
11
.add_systems(Startup, setup)
12
.add_systems(
13
Update,
14
(
15
spin,
16
move_camera,
17
update_parallax_depth_scale,
18
update_parallax_layers,
19
switch_method,
20
),
21
)
22
.run();
23
}
24
25
#[derive(Component)]
26
struct Spin {
27
speed: f32,
28
}
29
30
/// The camera, used to move camera on click.
31
#[derive(Component)]
32
struct CameraController;
33
34
const DEPTH_CHANGE_RATE: f32 = 0.1;
35
const DEPTH_UPDATE_STEP: f32 = 0.03;
36
const MAX_DEPTH: f32 = 0.3;
37
38
struct TargetDepth(f32);
39
impl Default for TargetDepth {
40
fn default() -> Self {
41
TargetDepth(0.09)
42
}
43
}
44
struct TargetLayers(f32);
45
impl Default for TargetLayers {
46
fn default() -> Self {
47
TargetLayers(5.0)
48
}
49
}
50
struct CurrentMethod(ParallaxMappingMethod);
51
impl Default for CurrentMethod {
52
fn default() -> Self {
53
CurrentMethod(ParallaxMappingMethod::Relief { max_steps: 4 })
54
}
55
}
56
57
impl fmt::Display for CurrentMethod {
58
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59
match self.0 {
60
ParallaxMappingMethod::Occlusion => write!(f, "Parallax Occlusion Mapping"),
61
ParallaxMappingMethod::Relief { max_steps } => {
62
write!(f, "Relief Mapping with {max_steps} steps")
63
}
64
}
65
}
66
}
67
68
impl CurrentMethod {
69
fn next_method(&mut self) {
70
use ParallaxMappingMethod::*;
71
self.0 = match self.0 {
72
Occlusion => Relief { max_steps: 2 },
73
Relief { max_steps } if max_steps < 3 => Relief { max_steps: 4 },
74
Relief { max_steps } if max_steps < 5 => Relief { max_steps: 8 },
75
Relief { .. } => Occlusion,
76
}
77
}
78
}
79
80
fn update_parallax_depth_scale(
81
input: Res<ButtonInput<KeyCode>>,
82
mut materials: ResMut<Assets<StandardMaterial>>,
83
mut target_depth: Local<TargetDepth>,
84
mut depth_update: Local<bool>,
85
mut writer: TextUiWriter,
86
text: Single<Entity, With<Text>>,
87
) {
88
if input.just_pressed(KeyCode::Digit1) {
89
target_depth.0 -= DEPTH_UPDATE_STEP;
90
target_depth.0 = target_depth.0.max(0.0);
91
*depth_update = true;
92
}
93
if input.just_pressed(KeyCode::Digit2) {
94
target_depth.0 += DEPTH_UPDATE_STEP;
95
target_depth.0 = target_depth.0.min(MAX_DEPTH);
96
*depth_update = true;
97
}
98
if *depth_update {
99
for (_, mat) in materials.iter_mut() {
100
let current_depth = mat.parallax_depth_scale;
101
let new_depth = current_depth.lerp(target_depth.0, DEPTH_CHANGE_RATE);
102
mat.parallax_depth_scale = new_depth;
103
*writer.text(*text, 1) = format!("Parallax depth scale: {new_depth:.5}\n");
104
if (new_depth - current_depth).abs() <= 0.000000001 {
105
*depth_update = false;
106
}
107
}
108
}
109
}
110
111
fn switch_method(
112
input: Res<ButtonInput<KeyCode>>,
113
mut materials: ResMut<Assets<StandardMaterial>>,
114
text: Single<Entity, With<Text>>,
115
mut writer: TextUiWriter,
116
mut current: Local<CurrentMethod>,
117
) {
118
if input.just_pressed(KeyCode::Space) {
119
current.next_method();
120
} else {
121
return;
122
}
123
let text_entity = *text;
124
*writer.text(text_entity, 3) = format!("Method: {}\n", *current);
125
126
for (_, mat) in materials.iter_mut() {
127
mat.parallax_mapping_method = current.0;
128
}
129
}
130
131
fn update_parallax_layers(
132
input: Res<ButtonInput<KeyCode>>,
133
mut materials: ResMut<Assets<StandardMaterial>>,
134
mut target_layers: Local<TargetLayers>,
135
text: Single<Entity, With<Text>>,
136
mut writer: TextUiWriter,
137
) {
138
if input.just_pressed(KeyCode::Digit3) {
139
target_layers.0 -= 1.0;
140
target_layers.0 = target_layers.0.max(0.0);
141
} else if input.just_pressed(KeyCode::Digit4) {
142
target_layers.0 += 1.0;
143
} else {
144
return;
145
}
146
let layer_count = ops::exp2(target_layers.0);
147
let text_entity = *text;
148
*writer.text(text_entity, 2) = format!("Layers: {layer_count:.0}\n");
149
150
for (_, mat) in materials.iter_mut() {
151
mat.max_parallax_layer_count = layer_count;
152
}
153
}
154
155
fn spin(time: Res<Time>, mut query: Query<(&mut Transform, &Spin)>) {
156
for (mut transform, spin) in query.iter_mut() {
157
transform.rotate_local_y(spin.speed * time.delta_secs());
158
transform.rotate_local_x(spin.speed * time.delta_secs());
159
transform.rotate_local_z(-spin.speed * time.delta_secs());
160
}
161
}
162
163
// Camera positions to cycle through when left-clicking.
164
const CAMERA_POSITIONS: &[Transform] = &[
165
Transform {
166
translation: Vec3::new(1.5, 1.5, 1.5),
167
rotation: Quat::from_xyzw(-0.279, 0.364, 0.115, 0.880),
168
scale: Vec3::ONE,
169
},
170
Transform {
171
translation: Vec3::new(2.4, 0.0, 0.2),
172
rotation: Quat::from_xyzw(0.094, 0.676, 0.116, 0.721),
173
scale: Vec3::ONE,
174
},
175
Transform {
176
translation: Vec3::new(2.4, 2.6, -4.3),
177
rotation: Quat::from_xyzw(0.170, 0.908, 0.308, 0.225),
178
scale: Vec3::ONE,
179
},
180
Transform {
181
translation: Vec3::new(-1.0, 0.8, -1.2),
182
rotation: Quat::from_xyzw(-0.004, 0.909, 0.247, -0.335),
183
scale: Vec3::ONE,
184
},
185
];
186
187
fn move_camera(
188
mut camera: Single<&mut Transform, With<CameraController>>,
189
mut current_view: Local<usize>,
190
button: Res<ButtonInput<MouseButton>>,
191
) {
192
if button.just_pressed(MouseButton::Left) {
193
*current_view = (*current_view + 1) % CAMERA_POSITIONS.len();
194
}
195
let target = CAMERA_POSITIONS[*current_view];
196
camera.translation = camera.translation.lerp(target.translation, 0.2);
197
camera.rotation = camera.rotation.slerp(target.rotation, 0.2);
198
}
199
200
fn setup(
201
mut commands: Commands,
202
mut materials: ResMut<Assets<StandardMaterial>>,
203
mut meshes: ResMut<Assets<Mesh>>,
204
asset_server: Res<AssetServer>,
205
) {
206
// The normal map. Note that to generate it in the GIMP image editor, you should
207
// open the depth map, and do Filters → Generic → Normal Map
208
// You should enable the "flip X" checkbox.
209
let normal_handle = asset_server.load_with_settings(
210
"textures/parallax_example/cube_normal.png",
211
// The normal map texture is in linear color space. Lighting won't look correct
212
// if `is_srgb` is `true`, which is the default.
213
|settings: &mut ImageLoaderSettings| settings.is_srgb = false,
214
);
215
216
// Camera
217
commands.spawn((
218
Camera3d::default(),
219
Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::ZERO, Vec3::Y),
220
CameraController,
221
));
222
223
// represent the light source as a sphere
224
let mesh = meshes.add(Sphere::new(0.05).mesh().ico(3).unwrap());
225
226
// light
227
commands.spawn((
228
PointLight {
229
shadows_enabled: true,
230
..default()
231
},
232
Transform::from_xyz(2.0, 1.0, -1.1),
233
children![(Mesh3d(mesh), MeshMaterial3d(materials.add(Color::WHITE)))],
234
));
235
236
// Plane
237
commands.spawn((
238
Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
239
MeshMaterial3d(materials.add(StandardMaterial {
240
// standard material derived from dark green, but
241
// with roughness and reflectance set.
242
perceptual_roughness: 0.45,
243
reflectance: 0.18,
244
..Color::srgb_u8(0, 80, 0).into()
245
})),
246
Transform::from_xyz(0.0, -1.0, 0.0),
247
));
248
249
let parallax_depth_scale = TargetDepth::default().0;
250
let max_parallax_layer_count = ops::exp2(TargetLayers::default().0);
251
let parallax_mapping_method = CurrentMethod::default();
252
let parallax_material = materials.add(StandardMaterial {
253
perceptual_roughness: 0.4,
254
base_color_texture: Some(asset_server.load("textures/parallax_example/cube_color.png")),
255
normal_map_texture: Some(normal_handle),
256
// The depth map is a grayscale texture where black is the highest level and
257
// white the lowest.
258
depth_map: Some(asset_server.load("textures/parallax_example/cube_depth.png")),
259
parallax_depth_scale,
260
parallax_mapping_method: parallax_mapping_method.0,
261
max_parallax_layer_count,
262
..default()
263
});
264
commands.spawn((
265
Mesh3d(
266
meshes.add(
267
// NOTE: for normal maps and depth maps to work, the mesh
268
// needs tangents generated.
269
Mesh::from(Cuboid::default())
270
.with_generated_tangents()
271
.unwrap(),
272
),
273
),
274
MeshMaterial3d(parallax_material.clone()),
275
Spin { speed: 0.3 },
276
));
277
278
let background_cube = meshes.add(
279
Mesh::from(Cuboid::new(40.0, 40.0, 40.0))
280
.with_generated_tangents()
281
.unwrap(),
282
);
283
284
let background_cube_bundle = |translation| {
285
(
286
Mesh3d(background_cube.clone()),
287
MeshMaterial3d(parallax_material.clone()),
288
Transform::from_translation(translation),
289
Spin { speed: -0.1 },
290
)
291
};
292
commands.spawn(background_cube_bundle(Vec3::new(45., 0., 0.)));
293
commands.spawn(background_cube_bundle(Vec3::new(-45., 0., 0.)));
294
commands.spawn(background_cube_bundle(Vec3::new(0., 0., 45.)));
295
commands.spawn(background_cube_bundle(Vec3::new(0., 0., -45.)));
296
297
// example instructions
298
commands.spawn((
299
Text::default(),
300
Node {
301
position_type: PositionType::Absolute,
302
top: px(12),
303
left: px(12),
304
..default()
305
},
306
children![
307
(TextSpan(format!("Parallax depth scale: {parallax_depth_scale:.5}\n"))),
308
(TextSpan(format!("Layers: {max_parallax_layer_count:.0}\n"))),
309
(TextSpan(format!("{parallax_mapping_method}\n"))),
310
(TextSpan::new("\n\n")),
311
(TextSpan::new("Controls:\n")),
312
(TextSpan::new("Left click - Change view angle\n")),
313
(TextSpan::new("1/2 - Decrease/Increase parallax depth scale\n",)),
314
(TextSpan::new("3/4 - Decrease/Increase layer count\n")),
315
(TextSpan::new("Space - Switch parallaxing algorithm\n")),
316
],
317
));
318
}
319
320