Path: blob/main/examples/stress_tests/many_morph_targets.rs
9735 views
//! Simple benchmark to test rendering many meshes with animated morph targets.12use argh::FromArgs;3use bevy::{4diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},5post_process::motion_blur::MotionBlur,6prelude::*,7scene::SceneInstanceReady,8window::{PresentMode, WindowResolution},9winit::WinitSettings,10};11use chacha20::ChaCha8Rng;12use core::{f32::consts::PI, str::FromStr};13use rand::{RngExt, SeedableRng};1415/// Controls the morph weights.16#[derive(PartialEq)]17enum ArgWeights {18/// Weights will be animated by an `AnimationClip`.19Animated,2021/// Set all the weights to one.22One,2324/// Set all the weights to zero, minimizing vertex shader cost.25Zero,2627/// Set all the weights to a very small value, so the pixel shader cost28/// should be similar to `Zero` but vertex shader cost the same as `One`.29Tiny,30}3132impl FromStr for ArgWeights {33type Err = String;3435fn from_str(s: &str) -> Result<Self, Self::Err> {36match s {37"animated" => Ok(Self::Animated),38"zero" => Ok(Self::Zero),39"one" => Ok(Self::One),40"tiny" => Ok(Self::Tiny),41_ => Err("must be 'animated', 'one', `zero`, or 'tiny'".into()),42}43}44}4546/// Controls the camera.47#[derive(PartialEq)]48enum ArgCamera {49/// Fill the screen with meshes.50Near,5152/// Zoom far out. This is used to reduce pixel shader costs and so emphasize53/// vertex shader costs.54Far,55}5657impl FromStr for ArgCamera {58type Err = String;5960fn from_str(s: &str) -> Result<Self, Self::Err> {61match s {62"near" => Ok(Self::Near),63"far" => Ok(Self::Far),64_ => Err("must be 'near' or 'far'".into()),65}66}67}6869/// `many_morph_targets` stress test70#[derive(FromArgs, Resource)]71struct Args {72/// number of meshes - default = 102473#[argh(option, default = "1024")]74count: usize,7576/// options: 'animated', 'one', 'zero', 'tiny' - default = 'animated'77#[argh(option, default = "ArgWeights::Animated")]78weights: ArgWeights,7980/// options: 'near', 'far' - default = 'near'81#[argh(option, default = "ArgCamera::Near")]82camera: ArgCamera,8384/// enable motion blur85#[argh(switch)]86motion_blur: bool,87}8889fn main() {90// `from_env` panics on the web91#[cfg(not(target_arch = "wasm32"))]92let args: Args = argh::from_env();93#[cfg(target_arch = "wasm32")]94let args = Args::from_args(&[], &[]).unwrap();9596App::new()97.add_plugins((98DefaultPlugins.set(WindowPlugin {99primary_window: Some(Window {100title: "Many Morph Targets".to_string(),101present_mode: PresentMode::AutoNoVsync,102resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(1.0),103..Default::default()104}),105..Default::default()106}),107FrameTimeDiagnosticsPlugin::default(),108LogDiagnosticsPlugin::default(),109))110.insert_resource(WinitSettings::continuous())111.insert_resource(GlobalAmbientLight {112brightness: 1000.0,113..Default::default()114})115.insert_resource(args)116.add_systems(Startup, setup)117.run();118}119120#[derive(Component, Clone)]121struct AnimationToPlay {122graph_handle: Handle<AnimationGraph>,123index: AnimationNodeIndex,124speed: f32,125}126127impl AnimationToPlay {128fn with_speed(&self, speed: f32) -> Self {129AnimationToPlay {130speed,131..self.clone()132}133}134}135136fn setup(137args: Res<Args>,138asset_server: Res<AssetServer>,139mut graphs: ResMut<Assets<AnimationGraph>>,140mut commands: Commands,141) {142const ASSET_PATH: &str = "models/animated/MorphStressTest.gltf";143144let scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(ASSET_PATH)));145146let mut rng = ChaCha8Rng::seed_from_u64(856673);147148let animations = (0..3)149.map(|gltf_index| {150let (graph, index) = AnimationGraph::from_clip(151asset_server.load(GltfAssetLabel::Animation(gltf_index).from_asset(ASSET_PATH)),152);153AnimationToPlay {154graph_handle: graphs.add(graph),155index,156speed: 1.0,157}158})159.collect::<Vec<_>>();160161// Arrange the meshes in a grid.162163let count = args.count;164let x_dim = ((count as f32).sqrt().ceil() as usize).max(1);165let y_dim = count.div_ceil(x_dim);166167for mesh_index in 0..count {168let animation = animations[mesh_index.rem_euclid(animations.len())].clone();169170let x = 2.5 + (5.0 * ((mesh_index.rem_euclid(x_dim) as f32) - ((x_dim as f32) * 0.5)));171let y = -2.2 - (3.0 * ((mesh_index.div_euclid(x_dim) as f32) - ((y_dim as f32) * 0.5)));172173// Randomly vary the animation speed so that the number of morph targets174// active on each frame is more likely to be stable.175176let animation_speed = rng.random_range(0.5..=1.5);177178commands179.spawn((180animation.with_speed(animation_speed),181scene.clone(),182Transform::from_xyz(x, y, 0.0),183))184.observe(play_animation)185.observe(set_weights);186}187188commands.spawn((189DirectionalLight::default(),190Transform::from_rotation(Quat::from_rotation_z(PI / 2.0)),191));192193let camera_distance = (x_dim as f32)194* match args.camera {195ArgCamera::Near => 4.0,196ArgCamera::Far => 200.0,197};198199let mut camera = commands.spawn((200Camera3d::default(),201Transform::from_xyz(0.0, 0.0, camera_distance).looking_at(Vec3::ZERO, Vec3::Y),202));203204if args.motion_blur {205camera.insert((206MotionBlur {207// Use an unrealistically large shutter angle so that motion blur is clearly visible.208shutter_angle: 3.0,209..Default::default()210},211// MSAA and MotionBlur are not compatible on WebGL.212#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]213Msaa::Off,214));215}216}217218fn play_animation(219trigger: On<SceneInstanceReady>,220mut commands: Commands,221args: Res<Args>,222children: Query<&Children>,223animations_to_play: Query<&AnimationToPlay>,224mut players: Query<&mut AnimationPlayer>,225) {226if args.weights == ArgWeights::Animated227&& let Ok(animation_to_play) = animations_to_play.get(trigger.entity)228{229for child in children.iter_descendants(trigger.entity) {230if let Ok(mut player) = players.get_mut(child) {231commands232.entity(child)233.insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));234235player236.play(animation_to_play.index)237.repeat()238.set_speed(animation_to_play.speed);239}240}241}242}243244fn set_weights(245trigger: On<SceneInstanceReady>,246args: Res<Args>,247children: Query<&Children>,248mut weight_components: Query<&mut MorphWeights>,249) {250if let Some(weight_value) = match args.weights {251ArgWeights::One => Some(1.0),252ArgWeights::Zero => Some(0.0),253ArgWeights::Tiny => Some(0.00001),254_ => None,255} {256for child in children.iter_descendants(trigger.entity) {257if let Ok(mut weight_component) = weight_components.get_mut(child) {258weight_component.weights_mut().fill(weight_value);259}260}261}262}263264265