mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
many_cubes: Add no automatic batching and generation of different meshes (#12363)
# Objective - Enable stressing of more of the material mesh entity draw code paths ## Solution - Support generation of a number of different mesh assets from the built-in primitives, and select randomly from them. This breaks batches based on different meshes. - Support disabling automatic batching. This skips the batching cost at the expense of stressing render pass draw command encoding. - Support enabling directional light cascaded shadow mapping - this is commonly a big source of slow down in normal scenes. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: François Mockers <francois.mockers@vleue.com>
This commit is contained in:
parent
37522fd0ae
commit
d0a5ddacd9
1 changed files with 167 additions and 18 deletions
|
@ -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,
|
||||
|
@ -39,15 +41,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)]
|
||||
|
@ -109,7 +123,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>>,
|
||||
) {
|
||||
|
@ -118,8 +132,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);
|
||||
|
@ -139,23 +154,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((
|
||||
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
|
||||
|
@ -164,44 +196,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>> {
|
||||
|
@ -234,7 +284,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,
|
||||
|
@ -269,6 +319,105 @@ fn init_materials(
|
|||
materials
|
||||
}
|
||||
|
||||
fn init_meshes(args: &Args, assets: &mut Assets<Mesh>) -> Vec<(Handle<Mesh>, Transform)> {
|
||||
let capacity = args.mesh_count.max(1);
|
||||
|
||||
// We're seeding the PRNG here to make this example deterministic for testing purposes.
|
||||
// This isn't strictly required in practical use unless you need your app to be deterministic.
|
||||
let mut radius_rng = ChaCha8Rng::seed_from_u64(42);
|
||||
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/
|
||||
|
|
Loading…
Reference in a new issue