探索Unity動(dòng)畫切片(AnimationClip)在存取捐凭,播放和平滑跳轉(zhuǎn)方面的實(shí)現(xiàn)策略

前言:

本文寫作的目的主要是為了解惑自己平時(shí)積攢下的關(guān)于Unity Animation動(dòng)畫播放方面的一些問題拨扶,我把它們簡單羅列如下:

  • Unity的Animator系統(tǒng)是如何存取動(dòng)畫切片(Animation Clip)的?
  • 動(dòng)畫切片數(shù)據(jù)結(jié)構(gòu)長什么樣子茁肠?
  • 動(dòng)畫A到動(dòng)畫B的轉(zhuǎn)移(transition)是如何做到平滑過渡(blend)的患民?
  • 我們?yōu)楹文茉趧?dòng)畫播放的任何一幀發(fā)起平滑的動(dòng)畫過渡?
  • Animator的CrossFade接口為何能在沒有轉(zhuǎn)移的情況下正確的過渡到目標(biāo)動(dòng)畫垦梆?它在運(yùn)行時(shí)開銷大么匹颤?

如果大家也有同樣的疑問仅孩,不妨看看后面的文章,興許能有所收獲印蓖,另外鑒于Unity底層資料有限辽慕,拼湊不易,如果有誤煩請(qǐng)指正 :)

正文

在討論Unity的root motion時(shí)(另一個(gè)分支是blend shape)我們常常會(huì)提到Animator組件及其對(duì)應(yīng)的Animator Controller資產(chǎn)赦肃,知道要往Animator Graph中添加Layer溅蛉,State MachineAnimation State,也知道如果希望2個(gè)動(dòng)畫切片(Animation Clip)能夠順利過渡他宛,前后銜接船侧,我們還需要在它們之間建立起轉(zhuǎn)移(Transition),并設(shè)置合適的轉(zhuǎn)移起始時(shí)機(jī)+終止時(shí)機(jī)堕汞。動(dòng)畫狀態(tài)之間的跳轉(zhuǎn)關(guān)系一旦正確建立勺爱,運(yùn)行時(shí)我們就可以通過向Animator控件發(fā)送SetTriggerSetFloat等指令實(shí)時(shí)修改Parameters參數(shù)讯检,進(jìn)而激活定義在Transition中的跳轉(zhuǎn)條件(Condition),觸發(fā)向目標(biāo)動(dòng)畫的過渡卫旱。不難觀察到人灼,Unity在播放這些Animation Clip時(shí)表現(xiàn)得非常絲滑,柔順顾翼,即便將Unity的內(nèi)置時(shí)間調(diào)慢數(shù)倍投放,用數(shù)倍長的時(shí)間去播放一段30幀長的動(dòng)畫切片,我們也很難見到因采樣不足而導(dǎo)致的動(dòng)畫失真和跳變适贸。另一方面灸芳,對(duì)于任意動(dòng)畫狀態(tài)轉(zhuǎn)移,融合動(dòng)畫也總是表現(xiàn)得同樣順滑拜姿,仿佛預(yù)先烘焙好似是烙样,可若真?zhèn)€是烘焙的融合動(dòng)畫,似乎又沒有見著Unity創(chuàng)建過任何新的融合動(dòng)畫Clip蕊肥,同時(shí)也無法解釋為何Unity支持從當(dāng)前動(dòng)畫播放的任何時(shí)刻發(fā)起向目標(biāo)動(dòng)畫的融合轉(zhuǎn)移谒获,難道Unity將當(dāng)前動(dòng)畫的每一幀到目標(biāo)動(dòng)畫都做了烘焙?

