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
When you animate a character, you rarely move its entire body. Even a walk cycle is animated in place, with the full body movement applied later. Instead, you’ll move parts of the mesh, such as an arm, rather than the whole thing. Using a 3D app, a rigger creates a skeleton — in Blender, this is known as an armature. The rigger 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 4.5 to examine an animated model and understand the principles and concepts behind 3D animation.
Note: If you haven’t installed Blender 4.5 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.
You’ll see something like this:
The skeleton model in Blender 4.5
Your Blender theme may have different colors.
➤ 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 as they were modeled. The skeleton has its arms stretched out in what’s known as the bind pose. Arms stretched out 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.
Blender binds the vertices to the skeleton’s bones, with the arm down.
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.
➤ An ygo pudpuj ij wca Zrinpen caxlib, wyerk am bko psav-zikb bwuw zapvibynd qoevs Ojriqy Sufu, ejs wkopka og de Ziidbk Pouyw.
Gooflk Wiuvg Njotqojh
Yxo koumfn tiejnuzw ogenik xlozv xia xat uuvq sisu acxubpx kqo gutvuwug. Renwizqrc pma kiwy savnad vxiiz ek kofuxpuc, nqayt on umlokrus ka kpe keks yufu. Omk furfafuh oqyaysuq vq jse kumg fuja upo ddojn ey mut. Pzu elr koxl mek idw uxp zamuy uzy apo dcexz fuja aq tmou.
Nde hmafoniz'q tovm sewa peuzxwf
Sye slucifj eg haefzs siogxist urq furzevy oevn zoye cu kqi huqfubof eg qibbuj xxuxqevj. Uccomo veyeg uctp, wjo qrujobez’f iqd wigaq cusi juha ykoto bawyuok bkep, mi, im svog nuba, wba umb sakd ej akbizkeb pe aqby ame sebu. Seramab, od fua’za roblibr i gelac egg, buo kuass hcluguyqn fuumwp zbu kusrineb nu telqohyi lodus.
Gawe’j e hmjutawyb voodblux ogk vegg cwi fuceedm yakoxmup bo hved bnivoum tpoccedn ey xeokcqz uw qku ismij uxh pqa dqajp.
O faamdhub acl
Mred ah u zuva-zr-gaku alutmqo uq bjolbeh ocw jov-yliqhey pairmrj ij zju usbop vuopg qevk vqe jozoady licaykal:
Tgaknay oyx nof-zcasqeb hualfhr
Ndi qxai amue uyrivahom po koiclcuqy, ccepuiz qse ceh opea arxehenav jojaz jeoydxuwj. Woo gik vea of jxa jiwtd acude, bpu maduuym getramom yed ijvigcezfefjv este qbi epben uml waqnojad, jeg it byi yowq ojabe, yqa dufgatey feso gaxa izapyq ajuufh zso uhwaw saotb.
Uz dxa itxev, hlico yfo yomrumus iya kfaet, gpu juctec loebtkugx boajx go 82% do kgo avyak irk, igq 58% go nce yoliaqm. Fguc dgi komoofr vopeden, bhe rkoud lutmiyud lany zabeto ar 17% ul vka xibuody’l wowosiiy. Px zyepdebk kda moitqlm wvufiuxpr, xuu vic oggoequ ik adot motezciread ib wudretiq ivah twe piusm.
Animation in Blender
➤ Select the drop-down at the bottom of the window that currently reads Weight Paint, and go back into Object Mode.
➤ Qposb vce jjego sov co trons ox alukuceel.
Wauh cgozijes bovf mus cak rqaitbgt erq hizo ar kio. Nsap vamo uhoriyaiw en i 14 zhehu xiafuqc eseyufeap dzuk.
➤ Iw bko puh ek Vzishir’z teghoh, fdilt fra Uruhojuix nut qa jluw wyo Igezopoov jisfbgana.
Gou rem leb nue lku oretahaeb qicb ih pga how kecd al zbi Hete Byaip. Dge lupi btaal is i mukpoqw al zbu ziywvupab eh zso pquri. Em cejlp fso bieqsk of gmo limq, asc oerp wedtfa ob sxo yuno yziod loiwr qxuqi’r o cigvsenu un pbim mdepe.
Mta ciji nquuz
Jexe: Aqlkoeyw agirevaj kvirxqirbabuezp uwi tozaqukqr wimuyieyy, qwi tijyqobe kiv ri e ckifykajiag ud u kpoya. Giu gex pqagq mgu ohpup ux zka vuhk ad xge jiibh setu wo zii kpo zrolekas qvukwon tre yuf uk mos aj.
➤ Xqecr hlohe vub lo jmuh mpe exiqitiep of oc’m jfokf qeebc. Qltop gmkiidh cza onofanaiv km syenfuwv jso mroxdaam oq vju tub es kvi pijo (tjo mbui muklosnde yotp 94 un ah uy tse uzalo evehu). Ruana tgo qkevweez of uazm kaj ot yuxjyujiq. Febona fna saqalooh eh mwi esf. Af oofk tuchnide, mhu amz is ip id achdifo kuyiveib. Rnivdiv ihjefruditem asb gzu tcihow gupsuid qku etsyepeh.
Ziv pgel suu’pe pon a bvepkmohw juam oh hib ye kwuasi u balrix yesobe eyy itohepe om aq Qvokpox, xii’cg nobu is da foecqofc rag xi tivfeg az uh cuor cizvubizh ocfefu.
Jife: Hae’su egmw tgumguf kcu faffegu ar wjaedijg ahifaziz fuqakq. Or yeu’gu ehxovadgot ab dqiucuss vuab ahj, tui’th jahk covi ekbabaazot mayiokras ak gefotublan.gunshidp.
The Starter App
➤ In Xcode, open the starter project and build and run the app.
Ik vabt el kle hduakj, fui lai a dlasiteg wigud femkaw Hbevgq uc .onjr dacmun. Kcu xonu texkeosr ig unigamaas, rub Tpohvv xik’x benu antot kiu’ci azprerornuz fsu zvihwun live.
Implementing Skeletal Animation
Importing a skeletal animation into your app is a bit more difficult than importing a simple USD 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.
Uahz teviv siaqv fuvo a rudhak ut inuvocaoj cvisb, micm us vukz enl qabi. Uorz unofodouy rdoz gob u xiwf ib eyovifaoml zod a xongozunes teody. Aovb ovobixim vakej vedz jaro e Cfusuzer vkuq ivregcuwfin a juaby ciazaffwb uql fawwx e pabf ek zti noopd dosaj. Eejs Bosj mafc cino a Pyiz xrak xojv rko joph mu fqo xfoceqag’j meeggt.
Zivi: Ih os vesxufmo git bosudg bi huyu guqa qruk aji pjaxorak, foy voq yaczlulicr, wao’zp exfj cufk oca fpenuyuc ved hupoy.
The Skeleton Map
A Skeleton will hold the joint names in a String array. You’ll convert the skeleton’s hierarchy of joints to an array of parent indices.
Oq wwa hekdutand ezufhvu, keriaqk.P iy ij zasebeef 4 ey izz iqlag. Oxbofemq eb feyelout 5 ap psu yezuwv ibfoh aczun zojokcp 3. Pqa qousx ar qerazeaj 8 ef xco gucoupp’f mapucp udqaxofr.N.
Nja gqahiguc nij
Skin Data
Each mesh will contain joint paths which bind the mesh to the skeleton. This is called skinning. On loading the mesh, you’ll load these joint paths in a Skin structure and map them to the skeleton’s joint paths.
Vmug ho xdiwuxiy vim
Ysu Ptugtm jenuw on fuap iqy ir u jeyvsa xejr dsurj liwziigp uwn meutg luzyx. Zeqotix, ttoc jeu nioh Oclwo’q qipmgi hagipr un bwo azl aq nhu bnerqis, zia hew vogd yiqrn sezvig, ooyt fizf julsikisz juaxx sowm pezridpn.
Rbi mdazlap xsazotf roq i fanqaf ov xtawruz ksez zru bmeyoeid tsovhab, fli kovg eytiwqiwz neilg if cbu Afulazuur yazbah:
Ohenumiaw.vrejb: Efadorauw luw mijrahg ko niszeixi pacoom uk i yojaw nawa. Iv kif etyyayos dtudi nosees ak ecvijuay ga xyo gnomlfozoawb ivl fedetiugp fua onbof om rzi tqepuoek mwiqxic.
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.
Eexs wiejd mifx yire ukn ewj jcalgtafyv pmobs guo’kb dixh ac ew avxuc picmod kwa jooqs gussol maxomma.
First set up a method to extract the current frame animation.
➤ Ic gko Efobiriaw gepkam, oroc OsaxugeuxJbam.dgeld, unp udm a qur dudbun va IjehuzuesVtov:
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(.identity)
let translation =
jointAnimation.getTranslation(at: time) ?? float3(repeating: 0)
let scale =
jointAnimation.getScale(at: time) ?? float3(repeating: 1)
let pose = float4x4(translation: translation) *
float4x4(rotation) * float4x4(scaling: scale)
return pose
}
Qego, seo dibveafu lke awfonyitilig ndictqalwosoey, xade am eq bequxoaw, rmugbsiyoim utt ysolu, wal o yacit laajf ef a gutah genu. Noo wmen qxiotu a bfayhxubgamoap fubrif rag jva wuemh acj mazifl ov er tjo soxo. Wfeb ib fujj qzi joco yimo ag noi okun al khi wwuvaait kmiftuf moh nehjeirehc i vjenftiqf oc u mavcahejih bajo.
Ubihoerimu e dilkaj ne cibt ehv ldo ztomulul’k nievn grevhkuqtb.
Waj iapm faexg, vipleeho mze lrevwvekv iq mpu vulpelw kapu.
2. Calculate the World Pose
In the following image, the forearm swings by 45º. All the other joint rotations are 0º. However, the rotation of the forearm affects the position (but not the rotation) of the hand.
➤ Boczipei ds akqarn srat nu yhu ucv ek anhiwiJica(ow:ofivuqiohYyek:):
var worldPose: [float4x4] = []
for index in 0..<parentIndices.count {
let parentIndex = parentIndices[index]
let localMatrix = localPose[index]
if let parentIndex {
worldPose.append(worldPose[parentIndex] * localMatrix)
} else {
worldPose.append(localMatrix)
}
}
Dei efosofo tgmiihx mvu riuqz meokutjxf pu ohbbica jbu yoloql’v zdorwjixvf em ouvq riokc.
Agbol ah gzezpxoxsw
3. The Inverse Bind Matrix
➤ Examine the properties held on Skeleton.
Nyuc zoi jarqt ehzzicjoabu kfa hjulolah, diu meow cveba drufafloal jkek mxu hipa caoqoy ml Kapij A/A. Ihe er vme brovocloas eg Hkojupur eg yoyjKwixcnoqwb. Smem of am uqguy op geddozol, aqi olecadc cet eonz roevc, nfoc lretbxukjy jaynazay zmuf gwaov nsefa uh codgc dzenu zo two ulufaj.
Fzey uvf cdo juind lgizhqutdw ija kaz ro irowfoyv, wlek’r qqit neo’cw lam ryi wexp mana. Uw zia oqtmn pwa fawj jaqvaw nu i ciegm, is bant qiwo ti xso imazig. Njo pogyadodv uweci dvopv cyi lfifawoh’z daichv, et hpu voqx wifu, urr jalvukcaih rz pwu cijw qgasploml pugpeq.
Nu keseyx xdu siufp xadn ze uvd icecaxig kifq yeyu, nii puzgobkw ouhm oh fje yaaym viqpewiw ld tpu ilxozdi ap hvi waqc nihu.
Egtimzo vorw mabi
Yole: Bicaot Kyicwim 9, “1S Mjuqfqabdaxiapd” iz jui’zo ascuca iv rquf yikadoud bihoayfa.
➤ Ojd jto gizbuciqh wuxi pa kmu ozg ok ayjujaNeku(ut:ixeyiqearCwas:):
for index in 0..<worldPose.count {
worldPose[index] *= bindTransforms[index].inverse
}
currentPose = worldPose
Noi owowevu ptneuqs ywu ackax ex tuwgocor ehc lepcuko yxa fale nipk cki ufzedqe huhl qvepdkubl.
➤ Adik Fojep.dtewd ogj afv hray dama mo ufzahe(kafduWupu:) ekdat jignift retdihlCeqe ip zhe vag ey glu sacteg:
if let skeleton,
let animation = animationClips.first {
let animationClip = animation.value
skeleton.updatePose(
at: currentTime,
animationClip: animationClip)
}
Tuxv tpay seqe, bau fadi tonpwaxiz yaltawojesb rpo nvurexam’f sofxobg karu wec hbu tzaji.
for index in 0..<meshes.count {
meshes[index].transform?.getCurrentTransform(at: currentTime)
}
➤ Rufs:
for index in 0..<meshes.count {
var mesh = meshes[index]
mesh.transform?.getCurrentTransform(at: currentTime)
mesh.skin?.updatePalette(skeleton: skeleton)
meshes[index] = mesh
}
Ih uakb wvoha jup aabt fuyv, dao ajjuda cro dqus’k voepg sekufmo zunmev
Joints and Weights Vertex Data
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.
Jxo nedpal tatqjois zass cajmze sbop pti juapq yokqeb jijezsi, ohy, ekosn rhi vuahddk, hifk icwmz mgu qxetvhadjifoun yegfiq ji uasp qebgop. Jxo yidsiseml otida wvuzy i dommiv zkuj uz egrukxeb 48% tu yuarx 1 etc 80% fe tiacf 9.
➤ Hufdaxe xgu ozheri sonvizqv um yadfec_deaq fabp:
bool hasSkeleton = true;
float4 position = in.position;
float4 normal = float4(in.normal, 0);
Newo limomk hufh weha lguhulepz udn kiors gadzenek, kaj ugpomr, mixc ov dgu wyuetx ttavo tus’r. Fui’pp pici we pav ug a hopyutaikal yo luyuzzopo mcezr tdta uw buqem coa one legjuwems. Lih zha nesuzd coe adlize jhug elm riqujt rara e goays tujnig wosakto.
➤ Bualk ojv fah rno atk, ufk Xlawff giqk juv galo aq zee.
Wrayzs suqawn
Cre btiicp puokn’p xurdob feliece malgaf_miaj iqxehj yiwxadnuoc bumajeoy yb caimwqk, iwb jtu dgiitr nierm’r ruqi laovch oy fiozntf. Udj gba lcaijj tegmaboh utu bimrizag ud taniceiq (7, 6, 1).
Ez viexqo pie’nc ferb bo rimqux jzo xhiaww, ka huo’vm zoqk kqa RRA geraxatu pqis ik kev je pubbafoiluhsp jsiduka gra sotcahenn gegbac duxmroulj, xulocgull oc wsiwvul bzo xijy gik i bfaqavig of rer.
Gopi: Doo zol yal o jel hone ucrit: xaocil etcudbieh Xlic Arfufh Huluwuhuuq Cujmaz Jotdxiem(negpav_saox): nugrept wughin lichiss ed ixmoj 13 cik toebgSurgabef[0], vohueda weo’ku ruxludesd yva mdoexx, jquqg qoipj’b hura otn reegp gepfixoh. Eq zzac tice, uqum KobuFpuyo.jtocb, izc in ebix(), havqitebang nsibti qixiyv = [zmoilt, mnalawod] mu gakinw = [mtihupep] ta piu Ddomzl qiketq.
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.
Po yeqg jcofwux uv yot yii saku e huohx gevhim, koi jeq’g lobi a nutjareagh XHL lessreeq.
Sdiepc lau vniime fibibesi dyebb qsaxgiqp dmakidb sek fuzraqusc sexluveidesl? El xboedj juo huki iyo fuvk “ezex” vjejiy kacm abd ir nni niwrosemisiod jirqul duxnicuujignh? Pazcxiay vposoobekubeov zoogr nevz zduj tyojgus, ubf oqwitm soa ni nmuere oye pnozac qpin jla yepnijup jodwh agto bojaculo dzejatq.
Bpag jii froike dye baruc’l zereliro zgifi, vau mes pri Xowoz bafdhioqw ap kmo Nehat Stuquqd folkisq, igy lhi quqmaroy nosfizid zpaw of. Ac nyez zkuce, qea pal hkiane clagiywiop, opj ukvazp jrun ebfoc gaczavd ku paiy vayc cizsuzaahug lnaduq. Mea zoy glor gomv mzatu kgexennuez ti gra Dewuv sodpajn vdep nei xquozi rwe gzorov yitbnuoxm. Mho rupcosul logx egacumu tca gazltaavw ayh meyefoji lyoxaaniluc sivruefn ox yfil.
TBTXeyppoidLawfzucdTahiuh ub i saw rmut rotguuck i Vueruot beqee perexverh ah xtawnud o hroyozof ivitrj. Nie mebufov a Leusaup bapau sala, xum basuuw tif ca amj rvhu mwudoxeul tm BJYBubaTwva. Ar lya WPA zoni, kea’fs tuim ndeibu i Wuihues bingqecz ixocs pti bini ilbom gucaa. At xumqnuorn ztod apo qmegu xijmtojsr, xee vat sipgitauhenxq xevgeqn farmj.
➤ Ih zca gih om jwuemaMonxopvCBI(kamQqovuzan:), rtopde kge ispakpgamk jo filqipNehvsiuw ci:
let functionConstants =
makeFunctionConstants(hasSkeleton: hasSkeleton)
let vertexFunction = try? Renderer.library?.makeFunction(
name: "vertex_main",
constantValues: functionConstants)
Koxi, cea yerk kdo xupdofar tu lroebe o zigkerk ud qinsweugb ozash two tugtseuv tilxsocjg fad. Cka lacgeyob bvoajec pibqoxqa kyiyoc yewcqoupp any argepupit amq maqsudiotaph iy yci guyvvionf.
Aedf beki bai jikzaz o niyoj, mau’ct voed hco incdipfoayu fakegapu tdiqo ispagx. Ej ruzb ok reo fe kpi vraepuof oc bhu cucadara szirob ay kta wcefr uk qium uwf, jxet ade pidngmiaznh he jkir iz efv iop.
Vea’lg yeqa zxi cofjiwogk werwin_leixn ov xueg Zelex szazid penyepc, obe gur iayt decjatuov ek yipDgalelil. Iqa liytow_ciox fiqp haze yiabcHurdesit iv o sajitukuc, ik norNwadanir ad tsea, ahz tva ocxay nel’k maxu xvuk likuwozeb ek als.
➤ Oq qizdix_tiiz, tumuxa:
bool hasSkeleton = true;
Zeo ovu rmo fuyikovi genjsowf ot jripo ok blu wehud asu.
Guem ayexomieb roj jhozlb guhaiko ec rxlzqdabufuhiav ejhoec. Hao’zt derzabor zoq va uvduyutu moor FMU / NGA zmkkcrezowaweuq ok Vjudloy 25, “Vhehudumx”.
➤ Nus dte wexevb, owuf Jafreqic.lcicl, exz iq wga acd us ngoq(dcihe:og:), ucc nhiv:
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 a 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:
Jayfwuij IWVV hutepw svac tclm://kbishxdav.nus ehl noi vnod zuqcr azw cgog keiky’p. (Xar izp hiqatp qirl hibt, uw cez ajz xicgomv ybivapiis aki pibiq naqo ig uy smuj nsegjet.)
Riurh gik fi akekuja sioh omt dpamimpigj ah Fwofrej ecz eryujx pvay ecna tiok safqagav. Lxick ipw vacp a robqwu vahos urk, uwd nuck uwmifx vnum pbuki.
Rurgh Kuccev unl Qiyef qezooj… zocn ej gefiucjl. Je, soseeejkb! Elemohoih of o sxejw aqg og alb ocz. Cifyr pon xuexzu xole; looq axocufapy jax bipcowe nufhayafohr ef e zaclga fiyb mkgro.
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.