From ce7e88dfc24bb7b7cc8b4c11f9ee54401f244498 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 7 Mar 2022 00:53:08 +0100 Subject: [PATCH 1/2] many_cubes: Add a cube pattern suitable for benchmarking culling changes --- examples/3d/many_cubes.rs | 124 ++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 39 deletions(-) diff --git a/examples/3d/many_cubes.rs b/examples/3d/many_cubes.rs index acc2bb677c6ed..086a227a0222a 100644 --- a/examples/3d/many_cubes.rs +++ b/examples/3d/many_cubes.rs @@ -1,8 +1,8 @@ use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + math::{DVec2, DVec3}, prelude::*, }; - fn main() { App::new() .add_plugins(DefaultPlugins) @@ -26,41 +26,75 @@ fn setup( base_color: Color::PINK, ..default() }); - for x in 0..WIDTH { - for y in 0..HEIGHT { - // introduce spaces to break any kind of moiré pattern - if x % 10 == 0 || y % 10 == 0 { - continue; + + match std::env::args().nth(1).as_deref() { + Some("sphere") => { + // NOTE: This pattern is good for testing performance of culling as it provides roughly + // the same number of visible meshes regardless of the viewing angle. + const N_POINTS: usize = WIDTH * HEIGHT * 4; + // NOTE: f64 is used to avoid precision issues that produce visual artifacts in the distribution + let radius = WIDTH as f64 * 2.5; + let golden_ratio = 0.5f64 * (1.0f64 + 5.0f64.sqrt()); + for i in 0..N_POINTS { + let spherical_polar_theta_phi = + fibonacci_spiral_on_sphere(golden_ratio, i, N_POINTS); + let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi); + commands.spawn_bundle(PbrBundle { + mesh: mesh.clone_weak(), + material: material.clone_weak(), + transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()), + ..default() + }); + } + + // camera + commands.spawn_bundle(PerspectiveCameraBundle::default()); + } + _ => { + // NOTE: This pattern is good for demonstrating that frustum culling is working correctly + // as the number of visible meshes rises and falls depending on the viewing angle. + for x in 0..WIDTH { + for y in 0..HEIGHT { + // introduce spaces to break any kind of moiré pattern + if x % 10 == 0 || y % 10 == 0 { + continue; + } + // cube + commands.spawn_bundle(PbrBundle { + mesh: mesh.clone_weak(), + material: material.clone_weak(), + transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0), + ..default() + }); + commands.spawn_bundle(PbrBundle { + mesh: mesh.clone_weak(), + material: material.clone_weak(), + transform: Transform::from_xyz( + (x as f32) * 2.5, + HEIGHT as f32 * 2.5, + (y as f32) * 2.5, + ), + ..Default::default() + }); + commands.spawn_bundle(PbrBundle { + mesh: mesh.clone_weak(), + material: material.clone_weak(), + transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5), + ..Default::default() + }); + commands.spawn_bundle(PbrBundle { + mesh: mesh.clone_weak(), + material: material.clone_weak(), + transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5), + ..Default::default() + }); + } } - // cube - commands.spawn_bundle(PbrBundle { - mesh: mesh.clone_weak(), - material: material.clone_weak(), - transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0), + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(WIDTH as f32, HEIGHT as f32, WIDTH as f32), ..default() }); - commands.spawn_bundle(PbrBundle { - mesh: mesh.clone_weak(), - material: material.clone_weak(), - transform: Transform::from_xyz( - (x as f32) * 2.5, - HEIGHT as f32 * 2.5, - (y as f32) * 2.5, - ), - ..Default::default() - }); - commands.spawn_bundle(PbrBundle { - mesh: mesh.clone_weak(), - material: material.clone_weak(), - transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5), - ..Default::default() - }); - commands.spawn_bundle(PbrBundle { - mesh: mesh.clone_weak(), - material: material.clone_weak(), - transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5), - ..Default::default() - }); } } @@ -77,17 +111,29 @@ fn setup( ..Default::default() }); - // camera - commands.spawn_bundle(PerspectiveCameraBundle { - transform: Transform::from_xyz(WIDTH as f32, HEIGHT as f32, WIDTH as f32), - ..default() - }); - commands.spawn_bundle(DirectionalLightBundle { ..Default::default() }); } +// NOTE: This epsilon value is apparently optimal for optimizing for the average +// nearest-neighbor distance. See: +// http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/ +// for details. +const EPSILON: f64 = 0.36; +fn fibonacci_spiral_on_sphere(golden_ratio: f64, i: usize, n: usize) -> DVec2 { + DVec2::new( + 2.0 * std::f64::consts::PI * (i as f64 / golden_ratio), + (1.0 - 2.0 * (i as f64 + EPSILON) / (n as f64 - 1.0 + 2.0 * EPSILON)).acos(), + ) +} + +fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 { + let (sin_theta, cos_theta) = p.x.sin_cos(); + let (sin_phi, cos_phi) = p.y.sin_cos(); + DVec3::new(cos_theta * sin_phi, sin_theta * sin_phi, cos_phi) +} + // System for rotating the camera fn move_camera(time: Res