Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/stress_tests/many_morph_targets.rs
9735 views
1
//! Simple benchmark to test rendering many meshes with animated morph targets.
2
3
use argh::FromArgs;
4
use bevy::{
5
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
6
post_process::motion_blur::MotionBlur,
7
prelude::*,
8
scene::SceneInstanceReady,
9
window::{PresentMode, WindowResolution},
10
winit::WinitSettings,
11
};
12
use chacha20::ChaCha8Rng;
13
use core::{f32::consts::PI, str::FromStr};
14
use rand::{RngExt, SeedableRng};
15
16
/// Controls the morph weights.
17
#[derive(PartialEq)]
18
enum ArgWeights {
19
/// Weights will be animated by an `AnimationClip`.
20
Animated,
21
22
/// Set all the weights to one.
23
One,
24
25
/// Set all the weights to zero, minimizing vertex shader cost.
26
Zero,
27
28
/// Set all the weights to a very small value, so the pixel shader cost
29
/// should be similar to `Zero` but vertex shader cost the same as `One`.
30
Tiny,
31
}
32
33
impl FromStr for ArgWeights {
34
type Err = String;
35
36
fn from_str(s: &str) -> Result<Self, Self::Err> {
37
match s {
38
"animated" => Ok(Self::Animated),
39
"zero" => Ok(Self::Zero),
40
"one" => Ok(Self::One),
41
"tiny" => Ok(Self::Tiny),
42
_ => Err("must be 'animated', 'one', `zero`, or 'tiny'".into()),
43
}
44
}
45
}
46
47
/// Controls the camera.
48
#[derive(PartialEq)]
49
enum ArgCamera {
50
/// Fill the screen with meshes.
51
Near,
52
53
/// Zoom far out. This is used to reduce pixel shader costs and so emphasize
54
/// vertex shader costs.
55
Far,
56
}
57
58
impl FromStr for ArgCamera {
59
type Err = String;
60
61
fn from_str(s: &str) -> Result<Self, Self::Err> {
62
match s {
63
"near" => Ok(Self::Near),
64
"far" => Ok(Self::Far),
65
_ => Err("must be 'near' or 'far'".into()),
66
}
67
}
68
}
69
70
/// `many_morph_targets` stress test
71
#[derive(FromArgs, Resource)]
72
struct Args {
73
/// number of meshes - default = 1024
74
#[argh(option, default = "1024")]
75
count: usize,
76
77
/// options: 'animated', 'one', 'zero', 'tiny' - default = 'animated'
78
#[argh(option, default = "ArgWeights::Animated")]
79
weights: ArgWeights,
80
81
/// options: 'near', 'far' - default = 'near'
82
#[argh(option, default = "ArgCamera::Near")]
83
camera: ArgCamera,
84
85
/// enable motion blur
86
#[argh(switch)]
87
motion_blur: bool,
88
}
89
90
fn main() {
91
// `from_env` panics on the web
92
#[cfg(not(target_arch = "wasm32"))]
93
let args: Args = argh::from_env();
94
#[cfg(target_arch = "wasm32")]
95
let args = Args::from_args(&[], &[]).unwrap();
96
97
App::new()
98
.add_plugins((
99
DefaultPlugins.set(WindowPlugin {
100
primary_window: Some(Window {
101
title: "Many Morph Targets".to_string(),
102
present_mode: PresentMode::AutoNoVsync,
103
resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(1.0),
104
..Default::default()
105
}),
106
..Default::default()
107
}),
108
FrameTimeDiagnosticsPlugin::default(),
109
LogDiagnosticsPlugin::default(),
110
))
111
.insert_resource(WinitSettings::continuous())
112
.insert_resource(GlobalAmbientLight {
113
brightness: 1000.0,
114
..Default::default()
115
})
116
.insert_resource(args)
117
.add_systems(Startup, setup)
118
.run();
119
}
120
121
#[derive(Component, Clone)]
122
struct AnimationToPlay {
123
graph_handle: Handle<AnimationGraph>,
124
index: AnimationNodeIndex,
125
speed: f32,
126
}
127
128
impl AnimationToPlay {
129
fn with_speed(&self, speed: f32) -> Self {
130
AnimationToPlay {
131
speed,
132
..self.clone()
133
}
134
}
135
}
136
137
fn setup(
138
args: Res<Args>,
139
asset_server: Res<AssetServer>,
140
mut graphs: ResMut<Assets<AnimationGraph>>,
141
mut commands: Commands,
142
) {
143
const ASSET_PATH: &str = "models/animated/MorphStressTest.gltf";
144
145
let scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(ASSET_PATH)));
146
147
let mut rng = ChaCha8Rng::seed_from_u64(856673);
148
149
let animations = (0..3)
150
.map(|gltf_index| {
151
let (graph, index) = AnimationGraph::from_clip(
152
asset_server.load(GltfAssetLabel::Animation(gltf_index).from_asset(ASSET_PATH)),
153
);
154
AnimationToPlay {
155
graph_handle: graphs.add(graph),
156
index,
157
speed: 1.0,
158
}
159
})
160
.collect::<Vec<_>>();
161
162
// Arrange the meshes in a grid.
163
164
let count = args.count;
165
let x_dim = ((count as f32).sqrt().ceil() as usize).max(1);
166
let y_dim = count.div_ceil(x_dim);
167
168
for mesh_index in 0..count {
169
let animation = animations[mesh_index.rem_euclid(animations.len())].clone();
170
171
let x = 2.5 + (5.0 * ((mesh_index.rem_euclid(x_dim) as f32) - ((x_dim as f32) * 0.5)));
172
let y = -2.2 - (3.0 * ((mesh_index.div_euclid(x_dim) as f32) - ((y_dim as f32) * 0.5)));
173
174
// Randomly vary the animation speed so that the number of morph targets
175
// active on each frame is more likely to be stable.
176
177
let animation_speed = rng.random_range(0.5..=1.5);
178
179
commands
180
.spawn((
181
animation.with_speed(animation_speed),
182
scene.clone(),
183
Transform::from_xyz(x, y, 0.0),
184
))
185
.observe(play_animation)
186
.observe(set_weights);
187
}
188
189
commands.spawn((
190
DirectionalLight::default(),
191
Transform::from_rotation(Quat::from_rotation_z(PI / 2.0)),
192
));
193
194
let camera_distance = (x_dim as f32)
195
* match args.camera {
196
ArgCamera::Near => 4.0,
197
ArgCamera::Far => 200.0,
198
};
199
200
let mut camera = commands.spawn((
201
Camera3d::default(),
202
Transform::from_xyz(0.0, 0.0, camera_distance).looking_at(Vec3::ZERO, Vec3::Y),
203
));
204
205
if args.motion_blur {
206
camera.insert((
207
MotionBlur {
208
// Use an unrealistically large shutter angle so that motion blur is clearly visible.
209
shutter_angle: 3.0,
210
..Default::default()
211
},
212
// MSAA and MotionBlur are not compatible on WebGL.
213
#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]
214
Msaa::Off,
215
));
216
}
217
}
218
219
fn play_animation(
220
trigger: On<SceneInstanceReady>,
221
mut commands: Commands,
222
args: Res<Args>,
223
children: Query<&Children>,
224
animations_to_play: Query<&AnimationToPlay>,
225
mut players: Query<&mut AnimationPlayer>,
226
) {
227
if args.weights == ArgWeights::Animated
228
&& let Ok(animation_to_play) = animations_to_play.get(trigger.entity)
229
{
230
for child in children.iter_descendants(trigger.entity) {
231
if let Ok(mut player) = players.get_mut(child) {
232
commands
233
.entity(child)
234
.insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));
235
236
player
237
.play(animation_to_play.index)
238
.repeat()
239
.set_speed(animation_to_play.speed);
240
}
241
}
242
}
243
}
244
245
fn set_weights(
246
trigger: On<SceneInstanceReady>,
247
args: Res<Args>,
248
children: Query<&Children>,
249
mut weight_components: Query<&mut MorphWeights>,
250
) {
251
if let Some(weight_value) = match args.weights {
252
ArgWeights::One => Some(1.0),
253
ArgWeights::Zero => Some(0.0),
254
ArgWeights::Tiny => Some(0.00001),
255
_ => None,
256
} {
257
for child in children.iter_descendants(trigger.entity) {
258
if let Ok(mut weight_component) = weight_components.get_mut(child) {
259
weight_component.weights_mut().fill(weight_value);
260
}
261
}
262
}
263
}
264
265