Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

many_cubes: Add no automatic batching and generation of different meshes #12363

183 changes: 165 additions & 18 deletions examples/stress_tests/many_cubes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ use argh::FromArgs;
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
math::{DVec2, DVec3},
pbr::NotShadowCaster,
prelude::*,
render::{
batching::NoAutomaticBatching,
render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat},
view::NoFrustumCulling,
Expand All @@ -38,15 +40,27 @@ struct Args {

/// whether to vary the material data in each instance.
#[argh(switch)]
vary_per_instance: bool,
vary_material_data_per_instance: bool,

/// the number of different textures from which to randomly select the material base color. 0 means no textures.
#[argh(option, default = "0")]
material_texture_count: usize,

/// whether to disable frustum culling, for stress testing purposes
/// the number of different meshes from which to randomly select. Clamped to at least 1.
#[argh(option, default = "1")]
mesh_count: usize,

/// whether to disable frustum culling. Stresses queuing and batching as all mesh material entities in the scene are always drawn.
#[argh(switch)]
no_frustum_culling: bool,

/// whether to disable automatic batching. Skips batching resulting in heavy stress on render pass draw command encoding.
#[argh(switch)]
no_automatic_batching: bool,

/// whether to enable directional light cascaded shadow mapping.
#[argh(switch)]
shadows: bool,
}

#[derive(Default, Clone)]
Expand Down Expand Up @@ -108,7 +122,7 @@ const HEIGHT: usize = 200;
fn setup(
mut commands: Commands,
args: Res<Args>,
mut meshes: ResMut<Assets<Mesh>>,
mesh_assets: ResMut<Assets<Mesh>>,
material_assets: ResMut<Assets<StandardMaterial>>,
images: ResMut<Assets<Image>>,
) {
Expand All @@ -117,8 +131,9 @@ fn setup(
let args = args.into_inner();
let images = images.into_inner();
let material_assets = material_assets.into_inner();
let mesh_assets = mesh_assets.into_inner();

let mesh = meshes.add(Cuboid::default());
let meshes = init_meshes(args, mesh_assets);

let material_textures = init_textures(args, images);
let materials = init_materials(args, &material_textures, material_assets);
Expand All @@ -136,23 +151,40 @@ fn setup(
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);
let (mesh, transform) = meshes.choose(&mut material_rng).unwrap();
let mut cube = commands.spawn(PbrBundle {
mesh: mesh.clone(),
material: materials.choose(&mut material_rng).unwrap().clone(),
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()),
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3())
.looking_at(Vec3::ZERO, Vec3::Y)
.mul_transform(*transform),
..default()
});
if args.no_frustum_culling {
cube.insert(NoFrustumCulling);
}
if args.no_automatic_batching {
cube.insert(NoAutomaticBatching);
}
}

// camera
commands.spawn(Camera3dBundle::default());
// Inside-out box around the meshes onto which shadows are cast (though you cannot see them...)
commands.spawn((
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what this does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It spawns a box that all the other cubes fit within. It was meant to be just a surface for the cubes to cast shadows on. But because of the camera position, the shadows don’t seem to be visible, as far as I can tell.

PbrBundle {
mesh: mesh_assets.add(Cuboid::from_size(Vec3::splat(radius as f32 * 2.2))),
material: material_assets.add(StandardMaterial::from(Color::WHITE)),
transform: Transform::from_scale(-Vec3::ONE),
..default()
},
NotShadowCaster,
));
}
_ => {
// 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.
let scale = 2.5;
for x in 0..WIDTH {
for y in 0..HEIGHT {
// introduce spaces to break any kind of moiré pattern
Expand All @@ -161,44 +193,62 @@ fn setup(
}
// cube
commands.spawn(PbrBundle {
mesh: mesh.clone(),
mesh: meshes.choose(&mut material_rng).unwrap().0.clone(),
material: materials.choose(&mut material_rng).unwrap().clone(),
transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0),
transform: Transform::from_xyz((x as f32) * scale, (y as f32) * scale, 0.0),
..default()
});
commands.spawn(PbrBundle {
mesh: mesh.clone(),
mesh: meshes.choose(&mut material_rng).unwrap().0.clone(),
material: materials.choose(&mut material_rng).unwrap().clone(),
transform: Transform::from_xyz(
(x as f32) * 2.5,
HEIGHT as f32 * 2.5,
(y as f32) * 2.5,
(x as f32) * scale,
HEIGHT as f32 * scale,
(y as f32) * scale,
),
..default()
});
commands.spawn(PbrBundle {
mesh: mesh.clone(),
mesh: meshes.choose(&mut material_rng).unwrap().0.clone(),
material: materials.choose(&mut material_rng).unwrap().clone(),
transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5),
transform: Transform::from_xyz((x as f32) * scale, 0.0, (y as f32) * scale),
..default()
});
commands.spawn(PbrBundle {
mesh: mesh.clone(),
mesh: meshes.choose(&mut material_rng).unwrap().0.clone(),
material: materials.choose(&mut material_rng).unwrap().clone(),
transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5),
transform: Transform::from_xyz(0.0, (x as f32) * scale, (y as f32) * scale),
..default()
});
}
}
// camera
let center = 0.5 * scale * Vec3::new(WIDTH as f32, HEIGHT as f32, WIDTH as f32);
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(WIDTH as f32, HEIGHT as f32, WIDTH as f32),
transform: Transform::from_translation(center),
..default()
});
// Inside-out box around the meshes onto which shadows are cast (though you cannot see them...)
commands.spawn((
PbrBundle {
mesh: mesh_assets.add(Cuboid::from_size(2.0 * 1.1 * center)),
material: material_assets.add(StandardMaterial::from(Color::WHITE)),
transform: Transform::from_scale(-Vec3::ONE).with_translation(center),
..default()
},
NotShadowCaster,
));
}
}

