Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/solari.rs
6849 views
1
//! Demonstrates realtime dynamic raytraced lighting using Bevy Solari.
2
3
#[path = "../helpers/camera_controller.rs"]
4
mod camera_controller;
5
6
use argh::FromArgs;
7
use bevy::{
8
camera::CameraMainTextureUsages,
9
gltf::GltfMaterialName,
10
prelude::*,
11
render::render_resource::TextureUsages,
12
scene::SceneInstanceReady,
13
solari::{
14
pathtracer::{Pathtracer, PathtracingPlugin},
15
prelude::{RaytracingMesh3d, SolariLighting, SolariPlugins},
16
},
17
};
18
use camera_controller::{CameraController, CameraControllerPlugin};
19
use std::f32::consts::PI;
20
21
#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
22
use bevy::anti_alias::dlss::{
23
Dlss, DlssProjectId, DlssRayReconstructionFeature, DlssRayReconstructionSupported,
24
};
25
26
/// `bevy_solari` demo.
27
#[derive(FromArgs, Resource, Clone, Copy)]
28
struct Args {
29
/// use the reference pathtracer instead of the realtime lighting system.
30
#[argh(switch)]
31
pathtracer: Option<bool>,
32
}
33
34
fn main() {
35
let args: Args = argh::from_env();
36
37
let mut app = App::new();
38
39
#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
40
app.insert_resource(DlssProjectId(bevy_asset::uuid::uuid!(
41
"5417916c-0291-4e3f-8f65-326c1858ab96" // Don't copy paste this - generate your own UUID!
42
)));
43
44
app.add_plugins((DefaultPlugins, SolariPlugins, CameraControllerPlugin))
45
.insert_resource(args)
46
.add_systems(Startup, setup);
47
48
if args.pathtracer == Some(true) {
49
app.add_plugins(PathtracingPlugin);
50
} else {
51
app.add_systems(Update, (pause_scene, toggle_lights, patrol_path));
52
app.add_systems(PostUpdate, update_text);
53
}
54
55
app.run();
56
}
57
58
fn setup(
59
mut commands: Commands,
60
asset_server: Res<AssetServer>,
61
args: Res<Args>,
62
#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_rr_supported: Option<
63
Res<DlssRayReconstructionSupported>,
64
>,
65
) {
66
commands
67
.spawn((
68
SceneRoot(
69
asset_server.load(
70
GltfAssetLabel::Scene(0)
71
.from_asset("https://github.com/bevyengine/bevy_asset_files/raw/2a5950295a8b6d9d051d59c0df69e87abcda58c3/pica_pica/mini_diorama_01.glb")
72
),
73
),
74
Transform::from_scale(Vec3::splat(10.0)),
75
))
76
.observe(add_raytracing_meshes_on_scene_load);
77
78
commands
79
.spawn((
80
SceneRoot(asset_server.load(
81
GltfAssetLabel::Scene(0).from_asset("https://github.com/bevyengine/bevy_asset_files/raw/2a5950295a8b6d9d051d59c0df69e87abcda58c3/pica_pica/robot_01.glb")
82
)),
83
Transform::from_scale(Vec3::splat(2.0))
84
.with_translation(Vec3::new(-2.0, 0.05, -2.1))
85
.with_rotation(Quat::from_rotation_y(PI / 2.0)),
86
PatrolPath {
87
path: vec![
88
(Vec3::new(-2.0, 0.05, -2.1), Quat::from_rotation_y(PI / 2.0)),
89
(Vec3::new(2.2, 0.05, -2.1), Quat::from_rotation_y(0.0)),
90
(
91
Vec3::new(2.2, 0.05, 2.1),
92
Quat::from_rotation_y(3.0 * PI / 2.0),
93
),
94
(Vec3::new(-2.0, 0.05, 2.1), Quat::from_rotation_y(PI)),
95
],
96
i: 0,
97
},
98
))
99
.observe(add_raytracing_meshes_on_scene_load);
100
101
commands.spawn((
102
DirectionalLight {
103
illuminance: light_consts::lux::FULL_DAYLIGHT,
104
shadows_enabled: false, // Solari replaces shadow mapping
105
..default()
106
},
107
Transform::from_rotation(Quat::from_xyzw(
108
-0.13334629,
109
-0.86597735,
110
-0.3586996,
111
0.3219264,
112
)),
113
));
114
115
let mut camera = commands.spawn((
116
Camera3d::default(),
117
Camera {
118
clear_color: ClearColorConfig::Custom(Color::BLACK),
119
..default()
120
},
121
CameraController {
122
walk_speed: 3.0,
123
run_speed: 10.0,
124
..Default::default()
125
},
126
Transform::from_translation(Vec3::new(0.219417, 2.5764852, 6.9718704)).with_rotation(
127
Quat::from_xyzw(-0.1466768, 0.013738206, 0.002037309, 0.989087),
128
),
129
// Msaa::Off and CameraMainTextureUsages with STORAGE_BINDING are required for Solari
130
CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING),
131
Msaa::Off,
132
));
133
134
if args.pathtracer == Some(true) {
135
camera.insert(Pathtracer::default());
136
} else {
137
camera.insert(SolariLighting::default());
138
}
139
140
// Using DLSS Ray Reconstruction for denoising (and cheaper rendering via upscaling) is _highly_ recommended when using Solari
141
#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
142
if dlss_rr_supported.is_some() {
143
camera.insert(Dlss::<DlssRayReconstructionFeature> {
144
perf_quality_mode: Default::default(),
145
reset: Default::default(),
146
_phantom_data: Default::default(),
147
});
148
}
149
150
commands.spawn((
151
Text::default(),
152
Node {
153
position_type: PositionType::Absolute,
154
bottom: Val::Px(12.0),
155
left: Val::Px(12.0),
156
..default()
157
},
158
));
159
}
160
161
fn add_raytracing_meshes_on_scene_load(
162
scene_ready: On<SceneInstanceReady>,
163
children: Query<&Children>,
164
mesh_query: Query<(
165
&Mesh3d,
166
&MeshMaterial3d<StandardMaterial>,
167
Option<&GltfMaterialName>,
168
)>,
169
mut meshes: ResMut<Assets<Mesh>>,
170
mut materials: ResMut<Assets<StandardMaterial>>,
171
mut commands: Commands,
172
args: Res<Args>,
173
) {
174
for descendant in children.iter_descendants(scene_ready.entity) {
175
if let Ok((Mesh3d(mesh_handle), MeshMaterial3d(material_handle), material_name)) =
176
mesh_query.get(descendant)
177
{
178
// Add raytracing mesh component
179
commands
180
.entity(descendant)
181
.insert(RaytracingMesh3d(mesh_handle.clone()));
182
183
// Ensure meshes are Solari compatible
184
let mesh = meshes.get_mut(mesh_handle).unwrap();
185
if !mesh.contains_attribute(Mesh::ATTRIBUTE_UV_0) {
186
let vertex_count = mesh.count_vertices();
187
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0]; vertex_count]);
188
mesh.insert_attribute(
189
Mesh::ATTRIBUTE_TANGENT,
190
vec![[0.0, 0.0, 0.0, 0.0]; vertex_count],
191
);
192
}
193
if !mesh.contains_attribute(Mesh::ATTRIBUTE_TANGENT) {
194
mesh.generate_tangents().unwrap();
195
}
196
if mesh.contains_attribute(Mesh::ATTRIBUTE_UV_1) {
197
mesh.remove_attribute(Mesh::ATTRIBUTE_UV_1);
198
}
199
200
// Prevent rasterization if using pathtracer
201
if args.pathtracer == Some(true) {
202
commands.entity(descendant).remove::<Mesh3d>();
203
}
204
205
// Adjust scene materials to better demo Solari features
206
if material_name.map(|s| s.0.as_str()) == Some("material") {
207
let material = materials.get_mut(material_handle).unwrap();
208
material.emissive = LinearRgba::BLACK;
209
}
210
if material_name.map(|s| s.0.as_str()) == Some("Lights") {
211
let material = materials.get_mut(material_handle).unwrap();
212
material.emissive =
213
LinearRgba::from(Color::srgb(0.941, 0.714, 0.043)) * 1_000_000.0;
214
material.alpha_mode = AlphaMode::Opaque;
215
material.specular_transmission = 0.0;
216
217
commands.insert_resource(RobotLightMaterial(material_handle.clone()));
218
}
219
if material_name.map(|s| s.0.as_str()) == Some("Glass_Dark_01") {
220
let material = materials.get_mut(material_handle).unwrap();
221
material.alpha_mode = AlphaMode::Opaque;
222
material.specular_transmission = 0.0;
223
}
224
}
225
}
226
}
227
228
fn pause_scene(mut time: ResMut<Time<Virtual>>, key_input: Res<ButtonInput<KeyCode>>) {
229
if key_input.just_pressed(KeyCode::Space) {
230
if time.is_paused() {
231
time.unpause();
232
} else {
233
time.pause();
234
}
235
}
236
}
237
238
#[derive(Resource)]
239
struct RobotLightMaterial(Handle<StandardMaterial>);
240
241
fn toggle_lights(
242
key_input: Res<ButtonInput<KeyCode>>,
243
robot_light_material: Option<Res<RobotLightMaterial>>,
244
mut materials: ResMut<Assets<StandardMaterial>>,
245
directional_light: Query<Entity, With<DirectionalLight>>,
246
mut commands: Commands,
247
) {
248
if key_input.just_pressed(KeyCode::Digit1) {
249
if let Ok(directional_light) = directional_light.single() {
250
commands.entity(directional_light).despawn();
251
} else {
252
commands.spawn((
253
DirectionalLight {
254
illuminance: light_consts::lux::FULL_DAYLIGHT,
255
shadows_enabled: false, // Solari replaces shadow mapping
256
..default()
257
},
258
Transform::from_rotation(Quat::from_xyzw(
259
-0.13334629,
260
-0.86597735,
261
-0.3586996,
262
0.3219264,
263
)),
264
));
265
}
266
}
267
268
if key_input.just_pressed(KeyCode::Digit2)
269
&& let Some(robot_light_material) = robot_light_material
270
{
271
let material = materials.get_mut(&robot_light_material.0).unwrap();
272
if material.emissive == LinearRgba::BLACK {
273
material.emissive = LinearRgba::from(Color::srgb(0.941, 0.714, 0.043)) * 1_000_000.0;
274
} else {
275
material.emissive = LinearRgba::BLACK;
276
}
277
}
278
}
279
280
#[derive(Component)]
281
struct PatrolPath {
282
path: Vec<(Vec3, Quat)>,
283
i: usize,
284
}
285
286
fn patrol_path(mut query: Query<(&mut PatrolPath, &mut Transform)>, time: Res<Time<Virtual>>) {
287
for (mut path, mut transform) in query.iter_mut() {
288
let (mut target_position, mut target_rotation) = path.path[path.i];
289
let mut distance_to_target = transform.translation.distance(target_position);
290
if distance_to_target < 0.01 {
291
transform.translation = target_position;
292
transform.rotation = target_rotation;
293
294
path.i = (path.i + 1) % path.path.len();
295
(target_position, target_rotation) = path.path[path.i];
296
distance_to_target = transform.translation.distance(target_position);
297
}
298
299
let direction = (target_position - transform.translation).normalize();
300
let movement = direction * time.delta_secs();
301
302
if movement.length() > distance_to_target {
303
transform.translation = target_position;
304
transform.rotation = target_rotation;
305
} else {
306
transform.translation += movement;
307
}
308
}
309
}
310
311
fn update_text(
312
mut text: Single<&mut Text>,
313
robot_light_material: Option<Res<RobotLightMaterial>>,
314
materials: Res<Assets<StandardMaterial>>,
315
directional_light: Query<Entity, With<DirectionalLight>>,
316
time: Res<Time<Virtual>>,
317
#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] dlss_rr_supported: Option<
318
Res<DlssRayReconstructionSupported>,
319
>,
320
) {
321
text.0.clear();
322
323
if time.is_paused() {
324
text.0.push_str("(Space): Resume");
325
} else {
326
text.0.push_str("(Space): Pause");
327
}
328
329
if directional_light.single().is_ok() {
330
text.0.push_str("\n(1): Disable directional light");
331
} else {
332
text.0.push_str("\n(1): Enable directional light");
333
}
334
335
match robot_light_material.and_then(|m| materials.get(&m.0)) {
336
Some(robot_light_material) if robot_light_material.emissive != LinearRgba::BLACK => {
337
text.0.push_str("\n(2): Disable robot emissive light");
338
}
339
_ => {
340
text.0.push_str("\n(2): Enable robot emissive light");
341
}
342
}
343
344
#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))]
345
if dlss_rr_supported.is_some() {
346
text.0
347
.push_str("\nDenoising: DLSS Ray Reconstruction enabled");
348
} else {
349
text.0
350
.push_str("\nDenoising: DLSS Ray Reconstruction not supported");
351
}
352
353
#[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))]
354
text.0
355
.push_str("\nDenoising: App not compiled with DLSS support");
356
}
357
358