@@ -15,9 +15,125 @@ Since our last release a few months ago we've added a _ton_ of new features, bug
15
15
16
16
<!-- more -->
17
17
18
+ * ** Morph targets** : Vertex-based animations
18
19
* ** Parallax mapping** : Materials now support an optional depth map, giving
19
20
flat surfaces a feel of depth through parallaxing the material's textures.
20
21
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
+
21
137
## Parallax Mapping
22
138
23
139
<div class =" release-feature-authors " >author: @nicopap</div >
0 commit comments