Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/clearcoat.rs
6592 views
1
//! Demonstrates the clearcoat PBR feature.
2
//!
3
//! Clearcoat is a separate material layer that represents a thin translucent
4
//! layer over a material. Examples include (from the Filament spec [1]) car paint,
5
//! soda cans, and lacquered wood.
6
//!
7
//! In glTF, clearcoat is supported via the `KHR_materials_clearcoat` [2]
8
//! extension. This extension is well supported by tools; in particular,
9
//! Blender's glTF exporter maps the clearcoat feature of its Principled BSDF
10
//! node to this extension, allowing it to appear in Bevy.
11
//!
12
//! This Bevy example is inspired by the corresponding three.js example [3].
13
//!
14
//! [1]: https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel
15
//!
16
//! [2]: https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md
17
//!
18
//! [3]: https://threejs.org/examples/webgl_materials_physical_clearcoat.html
19
20
use std::f32::consts::PI;
21
22
use bevy::{
23
color::palettes::css::{BLUE, GOLD, WHITE},
24
core_pipeline::{tonemapping::Tonemapping::AcesFitted, Skybox},
25
image::ImageLoaderSettings,
26
math::vec3,
27
prelude::*,
28
render::view::Hdr,
29
};
30
31
/// The size of each sphere.
32
const SPHERE_SCALE: f32 = 0.9;
33
34
/// The speed at which the spheres rotate, in radians per second.
35
const SPHERE_ROTATION_SPEED: f32 = 0.8;
36
37
/// Which type of light we're using: a point light or a directional light.
38
#[derive(Clone, Copy, PartialEq, Resource, Default)]
39
enum LightMode {
40
#[default]
41
Point,
42
Directional,
43
}
44
45
/// Tags the example spheres.
46
#[derive(Component)]
47
struct ExampleSphere;
48
49
/// Entry point.
50
pub fn main() {
51
App::new()
52
.init_resource::<LightMode>()
53
.add_plugins(DefaultPlugins)
54
.add_systems(Startup, setup)
55
.add_systems(Update, animate_light)
56
.add_systems(Update, animate_spheres)
57
.add_systems(Update, (handle_input, update_help_text).chain())
58
.run();
59
}
60
61
/// Initializes the scene.
62
fn setup(
63
mut commands: Commands,
64
mut meshes: ResMut<Assets<Mesh>>,
65
mut materials: ResMut<Assets<StandardMaterial>>,
66
asset_server: Res<AssetServer>,
67
light_mode: Res<LightMode>,
68
) {
69
let sphere = create_sphere_mesh(&mut meshes);
70
spawn_car_paint_sphere(&mut commands, &mut materials, &asset_server, &sphere);
71
spawn_coated_glass_bubble_sphere(&mut commands, &mut materials, &sphere);
72
spawn_golf_ball(&mut commands, &asset_server);
73
spawn_scratched_gold_ball(&mut commands, &mut materials, &asset_server, &sphere);
74
75
spawn_light(&mut commands);
76
spawn_camera(&mut commands, &asset_server);
77
spawn_text(&mut commands, &light_mode);
78
}
79
80
/// Generates a sphere.
81
fn create_sphere_mesh(meshes: &mut Assets<Mesh>) -> Handle<Mesh> {
82
// We're going to use normal maps, so make sure we've generated tangents, or
83
// else the normal maps won't show up.
84
85
let mut sphere_mesh = Sphere::new(1.0).mesh().build();
86
sphere_mesh
87
.generate_tangents()
88
.expect("Failed to generate tangents");
89
meshes.add(sphere_mesh)
90
}
91
92
/// Spawn a regular object with a clearcoat layer. This looks like car paint.
93
fn spawn_car_paint_sphere(
94
commands: &mut Commands,
95
materials: &mut Assets<StandardMaterial>,
96
asset_server: &AssetServer,
97
sphere: &Handle<Mesh>,
98
) {
99
commands
100
.spawn((
101
Mesh3d(sphere.clone()),
102
MeshMaterial3d(materials.add(StandardMaterial {
103
clearcoat: 1.0,
104
clearcoat_perceptual_roughness: 0.1,
105
normal_map_texture: Some(asset_server.load_with_settings(
106
"textures/BlueNoise-Normal.png",
107
|settings: &mut ImageLoaderSettings| settings.is_srgb = false,
108
)),
109
metallic: 0.9,
110
perceptual_roughness: 0.5,
111
base_color: BLUE.into(),
112
..default()
113
})),
114
Transform::from_xyz(-1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
115
))
116
.insert(ExampleSphere);
117
}
118
119
/// Spawn a semitransparent object with a clearcoat layer.
120
fn spawn_coated_glass_bubble_sphere(
121
commands: &mut Commands,
122
materials: &mut Assets<StandardMaterial>,
123
sphere: &Handle<Mesh>,
124
) {
125
commands
126
.spawn((
127
Mesh3d(sphere.clone()),
128
MeshMaterial3d(materials.add(StandardMaterial {
129
clearcoat: 1.0,
130
clearcoat_perceptual_roughness: 0.1,
131
metallic: 0.5,
132
perceptual_roughness: 0.1,
133
base_color: Color::srgba(0.9, 0.9, 0.9, 0.3),
134
alpha_mode: AlphaMode::Blend,
135
..default()
136
})),
137
Transform::from_xyz(-1.0, -1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
138
))
139
.insert(ExampleSphere);
140
}
141
142
/// Spawns an object with both a clearcoat normal map (a scratched varnish) and
143
/// a main layer normal map (the golf ball pattern).
144
///
145
/// This object is in glTF format, using the `KHR_materials_clearcoat`
146
/// extension.
147
fn spawn_golf_ball(commands: &mut Commands, asset_server: &AssetServer) {
148
commands.spawn((
149
SceneRoot(
150
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/GolfBall/GolfBall.glb")),
151
),
152
Transform::from_xyz(1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
153
ExampleSphere,
154
));
155
}
156
157
/// Spawns an object with only a clearcoat normal map (a scratch pattern) and no
158
/// main layer normal map.
159
fn spawn_scratched_gold_ball(
160
commands: &mut Commands,
161
materials: &mut Assets<StandardMaterial>,
162
asset_server: &AssetServer,
163
sphere: &Handle<Mesh>,
164
) {
165
commands
166
.spawn((
167
Mesh3d(sphere.clone()),
168
MeshMaterial3d(materials.add(StandardMaterial {
169
clearcoat: 1.0,
170
clearcoat_perceptual_roughness: 0.3,
171
clearcoat_normal_texture: Some(asset_server.load_with_settings(
172
"textures/ScratchedGold-Normal.png",
173
|settings: &mut ImageLoaderSettings| settings.is_srgb = false,
174
)),
175
metallic: 0.9,
176
perceptual_roughness: 0.1,
177
base_color: GOLD.into(),
178
..default()
179
})),
180
Transform::from_xyz(1.0, -1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
181
))
182
.insert(ExampleSphere);
183
}
184
185
/// Spawns a light.
186
fn spawn_light(commands: &mut Commands) {
187
commands.spawn(create_point_light());
188
}
189
190
/// Spawns a camera with associated skybox and environment map.
191
fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
192
commands
193
.spawn((
194
Camera3d::default(),
195
Hdr,
196
Projection::Perspective(PerspectiveProjection {
197
fov: 27.0 / 180.0 * PI,
198
..default()
199
}),
200
Transform::from_xyz(0.0, 0.0, 10.0),
201
AcesFitted,
202
))
203
.insert(Skybox {
204
brightness: 5000.0,
205
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
206
..default()
207
})
208
.insert(EnvironmentMapLight {
209
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
210
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
211
intensity: 2000.0,
212
..default()
213
});
214
}
215
216
/// Spawns the help text.
217
fn spawn_text(commands: &mut Commands, light_mode: &LightMode) {
218
commands.spawn((
219
light_mode.create_help_text(),
220
Node {
221
position_type: PositionType::Absolute,
222
bottom: px(12),
223
left: px(12),
224
..default()
225
},
226
));
227
}
228
229
/// Moves the light around.
230
fn animate_light(
231
mut lights: Query<&mut Transform, Or<(With<PointLight>, With<DirectionalLight>)>>,
232
time: Res<Time>,
233
) {
234
let now = time.elapsed_secs();
235
for mut transform in lights.iter_mut() {
236
transform.translation = vec3(
237
ops::sin(now * 1.4),
238
ops::cos(now * 1.0),
239
ops::cos(now * 0.6),
240
) * vec3(3.0, 4.0, 3.0);
241
transform.look_at(Vec3::ZERO, Vec3::Y);
242
}
243
}
244
245
/// Rotates the spheres.
246
fn animate_spheres(mut spheres: Query<&mut Transform, With<ExampleSphere>>, time: Res<Time>) {
247
let now = time.elapsed_secs();
248
for mut transform in spheres.iter_mut() {
249
transform.rotation = Quat::from_rotation_y(SPHERE_ROTATION_SPEED * now);
250
}
251
}
252
253
/// Handles the user pressing Space to change the type of light from point to
254
/// directional and vice versa.
255
fn handle_input(
256
mut commands: Commands,
257
mut light_query: Query<Entity, Or<(With<PointLight>, With<DirectionalLight>)>>,
258
keyboard: Res<ButtonInput<KeyCode>>,
259
mut light_mode: ResMut<LightMode>,
260
) {
261
if !keyboard.just_pressed(KeyCode::Space) {
262
return;
263
}
264
265
for light in light_query.iter_mut() {
266
match *light_mode {
267
LightMode::Point => {
268
*light_mode = LightMode::Directional;
269
commands
270
.entity(light)
271
.remove::<PointLight>()
272
.insert(create_directional_light());
273
}
274
LightMode::Directional => {
275
*light_mode = LightMode::Point;
276
commands
277
.entity(light)
278
.remove::<DirectionalLight>()
279
.insert(create_point_light());
280
}
281
}
282
}
283
}
284
285
/// Updates the help text at the bottom of the screen.
286
fn update_help_text(mut text_query: Query<&mut Text>, light_mode: Res<LightMode>) {
287
for mut text in text_query.iter_mut() {
288
*text = light_mode.create_help_text();
289
}
290
}
291
292
/// Creates or recreates the moving point light.
293
fn create_point_light() -> PointLight {
294
PointLight {
295
color: WHITE.into(),
296
intensity: 100000.0,
297
..default()
298
}
299
}
300
301
/// Creates or recreates the moving directional light.
302
fn create_directional_light() -> DirectionalLight {
303
DirectionalLight {
304
color: WHITE.into(),
305
illuminance: 1000.0,
306
..default()
307
}
308
}
309
310
impl LightMode {
311
/// Creates the help text at the bottom of the screen.
312
fn create_help_text(&self) -> Text {
313
let help_text = match *self {
314
LightMode::Point => "Press Space to switch to a directional light",
315
LightMode::Directional => "Press Space to switch to a point light",
316
};
317
318
Text::new(help_text)
319
}
320
}
321
322