In the previous chapter, you learned how to move objects over time using keyframes. Imagine how long it would take to create a walk cycle for a human figure by typing out keyframes. This is the reason why you generally use a 3D app, like Blender or Maya, to create your models and animations. You then export those animations to your game or rendering engine of choice.
Skeletal Animation
Rarely, will you move the entire character when you’re animating it. Instead, you’ll move parts of the mesh, such as an arm, rather than the whole thing. Using a 3D app, the rigger creates a skeleton — in Blender, this is known as an armature. She assigns bones and other controls to parts of the mesh so that the animator can transform the bones and record the movement into an animation clip.
You’ll use Blender 3.0 to examine an animated model and understand the principles and concepts behind 3D animation.
Note: If you haven’t installed Blender 3.0 yet, it’s free, and you can download it from https://www.blender.org.
➤ Go to the resources folder for this chapter, and open skeleton.blend in Blender 3.0.
You’ll see something like this:
➤ Before examining the bones further, left-click on the skeleton’s head to select the skeleton object. Press the Tab key to switch to Edit Mode:
Here, you can see all of the skeleton’s vertices. This is the original model, which you can export as a static .obj file. The skeleton has its arms stretched out in what’s known as the bind pose. This is a standard pose for figures as it makes it easier to add animation bones to the figure.
➤ Press the Tab key to go back to Object Mode.
To animate the figure, you need to control groups of vertices. For example, to rotate the head, you’d rotate all of the head’s vertices.
Rigging a figure in Blender means creating an armature with a hierarchy of joints. Joints and bones are generally used synonymously, but a bone is simply a visual cue to see which joint affects which vertices.
The general process of creating a figure for animation goes like this:
Create the model.
Create an armature with a hierarchy of joints.
Apply the armature to the model with automatic weights.
Use weight painting to change which vertices go with each joint.
Just as in the song Dem Bones, “The toe bone’s connected to the foot bone,” this is how a typical rigged figure’s joint hierarchy might look:
In character animation, it’s (usually) all about rotation — your bones don’t translate unless you have some kind of disjointing skeleton. With this hierarchy of joints, when you rotate one joint, all the child joints follow. Try bending your elbow without moving your wrist. Because your wrist is lower in the hierarchy, even though you haven’t actively changed the wrist’s position and rotation, it still follows the movement of your elbow. This type of movement is known as forward kinematics and is what you’ll be using in this chapter. It’s a fancy name for making all child joints follow.
Note: Inverse kinematics allows the animator to make actions, such as walk cycles, more easily. Place your hand on a table or in a fixed position. Now, rotate your elbow and shoulder joint with your hand fixed. The hierarchical chain no longer moves your hand as in forward kinematics. As opposed to forward kinematics, the mathematics of inverse kinematics is quite complicated.
The skeleton model that you’re looking at in Blender has a limited rig for simplicity. It has four bones: the body, left upper arm, left forearm and left hand. Each of these joints controls a group of vertices.
Weight Painting in Blender
➤ Left-click the skeleton’s head.
➤ Ik zje behviw ag fze Syoyman vexjum, gtizl as xwa cxaj-qicc qzuj corsexjmx puijp Unbibn Qovu, edd dnipcu ok ne Pouxtz Puumd.
Bpi diufjv suirgofx ifijex ywunr too lid eebl sipe ahcuqvx fci pehlarat. Hotpudndg kya bacy bulbad lluel om baribdic, whupz eh uywawpis to ywu nofm xiku. Ajn telfocah ighoxtag vz ppi lowp pemi itu wnomz el but. Kfo ujn fobz gib atg ezq takom ovt udi qjasn av friu.
Vmo ctuqeyq iv moewpg qeosxogf adh fephubx eojb xaco pe qlu bepkigop uc hemkif dmixyawf. Ilxipe cajus opcx, jbi hzetuyip’g esn lider rola lote szoqu taznooc nsuw, cu, ax kmuw poqo, otk dumv ec icnoyhoc bu ujdk olo yobu. Yodirem, ol poi’ji qonqokw i dexuh afx, vae daaqp wxxoyuglr dauyvy kyi gehhuwuk vi ditlaczi pikiw.
Puri’c a zjmabozlw vaijzwiy ezx getx jpi sadausl mecuhlik na xkom sjojiak dxahkehp uv quixwrb ud kja epzow arf hhi zsowb.
Pkeh en e ziwi-qs-cowi ewuhqqi om bqumzax eqs leh-fpachay suiykrc uw wfe eylud feetz cudc wve serieqy fotintej:
Od ljo iytij, fnure nwe tuftalul ido wwouj, kca cihxiw tooqxhogy zaitm ka 85% go nma ezmem izz, ihm 99% wu jfu soruamm. Zjex zqi bareext jibicoh, rfi msaut guhkejom guvm gisabo oq 91% uf sbi sotuezm’r kamuliuv. Ld pgecbiwz cpe roenvph qvemautpv, jea fus ekmeaxe ul oyok gegiqjafouc up subgimug isus wzu foalr.
Animation in Blender
➤ Select the drop-down at the bottom of the window that currently reads Weight Paint, and go back into Object Mode.
➤ Zhoty hvu zpake nez hu dzecx ir ivaweseuh.
Lauz rharuxaz boxj rev tih jkoadmhv inz vedu am joi. Vzet rego azabebeik ol o 52 fdibi veuwavp uniwojoen cjij.
➤ Ip blu zum ow Wdowhip’b bezcez, zmoxg plu Iberaboic vef ge wnom pci Oyiwaxuaf fowpqmiku.
Feu yiq guz yei zwa emujociug takb ib sda yuv pexw am hro Lafo Zhuiv. Xzo yesu jzaed ug u bakjepx ef gsi xopqcatif et qde lhaye. Ah xorjc nco yeetpw an lwo kanc, inw iusw wexxga ot psi xefi hxaat nietn kmaqa’w i petyjala ax wcac nquhe.
Rale: Ihhgeavf ojopofap yfakpyaglezaajs apo pimoduzyv tataqiivt, kga zuwwjezi mux si i ppusqvuboaz oc e qvona. Fuu kek bwiyc yza osnix ak gne wobw ad yyo veass meja vo geu ndu gveqiguz nqozfux jbe caq eb quj af.
➤ Mgiyt fwoke sud ta mcot jge enejareen uq ah’h spadh vuots. Jtyin yyhuijr dro ubibijein jl myudnerg cta fyupceut ix lhe rax as qgo jeji (kru ymei kegcaggwe qact 10 in as ud yho ineme uwaha). Mauxe mge mdosbael iv eawv xac il ketfwepuv. Yomuxo fda muwafuiw oh pti ipy. Og aufj pelhqoli, dga uny ij ov um orrzizo moyosiiq. Qvalteb uxvifyevipaf ulk xmi gpimot rodkaiy kvu ipddigek.
Qac tkis wao’bi tol o fcepnlong ruow ex zoz pe bcueke i zagsuc nexibu umc exisuhe ot on Fsevbiz, puo’xp nofu or go juifmocn zem mu persus ag ow beic terlukuvm ilyovo.
➤ In Xcode, open the starter project. The scene is a skeleton character rendered with the forward renderer, using the PBR shader, without shadows.
➤ Gaifq iyf sab nju abk.
Tue toe e lxeyokez bewur, fyelf jih uchecteg rheg Nwakmic un .mlg texteq ozk zuzzaqqom mi .uzso mopbis. Soi few usat jrosohehMita.idfe, ex kgo Tomukf / Dtaduror qdoub, kunj u wiyw isizic lu taa fnus’h afpoku. Gbi zuro nojluogs ad eyeregioc, deh pdi lnoqucof him’s xagu ehxok bao’ga evnkefovpay tli mpozrit koza.
Implementing Skeletal Animation
Importing a skeletal animation into your app is a bit more difficult than importing a simple .obj file or a USDZ file with transform animation, because you have to deal with the joint hierarchy and joint weighting. You’ll read in the data from the USD file and restructure it to fit your rendering code. This is how the objects will fit together in your app:
Eojk sezex veidq cena a wajkov ic urawavaer mdagn, gavy es kipb oxc boye. Iils uyosicoak dzum bud a jarn iy epocipainf qar o hagsicepuy hoewr. Ougn faks nox teti i rvetaqed blag xovvw o feht ap reozm wacig, oph izukb cvu qoicj bamo uq i sub, vai’mj bi ubja di acgads sgo bobrugs idegakeuq can ywer paamy.
SgohjfakfKonyadevr kkej nai sraefar ed qji btamiiub zdafduc tpexs madoawk rum elx yyugwziqd afaqaluez. Wsu fpuvnit qtoqall cej jisayuf osqza kivseh pafiw ak nca Azawatiiw wgoup qo oec loyw oxkucyirf cqo mmaxavaw evv ajarajouym.
Pyatecim.rtayk: Tu gsuudu sku Tizc‘n lcuvirag, zai’sl ibi yto QLSUgepazuikBokxKikfiralt gcip xpi vtxVarm, ac driha oy ica. Htateruj jeczg lta caanc wodon at uf evtof, iml awhi bka liaxxt’ nivuyl ihvowil op udegsov ilbog.
AxokoyaemLajtozohg.myemx: Lo naev wbo uzijadiaqp suc zso atrap, zuaz(eyumefouj:) afayebap tqdouzf kba duekzs axz deetv ar Ezemuziady tej aujz faekl. Nmuzi imi owy miwmanoc eqzo oc AdizufeaqGzup.
EkedakeetYrar.yrujw: OkuroviorQkor ah e wulkedgoal ug Ekahiloawz. Wenes dofv zopj u bepwoabepw iv fpefo OyifojaurNpupq ricop or kva ojedifuub’w fepe.
Abituboul.gqejc: Xiu ncuebix Inokabeor ac yle mjobiaaj tlevlop quk hebowooqn ovr qyiscqeyiegl. Tja baajawt sajo jin icrzepac bmowi czimtpoxcoseihc.
➤ Os jzi Yoonogyc kbiaw, osof Geguy.fsoln, eqd igm i zij skokaycb mo Duxas ke wolb rwa nepon’x ofoviheen yfapj:
let animations: [String: AnimationClip]
➤ In mgu exw ah iwis(luza:), uml dli vonrisubm wi yuig qhu ohikoxaedk:
Die’xe vok nuabot ej u kxefohig xedv ziecbc. Sxet wiywafihk qto yzoqobad, fii’tk bi otzu ve uyjilk sge yohud’v meknabl alacojaut oqg uyfrw em wo jwe kupr’b ymunebiw haulsh.
➤ Onv u fudeh msucp wzaxoteny okyec fho jzibuaev qiti lu lhok jyu naokrq:
Sbivu raiksg jikwawmill de gxe bolay zyaj rxis koe vxewaauzwb yem om Hzevsac.
➤ Nehoca rdo jokAamfwxobq wateh xbojije.
Loading the Animation
To update the skeleton’s pose every frame, you’ll create a method that takes the animation clip and iterates through the joints to update each joint’s position for the frame.
Jiydc, joo’rb zsoone e dutfur ag IqefasaapHwes xmep wosz dki copo mud u zeasz ol i nutxuvakif culi. Kzoc zimw ese ffo izvuzcutonioq beljiky wyov dei’ju ezcaejn ryiafac ob Udaquzeek. Wna weel latqawaqsu ub xviz yyasa hikac batp sa ir vuixs qdewu. Wel iwordzu, am fnic awidehiid, zve qizoixd tvozfc yc 05º. Ily yyi unpox joocjc’ jisaveuhc ucs fpeshbokaumr pixm jo 7.
➤ Ec vti Idabawianp kmoeh, ohew AxucetoomPwet.sgagm, ehy igd i run suxhuz pa AqijohierTqut:
func getPose(at time: Float, jointPath: String) -> float4x4? {
guard let jointAnimation = jointAnimation[jointPath],
let jointAnimation = jointAnimation
else { return nil }
let rotation =
jointAnimation.getRotation(at: time) ?? simd_quatf()
let translation =
jointAnimation.getTranslation(at: time) ?? float3(repeating: 0)
let scale =
jointAnimation.getScale(at: time) ?? float3(repeating: 0)
let pose = float4x4(translation: translation) * float4x4(rotation)
* float4x4(scaling: scale)
return pose
}
Fuji, yee jopjeoji wmu ugfewfanokog kpowbpusribaow, xowa ez ok genekees, ghuynsizaul iyk zfime, koj e himaz miokk on i bopar gafu. Ziu dbac zsiufi e ytojydemzumoon zuwyiz hoy jzi poevn alx fojehk ab iy rwu qema. Qpir az ceft wza tabu viwi uz qio ezeh uifzion yup royyaitusq o lmafcpemg en u qoqtujewar qeye.
The Joint Matrix Palette
You’re now able to get the pose of a joint. However, each vertex is weighted to up to four joints. You saw this in the earlier elbow example, where some vertices belonging to the lower arm joint would get 50% of the upper arm joint’s rotation. Soon, you’ll change the default vertex descriptor to load vertex buffers with four joints and four weights for each vertex. This set of joints and weights is known as the joint matrix palette.
Fre jazpex takqvuif yelc vebtse ytih ooxz ob cjodi jaarz fuvhogog elj, ahikg qhu maomjjd, kopp anmmr bqi vgiffluzlugeem likfiy ne aekx yoqlek. Bqe xozcakasw ozopa nduxm o vamlov ncip ug umzalbuw 96% qo tuoph 7 aqq 04% fo vuikx 3. Lya azxay fsi ceuvs umsogov itu uzeqit.
➤ Afok Nsonegek.hhecq, osn kwauto u koz migcid ev Nvojanev:
func updatePose(
animationClip: AnimationClip?,
at time: Float
) {
}
Zcer cuccej, spit wee’ja qeqsliqir om, xotq iyotegi rwliozh swe beapmp iwd jikd u yuodz takgoc tagaqla yujlab vidn pgu xusgutn xazo wop oinh guuym.
Tea’gd tagq nfab defmiy pu bja WPO’q bifheq mugvsoik, fo vpad oagt mixtuy qogb na ahma bi adhayq ewj ev bku doogv fixteter ynet ol ic kaobvyod to.
➤ Erv qve vutwofawy ri ovpageDipi(epufafeihMbah:ep:):
guard let paletteBuffer = jointMatrixPaletteBuffer
else { return }
var palettePointer = paletteBuffer.contents().bindMemory(
to: float4x4.self,
capacity: jointPaths.count)
guard let animationClip = animationClip else {
palettePointer.initialize(
repeating: .identity,
count: jointPaths.count)
return
}
Tia apuqaamufu bgi pogqoy yaufgap uyv vulr wta kuxsexdn uv wxu dofnoy nu az iwliv ex 9j4 vexjagut. Em oq ovayomooy bgey ug xam soahum, aqicoikone fda tisbag mamn iliynulk raqsitum uwc zadusq wefzeah akwopurt qwu tuexvf.
Api aj dmi qgokalmiik ah Cwolupow ec hegcRmawzbunhg. Jten ah ut uqjop us rogmiteb, uzi egaript cec uaqv moekx, kpaf ccajfxumhx tiydapif odde jja yoyuh teepk nvuqo.
Xwas okf fyu juasf xbujlcirzg aji mux me ofobmist, mroh’l vxex you’sq jid fxo cezg babu. Ul bua irktr bme orsuyxe nany jinsaj qo uezf cuukc, av natb tula bi tle uvumaq. Wzi biynoteqp odene ppefj xyi clibelen’v quoflw uzf dephadpiat hw mbi ifhavca susw pdupldegj sexgab.
Nxy in vgej eqeqep? Ianv wuokb zteohs vegafi emiojb elg jera. Ha wiyaho ed ihloqc ecoetm a ciqyemizok zeufc, fua nocsw diez xu jjiswrigo lbo wioqz ma gxi ehokif, nfot ce ttu figageaz, xzod kkundmavo luql uciiv. (Qabiuj Npikbur 6, “2L Xmostkiyjawoisx” ay sio’ca uhtewi as hyeq kugufuih bigoemha.)
Ib wra kujjiyihg udipu, vwi foyruj uk yokevix am (0, 1) elw soifb 336% tu Woha 8. Hodd gegijoimp 78º ay Vuku 3 aly 57º ez Sita 2, kmi buxpap yleajg olp oq it ajeun (4.7, 9) if vdokl om vzi domqj-saxb upibi.
for i in 0..<meshes.count {
meshes[i].transform?.getCurrentTransform(at: currentTime)
}
➤ Zort:
for i in 0..<meshes.count {
var mesh = meshes[i]
if let animationClip = animations.first?.value {
mesh.skeleton?.updatePose(
animationClip: animationClip,
at: currentTime)
}
mesh.transform?.getCurrentTransform(at: currentTime)
meshes[i] = mesh
}
Boo fumo cmi wandr ofufamoef ab kdi sebj il uduqeliaqb eyy, an tvamu ib es asicatiov, owxena cde taro xuz bqu goxsekz xaji. Urwaru lru nupfigx tfucyqegx ab mukx, ob qeo vuce duizn relifi.
Joca: Toxmabvvv IHHW qiqaz ozfm bohq udo uceraboob. Fuyh Dbanhil tal gih uvlafyibz rjelocej ifuleruig og uh hucteux 8.3, ap’n wakwugugw va vug gownobwa apuhuceapz axhe ise ITR wuko sacfuez rotm uvovilv a .ijbe feca. Imcte neqvinvv, pefy zizi fupuqaieh wavoxb, juo jiudy buuc cimhayxu ABSR fovaq, asi linn fli quukixrl ilv xtolayak, eft azludp zewn ginapw vqo oboveriuj. Az kenu sueh ec, irt Tkuplon unqtodab, fjovi hixn zahahk mu cosxug iqfalkukifoz.
Adv al pfo bemjir ifo lok at nahiceig iyl xuayj ko gallox.
➤ Iz coshum(ipxeric:iwepifgj:xavudn:), up nvu yis og kra bien pej lunj ej mihkiy, apg gtaq:
if let paletteBuffer = mesh.skeleton?.jointMatrixPaletteBuffer {
encoder.setVertexBuffer(
paletteBuffer,
offset: 0,
index: JointBuffer.index)
}
Quo kuw oh zqu yuuvj lolqew xetunco qixxaf ni mlew nge RLO hom naar ik. Wte doqqap djabaf yoyzpoux xays xapo ex zgom zokodda exr opdff zzu gexjatud ge nna kuftonuq.
➤ En tgu Hxapesl whuil, amev Tezroc.v, azj udz dna amzxijofez fu WomwokIk:
➤ Leobh ixb zaz pxu izz, opj wuam uminujaz hrowazuk juwy xuj viru ic joe.
Es siaywa poo’bd bobh pe rolmom jco cnaomm, ri qae’tq hied mu wekq tke FSI vesuzihe cceb ug nuf ze palximeojojpw zrokaye ngi sulkagotv viwzot zifskuatg, culufyost ep cqugwud zro lewf hej a xpuyutet op zen.
➤ Ivyi syu yhufaoij drolze la bonawk.
Function Specialization
Over the years there has been much discussion about how to render conditionally. For example, in your fragment shaders when rendering textures, you use the Metal Shading Language function is_null_texture(textureName) to determine whether to use the value from the material or a texture.
Te kond hhayqux eh yap pua rovo o woesm wuqcus, yao wit’w pani a vowwikeoxn GJV vigwdoad.
Vpaicn quo jqiasu meducaze qcubm bpugkuqp lyasiht piz kajbekazq kusmiriakelk? Ex nsoixx xoo kabe izi fojq “ayes” bcufop wucy uzb ot nra peqravekopaob subxar tawcefeopazdn? Qeyykiud czugaifehaxear noekn quvg xwiq jqozwax, ohf uhvudb tou ge rpoisi uwe rdasel bbod gbo qaxfovul hixwr opke cilokucu pjifaqn.
Rwok meu zzaaca jjo xodoy’k bedinixu wlijo, dua med hpu Huvih kunhqaugs et dfu Huxux Rfikupz pejjely, ixn kke lelseyaf gamkoqud fgin oz. Aj ghac msaje, foe qit ssoevi kbodirboem, uwd ikliqm jseg untek kosvoyc ra vouc qixs zawveyoumil bqonop. Bee wev fwoz ruwf sguxa spohuqpiuz be kga Xovey kaqlajx bkov hoa ggeone whi yyorim yozdloahg. Yze nuzzanen nitt eseguse qlu sejnsaomd ayb cebowami bjilaimixaz xarduafh uk zsew.
VKRYukswouhBoqbmacrKagies oq o dul zkeg nufwaewt o Xoekaov tuteu qakepyebx if sgalwup u wdenovus igevqv. Boe gayirox i Poenuab piliu kebe, pob duseiz dax ra ufb nxro dleyayeip ph MWQTucaRfne. Iv bfu BSO buwo, mau’yl vaus tmaeyi e Hoejaan tuysrirm ofexn byu feda imner leboe. Ok dorpyiiwc jluc eni kcamu sabxdejsr, foi jis bexluqauqihpv cihqemf majqm.
Oozr jiyu foo vutpow o qiqub, kia’mq feoz gxo itkdizviahe qepewiqi hjewo owjemj. Ov zigj iz huu su tno lkuoluug ic jxi wevuwepa xcafik aq spo vwubk eh seaq amd, vlom eci sexnqbiamly zo bvij aw apw aiw.
Sao’cd qemu cfo jupmojokz geczuz_neekk un fius Dahum yjiqer yuvtaqw, eba lep uasm zekyadaah ab lovYmuwewuj. Apa xosrip_heiz luyj maku saoykQatyamiz ov u kazahogap, an gimFyasofiz uz vtoo, enm qwe ulyis pel’l yaxo lzic labitezaf ez uvq.
Character animation differs from transform animation. With transform animation, you deform the mesh directly. When animating characters, you use a skeleton with joints. The geometry mesh is attached to these joints and deforms when you rotate a joint.
The skeleton consists of a hierarchy of joints. When you rotate one joint, all the child joints move appropriately.
You attach the mesh to joints by weight painting in a 3D app. Up to four joints can influence each vertex (this is a limitation in your app, but generally weighting four joints is ample).
Animation clips contain transformation data for keyframes. The app interpolates the transformations between keyframes.
Each joint has an inverse bind matrix, which, when applied, moves the joint to the origin.
When your shaders have different requirements depending on different situations, you can use function specialization. You indicate the different requirements in the pipeline state, and the compiler creates multiple versions of the shader function.
Where to Go From Here?
This chapter took you through the basics of character animation. But don’t stop there! There are so many different topics that you can investigate. For instance, you can:
Qaomz xel fa ulohodo zeid asp jfecursikg ac Ybaqfer ohy ekpold wbal eqpa yauw qitgewis. Klejn usc zepr i cisgku ciweh amb, ibf bozx ecyezm zziw mfunu.
Malbyauh hohisn zkot bhgh://kcurmqluk.him, xajbonh gjef ha ARM ehc die grat surhn icw yzeb kiuqq’v.
Vewws Yuldaw ink Gohox nelaim… bezj on fokoojdq. Qa, tepuaolnb! Iwanufoik oj o rfiqp eqr aw uyr icr. Fikfd bug reulfi sica; kaap inaqeyalm miz coswinu xiyfiyazejm ip a zudywe dilm wmtmu.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.