Clip所包含的數(shù)據(jù)信息可從Unity的Animation面板中窺見個(gè)大概壁却。如下圖批狱,左側(cè)是由骨骼節(jié)點(diǎn)構(gòu)成的樹狀層級(jí)結(jié)構(gòu),依紅色箭頭所示展东,每個(gè)骨骼分別由Position赔硫,Rotation和Scale三個(gè)部分組成,正好對(duì)應(yīng)了Transform面板上的TRS三劍客盐肃。右側(cè)的橫坐標(biāo)對(duì)應(yīng)了Clip在時(shí)間維度上的展開爪膊,每一個(gè)刻度代表了一幀权悟,縱軸則直接反應(yīng)了節(jié)點(diǎn)的取值。截圖中展示的是名為Skinbone005的骨骼節(jié)點(diǎn)的旋轉(zhuǎn)(Rotation)三個(gè)分量(三種顏色)隨動(dòng)畫推進(jìn)不斷變換而形成的曲線惊完。對(duì)于還不了解root motion同學(xué)我先做個(gè)快速回顧僵芹,簡單來說根運(yùn)動(dòng)的本質(zhì)是基于彼此關(guān)聯(lián)的樹狀骨骼節(jié)點(diǎn)變換矩陣,將root節(jié)點(diǎn)視為模型空間的不變量小槐,將每一幀骨骼自身相對(duì)其父節(jié)點(diǎn)的局部變換矩陣烘焙到數(shù)據(jù)結(jié)構(gòu)中拇派,然后在運(yùn)行時(shí)采樣并解碼出當(dāng)前幀全部骨骼節(jié)點(diǎn)的TRS仿射變換矩陣,進(jìn)而從根節(jié)點(diǎn)開始逐步向葉子節(jié)點(diǎn)遍歷 + 變換凿跳,這樣就能計(jì)算出骨架(Skeleton)中每一塊骨頭(bone)的正確姿勢(shì)(bonePose)了件豌,這就是root motion。額外補(bǔ)充一點(diǎn)控嗜,我們有了模型架子的正確位置還不夠茧彤,還需要將皮膚還原到當(dāng)初之于骨骼的相對(duì)位置才行,對(duì)于一個(gè)SkinnedMesh頂點(diǎn)來說疆栏,便是通過骨骼綁定(binding)映射表找到所有綁定的骨骼(從1到4個(gè)不等)曾掂,通過左乘(bonePose_i * bindPose_i)矩陣將頂點(diǎn)變換到每個(gè)骨骼原有的相對(duì)位置上,最后加權(quán)求解出這個(gè)SkinnedMesh頂點(diǎn)的最終模型空間位置壁顶。在整個(gè)動(dòng)畫模型頂點(diǎn)的計(jì)算流程中珠洗,bindpose是完全靜態(tài)的常量,它不影響骨骼的運(yùn)動(dòng)若专,只影響骨骼和皮膚之間的相對(duì)位置许蓖,因此真正體現(xiàn)骨骼動(dòng)畫的部分就是下圖的這些骨骼層級(jí)結(jié)構(gòu)和每個(gè)骨骼所記錄的不同幀下的數(shù)值,它們是動(dòng)畫的本體调衰,構(gòu)成了Animation Clip數(shù)據(jù)主體膊爪。

回到我們今天的主題之一,Unity是如何記錄Animation Clip的呢嚎莉?特別是那些記錄了每幀每塊骨骼變換矩陣的信息米酬,顯然會(huì)隨著動(dòng)畫時(shí)長(Duration)的增長和幀率(Frame rate)的增加而消耗更多的存儲(chǔ)空間:以一段1分鐘時(shí)長,30FPS萝喘,帶有100個(gè)骨骼的動(dòng)畫Clip為例淮逻,總共需要:60 * 30 * 100 * (3 + 3+ 1)* 4byte = 4.9MB ! 考慮到這只是一段動(dòng)畫剪影,實(shí)際上我們?cè)趹?yīng)用中可能會(huì)出現(xiàn)多至數(shù)十上百段Clip同時(shí)存在的情況阁簸,如果不對(duì)動(dòng)畫數(shù)據(jù)進(jìn)行處理,那么單單存放動(dòng)畫剪影一項(xiàng)就將耗去數(shù)百兆寶貴的內(nèi)存資源启妹。為了解決這對(duì)矛盾,于是就引出了動(dòng)畫壓縮(animation compression)的概念饶米。在Unity中動(dòng)畫壓縮是一個(gè)優(yōu)化選項(xiàng)(optimization term)桨啃,你可以在fbx文件 inspector -> Animation tab中找到它們:

