Skip to content

Commit ffad065

Browse files
committed
examples: Add many_lights example for testing many point lights
1 parent 048885c commit ffad065

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ path = "examples/3d/manual_gltf_animation_player.rs"
191191
name = "many_cubes"
192192
path = "examples/3d/many_cubes.rs"
193193

194+
[[example]]
195+
name = "many_lights"
196+
path = "examples/3d/many_lights.rs"
197+
194198
[[example]]
195199
name = "msaa"
196200
path = "examples/3d/msaa.rs"

examples/3d/many_lights.rs

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use bevy::{
2+
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
3+
math::{DVec2, DVec3},
4+
pbr::{ExtractedPointLight, GlobalLightMeta},
5+
prelude::*,
6+
render::{RenderApp, RenderStage},
7+
};
8+
9+
fn main() {
10+
App::new()
11+
.insert_resource(WindowDescriptor {
12+
width: 1024.0,
13+
height: 768.0,
14+
title: "many_lights".to_string(),
15+
present_mode: bevy::window::PresentMode::Immediate,
16+
..default()
17+
})
18+
.add_plugins(DefaultPlugins)
19+
.add_plugin(FrameTimeDiagnosticsPlugin::default())
20+
.add_plugin(LogDiagnosticsPlugin::default())
21+
.add_startup_system(setup)
22+
.add_system(move_camera)
23+
.add_system(print_light_count)
24+
.add_plugin(LogVisibleLights)
25+
.run();
26+
}
27+
28+
fn setup(
29+
mut commands: Commands,
30+
mut meshes: ResMut<Assets<Mesh>>,
31+
mut materials: ResMut<Assets<StandardMaterial>>,
32+
) {
33+
const LIGHT_RADIUS: f32 = 0.3;
34+
const LIGHT_INTENSITY: f32 = 5.0;
35+
const RADIUS: f32 = 50.0;
36+
const N_LIGHTS: usize = 100_000;
37+
38+
commands.spawn_bundle(PbrBundle {
39+
mesh: meshes.add(Mesh::from(shape::Icosphere {
40+
radius: RADIUS,
41+
subdivisions: 9,
42+
})),
43+
material: materials.add(StandardMaterial::from(Color::WHITE)),
44+
transform: Transform::from_scale(Vec3::splat(-1.0)),
45+
..default()
46+
});
47+
48+
let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 }));
49+
let material = materials.add(StandardMaterial {
50+
base_color: Color::PINK,
51+
..default()
52+
});
53+
54+
// NOTE: This pattern is good for testing performance of culling as it provides roughly
55+
// the same number of visible meshes regardless of the viewing angle.
56+
// NOTE: f64 is used to avoid precision issues that produce visual artifacts in the distribution
57+
let golden_ratio = 0.5f64 * (1.0f64 + 5.0f64.sqrt());
58+
for i in 0..N_LIGHTS {
59+
let spherical_polar_theta_phi = fibonacci_spiral_on_sphere(golden_ratio, i, N_LIGHTS);
60+
let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi);
61+
commands.spawn_bundle(PointLightBundle {
62+
point_light: PointLight {
63+
range: LIGHT_RADIUS,
64+
intensity: LIGHT_INTENSITY,
65+
..default()
66+
},
67+
transform: Transform::from_translation((RADIUS as f64 * unit_sphere_p).as_vec3()),
68+
..default()
69+
});
70+
}
71+
72+
// camera
73+
commands.spawn_bundle(PerspectiveCameraBundle::default());
74+
75+
// add one cube, the only one with strong handles
76+
// also serves as a reference point during rotation
77+
commands.spawn_bundle(PbrBundle {
78+
mesh,
79+
material,
80+
transform: Transform {
81+
translation: Vec3::new(0.0, RADIUS as f32, 0.0),
82+
scale: Vec3::splat(5.0),
83+
..default()
84+
},
85+
..default()
86+
});
87+
}
88+
89+
// NOTE: This epsilon value is apparently optimal for optimizing for the average
90+
// nearest-neighbor distance. See:
91+
// http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/
92+
// for details.
93+
const EPSILON: f64 = 0.36;
94+
fn fibonacci_spiral_on_sphere(golden_ratio: f64, i: usize, n: usize) -> DVec2 {
95+
DVec2::new(
96+
2.0 * std::f64::consts::PI * (i as f64 / golden_ratio),
97+
(1.0 - 2.0 * (i as f64 + EPSILON) / (n as f64 - 1.0 + 2.0 * EPSILON)).acos(),
98+
)
99+
}
100+
101+
fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
102+
let (sin_theta, cos_theta) = p.x.sin_cos();
103+
let (sin_phi, cos_phi) = p.y.sin_cos();
104+
DVec3::new(cos_theta * sin_phi, sin_theta * sin_phi, cos_phi)
105+
}
106+
107+
// System for rotating the camera
108+
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
109+
let mut camera_transform = camera_query.single_mut();
110+
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.15));
111+
camera_transform.rotate(Quat::from_rotation_x(time.delta_seconds() * 0.15));
112+
}
113+
114+
// System for printing the number of meshes on every tick of the timer
115+
fn print_light_count(time: Res<Time>, mut timer: Local<PrintingTimer>, lights: Query<&PointLight>) {
116+
timer.0.tick(time.delta());
117+
118+
if timer.0.just_finished() {
119+
info!("Lights: {}", lights.iter().len(),);
120+
}
121+
}
122+
123+
struct LogVisibleLights;
124+
125+
impl Plugin for LogVisibleLights {
126+
fn build(&self, app: &mut App) {
127+
let render_app = match app.get_sub_app_mut(RenderApp) {
128+
Ok(render_app) => render_app,
129+
Err(_) => return,
130+
};
131+
132+
render_app
133+
.add_system_to_stage(RenderStage::Extract, extract_time)
134+
.add_system_to_stage(RenderStage::Prepare, print_visible_light_count);
135+
}
136+
}
137+
138+
// System for printing the number of meshes on every tick of the timer
139+
fn print_visible_light_count(
140+
time: Res<Time>,
141+
mut timer: Local<PrintingTimer>,
142+
visible: Query<&ExtractedPointLight>,
143+
global_light_meta: Res<GlobalLightMeta>,
144+
) {
145+
timer.0.tick(time.delta());
146+
147+
if timer.0.just_finished() {
148+
info!(
149+
"Visible Lights: {}, Rendered Lights: {}",
150+
visible.iter().len(),
151+
global_light_meta.entity_to_index.len()
152+
);
153+
}
154+
}
155+
156+
fn extract_time(mut commands: Commands, time: Res<Time>) {
157+
commands.insert_resource(time.into_inner().clone());
158+
}
159+
160+
struct PrintingTimer(Timer);
161+
162+
impl Default for PrintingTimer {
163+
fn default() -> Self {
164+
Self(Timer::from_seconds(1.0, true))
165+
}
166+
}

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Example | File | Description
106106
`load_gltf` | [`3d/load_gltf.rs`](./3d/load_gltf.rs) | Loads and renders a gltf file as a scene
107107
`manual_gltf_animation_player` | [`3d/manual_gltf_animation_player.rs`](./3d/manual_gltf_animation_player.rs) | Loads and manually renders a gltf file with animations
108108
`many_cubes` | [`3d/many_cubes.rs`](./3d/many_cubes.rs) | Simple benchmark to test per-entity draw overhead
109+
`many_lights` | [`3d/many_lights.rs`](./3d/many_lights.rs) | Simple benchmark to test rendering many point lights
109110
`msaa` | [`3d/msaa.rs`](./3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges
110111
`orthographic` | [`3d/orthographic.rs`](./3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications)
111112
`parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations

0 commit comments

Comments
 (0)