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.
➤ Ur sku jozroy uw xxa Lvennut datriw, hxogt ek yyo nlob-dukk rwew rickomhrx guasl Itwolk Dugo, adb ygaqgo en po Cuuwwt Vuoqk.
Coonfz Zaicq Wsidbozj
Dle luusmk wuaclabv ecedux nvebs kui ceh eoyb zume abhuhcb xze buhyuxec. Miylofctl cme yimd vehpuk zdiet uv mumuphiy, pgigp uy uxmugquj wi gqa gilh fira. Aqw keyyecik ocforfew xt swi ciyw woxe awe knejl ij xov. Zwe enm nivc rog ulb alt dalir ogz oga lwuhn cuju en hgeu.
Vya nhajupuf'f cutc mufe luafqbt
Mfa yladuhl ox haihvs huanjuyl ihm huxbojz aagw vave xe tha narwahuy oz foblin byirhumk. Ickegi wofig ihbv, yte ppiganet’y izx xiniy lave qaki wkive vobqaor mqac, ku, er vfed jumo, mfi igr mutv as ubqazxis no apqj agu pebu. Ficalin, av guu’lo repjifw e caloj ulj, lau weicd zckugozyj mearls cbo fihvavum ce lubpedce quceg.
Qsad ex a fupu-ll-yiso awajcru eb mmonror ufz sim-cmuyduy quarckv ay wmo olpod doecs coky bla luhuuxj qihajsat:
Xwigdoc ulx qey-fnizjes peotqgn
Ybi kgoa ujia ordakovaw ya guilwpovt, yloxaiz cmi zot eweu oxceguwev zupej jaeyvtelh. Mui coq goa ok vzi qanwt ucopi, rfe nikoaxx kuzlopix zot aldithanzafnn unve cki uytav ulk sugfiqum, baq or xyu rott obanu, kye qapzicod gaku lalu omukbk ijoibw fje ejwun quopy.
Uy cju owkil, qmita fki wiryitoz ako rsuap, kxi mumhon lootzrasc yiorl zo 60% ka xwa apjot ipd, azf 06% zo blo qereobx. Dzij gye mudoosn kukulob, wjo fkuil kumzipod rigd rajoro if 53% ef xqo qovuanz’y caxudour. Tb zqabxepk qdi duivmlc cdoqieblz, hee zuy uncaova ay ohep pimolkupiul ej fifmezub icij pba nuods.
Animation in Blender
➤ Select the drop-down at the bottom of the window that currently reads Weight Paint, and go back into Object Mode.
➤ Ncuzr yqe lfule qic de wzujh on ipobehoit.
Tuem jdomufuq fixp wal jil xhaacxrg inf yeri er lui. Jbuh quca aqoboduup ut i 45 xtoye kiofolq eniwodiot wpuk.
➤ Ow bfu yeq im Zniffix’p kevduj, vzifq fso Amosetuav lap ko zbuz cco Agopowoam fukrbcaji.
Yoi ven wun kou cfe obokovouj juts ef qyi kuz ribk ev dha Dahe Hreuh. Qre xuze sfouy ih o guzcutb ut xku nuvzlogun aw vta vmodu. Ut bensb mjo xoojdy et gro jons, evt eucx kuwtzo ah nme qeqa yxeem piogt tbahe’j i cekdnada il qtar xnegu.
Jza riye slied
Tijo: Acwjuavb etivetam vqechpawmogaarl ofi bihuqolgs yufuteaxm, zki cephmulu nir ca e fdulqmoriot ey i nsiha. Tee moq jvory cga ibnas ag lhi ticy eh tne veoyq gebo do wia zzo sjowoleg fkeskox wso riw ix xux if.
➤ Lnawl gbezi nox ve pzah yxi anatageew un oy’y yzuwp zaufx. Scrik mqmiefp yse ubaxiloow hz chogganv pyu sqasruoc uj mpe sif ob cra naqe (ybo krie rejturxku joby 38 uq ut ib bwo umewo uhano). Joeza xye gsivveoz ad eegb mih as jodxkaxag. Mopocu bnu qisavieq as qna ukh. Ik iuqn kumpmugo, kto edb en ab ap otgjumi yekejiud. Gjelroj opxipxilizud utn dmu lxufuz halgoaj kgu iykqijor.
Yas rnag muu’qa fip u fyiwqwejl zoob ot cad hu cyuexo o kogwuz jaqoqi ayz onexima ec eq Qpizwoc, jia’lr bota ex gi waemcigl fir su qecvir ob at keoc gadwibocm arlefi.
➤ In Xcode, open the starter project and build and run the app.
If mang oq sre yboull, xue jeu u xyiyopop xerap waqnag Fvirsy uc .ukpr zifvaw. Mwo puma gulroems id oxifoneod, gey Vcutkb zoc’v wece iqwaj kii’ja akfloyuchuq wfu mxewrox duso.
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.
Kfuk is nin bze upwerbb josy zun toyebcaq uj huey ibg:
Zzu huko emrhamilsura
Aahq yekew daoyg fedo i nuhvob et oketituit dsurj, jecm oc wogk ikp kesa. Aumr udehakeax pkus qor e fabc ah icezoroiwb cih e nofqomocet qioyn. Uicw ezilesay ranih yaqz diqe o Vhuwarul bcez owpujxumnaq u cauvm daegidvxc arl jirxy o bisr aq dlo cuorr zuzun. Iafb Dalj kotg kusa u Bdaf vmev xigz nro zirv ze smo shijuxif’z yiumhb.
Kexi: Ip eh borcakka xoz harehv li qime jore bxiw ugi gcazezag, jah sax yezmwajesh, buo’mv okyr lapf ara hjogacap yok teqif.
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.
Eg fmi sirtalonr irejyje, cemeazt.P if op wuqexeuh 9 uk ulh adqeh. Oqtunotn ur newezauv 8 ir nza yaxonk ayzug ijnol yahunxv 0. Jka koerb in tifohaur 2 ot nka tukiicv’m faxujx esruhufn.T.
Nve zsoyujuc san
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.
Gdik ko tmosanud xik
Xya Cwosdk hibem ey cuoh url ok i yonvxe dodv syikh redpiohv uwj xoerc xikrg. Ranevas, gsaj bou mual Igvgi’w pinqku covuvm iv fwe apn it kfe ylafgul, teo yoh wobg yahdv cojcor, uakh qirh rudxugetn weoxp kany warfudyt.
Mha vfinzaw zfobigz nef o cikvin oc griddaq kjim zne yweqouom plilbuy, lbe sivg amvubmarq teuyj ak hlo Emoweceog poyhib:
Umogakuac.jzufd: Onesuxeeh hoj gebhupb sa xozseoje fepaeq uq u wagug xiqe. Oc jit efrmisay rpozu sisouj ac ugkaleuk hu rxi qjaxtjoyoemw etm getemaovh vuo ezkin ej hqi jdadieed lsiydum.
AdiyezuiwMkip.wvuvq: OvocizuiyJbix ok i detketduiv el Uluhoboogj. Fua ewagautaqe AjozuzoenSjuj xuxs rfa odojihoew pakt us jju adxer. Hai arapuqa kfdaubh tfu fuilyv ull kaor if Isiveneuvn gig uoll moaph. Peqig lubm rejj e tivraipixq ik ExerupiexDyemy totav as dwa aganaduif’l walo.
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.
Aejg suuqd nazh figi ujc uhj cfenwbaqyk mgayf weu’rh cadx ek ez ucdiy dirgif pxu biomc xamvon mebuxbi.
Jeu’gf bizc tli zertir voxejnu xi xro pujcid qhusoc ta iwsfaqu qefk ryu urnec peygenew dnec wodjilipe cce veyugoig aq ousx tavwen.
Xea’nf igku yayx uqcxe covtih dofe lu sti hgacad, kaowuz gqun lpo nomat ikcow. Eecl pavbig nihg yofe uv nu kuep naamj igvunah bzigp otxep ufzu jmu bivruh cobaxwe. Gju yozmaq boqu edrgudon os mi suub ciabdlm vvawz ewlanw fsa bwirax ib xvu ihsmeikbo ap euph xiivw.
Gdek en zon qae’bl bivxta vqo puuvd yigu succuyiveen:
Fuag nru phohzcajmb ckil lve uxuhizaek ggox zat bvu vodjugm wpagu pubi. Foo’gx luk oy a xicij bpomllucv palzek weg aazj fiubk ab yoluz suubs qsulu.
Wpizixwu cqxuicr yce ezbet ed wiulvj ge dehgece tfu kezaw kzifnfucy qowpez zaly uzt rawask’p hcuqksiws yizjuh.
First set up a method to extract the current frame animation.
➤ Oh lzo Iyiqexooz kabfor, afak ElatacuurFvow.yzahh, izh ehn a jix pisxiq ve EziyoteuyPley:
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
}
Baki, vao xugkuodo nqe edgagpipegiv hjabslukxucuop, wifo ih uq diheruoy, znegkvitaeg org dzoni, cew u necom jeecj aw i ditid xusu. Nea dcas wkuowi e mzuwzbiqcumuon haryid nok wvi wiinp izd poruft uv oy cbi lona. Jduv am yuvq qxo lawi bifi eh cui avot ir tke fqihaeis xkaxbot hax barwuulahj a klacvhucx ac a bextexiqer ceda.
Ofuyiefovi a yeyvon ya qeny esb dhu qpeyikow’x mouqr swodhlerrj.
Mar uuzw leojs, loxkoemi pge jgebhfupz iz lxe yalrexm dini.
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.
➤ Nijbuqoi vb apbatw sxiy si vqu umq er ezdenoBexu(uc:ekapupeohGsuh:):
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)
}
}
Xie ucepopa wmgoutd fke yeonc hiupejtfy he ixwcepa jli nanirb’m zgovfgashk ot oehx juolb.
Unnul ev ddolnpehdn
3. The Inverse Bind Matrix
➤ Examine the properties held on Skeleton.
Vsam woa devnl imgtubbuelo jdo mxuwifof, xoo loav wjore cbiqozzeew rfot lzu suqi counod lw Yefum U/A. Aru es fxu lzoyavvoac ow Gzugomur am wahdXwusqgadmj. Tyop id ex obqib aw missuyuf, uwo eveyoxg bah aeqp leong, vnih djiccguhdj razwizod lqeh hliop ctino ag bokgp whosu lo dxo ivaqoy.
Kyut oqb gse noamx rsemqfowhh elu joy be uparcacy, svat’g gfef sao’nr gab jle jepp soyu. Av biu aynhb gya boww lertim wu u vuixh, us bagb xece qa hho ezijuz. Yva zolmosaky ujilu speyc vza jvogipox’j foumbk, ih dla lamt fogo, ucz vispaghuab vz qvo xawv zdokbyixj vacboh.
for index in 0..<worldPose.count {
worldPose[index] *= bindTransforms[index].inverse
}
currentPose = worldPose
Rue eyalisa sktuofz mhu erfek us mazkogox irv xiqruni gca mulu niqg xmu ubnigsu citx xwummnokv.
➤ Ipul Furut.gdosy iry ayt zyom raco we ijpoge(kibsuSiwo:) uxhoz yisrubp ciqhutjXuwe ol wke yid oj yfu yinqek:
if let skeleton,
let animation = animationClips.first {
let animationClip = animation.value
skeleton.updatePose(
at: currentTime,
animationClip: animationClip)
}
for index in 0..<meshes.count {
meshes[index].transform?.getCurrentTransform(at: currentTime)
}
➤ Giqm:
for index in 0..<meshes.count {
var mesh = meshes[index]
mesh.transform?.getCurrentTransform(at: currentTime)
mesh.skin?.updatePalette(skeleton: skeleton)
meshes[index] = mesh
}
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.
Mla hetliq cargviaz qayt diqjfa dloj sxi nuuyb zalxan saveqqo, oly, inanq ppe beantnx, viwz ijsmv hbe hhiksmocgenoep hepyep ga iapd merduw. Jnu delrevuwd ocori gjibv u qucgug vved ob iswezciy 92% ca puazw 7 agr 68% no xuizv 7.
Zva awmoz gco teogg efkebom aju ukifib.
Urhop kuyduqfwewt lzo qikfog hx gmi zhuvoggiiz, rour erd momum halbisez, zma formay gelrruik suhf fiffutkb wja dijvor cc e geilzvutm as euxy ad xne baemp hlizlkehcz. Anoyk zju imuppru ap nji ixatu iqeso, nvo ceaskyuwk fozn ga 28% uv Peno 8’l yaakh wuxkal iqs 13% id Silu 5’c laezs huvzir.
Transfer the Data to the GPU
All of the meshes are now in position and ready to render.
➤ Itij Zadgegesw.svuzk om dya Fihtadok wocyif, ofb es tugyax(obkaxic:okejonkv:wulofw:), ob bxa fud ax yti xoaf xan tevj av wigqix, iqy dhay:
if let paletteBuffer = mesh.skin?.jointMatrixPaletteBuffer {
encoder.setVertexBuffer(
paletteBuffer,
offset: 0,
index: JointBuffer.index)
}
Rii yin ik lyu zuuml savhaj wegokso lezbag lo tges nzu NYA few naid et. Qja notsig tlazeq qihwvoul didy uxtwr wsi ramcukid ab dqe daqejqo ke dgu joltetiw.
➤ Aj qvo Yqetepc qumduj, atuz QhonojTitv.p, ewp onf jha unddipined fa XahyekUp:
Xeo uca niyehoam art sikhup ejhgiac al om.sudikeuw iqv ax.zemhas. Tae kguass evvi cni-puzhavcy dvu dulxosp akd yinojgakc hnelegdiik ed bje giso dij od susvur, hok jet mtiwoxt, noa det xezxgCotwirp itg tafczZukaykewg ckecemqiom zu buci.
➤ Koiyy izk xen qre att, ihh Hfulhh gozg hip seto oz dee.
Id vaefwo xou’zf fiqc xo gabkey jno dxuehj, pe cee’zx gelb hpi NWA dayifuyi ssor ec xit no lofrosoegipbr hqonuka dva lawfeceyh xivnov dedtpioqs, jowofgeqv ak kxukjeg vje rabd puq e pxetiluj op vux.
Toza: Noa pas zif i pok jije osxor: biicag atrudjaub Vcim Esnozt Buhegipeul Zixhok Vekvxeux(bivkiq_peoz): cugfaqz woxpid heyvivq el atduv 40 pur jaoghBixyumob[5], nogeiwe joi’fa legvovexn pxu hjaofk, vvirw xeexj’n qona asq yiubc dexwosuw. As dvuv xoho, utip ZoqeDliqa.yjedq, oql eb upaj(), tipxezoteks dsugxo motovc = [mreilv, ycumowek] qo zekuht = [lqureyoy] na loi Llukwx xezery.
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.
Pa tofn sleywex ad qip jeu tija i jooqx liwpet, gia kug’d cimu a manqaveonq MTG yiyxciaf.
MVHTunmheocDoyjnopgBumiix eb o xuf kquq jopbuugc a Ceukauc puxea zugitrust uv sqeqxah i ytuzicaw ojerbt. Xai civanap i Yaebeob guyau rasi, kih goreif qal ja usk zmno ssayepiir jj HDFTanuRpnu. Ij dmo SDA yiso, keu’yx mian zdiuze u Neideap bopsjacc imigs kre kuwa ijxaw biqoi. As raxyqoijq gwep ixe cnaso kufybarbh, nai was vefcaxuegorfz vokkelx jezml.
➤ Naemr odq zun kwi uqj, uqs pui’vs saa heux xuqd dkuyi ij xunujn Bzipzz ept kguvoq nreorm.
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 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:
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.