Skip to content

Commit b6a5ab7

Browse files
nicopapcart
andauthored
0.11 Section: Morph Targets (#665)
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
1 parent 25227bb commit b6a5ab7

12 files changed

+116
-0
lines changed
Loading
Loading

content/news/2023-07-07-bevy-0.11/index.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,125 @@ Since our last release a few months ago we've added a _ton_ of new features, bug
1515

1616
<!-- more -->
1717

18+
* **Morph targets**: Vertex-based animations
1819
* **Parallax mapping**: Materials now support an optional depth map, giving
1920
flat surfaces a feel of depth through parallaxing the material's textures.
2021

22+
## Morph Targets
23+
24+
<div class="release-feature-authors">authors: @nicopap, @cart</div>
25+
26+
Bevy, since the 0.7 release, supports 3D animations.
27+
28+
But it only supported _skeletal_ animations. Leaving on the sidewalk a common
29+
animation type called _morph targets_ (aka blendshapes, aka keyshapes, and a slew
30+
of other name). This is the grandparent of all 3D character animation!
31+
[Crash Bandicoot]'s run cycle used morph targets.
32+
33+
<video controls><source src="morph_targets_video.mp4" type="video/mp4"/></video>
34+
<div style="font-size: 1.0rem" class="release-feature-authors">Character model by <a href="https://www.artstation.com/zambrah">Samuel Rosario</a> (© all rights reserved), used with permission. Modified by nicopap, using the <a href="https://studio.blender.org/characters/snow/v2/">Snow</a> character texture by Demeter Dzadik for Blender Studios <a href="https://creativecommons.org/licenses/by/4.0/">(🅯 CC-BY)</a>.
35+
</div>
36+
<!-- The previous paragraph requires the <a href> tags, since zola doesn't
37+
process markdown markup within tags -->
38+
39+
Nowadays, an animation artist will typically use a skeleton rig for wide
40+
moves and morph targets to clean up the detailed movements.
41+
42+
When it comes to game assets, however, the complex skeleton rigs used by
43+
artists for faces and hands are too heavy. Usually, the poses are
44+
"baked" into morph poses, and facial expression transitions are handled
45+
in the engine through morph targets.
46+
47+
Morph targets is a very simple animation method. Take a model, have a base
48+
vertex position, move the vertices around to create several poses:
49+
50+
<div style="flex-direction:row;display:flex;justify-content:space-evenly">
51+
<div style="display:flex;flex-direction:column;align-items:center;width:20%"><p><b>Default</b></p><img alt="A wireframe rendering of a character's face with a neutral expression" src="default-pose-bw.png"></div>
52+
<div style="display:flex;flex-direction:column;align-items:center;width:20%"><p><b>Frown</b></p><img alt="Wireframe rendering of a frowning character" src="frown-pose-bw.png"></div>
53+
<div style="display:flex;flex-direction:column;align-items:center;width:20%"><p><b>Smirk</b></p><img alt="Wireframe rendering of a smirking character" src="smirk-pose-bw.png"></div>
54+
</div>
55+
56+
Store those poses as a difference between the default base mesh and the variant
57+
pose, then, at runtime, _mix_ each pose. Now that we have the difference with
58+
the base mesh, we can get the variant pose by simply adding to the base
59+
vertices positions.
60+
61+
That's it, the morph target shader looks like this:
62+
63+
```rust
64+
fn morph_vertex(vertex: Vertex) {
65+
for (var i: u32 = 0u; i < pose_count(); i++) {
66+
let weight = weight_for_pose(i);
67+
vertex.position += weight * get_difference(vertex.index, position_offset, i);
68+
vertex.normal += weight * get_difference(vertex.index, normal_offset, i);
69+
}
70+
}
71+
```
72+
73+
In Bevy, we store the weights per pose in the `MorphWeights` component.
74+
75+
```rust
76+
fn set_weights_system(mut morph_weights: Query<&mut MorphWeights>) {
77+
for mut entity_weights in &mut morph_weights {
78+
let weights = entity_weights.weights_mut();
79+
80+
weights[0] = 0.5;
81+
weights[1] = 0.25;
82+
}
83+
}
84+
```
85+
86+
Now assuming that we have two morph targets, (1) the frown pose, (2)
87+
the smirk pose:
88+
89+
<div style="flex-direction:row;display:flex;justify-content:space-evenly">
90+
<div style="display:flex;flex-direction:column;align-items:center;width:12%">
91+
<p><b>[0.0, 0.0]</b></p>
92+
<p style="margin:0;font-size:75%">default pose</p>
93+
<img alt="Neutral face expression" src="morph_target_default-0.png">
94+
</div>
95+
<div style="display:flex;flex-direction:column;align-items:center;width:12%">
96+
<p><b>[1.0, 0.0]</b></p>
97+
<p style="margin:0;font-size:75%">frown only</p>
98+
<img alt="Frowning" src="morph_target_frown-0.png">
99+
</div>
100+
<div style="display:flex;flex-direction:column;align-items:center;width:12%">
101+
<p><b>[0.0, 1.0]</b></p>
102+
<p style="margin:0;font-size:75%">smirk only</p>
103+
<img alt="Smirking" src="morph_target_smirk.png">
104+
</div>
105+
<div style="display:flex;flex-direction:column;align-items:center;width:12%">
106+
<p><b>[0.5, 0.0]</b></p>
107+
<p style="margin:0;font-size:75%">half frown</p>
108+
<img alt="Slightly frowning" src="morph_target_frown-half-0.png">
109+
</div>
110+
<div style="display:flex;flex-direction:column;align-items:center;width:12%">
111+
<p><b>[1.0, 1.0]</b></p>
112+
<p style="margin:0;font-size:75%">both at max</p>
113+
<img alt="Making faces" src="morph_target_both-0.png">
114+
</div>
115+
<div style="display:flex;flex-direction:column;align-items:center;width:12%">
116+
<p><b>[0.5, 0.25]</b></p>
117+
<p style="margin:0;font-size:75%">bit of both</p>
118+
<img alt="Slightly frowning/smirking" src="morph_target_smirk-quarter-frown-half-0.png">
119+
</div>
120+
</div>
121+
122+
While conceptually simple, it requires communicating to the GPU a tremendous
123+
amount of data. Thousand of vertices, each 288 bits, several model variations,
124+
sometimes a hundred.
125+
126+
Bevy's morph target implementation is similar to BabyloneJS's. We store the
127+
vertex data as pixels in a 3D texture. This allows morph targets to not only
128+
run on WebGPU, but also on the WebGL2 wgpu backend.
129+
130+
This could be improved in a number of ways, but it is sufficient for an
131+
initial implementation.
132+
133+
<video controls><source src="morph_target_smirk.mp4" type="video/mp4"/></video>
134+
135+
[Crash Bandicoot]: https://en.wikipedia.org/wiki/Crash_Bandicoot_(video_game)#Gameplay
136+
21137
## Parallax Mapping
22138

23139
<div class="release-feature-authors">author: @nicopap</div>
Loading
Loading
Loading
Loading
Loading
Binary file not shown.
Loading

0 commit comments

Comments
 (0)