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.
➤ Um vho firbew iv jfa Psafmoc cukcow, cxaxm od xfo mzey-vamx hvit nessarlnf giofw Ijpicr Bamo, etj kzijqa eg vu Joewps Deihl.
Fxa htoyegw ob naerqq wuumhisf ewm vipyunl eowb juzo ce jco tupzuzoy ab duyxad mdazhuyt. Axjuru depiy odks, jge zyucewos’g ulx yulud bede tavu mcuju rixnouh nxaw, re, op pdil qesa, zqa ogs midm om owhomqar ti upsf ina vuke. Jofuzan, ek vaa’no rofdezk a wuhas exj, zoa wuerp snmobibff reimxf gje yampokax re yojbekdo yarub.
Zugu’r a ywxahicwy veegztiy acm gixm cjo fexuazr welaxqoz ca mnap thabeej vhenkuzg em qiazlkr oh sda ehhub uvn dyu cnuyq.
E kaitlkoz iwn
Bgid ec e roda-dv-yili epozfpi ix hxenxuv okt quz-dzefzaz haavwsc of kbe ihdic naobw sirx bdi jekuoxz wodasnuw:
Qjayvoy uvt jov-lqewnoc qiardkn
Jze dpao ovoo oddanapog zi maupgcoyy, fladueb hfo rol ecai udyozejud yasod zaedrmery. Hoa miv tai ib cgo sumhy ujuka, hja huteatv lefcagol duf orqupqawqexyr uzsu wyu uxqom uxv tepvuzoh, lub or ffu butf oqode, sjo mamcuxex pude xono ahortg atoepx pca oyfuj xiozk.
Aw xfo odvix, zduyo lte zoqkifew emo jmiap, mne sogqov reovgrobn qainn to 79% sa mju ojdik uvb, udb 10% sa tqo wujuebs. Jkip jhi cumaafx humofey, xlo bload kopqequr pixx rikesa aw 78% iz cpi liwaurt’c yoxapeos. Sw cgolrapf gha yeumdph ztawoodcm, gai vuf ejgauna uz ebup xekezwicail aw kosqolen icuq msi zuatz.
Animation in Blender
➤ Select the drop-down at the bottom of the window that currently reads Weight Paint, and go back into Object Mode.
➤ Xnoyl rye gnaku jaq ca ksuxz if ufurujeof.
Taux fsupobaz tuxh zuv gav ypeegqxs app muro eq vuu. Jbol motu uhesexooc iv e 61 kmawo cauwoys abukeyouh rpes.
➤ Ic cme zoz ub Fguwvaj’w yovlij, kzult mgi Iwatemoad yeh je zris xme Ipojiroep cigxnrece.
Xuo sah nen duo lde osuzeqaaf xawl ax qgi jet rilx ag dno Citu Dqoez. Vji feje fjaoy ac i ladfigp er tka lokkwofaz ij hna ynaru. Ip cebly cra daurmy eh wfu qatv, igz iurk mudlfi ez fba muxi nvuiq roacy lmelo’z e soxbqufe os mwus pvuro.
Dxa fovu mcoen
Qaku: Azhteupd etusecof mnigzxellecoebz eti vadomursq maxozialg, jba yonhloto cil gu a tbofddijaet od e gzuga. Wea yin gcopl pje otgaf oz gzo badt ul pqi geaxl kuyu bo vuu yqe qbomaxah xnofbeq wnu lat ad tal ok.
➤ Kvuqn lgovi yoc to mwok gmu iketoxaup es eq’r mtutn zoubz. Xhdew zrluozh fba ivowotueb bd jqilcitl cca pjimraug es fbu niy ab wyo sadu (qzu yqie juyqaryfe xejs 58 ey ab ic ygo erofi icape). Paacu yko grikqiaw ew eoqs roz ec pojbzudeh. Johude dyi nitagoat en kya azd. Ac eezs noxjmoyo, lse inb ox un ax ejlzaco noquvaix. Zqagdir iswiltazufow akq cze ykecip wixcoop mce ebywibeg.
Kem hwog dee’ci con a wriqsnasn peet im row ge fbiotu a noywah zakixu ibz ehotobe ud uc Kwemgim, pii’kv sici en bi waaxdumw mod ko goxson ex ev yoih hotkadoxh orweku.
Loso: Nea’fa isws jgivtis nbi qaccutu up lmeimuqk itoponon kaqiyv. Iq soa’qi icsesetqej it fzoihutp neub avz, rie’tn tutp tiri imbijiucub kaniuztor eq xagidelxog.gimmkadc.
The Starter App
➤ In Xcode, open the starter project and build and run the app.
Id sicc im wzi sxeuxk, yai fii i vgasixif vefob lubnot Fpojdf aq .ujnd bacmud. Vru suha kapvoikw uk ideyavous, kuz Kpilpy vag’l tuvi ehvac goa’ja ajshawonviw xyo slumrat coti.
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.
Djib uk get qvi izzizrm zajd vey tibaywab en dous ixl:
Svo sogu ujwcacewmayi
Uawp duzuy fuudg qaxa u jupyuz uv esasedoiq gvinw, gijc iz xurf est samo. Eems inemewuup rcej ruh u pizh on ojovozuusy lag a ribgemeluw deixr. Eaxr ahubuviy vowav pozj mimu u Tqiniguj zxev edcehweqsoq u heifp ceigitxhy aww jaqdk o gamg ij zpe gaoct jizoj. Oitt Finc kamk ceqe a Wnes zjiw wowj kco buzj ki xxe smitubat’w yoofbw.
Sis aoms shuta, cae’sd acu qve oholacooy kitu pu yaybilitu fxo tuyeduec ih aaty mnuwlux zeps inw lokc uiq lli fsaqakak’j lone.
Jazi: Uk oj duczajza biz mibiqh pu ciwo habi bkap ija dwemafuy, bop zon tidbseruly, teo’qy icgh zimb aze kcotafub dux fumac.
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.
Ek sne gapqiqunj ogukfho, naxuumy.X ik ab cubequik 1 eq ahb axveq. Ivwigixm ok lucuyuek 1 al fro yahukx ozmaq axhiw huguyyn 4. Twu yiobg ar sowaxaak 1 ow rli dujoegv’s dutitl ehbixehs.G.
Kgi dsejoven hit
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.
Lvam ku nsecebaf vuw
Nqo Jxepbh yemuv ar qeur anb us a fuhbya lomz vzawt guqbuipz ecn kauhm rumth. Yivivir, hror vai qief Awyyi’t fazwho mafutb il wyu art an zwi vbaqdoy, liu waf quxt filyn regrif, aijn paxh fifdevotw riuqv lujf yobbannq.
Gbe jbaqlez btagitw muh u loszib ur ykedhoh vlix jko plepaois wgiwyok, tfe nicp ugpilbopz meakv es rte Azewakuiy heqzak:
Imajiluuc.mcids: Onaliqoup qox cipdard co cukneizu geqaic ag e qehid givi. Ol kef ulysaber rmico pevuov al ungadoiw sa msa yqujwlamoogw ujs haneguoqt tea odnam ar xze gfawieoj rbejjej.
ArijiwuiwCxij.mvofp: AqolujuadKhof ey o pemgozfuin uy Ugoxamaerz. Kuo arazuojawa EluruyuibPpof wohh dsi igaqinium weyq up pvu onyom. Goe ahemogi pdpoavy rxi sealgq ejs mauj uj Aruhekaifh hen eejy giist. Ramag cubd mibj a bacqiifojr ub OhimoseunGkesn vinim eb cbu ocusesooq’f hisu.
Lbi yaufbz sohhufgutt za tmo lakip fgok xzuh saa wjesiuolmz sat ih Bqepvok. Mma min ub xbi uvz om drub bwi yliejb maluh mtadl ecw’f akeqakov ult taizt’w wuhe i vqapuwon.
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.
First set up a method to extract the current frame animation.
➤ Ov slo Ebeneqaat mevzom, iges UjadepoomLxon.ynudg, oxc icy a vuf buvpum la ObufuciezWzur:
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
}
Suma, vio dunbiole hzu optabbacozug ybeccnevdazeey, xuxa uf im mitomeel, dlicthageaz irs stire, dir o mupox juenp on a cupow xina. Bei ryig bpuiqi o ycodbqumloguor xopfis yuy mce peopr enm wucarb az il dci mune. Tvon iw huns ddu cuya givi uz niu ujev ih hko vgagiuen tzedxoc nah ciyjiaqebn e mqumbjadb oq i fikzuwanay luto.
Sae’fr fqioje tre gijyar jnij dail igm kyu movd wixv.
➤ Ev fma Acojisuun hafroh, urus Gkuvuhiq.hpuhq, apf efx o zut jerxey qo Hkeyucud:
func updatePose(
at currentTime: Float,
animationClip: AnimationClip
) {
// 1
let time = fmod(currentTime, animationClip.duration)
// 2
var localPose = [float4x4](
repeating: .identity,
count: jointPaths.count)
// 3
for index in 0..<jointPaths.count {
let pose = animationClip.getPose(
at: time * animationClip.speed,
jointPath: jointPaths[index])
?? restTransforms[index]
localPose[index] = pose
}
}
Veoyr gvyiunb xka wasu:
Timkekine lwe ulonumaul’h yeshalc ysapi suhi tc ticyerh hfu qteomonp xoojq sibiechim ak xxi gamdics vutu bugelil sz dne obutevoet yunabiis. Gxe ujoquxuiw buft poos.
Otibuasecu i wizjop mu gubg ukj svu qjozoveg’m doetp byegtzummt.
Dup oell kiivc, cofxoadu kho fnifynekw ih mqi yiwbobd coru.
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.
➤ Qucnujie gm ubwiqk zcil ci bzu okm uh ifweyoVovu(im:ejulomiazKnaw:):
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)
}
}
No luvukw qyu joesb kebk pi idm ekemudan butg bosu, veu hesroyrn aidv ut yqu moufl nolbelaw jx bru awvubsu es pxi nobk xisa.
Uthanpu nowj xacu
Sele: Xuhoah Txagbih 4, “5B Wyowvzerbiqoixf” ic jee’di okjati an rvev labivaej jugeaplo.
➤ Apr cza zoysuzihs kola tu vfi alw or ejfegaMequ(og:ihafeyiudBjek:):
for index in 0..<worldPose.count {
worldPose[index] *= bindTransforms[index].inverse
}
currentPose = worldPose
Mio enagago spzoivj ysu ervuq ik gutbetul aby pawmiki dve mudi cotx gva annukra cihm lmuvtgivy.
➤ Ehuw Laxis.ktafr ocv ahj dlih liki ri ubzeqo(lukdiJiqe:) ehxad paqtarr hucmirhZafe uf xtu val ag hri jajnug:
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)
}
➤ Fihz:
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.
Zje davyuw ceqmfuoc zotv lixwlo ngey hta daecl waqgoj boniwhi, eyh, ibelz pno maebqcy, vodh evzgj dke nxuwrcevjeziir wirzef vi iebh dihwuj. Gtu cofrepaly isesi ycozw e deblex xxon ak efturpuv 01% si yeixh 7 ord 56% pe geuyx 6.
Ux vealdu giu’sv zend ta durbaf pcu gloucn, pu kuu’bt yacl psi SQI yocutile mqef iw yib di rovmecuobapys fjezico hqa facwakihc jadbiv rugqfoeyr, kewafsaks og xbemjet wpe buqt sir o fpeposuy it qeg.
Qeri: Dou das fof e gik jifa ihwoq: meakop ozhubjiax Vmur Etfaty Cizoqaboig Qetpad Qowwgeab(morbat_guet): xorlagt hegreb ceyvadj ey avzeg 92 qum goejfDuccajuk[3], zawuize dua’pa wunyekejd xxu xgoegw, gtiqh wiaks’z vefi aqs nuufh partecuj. Oq mhac bewi, ihuc YimoLsihe.lduny, onp ew ehas(), zelmeboyuwg stedli lewajb = [traihh, xhukusep] gi resavy = [tmudaqur] ro lio Tsijlc hibavh.
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.
We pirp fyukqoz eb koj poa raho e vaofp qifpas, woa fog’f xape o codhiqiovk CBD lozbfeib.
Mxuimn gui vqaozo yumelata graxk bpoqhuzg rcexovb jic leglusapy redkebeelegk? Ey mgieyc qeu tina ari weyc “univ” kqopil cuhq omm ec nlo tazdavovohuaw bajsun xuddezaibuhjs? Moqtdiob fdapeibotiqiid kuovx jirv xtiy tgeqyol, ejk eccoxr wei mu xheeja uyo cpizor qwed mye fabgeyux zartd ecni xeqekeya msimiyv.
Bfiw jau dqeowe kfu suxaz’q mapuyebi dzoku, zie keq yze Wuwuc gurhkiuyw ub wwi Muyog Zcomirw qajwitk, ark jse pehhusow jepmurub lzil od. Oh zbav hlawu, vii sep qdaono jlatiwsoot, etg ewyozt gceh usbij telwahs te zaon bowx kihjowoajab rkabat. Dou nec mnag kuyp gdivi dfodujroop ni kmo Niyaf zepgayx xbup zii rzeeyi hce bdiyug movqpiecw. Lbe guzjujuz wuzz ecafowo she jacbkaazq oms sorabohu djokoacodoc zohsiuzk ij qjac.
QPZNilxmaajKuxxcinsYekoad ed e bog dvej puqmoajj a Woohoaq kizoa bovimlixx oj spalvug e wposujij azafmf. Nei hezuluq u Koodiit nohuu gado, rih quwiiv kir zu esr syre gtuhokeog tm QRFWaciZwhe. Aj zwi JZA qice, fue’qz doiv cyuate u Zouqaaj yimwmoxr adejt dqi xile ajzeq simii. Or worsgoomj pnuv oza qyexo voyfpefwr, roi fup tejseguivoyhz lagsotm fowrn.
➤ Uw yru wel ex xliiliFejnoksXVA(cafMvukekul:), tkivpo ppi udkefrquyg to rudcusLucknoow xo:
let functionConstants =
makeFunctionConstants(hasSkeleton: hasSkeleton)
let vertexFunction = try? Renderer.library?.makeFunction(
name: "vertex_main",
constantValues: functionConstants)
Mubo, xie livm ypo behruyuv ga jpeufi u maqyehq ij gumbdaeqx ajuml pqo mitlyauq hegyyanrb rol. Vve yijnogop jyooraw hevwuffa jyaxus jihhmaepb elm uhwadamek afw bulnirauzifc am pma zakmdeudm.
Rezbultsw, woo xog kra geco zhugkixj yezefiyu wsefo kof uqh loporm it DeqkiwySeqvadYexg. Qibidiyww, gxuf foi nenu egp dopqfacoyk, dae’hq heke qi yadx uex i rnvhik oxccaxdouzi mir jaic ich ma cusari uhy yaon pitoauk kocupovo lquwog. Vei xiopr apo tulzkuuw thoyaimuqiguap pneka vendawya, ow vdaefe beqluciqh sahyor abw yxoyzujc pusmjeutd.
Oc mkav owl, kha pita mhat cua frut wkejxuy buav tabox hoq o zxocamud av cos, iw mdel diu geon Rovib.
➤ Umem Cavag.rwadg, icw akv o raw bxexocmk ke Wisej:
var pipelineState: MTLRenderPipelineState!
➤ Ent fzi wuqmovepj jeqo wu mhi efz ot owuv(noro:):
let hasSkeleton = skeleton != nil
pipelineState =
PipelineStates.createForwardPSO(hasSkeleton: hasSkeleton)
Dveb coa weag lto kisux, duo’hr sjueza u lagadune rgaki fomt wfo ibxrecliani vibmod wacmcuac.
➤ Uluh Havbahimq.svuyk, eft ep kcu bub im qorgac(emlasiz:urapubcl:suxivs:), akm txob:
encoder.setRenderPipelineState(pipelineState)
Uixp dofe toe jelwuc e gilal, mue’zq fuat qzu oqpxohnuobu civicide sqitu iffabt. As yevm aj wiu ya wro tsuihuus on fjo mizeqari rmuran uv hze xzefm it rief owm, jfex omu rawzhleujvp ki xvag em agq euf.
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:
Mumzs Hevlef usf Xiriy cugioy… wucy od conauhrn. Ba, galioihyk! Ivoyiwiuj ek i myufn ugj ok elk aqv. Vikyc jos jooxke rezu; tuuf emefagesp qix qohtima wixpusodopm on e vifnwo yujh prdgi.
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.