Unity提供的選項(xiàng)也不多,主要有兩類鹃愤,抽幀或者Optimal,最新版中追加了抽幀和壓縮,但并不壓縮運(yùn)行時(shí)內(nèi)存。首先來聊聊抽幀(Keyframe Reduction),顧名思義,抽幀作用于整個(gè)動(dòng)畫Clip而不是單獨(dú)的骨骼節(jié)點(diǎn)啊楚,它會(huì)通過內(nèi)置算法識(shí)別并剔除一些相對(duì)來說比較冗余的全局關(guān)鍵幀(Key Frame)浑彰,從而在總量上消減需要存儲(chǔ)的數(shù)據(jù)恭理。為了方便控制和比較,Unity還為其抽幀算法提供了一套誤差估算接口郭变,名叫Animation Compression Error颜价,會(huì)對(duì)Rotation,Position和Scale分別單獨(dú)評(píng)分饵较,單位是百分比拍嵌。注意天下沒有免費(fèi)的午餐,在壓縮比和錯(cuò)誤率之間是有一套此消彼長的規(guī)則運(yùn)行于背后的循诉,我們參考以下一段Unity官方動(dòng)畫片段*在不同抽幀錯(cuò)誤率下的壓縮比及其表現(xiàn):

原始動(dòng)畫横辆,關(guān)閉了所有壓縮方案,動(dòng)畫資源在運(yùn)行時(shí)占用 249KB茄猫, Error=0
接下來開啟抽幀狈蚤,并且將各項(xiàng)錯(cuò)誤率Error均調(diào)整為0.5,此時(shí)壓縮后占用降為119KB
進(jìn)一步提升錯(cuò)誤率到10(相較0.5提高了20倍;Α)脆侮,此時(shí)的占用率從119KB進(jìn)一步下降到69KB,動(dòng)畫表現(xiàn)如圖
最后試試全損畫質(zhì)表現(xiàn)如何勇劣,將錯(cuò)誤率上調(diào)到100(既Unity支持的最大容錯(cuò)率)靖避,可見此時(shí)動(dòng)畫資源占用率下降到了14KB,但是動(dòng)畫表現(xiàn)已經(jīng)和原版相去甚遠(yuǎn)了

作為最簡單直白的壓縮方式比默,它帶來的好處和壞處也是簡潔明了的幻捏,先說好處:抽幀本身不會(huì)對(duì)數(shù)據(jù)做額外的編解碼,因此若以數(shù)據(jù)的原始形態(tài)存儲(chǔ)命咐,那么在運(yùn)行時(shí)通過采樣獲取數(shù)據(jù)的效率是最高的篡九,同時(shí)對(duì)于變化簡單(少高頻)的動(dòng)畫來說,抽幀可以在維持良好錯(cuò)誤率的前提下輕易達(dá)到較為理想的壓縮比醋奠。余下則是缺點(diǎn):抽幀能提供的“可接受”壓縮率會(huì)受到動(dòng)畫復(fù)雜度的影響榛臼,動(dòng)畫越復(fù)雜,可接受的容錯(cuò)率就越低窜司,以上面的翻滾動(dòng)作為例沛善,它的“甜點(diǎn)”錯(cuò)誤率大約是1%左右,因?yàn)榇藭r(shí)高頻細(xì)節(jié)丟失不多例证,但是相對(duì)原始動(dòng)畫體積來說卻提供了超過50%的壓縮率路呜,可謂相當(dāng)性價(jià)比;但是如果我們對(duì)這段動(dòng)畫的期望體積是50KB以下,那么強(qiáng)行抽幀壓縮必然會(huì)導(dǎo)致不可接受的動(dòng)作細(xì)節(jié)損失胀葱,很多時(shí)候如果我們無法做到“既要又要”漠秋,那么抽幀方案可能就廢了。

Optimal是Unity提供給我們的另一種選擇抵屿,官方文檔中對(duì)它的解釋很有趣:“讓Unity決定如何壓縮”庆锦。這看起來像是個(gè)黑盒,模模糊糊轧葛,但是只要細(xì)心一些我們不難從Animation Tab和Clip文件Inspector中窺知一二:

回看前文提及的動(dòng)畫切片曲線(curves)搂抒。如下圖,首先觀察位于右上角紅圈中的黃色采樣點(diǎn)尿扯,很明顯這個(gè)點(diǎn)并沒有經(jīng)過一旁曲線的峰頂求晶,而且整條曲線除去首尾怎么看都感覺是高階連續(xù)的(至少?zèng)]有一階和二階間斷點(diǎn)),這其實(shí)暗示了我們Unity若想從這些個(gè)采樣點(diǎn)中重建樣條線(spline)衷笋,除了存儲(chǔ)采樣點(diǎn)的值(value)以外芳杏,至少還需存有當(dāng)前點(diǎn)導(dǎo)數(shù)信息,否則無法達(dá)成圖中紅圈里的效果辟宗。然后觀察下圖中藍(lán)色箭頭所指的兩端采樣點(diǎn)間隔爵赵,同樣可以明顯觀察到動(dòng)畫曲線已經(jīng)被有選擇的抽幀處理過了,也就是在被Optimal壓縮后的動(dòng)畫切片中泊脐,部分冗余的高頻采樣點(diǎn)基于某種閾值(用戶設(shè)定)或自適應(yīng)方式被Unity裁剪掉了空幻。可見Optimal是一種混合型的壓縮方式容客,至少結(jié)合了抽幀和編碼處理秕铛。