commands.spawn(DirectionalLightBundle::default());
commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight {
shadows_enabled: args.shadows,
..default()
},
transform: Transform::IDENTITY.looking_at(Vec3::new(0.0, -1.0, -1.0), Vec3::Y),
..default()
});
}

fn init_textures(args: &Args, images: &mut Assets<Image>) -> Vec<Handle<Image>> {
Expand Down Expand Up @@ -229,7 +279,7 @@ fn init_materials(
textures: &[Handle<Image>],
assets: &mut Assets<StandardMaterial>,
) -> Vec<Handle<StandardMaterial>> {
let capacity = if args.vary_per_instance {
let capacity = if args.vary_material_data_per_instance {
match args.layout {
Layout::Cube => (WIDTH - WIDTH / 10) * (HEIGHT - HEIGHT / 10),
Layout::Sphere => WIDTH * HEIGHT * 4,
Expand Down Expand Up @@ -262,6 +312,103 @@ fn init_materials(
materials
}

fn init_meshes(args: &Args, assets: &mut Assets<Mesh>) -> Vec<(Handle<Mesh>, Transform)> {
let capacity = args.mesh_count.max(1);

let mut radius_rng = StdRng::seed_from_u64(42);
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
let mut variant = 0;
std::iter::repeat_with(|| {
let radius = radius_rng.gen_range(0.25f32..=0.75f32);
let (handle, transform) = match variant % 15 {
0 => (
assets.add(Cuboid {
half_size: Vec3::splat(radius),
}),
Transform::IDENTITY,
),
1 => (
assets.add(Capsule3d {
radius,
half_length: radius,
}),
Transform::IDENTITY,
),
2 => (
assets.add(Circle { radius }),
Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y),
),
3 => {
let mut vertices = [Vec2::ZERO; 3];
let dtheta = std::f32::consts::TAU / 3.0;
for (i, vertex) in vertices.iter_mut().enumerate() {
let (s, c) = (i as f32 * dtheta).sin_cos();
*vertex = Vec2::new(c, s) * radius;
}
(
assets.add(Triangle2d { vertices }),
Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y),
)
}
4 => (
assets.add(Rectangle {
half_size: Vec2::splat(radius),
}),
Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y),
),
v if (5..=8).contains(&v) => (
assets.add(RegularPolygon {
circumcircle: Circle { radius },
sides: v,
}),
Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y),
),
9 => (
assets.add(Cylinder {
radius,
half_height: radius,
}),
Transform::IDENTITY,
),
10 => (
assets.add(Ellipse {
half_size: Vec2::new(radius, 0.5 * radius),
}),
Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y),
),
11 => (
assets.add(
Plane3d {
normal: Dir3::NEG_Z,
}
.mesh()
.size(radius, radius),
),
Transform::IDENTITY,
),
12 => (assets.add(Sphere { radius }), Transform::IDENTITY),
13 => (
assets.add(Torus {
minor_radius: 0.5 * radius,
major_radius: radius,
}),
Transform::IDENTITY.looking_at(Vec3::Y, Vec3::Y),
),
14 => (
assets.add(Capsule2d {
radius,
half_length: radius,
}),
Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y),
),
_ => unreachable!(),
};
variant += 1;
(handle, transform)
})
.take(capacity)
.collect()
}

// 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/
Expand Down