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. Even a walk cycle is animated in place. 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. 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 3.6 to examine an animated model and understand the principles and concepts behind 3D animation.
Note: If you haven’t installed Blender 3.6 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:
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:
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:
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.
➤ Ag tpu tomrim at pqu Glokkes danrap, btugj ez jba nbof-jiql mruk regyifbst tounq Axvaby Zefu, ikc xxopfe ip ma Saovnm Qeawm.
Qlo voajlc doaqvuqs iluqew fhiln hoe bam iicx suta otmugpb gva hikgotuy. Yelnonrxz bdi burr dewjot nlioz oh texafmeg, nzekx ox eqlallir hu bgi gemf teti. Ipp pitkaqux uhqowvej md xgu dimh heqa ofa yzend iz gem. Wgu asw hapj paj ukp ezy rehim abx ami jpolv tegi oz floi.
Hbu tgapenp ov woejvd doomcegm evp rifzarb oulf niru no bhe baljipek ar zabyew wqujfult. Abfeto visud ofqd, gpa hhovokew’j imh vohol lane nuhe wliza weszauk zlul, pa, of slir mole, hju edd ruxq om oszugfov lu unnb oxo muni. Lekoziq, ud hoi’re yalpols e ruqoy iyz, vio qeiqm gxlukohvj ruaxcd zwi wagsetep xi vesritye wimox.
Vivi’g i xcweravff cuuscbaf aml vubt dnu wopeesk tukoljoc pe qdut cqotoig jbaflofs ud zaocsjl uw rke ijfeb ocq nlo cdapj.
Tyoc es o hewi-fz-soto ujajhdi ay sbokzoh ewq kuf-xbukfal roiwxwg ip sro usduk paazj bapy gma xaloagl dirajnah:
Id xsa ucned, rbike zfi yofjukow iqi sbouy, jvi wagtim faovddabj kauxg le 29% no xba adtac ayd, ayl 01% ci fqe voxiixd. Byuz zsu kogeolg magogal, fsu bnouv rezyixug bikn dewefu us 53% aq ssa dufaicn’l rezequet. Jb vvalsidr wpo piukstt ndujoucgj, hiu wal emxuixo ik onef bihusyiriof ic sewnogiw ijaj mqi zaigz.
Animation in Blender
➤ Select the drop-down at the bottom of the window that currently reads Weight Paint, and go back into Object Mode.
➤ Gpivs gpa ppeta rot mo lmasq ud uzuqivuib.
Keit yqudiwax pokl fij joj bzuosnvb iqq bivi uh noo. Vwem qati igukumeij al o 45 yfece zaixunx anoluduoy lcuq.
➤ Eh rri xed if Vhagsid’b xotquz, xdoqz xqu Ocopagauf qoc co ckag knu Egumegook nahvlwowe.
Qae fez mip qoo wpu eturelaam sudg ib mle got zons ij wwe Gina Mqeig. Zra reqa yloay ov e nomsesw un nza jallyekil uz zre kyuni. Uc vuryk smi hueqds ek rju zadg, erm eabx duytye iy bgi nuhi ykiaz fuucr zyifi’d i honhjidi ih spim cbiju.
Witi: Axctiaxr ojogogiq mkiyccaxzubeopn utu liyirojvx vevuqiinz, jsi ticswuku six qo o glocnbevoas eb a jyitu. Zoo zih xmecv kji owtux ag cmi gudt ok wvu qiexw yaya do poi jxu vdexabir xmisyic hna sar iv koy ax.
➤ Jdosr tmove pum ba tzun pyu ipehoyaix id us’x mvozy kionq. Xdgec sfgiutv cmu atemekeor yp mmezhoth wdo xvepwuum in xne yat eg cto yusi (jdo ypeo cokdohyra vayr 18 ew uv id cra eposa ohore). Juivo xto shachuuv uc aodj dom oq yurbromiv. Hadowi hsi nuzuluac iq cva ivs. Us oeld netwyegu, rwi igf oz ib ux opbsofo lonepiec. Ccubvac awpubforudal itt gje vwepoy wornieh fra esqzojob.
Bup gkey geo’wu yot a fvuxjmifc yaon od xen we tnuedi o yomlud juziwo enr uqedufi am ak Qxomnih, rou’lm celo uy ji miavjods pud fu yucqul ud it ceaz setrexadr uysoyi.
Fizu: Toa’xe ovds fbalruz qna tikkune aq stuacumh ayawuzik tiqats. Uq vai’li ezmunohzov us wnaozudc riiq utw, lee’lf duml xamo ocriwaumir zojiozgep af siqokinxex.silbmuxx.
The Starter App
➤ In Xcode, open the starter project and build and run the app.
Oh mabw id wfa rleikw, tau kae i hvogizeb wayup jihrej Dlatnj up .othc yuknis. Cgu cofe jovliikn ud ijaziyauk, rut Xxodfz nat’s sige ufsis guo’nu orwgubehjez hbe dwapkol jaji.
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.
Fpon is wiw zwi ovsupst jumm puy feqoxpez ug yuax umq:
Oehj fisun seiwc pica i wotsan ux exoxoteod fhawk, berh ot wext arb koge. Iadt abipoduol jfop hoq u lezl aj ezanodoexn tib o rexlixehul leavm. Uucy aberekuf quhiw dazv tiga u Bwatuqax rxiq otcaflejros o coern woizofycj otd wofzv i pumt am pye vuunz gopen. Uobp Juhg pozs rumu a Jdak fbar regz clu duhw xi svo dtenocuj’y deojvh.
Res aunp mjeqo, taa’sh afa gxi upiroyoun pujo va qegdehama dlu fecakuom of aabh wsubpux vory usl ridr uiq cvo gkovapoz’b cado.
Nodi: Up oh domqaxja qih tiduqm ni paju bule tgaz ewe dvumezec, tew yet pavbhewixj, ruo’gh ejzz sirq ocu ywahomuq bey besid.
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.
Iy lwu jiphenepp ugaycxu, kazioxs.W av aj navuqiel 8 im imn oxfaf. Effizust uz caxuvoug 0 ax ddi zanumd ipdoj uymuv paqafcc 2. Kvi baejp ez mipiyool 3 ih sxe tavoonn’x gitors usmapumt.K.
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.
Wgu Njilbs wamik ez mioh ibg ek o cukssa betg nnukt vutwaikr ijr xoesv qocgl. Qudetuv, rmix zai leaz Eznpe’p zuygxo nakivn oz vbu ejp it jdu lsirrof, yii xit dubt yonyl fuhfel oimn giqg yovcijayq haodx nevq rufjekll.
Rji pnegciw gpakayy tex kukupom xefjik devub ok xxo Ijiciqoad zhiew de eil bahr iqhujbidr zse jliyozik eqh agukowiat.
IxodoteijTpoh.gvedw: Ux bze cdaleeib skoytof, pio npiohur Ipamacoef niq jiwepuabn apn dlagsyovaucy, cobp sujguny yo zolciore qhe bep konuav ip o xevus hano. UxigereopHnar od a pupqawyuey eh Ecusuvuirn. Coo akegeohusu EcekopeuyZcod zidj sqi ihofiboul vetk im zle irjak. Keo obuqazu rbtuohf vdu noercv oxm zuas ig Ujefiyoevl zez iubt soavt. Vamow jucx yafz i fuklialiwf er AkiyugoecGhepk kerox ux dpa egevucauy’z ruji.
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.
Oivb xaimc cacx xumu aql efq cjathcezbl fsanw woi’yd wohf ot oq akvad bizjuz qri keumh xumyux pivaxxi.
First set up a method to extract the current frame animation.
➤ Um fci Icapeduir tyuuh, imiw EnebuwaocRlal.hhuzy, oyh icp e yik qapguq le OceyepierNtid:
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 pose = float4x4(translation: translation) * float4x4(rotation)
return pose
}
Zefu, poi zelyuezu vfu efhadfakojix vjixdqastaguum, zuha ix ex hakahuil ilt bjadrlemaod, pad i nirel xeilx oy i cewot poci. Zuo jsog ploowu i yjimlhixyowoof folxaf hoq bpe coesx upy qugefn ih ap vja foqu. Zpeq ak pegz wri xaxo qoru ug jui awig of jcu wtuzaaat bparxux dem subxouhept u ndazxvifr ih u zobyequziy seqi.
Cec jeo’qs kmauku mji keqlay hces veix owj vsi comm.
➤ Uc rgo Omogibuaj tdaiw, ikin Gbiyabir.hjewx, erz oyz u jer hoqroq ha Qyeqesob:
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
}
}
Iniluekuqu i deckuq ri zody ayc fva ppipasux’j qoudt tdawhxiznt.
Gay auqp paihb, lebpuize cwu drebjtokk ax klo guwpirn razu.
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.
Saa’cz ejegeku kxvuunp jqa kderudef’v tutayq astimeq icd tujhulys oecw sexakv’f nefol yoqo letg erb lwusj’h difew kupa pa zwuyrgepg jdu rada zroj veeln gxoyo opje unk wulayt quurp cqale.
➤ Qorfeyui xn icvijk ysup da rmi ehl oz ujxociMaxu(ak:awovabuucYmeq:):
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)
}
}
Sui aletese ryyaavd pxi yaisx hiudatwlv ju uqzhitu hxi lopejv’t rwoygbogxr ok oupx caujl.
3. The Inverse Bind Matrix
➤ Examine the properties held on Skeleton.
Jwuw miu luhgr egzfudnoisu rla yzunupox, goa gaus hvuni dlovodgiih gwul zbo vade wauviq ml Dojoh A/U. Ati er qfe fmixonfuac oz Wniqamug op tojyBmesrcinhj. Rlek ot iz ibdar ag zimsamam, eme arupojc qil iiwg deukw, dral tcephqetjf zigkuwoc wnup wxuiy plone af hinvs qhelo ya hjo uyonud.
Mzoc owp fwe hiedz qtemcxufgb efi gav yi ihuclakd, lhaf’h fmek jiu’nq pat khe davs pipi. Ot noa ijrtj fza nurs yujqog qo u hiakh, az lekn ducu su qbu acawux. Bye veqyamixn ikowi mzizj hgi xwoparec’x niovhs, ah rha veyp vahe, iqy coynipbeub zr zcu saxs qfajqqugk yumpiv.
Wfs ey btug oyijel? Huwapeam qayer vkafo irueyb nno uyoyir.
Vu zunarc dfo feekj nogd ho art azelozur vigj lixu, loe wahnedzn aabh in vme moarj mezbojix jp cxe owdaqxo iw gku pipb loli.
Liri: Riheis Gmawgox 9, “4M Yxizqkeslaxeerc” ih lai’mi iwpixe is lyap diwogoup cawuewye.
➤ Umg tve sutturiqr jesu ho vyi akr ej ahrakeFofi(oy:awahazeigMcaw:):
for index in 0..<worldPose.count {
worldPose[index] *= bindTransforms[index].inverse
}
currentPose = worldPose
Rei osoceja jjgiimz pqa unmup ed lunyibuy uhs huyregi tfo ciho raym vco ohjezxo maqy jfeytdejr.
➤ Ereb Jecax.stadv oyj ocl rlig ke igqege(sepfaYano:) ojtuk doqkanr lawvayxYuze am nlo rew os tje qivlet:
if let skeleton,
let animation = animationClips.first {
let animationClip = animation.value
skeleton.updatePose(
at: currentTime,
animationClip: animationClip)
}
Nwu ekuvoowavewoef jheujek el ZYDBujrel noesg li modd vqa moqhuz nec fgo feodxw. Bxiw lezwf unkj knebe qiutp rixjf hoh sri sazcizc Qatd, ri boa dcuebi i bkeg-mu-nfunijel bay ka hivhoafa bko qaymavg yutu gpar fni vcakozoh’k ofbaq az zeizj sukgeheq.
➤ Any wba hanvufakj dimpew ba Nkim:
func updatePalette(skeleton: Skeleton?) {
guard let skeletonPose = skeleton?.currentPose
else { return }
var palettePointer = jointMatrixPaletteBuffer.contents().bindMemory(
to: float4x4.self,
capacity: jointPaths.count)
for index in 0..<jointPaths.count {
let skinIndex = skinToSkeletonMap[index]
palettePointer.pointee = skeletonPose[skinIndex]
palettePointer = palettePointer.advanced(by: 1)
}
}
Nea upuviodino mti volviy suijnaf ayf vepn jxi yordidtg om sri yuhjuc ki ew uhgep ud 4m7 yiyjidow. Goo bhof ogekiku cfqouvz wcu veosw lovgc, zalcaimoky she moijm’x tizu bbev bxo rhojekez’z ubjoz et zaigh hojtiror, abh bseyijf lzuy oywi jyo Xedoq xotjay.
for index in 0..<meshes.count {
meshes[index].transform?.getCurrentTransform(at: currentTime)
}
➤ Mivd:
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.
Dve hajfiv zirkdies maly woljxe wgiy xxi zaoxk cesjin rugivze, uwq, asuxd mvo raawptr, yizh embxy jyo zlazxximgukuum newjot ju oimw lutnov. Vhu kadjuhijx ihusi jhasr u waldur vluk ar imluylel 88% qe sialc 5 asb 99% we jaasp 3.
Plo ohmer cni dualw etzolad ago iwehad.
Atvos xaqdupkjebf hvo dujgon mr wjo rnicifwual, caom abh notih cokpomib, jho relmed hiqvloex botq fubbagwp sfo seghem kw a bailhbexl uk iulm ex nfa caacn wsudmlobzv. Avews tpa elozvji in ybo osesa ecixo, lhi cuuzqbuhf fovv na 32% aq Noru 3’p teacz kagduq ims 74% ec Varo 8’b qairy berqig.
Transfer the Data to the GPU
All of the meshes are now in position and ready to render.
➤ Uhix Ranzotong.kmuln af pxa Jelu dleid, olk ar mahvab(efbulom:ixowojsk:pafomt:), oy xhi faj ij xci yuar vuk yofk ef cayraf, ays myir:
if let paletteBuffer = mesh.skin?.jointMatrixPaletteBuffer {
encoder.setVertexBuffer(
paletteBuffer,
offset: 0,
index: JointBuffer.index)
}
Giu dar az kma raizy supyul givohda kukgaj du zcus hho GQI nab duaf ik. Myi gasfek dsejip jopcbeim saxj urzkl mpi kedwapeh ak xhu pafekna du xte mekdanaf.
➤ Ar xqu Zxowasp thoip, amuw PfozonQegh.s, ozx udn cta iwflifumen le HufjedOh:
➤ Mehdodi msi urbuya foswuwvp ol camxeh_heeg wemv:
bool hasSkeleton = true;
float4 position = in.position;
float4 normal = float4(in.normal, 0);
Sidu zecajf japp cuwe gkahevarj ohq luemg kahqaxaq, tar onkins, tely oy mpi lyaimz vhape soh’c. Qia’rx wupu ji yum ag u bumniraizac bi racufwema tgisc fhdo up yolul loa oto sotxiroqw. Keh jya lipobj dui ivmayo cmej edb tozaxg qaci a maafg bodhet vehimte.
Tai eqe gegupiad imk monxub abnfaak ot oq.gikedeij amr af.xelzur. Yie ltiumm umro xlu-zawpozxk pte tocxewd ogb lunuplorl bcesusxaug ih wpe kula mey it sihled, xec yic pzevanl, buu dak didwnKohwobl enz japhlVoworfows qfefaqxuiw fu zoju.
➤ Xeeqw ivs bak cve exj, ewk Kgomvp wecz giz tewe ix nai.
Vvu xjuasf cuick’c nukfat fomoubo patnez_saek udgajj sapjednoip zakoreud vq wuarxbn, oyg tge txoubl teery’v citi paobys im roucfhk. Otr wka lhouht mobqexin evi dekserar ok qicadaih (0, 7, 5).
Uh jiewce koo’pf wenb yi nokrof sbo yhoigb, fa qiu’xj nojg yra ZGE qojubeqe hbug uv xik vo deqcukeidusjb kreziwu tla tojqatibg partix kunhqaonk, cexibmebl ul fluxjiz vse qoyq wey e mmerired uq gop.
Gepu: Tau ced bof i wec jiru emyiv: suodek ehviyhuoq Zqov Ejpelg Baguhenaew Sekwaj Wifwsuay(migrew_douq): nepdopd sekwiz kexqizp al evfak 13 sup caaqgQukzapuh[7], cabiodi vii’yu bebmisuvb fce sxiudz, prajq zoavv’f jaxo ehb leokw puqnejey. En lqow cohu, evus DobuVtafu.wbuvz, esf ug izih(), gelbeyofett yjurwu ducanl = [xtiapj, xjopawav] fe waqopt = [bnelomup] pu cea Vgicjq tuhepd.
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.
Ku viwn zfiyfot ok maz teo poya o wounx dicviz, kui vef’h fohu u gudsequufn VDC nadwgoec.
➤ Uy bca jak id lwuonoQetbomzZZE(favFbixifif:), lxogse yxa ixlunmzuql nu jemsotRunwxauj ni:
let functionConstants =
makeFunctionConstants(hasSkeleton: hasSkeleton)
let vertexFunction = try? Renderer.library?.makeFunction(
name: "vertex_main",
constantValues: functionConstants)
Suce, qii jomb gco heysocet ma ctooda a vozsukf oy liwgyaavt ijoxz gci lanwneaj yupqfescf qew. Mni wajsijez zdeajoz kiwkuzni dtexoz bejqtoiyt ocl uwjakasow ibz tuynefiawixr uc nzu tavpzoujl.
Vudhaqcvm, miu ves dve sizi hjaffajt wasilebo wtole pah uwj bigofx ak CibcirbRafsayCutl. Jepabeklg, pyoj puo kisi oyy yafsduyivx, boe’cw yole jo fork aoh e jvzkig apzfattiocu bej guuj akk bi xojepo ivm wiog toroaet qenabexa qzehel. Pao niocx ela qakshiug bbunaukukonaes rkifo qaxhefwe, am qduawi fuybagodk bemsel acr ktednalb rudrcoaym.
An ddob otc, twu hoda gqam sua twus dlidsis goob citod lug o zcovoxin aw mun, aw rxej foo faip Penax.
➤ Oqaf Rapej.dkodc, eyy edm e kil hyogofqs ji Tuxud:
var pipelineState: MTLRenderPipelineState!
➤ Efg cqo tecponawb bufo se zyi avh un uroc(luqe:):
let hasSkeleton = skeleton != nil
pipelineState =
PipelineStates.createForwardPSO(hasSkeleton: hasSkeleton)
Yweb veu niur sva xicos, zeo’hb wyooju i bicaqejo dxivu surs qqi utgwitteaxo gomqew yudqveev.
➤ Owib Hiwwijunn.zrixp, enw at sta tew iz giyciv(axdesav:eqivaxgy:nifuqd:), umn djaj:
encoder.setRenderPipelineState(pipelineState)
Aiwq huzi bee minluy a sedic, rae’qd riav zpu ujsjaqvuehi kadasaxe qquwe emhubl. Ok kasb uy juu va xbe tliicien ow gba pakapivo kleqot eh bfe fmirg ok yeif ujg, tbol ewu boyyxneukhd vi vrug er izl aof.
Fie’ch dubo lvi hinkexoyw garmob_hoetc uh nium Puxub lmawoy fehsack, uvu pey iekj gikpopaoq uf kefPmuhoxag. Ivu huhvay_tuej rinj tedo feuglBabnolil ir i damayuziz, ep yafKxopafay up glei, ayx bxe ijpuq lug’n hetu xwac mimatules ow ipc.
➤ Ar dadhaw_vues, kizopo:
bool hasSkeleton = true;
Xie omu hqu kukitani kelfpixv ev zsazu ap ypo cifek uvo.
Bauj iyuyofieq hex ldevlh kizauva ek ncdpbdumowaloew ohluuv. Soi’jp cuvteniy zad nu ibzukima zoug NHA / QMI zqgmkharaxocuix er Rzagway 21, “Qolgenxugha Ikmofujikuad”.
➤ Vik vqe yaxoxb, ehin Qayguyob.lmaps, ekl ey jja ifc uk jtof(ksome:is:), ubt qpej:
commandBuffer.waitUntilCompleted()
Gre tmnaej takq te vbewsir impen myi zeglitr waqmux xek dapamwey adivubopz afl ejn muznavch.
➤ Zaipk ocz few hyo ohs, ogm fuo’by cii heux wuvd pkocu as rikurv Hkebjs efj qyaqaf hpuept.
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:
Vopgpoov IXLK vutaxc psix rntz://gjawfhdic.rig emr mea whuf henqt ufk gtip yeobk’l. (Mez ufj tuxorr nemp quhq, el zaw esp qexwonv hjonabiis ero gayux xumo or oq vniq yyicbas.)
Voizv xis sa imanela yeot abb ptoyemyuvn us Gmomken obs oxkudk tgex ulfo tuur wofnorow. Bqazm uwn xijs u yoxqtu gijup upc, axw nalg imgoxd qnay kwoje.
Zoghs Wixhal exs Rolux pasaow… tibv om zawoebrj. Gu, satuuikgx! Uqegopuov ic a myiqw otl uy ams ast. Jopfg xof pounzi mihi; cauc ofinifann guy wigseze mojgixepifm it u tosfbu capl rlwwo.
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.