當(dāng)我們點(diǎn)選一段導(dǎo)入U(xiǎn)nity的動(dòng)畫切片后,可以在Inspector窗口看到以下信息:

以這段名為Atk01的動(dòng)畫為例缩挑,它以30FPS的幀率共持續(xù)1.867秒時(shí)長如捅,全程需要處理1398條動(dòng)畫曲線(Curves Total)后面緊接著出現(xiàn)了3個(gè)比較陌生的單詞:ConstantDenseStream调煎。經(jīng)過查閱資料對(duì)比文檔后,我們可以大膽得作如下理解:

1)Constant

  • 節(jié)點(diǎn)存放的是未經(jīng)編碼的原始數(shù)據(jù)(value)
  • 采樣取值時(shí)“不會(huì)”在相鄰的2個(gè)Constant節(jié)點(diǎn)之間做插值運(yùn)算
  • 采樣取值多少取決于離采樣點(diǎn)最近的左側(cè)節(jié)點(diǎn)中所記錄的數(shù)值

圖示:

2) Dense

  • 節(jié)點(diǎn)內(nèi)存放的是未經(jīng)編碼的原始數(shù)據(jù)(value)
  • 采樣是“會(huì)”在相鄰的2個(gè)Dense節(jié)點(diǎn)之間做線性插值運(yùn)算

圖示:

3) Stream

  • 節(jié)點(diǎn)內(nèi)存放的是編碼后的數(shù)據(jù)己肮,不是原始值士袄。
  • 具體來說會(huì)將節(jié)點(diǎn)附近的連續(xù)樣條線編碼為4個(gè)Hermite coefficients*
  • 解碼時(shí)使用當(dāng)前采樣點(diǎn)的橫坐標(biāo)t的前4階冪指數(shù)與4個(gè)編碼系數(shù)做點(diǎn)乘即可
float t = sampleTime - cache.time;
return (t * (t * (t * cache.coeff[0] + cache.coeff[1]) + cache.coeff[2])) + cache.coeff[3]; 

圖示:

沒錯(cuò),Unity將同一份Animation Clip中具有不同特性的數(shù)據(jù)拆分成了不同的格式類型谎僻,分門別類得處理一番后又存放回了一處娄柳。還是以Atk01動(dòng)畫切片為例,它的1300+動(dòng)畫曲線被歸類成了620條Constant類型艘绍,0Dense類型以及778Stream赤拒。這里面存在700多條Steam類型很好理解,因?yàn)橛脜?shù)化的樣條線擬合真實(shí)動(dòng)畫曲線這個(gè)想法很Cool!它一舉解決了2個(gè)非常關(guān)鍵的痛點(diǎn)挎挖,其一是在編碼Rotation這類旋轉(zhuǎn)運(yùn)動(dòng)時(shí)这敬,使用傳統(tǒng)的float3數(shù)據(jù)結(jié)構(gòu)直接存放當(dāng)前采樣點(diǎn)數(shù)值的方法顯得過于奢侈 -> 對(duì)于動(dòng)畫來說唬滑,一個(gè)骨骼的旋轉(zhuǎn)相性并不要求那么高的精度阻荒,有時(shí)候甚至half3都嫌多。其二則是它能帶來非常順滑無跳變的插值體驗(yàn)糊秆,從而為插幀效果提供了極佳的數(shù)據(jù)基礎(chǔ)始衅。 那么為何會(huì)有600多條Constant類型呢冷蚂?理由請(qǐng)查看如下截圖:

在許多動(dòng)畫切片中難免會(huì)存在如上圖中這類自始至終都不曾有一點(diǎn)變化的常量(Constant)類型數(shù)值,對(duì)付他們的最好辦法就是只記錄這么一個(gè)常量汛闸,然后也無需編解碼處理蝙茶,隨用隨取即可。 這也是Unity設(shè)計(jì)和保留Constant類型的初衷吧诸老。
最后為何會(huì)很少出現(xiàn)Dense類型隆夯?按照Unity官方的說明,Dense類型適合編碼動(dòng)畫切片曲線看起來非常像噪聲(Noise)的數(shù)據(jù)孕锄,也就是說使用Stream編碼這些動(dòng)畫只是徒增編解碼開銷吮廉,因?yàn)榇朔ㄩT附帶的平滑過渡感并不重要,然而退回使用Constant編碼動(dòng)畫又會(huì)帶來過強(qiáng)的跳變感畸肆,只有在這種苛刻又奇怪的條件下Unity才會(huì)優(yōu)先考慮使用Dense類型宦芦。到底什么樣的需求會(huì)轉(zhuǎn)化成這樣的動(dòng)畫呢?很難想象轴脐!所以我們?cè)趯?shí)際工作生活中也很難遇到被編碼到Dense類型的Animation Curves调卑。

為了能夠深入理解Unity Animation Clip組織存放動(dòng)畫數(shù)據(jù)的邏輯,我們不妨將目光聚焦到Stream data上大咱,其他兩種類型的數(shù)據(jù)與之相似恬涧,就不做過多解讀了。我們知道數(shù)據(jù)結(jié)構(gòu)的組織形式能夠很大程度上影響CPU的計(jì)算效率碴巾,特別是在當(dāng)前處理幀動(dòng)畫數(shù)據(jù)的語境下溯捆,Unity面對(duì)的是一個(gè)既重IO又重邏輯計(jì)算環(huán)境。雖然Unity很早前就有了所謂的GPU加速動(dòng)畫計(jì)算工作流厦瓢,但那主要處理的是從骨骼到蒙皮頂點(diǎn)的最后一步運(yùn)算提揍,簡單說就是利用了GPU強(qiáng)大的并行運(yùn)算特性(計(jì)算不能太復(fù)雜),把大量原始模型狀態(tài)下的蒙皮頂點(diǎn)與對(duì)應(yīng)的變換矩陣乘法運(yùn)算安排到了GPU流處理器中執(zhí)行煮仇,反而涉及骨骼動(dòng)畫解算(rebuild bonePose)劳跃,以及動(dòng)畫之間的融合(blending)等大量業(yè)務(wù)需求任然在CPU端實(shí)時(shí)處理。繼續(xù)回到我們的Stream data上來浙垫,基于上述前提刨仑,我們不難聯(lián)想到Unity的Animator會(huì)在CPU端計(jì)算中大量應(yīng)用(借用郑诺?)DOTs的技術(shù),比如為每個(gè)AnimaitonPlayable實(shí)例創(chuàng)一個(gè)AnimationJob任務(wù)杉武,將繁重的IO訪問和矩陣運(yùn)算放在多線程中并行處理辙诞。又比如為了提高采樣Stream data效率,盡可能減少Cache-Line Miss艺智,Unity將所有編碼后的節(jié)點(diǎn)數(shù)據(jù)(CurveKey)按照動(dòng)畫播放的時(shí)間先后緊密存放在內(nèi)存中倘要,參考下圖:

其中CurveTimeData相當(dāng)于數(shù)據(jù)包的頭文件(Head),指明了后面出現(xiàn)的幀節(jié)點(diǎn)數(shù)據(jù)對(duì)應(yīng)了動(dòng)畫幀中的哪個(gè)時(shí)間點(diǎn)(time)十拣,以及有多少個(gè)幀節(jié)點(diǎn)屬于當(dāng)前時(shí)刻的(count)封拧。數(shù)據(jù)包的主體是在內(nèi)存上連續(xù)且對(duì)齊的一系列CurveKey結(jié)構(gòu),其中coeff[4]數(shù)組作為待解碼的payload而存在夭问,另一個(gè)curveIndex則表明了當(dāng)前幀節(jié)點(diǎn)數(shù)據(jù)屬于哪一個(gè)骨骼的哪一個(gè)變換分量泽西。很明顯,這樣安排數(shù)據(jù)的好處是缰趋,Unity能夠通過極少的內(nèi)存讀取指令捧杉,一口氣將當(dāng)前幀所需的全部原始變換信息加載到cache中。 同時(shí)由于在邏輯上具有較高訪問概率的前后關(guān)鍵幀數(shù)據(jù)也都緊挨著當(dāng)前幀存放秘血,基于CPU自身cache調(diào)度算法的加持味抖,實(shí)際運(yùn)算過程中還可能極大緩解Cache Miss的問題。

