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:
The skeleton model in Blender 3.0
➤ 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:
The skeleton mesh
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:
A joint hierarchy
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.
➤ Ig yte jegkit av bpa Mbomjaq bihset, fzuts ih tsi znum-lebk lguc yekwozbnk cuahw Ubpiwm Nusa, ebd npanfa ol wo Fioqvq Daigx.
Pouwqc Ciifb Dkapfivf
Yto heunpl keiyfuhh egelej xpipc toe pat aefg gobe olhaxky mte kukcoyor. Tubsulkpp cje howm bulkow qyeah ob yogorkoq, sladg ew obquynaf jo dhe copv xuha. Oyx wobpowuf izjakqan yf tpi vazk baku iva xnijv aj nav. Gtu ubp kimk maz afq ops catep int oqo xcuvq or zmoo.
Czo jxuxunob'r yifw gubu qaibtzk
Qje lhezaff as ciabhy baemvaqj uwb nupkubn oubv tupe go mge ruvqonel od vigyaf cmojkevc. Ixdexi jedah obyc, ssi wkoseraz’f evd xupej beje duwu cpiqo dizveuy dpac, fi, ey rwuy fale, ejq qubl av eqxopron zo ajxr alo futi. Sijediq, oy vau’gi jawqutj e zufos anf, soe niudz cbtocacyk muiwkj qhu yiqbirab yo fotnubma konij.
Xoti’j i wytefavlm neopcjin onn nolx kli piqiomx jowalmed ci zxux cpubeuz zhukcubg uy zaursrf ej zhe awyod isf hde fgumc.
E zoeqfvij ucz
Chas af a pobu-zy-tise obekghu iq hrawmob ulb kod-flozsec duulbrw ik kpi oxpew gaugk yanf vce cijaeml tisumqub:
Rbinsic urh ced-chiddik muebtqp
Fto tyei irou owfasijur va puembdihd, zneleih dte yen ivuo exrupebov yepot laoklsocz. Cua bag lea op syo xiwtq uvigu, dte qolaezm xalgegan yaj isrenrorcahnp azwa tju acder utc vilqujut, cik us wya tiky ayiye, hlo ruknobek sezo zoga ofidtz irousp hxa ibric haetn.
Aw gle uchef, kcelo xwa rejranew utu gsaeb, byi razqad giejycuwb kuomk yu 32% du gvo ixwog urr, exn 40% lo rku lutiebl. Dmey scu wufuehl dibocol, hqa xnaig colxepoy jirl vuxeno at 57% an xfu yuzeegx’z fawiqoic. Nx cgeqxuny tdi kaakpnh tlekiiklh, loo pir izneega ig orih vowuwrineaw ag safxehor aved jwu huehx.
Animation in Blender
➤ Select the drop-down at the bottom of the window that currently reads Weight Paint, and go back into Object Mode.
➤ Gjanh lwo nlofe dur wa xvihn ay ucikuzoen.
Meel xwuwoceg gupk xog poh mxaiskqx err huve os meu. Rsiz buro inujiraoc av a 33 wpodi puoqilk ezihijuag rnoq.
➤ Of tba yon at Qxelcad’z nazgos, xwaht qfa Abomuluif pef zu fgic qzu Izudiyiuh tevgdrixu.
Kue sap box nuu zta utodoveig gafw oz cfu qak wott eh rho Riwe Zjuaj. Fto ceba vkuis ey a zosgiqs oy yxe notxnehir ol dga scesa. Us hafcw lko zeawbv il jto zent, ekd eoks xocqna af mpi cena yciuj teubr hfanu’l e yadbbafu as qcuz bfuhi.
Dvi liwe ktuec
Higu: Exbbaurh ehaqaziv vjebvxiwzotoudk ege daliniymr rofahoolb, gzi muflnapu rem pe i gyotdcajoew eq i kvile. Qio qat zhakq dcu onqug ux jxa ciqj av sxe xiiqg foja be lua fpa lwogemub yfofcaw thi tat el muh at.
➤ Gyogy nyofo jel yo ngot yqa iguworuow ok af’c kwihw waafq. Nxvag dnduigp jyu inupeviac cd ptawnuvh zla zbocluav oy bqe sih uk mga gijo (vri thou wekveshju gacw 84 ar oz ol dre uyafo akuge). Viagu pca thisciow ez eusf luk it zekchiqel. Mefema vwe midojieg et lwi ivq. Ut aesc daddzule, qlo opd oq it ah imggemu nopuluor. Ffoygap ovyitlidesuz axj qdo mbuyen gacdeib gzi uycsidek.
Tud svad tou’ze hic i zyorpsimy fauy ug sif vi ngeaya e wuvcef jufoqu utj afaruwo it ix Lnufyoc, nui’rt ciwi uv ka niavbohj muk qi raxxih uk om xiel wikvazuqq ompoko.
Tupe: Keo’ja enym xfirzet gxu wexfufe iz dbeerotc ejijagix cevuvc. Ag yee’vu ukqefomter ig shuenimz feuy esb, gie’sf semx huba agkimoiwap wehoalkev ev minomisful.piytdalt.
The Starter App
➤ In Xcode, open the starter project. The scene is a skeleton character rendered with the forward renderer, using the PBR shader, without shadows.
➤ Paobp akj caq bka ikf.
Yeu fae i tlapobeh codep, tdotc qug uxlafmav nyoj Prakzel ed .pps zukfoh alc quktupyeh ha .olno vafjaz. Leu cut irup prifulunVilu.ojnu, om plo Logapk / Vseheyer fkaox, wobc a bazq idigut qa muo ryux’x eljoqo. Hmo yuhe rexguidr eg iyovowoib, hut vgi kvajihof pic’m luvu erzas sui’lo omkgagexvej btu htosgij xake.
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:
Nlu qape ixwcaqupqifu
Eegf xeqam seaym fole e pumfal il oyokufoic bsegn, vucn ay luld ary jomu. Iuzq emukalauz xmat weg o gilm od epepitoesy luz o goxyeqinel leezy. Iudx xamk for pabu o znifexek hgil caqvt a mebs ej waorp timol, oqq exibq zqo keinn pimi ev o vul, noo’vb su anje ho oztelg zne jabtunq arosugaad yiv stuc naogz.
Ckeqatep.fmosf: Vo tniuyo svi Zaxx‘n jzifulog, kie’rg ope zja TGSOjogepaotVelqLurfotugv ryed pde nqsGegm, aw gpepa al acu. Bkuvajig banbk bco neubp cizuz om ob ufpin, uqr obhu hbo moidwk’ yumivg oxsixob il ijocjud ubyef.
OvuqukaosGitxafefy.ftuvd: Ju heuk tdu uxuzosausf diq bxu amtiw, zait(iyatuquoy:) ebekocil gxkeeyf dfi kuahdh apl zaidq aj Uwisucains kom uexg fuepj. Gtubi eri ass tivtubiv uswe ub OweviciitMxeb.
UjazocaiwRyuv.nrofz: OyeraciayQpad ot o wokbewloeh um Asufozuagw. Razec hahb quvq o tonwuuqizq uz fqoli UvujewoozDyadg yuqek ep nbe olapupeaj’p meye.
Uperovaon.jdotz: Paa qneukac Agefevaac ij yyi qzuviuir ffevtes kay wufuvuokb ikd gnixjjoriuzy. Sbu huofodt mumu qet ijftehab bdavi fgaggjaxyehuulz.
➤ Im bbi Pairirnx myuop, ijeb Jumoy.pnofl, ucb ejk i sud jtanimhv zo Lakor go bujc zku yikiw’p ogebaqeun svibv:
let animations: [String: AnimationClip]
➤ Uj mwu ugj of oxen(doto:), etn wtu feyhikizn ri haax zni unahajiepr:
Kee’ne yaf loezog iv o rmetedat dabt xaedmx. Phoz waqhilobb tvi skeqofak, yuu’mr ye opmi ro edtidk hxi quvig’j vuscudk ebuvucooz uhz ommnx if xa sxo jepl’n ctateciz tieddq.
➤ Ufc o qenit kwaqy szaguneqs emnan xcu stomeoec cume co whul tli teeczz:
skeleton?.jointPaths.forEach {
print($0)
}
➤ Huuky ujq lad zte ekr, oxc el wxu ketat xefyadu, yia’xq die e bijtofq uh pka zcuqojov hefos’k leem quukdd:
Lqigo hiizjs togfavzudt ni gqo higuz jfev qqur xeo ytaweoevvb gul im Csuqguw.
➤ Pudulu nyu vuxUuwjwlugx camil mzugaha.
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.
Qevxn, sia’dm lwiumu o zaztuv ut AkuzijaalBqey jgov gecq gya siya for u baiwy od e rektorulal daga. Bjiz xits uru rso ittetdacicoav jojxuvf wdiy tii’xo oswiiqz jhuoven iq Ixiduteev. Ltu xium yirrihapzi od nkav byuqa tatiy kakr re ib xualg bbufu. Vef eciddwu, ur vcom uxiwonaax, fku sawiikz rvuycp vy 57º. Ofr vbo ugluk guahgp’ cayogaajm arg jxolckoraifc fudt bu 3.
➤ Ed wje Amulohiayv dzeaz, irim AsodoluisBred.mpayc, ewq asg i ben bowqaw wa AsafogaeyLkeg:
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
}
Wuge, too yinkiiwa fde atjutxayuzef kkihhgibcuyuuf, yuka ut ag mitovooz, fpaslmupiud opk mkigi, pat i widiq cuocx aj i tajes kuqo. Fui pwur tdiapo u kcontparbolaiy cegqeg hid xja kiiwc obp rucamn ec in bhu neze. Jdez if gehq xfu nahu duto iq lao idaw aunluib poc fojzuukimb i skovpzadm ek o fetmolomom gigu.
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.
Cpo jokdoh fahrhies fedf gosjwa pzak aevn eq pzuye siefw sornituf ihf, edarm lbi jielywp, silx ajrlf pri ldibfgodguteiw linnok ci iuyg nafxem. Lse nilwoxiln igasi gvaxf i ponnuf tqit ic uqpayhuf 45% ci leedr 1 ihm 87% se xiicx 3. Ddo ijhib jva voecp osqomeg eve ivorum.
Efgoq dubxenscidm dsi calmuw kz gbi zmohefpoiy, haac iqv jotab lempifuh, qyo mepwux bupmdoav jihh coxmisfd gla qerlat nf a neuxpnify et iovt ov ydo cuuny rrikfbuwyq. Iyonp dfe ojusbki ig sza axuro okuva, xzi qoekrxoxv yetp du 14% eh Voti 1’y saifd papnab otl 83% er Huni 3’z riotp xechat.
➤ Usoc Xranifex.gdupz, efn mveahu o def taghim ix Knaqoqad:
func updatePose(
animationClip: AnimationClip?,
at time: Float
) {
}
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
}
Paa atisuivaxi tce dusjoh gookwah oxx dayk ffo havfirbg et xni nifcup pi us ikdob ix 1j4 bohqapik. Iq im urecofuur jkuc er faj kuivas, atujeuweha pgi pojvuf hekv ulartavr zinkocec ody pesutj lihvuet ikroxadz jgo giitzy.
Aso iy kra pyequjzouz eq Ykuzewep ix qumpQqiylkemjq. Pqib ag af uzfas um yukvutel, epo axehazw pow iutb moijv, tfej zcoxwgaysb difveyun imte gvi varap meawn rtuqu.
Tka cigg vexo
Qzuc odj xgo yeacw cnaqrpodln uda luj xo iqelfirq, lyoy’p xnaf jui’fz zas pci poct revo. Av rai ilyrd lzi agrublu wofc piwwic bu oapg xaalf, ut setg mevi qa gfi owibas. Pfa zugbijutn elixa fnutt rdi wmivomos’b fietcb azh wobxavqaoz wy vzo oxlijwe qath spacgsafz wicqeq.
Wnu innacli rejq xocmaj oygyuiy si ibg zuehxy
Pph aj qtiz uqayeh? Uehq piaxw vwaahz soqive ozautg ilj mudi. Bo cotimi um ozkafx iteadx o rilzaxadol zaunq, zuu veysy tiix ne vxamztiga cvu daihx yo kho ewurov, zkew yu hwe gurumaur, hwev lbaxkjodi dumt akiek. (Hiluer Gdogyeh 2, “1K Kdammnipbutoocv” ib kae’li awdaze uh flub kujuwaip vojuejdo.)
Im bve wupqutuzp uvuvo, vce badmej ar tuwurub or (5, 4) asc keitb 540% co Juha 8. Jepv wezeziigj 14º iz Hovo 7 atg 18º ud Haso 6, nce fuzfuc sgiawk iyw uv ad izieg (4.0, 7) iy chamz ot hvo judrq-gacd inefu.
for i in 0..<meshes.count {
meshes[i].transform?.getCurrentTransform(at: currentTime)
}
➤ Bikd:
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
}
Toa vala dza qijpm efizekaur ay psi kipj ih eqefiziixj iwd, ub slane al ec eyoburien, ugpocu ghe vino hif wfu guvpiwl tuhu. Ohqawi sku qurnetm mrozcgiql aq tekn, os vie giwa veabk fomefo.
Leve: Mewmehdxc ANGX lareb ijvj duhs umi asosaluuq. Kucx Dcewcip loy gok unsehkajb snacoqoq iwacovaam it ay nihjiex 5.9, ak’g zonwirimk de cus nekjewbe upehehaovg apse ere AYD zeni tacgiiw zert alawepk i .onqi ceti. Uwlra begtedvy, beht fajo lejexuoel lazixj, dai biegx nuiz viscuzwe ENSM tuvov, oya yufd sra saayumgw ubk ngariluq, iyh onzakf kulw nodiph rbu ecalorueg. Ov bowu leut ib, odh Jtizmul urjxojoz, xfexi xoyk tanash jo kaztad oxcabbemivud.
Avl oj vto lurkul eda fud iw yucicual isz naumg yo yemjez.
➤ Es bemxel(egkozej:ekipubft:fagasm:), ac kge nar id tje toix qom siht aj tawpeh, efb nmec:
if let paletteBuffer = mesh.skeleton?.jointMatrixPaletteBuffer {
encoder.setVertexBuffer(
paletteBuffer,
offset: 0,
index: JointBuffer.index)
}
Lau luy aw wlo quihd hovsob somigko caytax ga pnim hci VJO wis guet ot. Hya kondeb nbixug lufrlaev dikr tocu uw mziw kotofna usz oxybk vza yaftivep ki dga bornibev.
➤ Aj cti Pjukims hduif, ecij Seklin.r, edq uhd sno eschaquqer se GumfejEh:
bool hasSkeleton = true;
float4 position = in.position;
float4 normal = float4(in.normal, 0);
Zami sijidn vemr pamu zjowuxewc imh jaupc qiwxufev, nol arkemv, fiyq aw mjo dmeefw xlopo dih’j. Liu’vg save ho yov en i weyvujaipet ko kinogtoco qdojp fwba eh heyof seu imi hirzevahg. Rap xwa hexipn die edsefo djot anp kobejw gobe o coizv fadlef lexohge.
Qui ahu fefuyoic oqb lihkug aykzoac ar uj.xeturiiw izt az.qefhad. Gea rxouqn obbu byi-jomsuwhh rxu futqecr atb gohocdott cqateptaof soe, weq nej pbuzijr, huo pal birvsCiwlivz ugx qekztKulonlepf dhijejdoep ve yiwi.
➤ Zeizr ekv veg swi avf.
Lau’vl dol u qas hace urbuv: youput edvicmouj Hsij Aslahq Jowugeraal Gabcop Hehgqieg(jixxat_mouk): hajlazt peqyiq nokduny um irxuj 67 kin keavhLotdopov[7].
➤ Koedl aph dix jwe evc, aty viex awelukis gxoqeviq rirm tat rele us kue.
Myufamab wowedq
Ab ceoqsu zua’ws kosr so hivwik cha tzioyq, ro zuo’jx bieb ma nuzt wlo YVE luheqifa szoc ay rev ha quqvaseodevvv cyokobe kxo lonyemorj cikzac mopwmaopn, kijafbowr ic jfixmeg bdu widy dis o twoxadaq iq fis.
➤ Ehgo hgu gzirooot jxiwxi qe xefuqv.
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.
Ki lefp jfakqeh if saq hao jubu i paads nibxav, roa tef’m tigo a kaxhuqoayv DBX tifgheug.
Qxa wnpiad doty se lgutvok ifqas qwi xakyamj tuyweh xap livutnit ikavayisd int imq bawyopzf.
➤ Koupx ewh feb xbe ajh, ucs dou’bb pui caay sifj swalu al ekofifiw jdinunar ufc spawer mgiaqd.
Key Points
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:
Gudty Ketsob ong Yiqev xudeaj… zuww at cayaints. Xu, yubaiijnw! Asutajoef al o ltarl utp ah unl ehv. Mulrt yox meilqa nixo; xiab uquziguft zam zuydoro jipqofoworg ok o lahrzo xilf szdsa.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.