不妨舉個(gè)例子來串一串我們到現(xiàn)在所述的數(shù)據(jù)結(jié)構(gòu)是如何應(yīng)用的:假設(shè)Unity想要獲取當(dāng)前動(dòng)畫幀全部骨骼節(jié)點(diǎn)的正確位置(對(duì)應(yīng)角色的某個(gè)炫酷造型)灰粮,那么自然需要先通過采樣上述內(nèi)存區(qū)段以找到骨骼變換矩陣的編碼信息仔涩。于是Unity調(diào)用EvaluateClip方法,傳入一個(gè)指向當(dāng)前幀內(nèi)存區(qū)段的游標(biāo)(動(dòng)畫未播放完畢游標(biāo)不會(huì)復(fù)位)粘舟,游標(biāo)指向的實(shí)際位置就是數(shù)據(jù)結(jié)構(gòu)CurveTimeData實(shí)例存放的地址熔脂,取出返回后,CPU會(huì)檢查確認(rèn)是否為想要的數(shù)據(jù)包頭柑肴,隨后Unity以游標(biāo)位置(address) + sizeof(CurveTimeData)作為真實(shí)起始地址偏移霞揉,一口氣讀取CurveTimeData.count * sizeof(CurveKey)個(gè)字節(jié)長度的數(shù)據(jù)到緩存中去,由于數(shù)據(jù)對(duì)齊的緣故晰骑,Unity可以直接將這段存放在緩存中的二進(jìn)制數(shù)據(jù)識(shí)別為CurveKey數(shù)組ck[]适秩。Unity從ck[0]中取出第一個(gè)編碼結(jié)構(gòu),通過映射表知道它的curveIndex對(duì)應(yīng)了骨骼節(jié)點(diǎn)bone_001的position.x動(dòng)畫曲線硕舆,于是通過SampleClip函數(shù)和當(dāng)前播放時(shí)間time隶症,將編碼信息反解出來,記為X岗宣,并按順序保存到另一個(gè)數(shù)據(jù)結(jié)構(gòu)ValueArray中。Unity步進(jìn)指向ck[]的指針淋样,從ck[1]中取出下一個(gè)CurveKey耗式,依據(jù)映射表知道curveIndex對(duì)應(yīng)了bone_001骨骼節(jié)點(diǎn)的position.y動(dòng)畫曲線,接下來如同處理坐標(biāo)X一樣解碼出坐標(biāo)Y,并保存到ValueArray中刊咳。等到position.xyz彪见,rotation.xyz以及scale.x全部解碼完畢,骨骼節(jié)點(diǎn)bone_001的解碼數(shù)據(jù)也就按照同樣的順序存入ValueArray中了娱挨,不過別急余指,這只是一塊骨頭的數(shù)據(jù),假設(shè)模型有100塊這樣的骨頭跷坝,那么接下來緩存中余下的內(nèi)容大概會(huì)對(duì)應(yīng)bone_002并以此類推酵镜,直到Unity處理完全部ck[]數(shù)組中的CurveKey數(shù)據(jù),解碼原始信息的工作就告一段落了柴钻,此時(shí)解碼后數(shù)據(jù)被縝密壓縮在ValueArray數(shù)組中淮韭,以供后續(xù)職能模塊提取和轉(zhuǎn)化成對(duì)應(yīng)的Transform,Rotation以及Scaler數(shù)據(jù)結(jié)構(gòu)贴届。

至此靠粪,我們總算明白了開篇第一個(gè)問題,既Animation Clip數(shù)據(jù)到底長什么樣毫蚓,而Unity又是如何存取它們的了占键。后面的問題似乎還很多,但是一旦我們搞明白了最底層的處理邏輯元潘,那些流于表面的問題自然可以很快得到闡明畔乙!簡便起見我把余下的問題和解釋歸納如下:

Q:動(dòng)畫A到動(dòng)畫B的轉(zhuǎn)移(transition)是如何做到平滑過渡(blend)的?
A: 依靠記錄在Transition數(shù)據(jù)中的start和end time柬批,以及目標(biāo)動(dòng)畫的起始偏移offsetTime來觸發(fā)動(dòng)畫融合邏輯啸澡,這套邏輯被Unity的內(nèi)部類AnimationMixerPlayable管理,基本邏輯很簡單氮帐,在觸發(fā)transition的時(shí)刻開始嗅虏,到end time位置,每一幀都正常獲取當(dāng)前動(dòng)畫幀的ValueArray_1上沐,同時(shí)提前獲取下一個(gè)動(dòng)畫切片位于offsetTime處的關(guān)鍵幀數(shù)據(jù)ValueArray_2皮服,依據(jù)跳轉(zhuǎn)流逝的時(shí)間和總跳轉(zhuǎn)時(shí)間計(jì)算出權(quán)重w,最后通過w插值ValueArray_1ValueArray_2以獲得最終的ValueArray参咙。

Q:我們?yōu)楹文茉趧?dòng)畫播放的任何一幀發(fā)起平滑的動(dòng)畫過渡龄广?
A: 因?yàn)閁nity的動(dòng)畫跳轉(zhuǎn)實(shí)例“Transition”中只存放了描述這次跳轉(zhuǎn)的少量元數(shù)據(jù),并沒有任何形式的預(yù)計(jì)算Animation Clip生成蕴侧,一切運(yùn)行時(shí)跳轉(zhuǎn)都是運(yùn)行時(shí)經(jīng)過采樣和插值獲得的择同。

Q: Animator的CrossFade接口為何能在沒有轉(zhuǎn)移的情況下正確的過渡到目標(biāo)動(dòng)畫?
A: CrossFade接口能夠臨時(shí)創(chuàng)建一條只能使用一次的Transition數(shù)據(jù)净宵,相比于依賴動(dòng)畫狀態(tài)連線的跳轉(zhuǎn)敲才,使用CrossFade的主要區(qū)別就在于Transition數(shù)據(jù)的臨時(shí)性裹纳,至于跳轉(zhuǎn)執(zhí)行期間的消耗則同普通跳轉(zhuǎn)沒有區(qū)別。

Q: 它在運(yùn)行時(shí)開銷大么紧武?
A: CorssFade對(duì)已有的AnimtionClip消耗不大剃氧,但是如果目標(biāo)Clip是動(dòng)態(tài)添加的,那么在首次播放時(shí)會(huì)多出數(shù)據(jù)的遷移和拷貝消耗阻星,但這已經(jīng)和跳轉(zhuǎn)本身關(guān)系不大了朋鞍。

Ref

Animation-compression-unity
Cubic Hermite spline
Animation Compression: Unity 5
Manual AnimationOverview
Manual AnimationClips

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市妥箕,隨后出現(xiàn)的幾起案子滥酥,更是在濱河造成了極大的恐慌,老刑警劉巖矾踱,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恨狈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡呛讲,警方通過查閱死者的電腦和手機(jī)禾怠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贝搁,“玉大人吗氏,你說我怎么就攤上這事±啄妫” “怎么了弦讽?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長膀哲。 經(jīng)常有香客問我往产,道長,這世上最難降的妖魔是什么某宪? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任仿村,我火速辦了婚禮,結(jié)果婚禮上兴喂,老公的妹妹穿的比我還像新娘蔼囊。我一直安慰自己,他們只是感情好衣迷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布畏鼓。 她就那樣靜靜地躺著,像睡著了一般壶谒。 火紅的嫁衣襯著肌膚如雪云矫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天汗菜,我揣著相機(jī)與錄音让禀,去河邊找鬼贵少。 笑死,一個(gè)胖子當(dāng)著我的面吹牛堆缘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播普碎,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吼肥,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了麻车?” 一聲冷哼從身側(cè)響起缀皱,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎动猬,沒想到半個(gè)月后啤斗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赁咙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年钮莲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彼水。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡崔拥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凤覆,到底是詐尸還是另有隱情链瓦,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布盯桦,位于F島的核電站慈俯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拥峦。R本人自食惡果不足惜贴膘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望事镣。 院中可真熱鬧步鉴,春花似錦、人聲如沸璃哟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽随闪。三九已至阳似,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铐伴,已是汗流浹背撮奏。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工俏讹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人畜吊。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓泽疆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親玲献。 傳聞我的和親對(duì)象是個(gè)殘疾皇子殉疼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容