《遊戲引擎架構》筆記十一

動畫系統數組

角色動畫的類型緩存

  • 賽璐璐動畫:手繪動畫
  • 剛性層階式動畫:角色由一堆剛性部分建模,這些剛性部分以層階式彼此約束。最大的問題:角色的身體在關節的位置產生礙眼的「裂縫」。
  • 每頂點動畫技術:存儲隨時間改變的頂點位置和法線。它是數據密集的技術,由於每一個頂點隨時間改變的動做信息都須要存儲下來。所以實時遊戲中不多使用。
  • 變形目標動畫: 移動網格頂點,僅製做相對少許的固定極端姿式,而後運行時混合姿式,線性差值(LERP),經常使用於面部動畫。
  • 蒙皮動畫:含有前兩個技術的優勢,容許組成網格的三角形變形,同時具備剛性層階式動畫的高效性和內存使用量。

把動畫方法視爲數據壓縮技術,對權衡各類動畫技術有所幫助。通常來講,咱們選擇動畫技術的目標是能提供最佳壓縮又不會產生不能接受的視覺瑕疵。網絡

骨骼數據結構

遊戲引擎不在乎骨頭,只在意關節。架構

骨骼層階結構app

骨骼的關節造成層階結構,即樹形結構。選擇一個關節爲根,其餘爲它的子孫。每一個關節僅有一個父關節,所以,在每一個關節中存儲父關節的索引,根關節的父關節索引爲-1。函數

內存中表示骨骼工具

骨骼有細小的頂層數據結構表示,它含有關節數組。關節的存儲次序保證每一個子關節在父關節以後,則根關節位於數組首位。oop

每一個關節的數據結構包含下面信息:post

  • 關節名字:字符串或32位字符串散列標識符。
  • 骨骼中的父節點索引。
  • 關節的綁定姿式的逆變換。關節的綁定姿式指蒙皮網格頂點綁定至骨骼時,關節的位置、定向及縮放。

骨骼的數據結構:

struct Joint
{
    Matrix4x3   m_invBindPose;//綁定姿式之逆變換
    const char* m_name;       //關節名字
    U8          m_iParent;    //父索引,或0xFF表明根關節
};

struct Skeleton
{
    U32       m_jointCount;//關節數目
    Joint*    m_aJoint;    //關節數組
};

姿式

綁定姿式

這是三維網格綁定至骨骼以前的姿式,即把網格當作正常、未蒙皮、徹底不涉及骨骼的三角形網格渲染的姿式。又稱爲參考姿式、放鬆姿式、T姿式。

局部姿式

關節姿式最多見的是相對於父關節來指定。相對父關節的姿式能令關節天然的移動。有時用局部姿式描述相對父的姿式。局部姿式幾乎都是存爲SQT格式。

數學上,關節姿式就是一個仿射變換。

  第j個關節姿式Pj可由平移矩陣Tj、放縮矩陣Sj、旋轉矩陣Rj構成;整個骨骼姿式Pskel寫成全部姿式Pj的集合。

關節縮放

有的引擎不容許縮放,Sj就是單位矩陣;有的引擎只容許統一縮放,Sj就是一個標量。統一縮放有利於平截頭體剔除和碰撞檢測。

struct JointPose
{
    Quaternion  m_rot;    //Q
    Vector3     m_trans;  //T
    F32         m_scale;  //S(僅爲統一縮放)
};

struct JointPose
{
    Quaternion  m_rot;    //Q
    Vector3     m_trans;  //T
    Vector3     m_scale;  //S
    U8          m_padding[8];
};

struct SkeletonPose
{
    Skeleton*  m_pSkeleton;//骨骼 + 關節數量
    JointPose* m_aLocalPose;//多個局部關節姿式,動態分配
};

當把關節姿式變換Pj施於以關節j座標系表示的點或矢量時,其變換結果是以父關節空間表示的點和矢量。這種從子關節空間變換到父關節空間的變換矩陣寫成(PC->P)j或者Pj->p(j)

全局姿式

把關節姿式表示爲模型空間或世界空間。這稱爲全局姿式。某關節的模型空間姿式(j->M),可經過從該關節遍歷到根關節時,在每一個關節乘上其局部姿式(j->p(j))算出。

任何關節j的全局姿式可寫成:

struct SkeletonPose
{
    Skeleton*  m_pSkeleton;//骨骼 + 關節數量
    JointPose* m_aLocalPose;//多個局部關節姿式,動態分配
    Matrix44*  m_aGlobalPose;//多個全局關節姿式
};

動畫片斷

遊戲角色的移動必須拆分紅大量小粒度的動做。這些動做爲動畫片斷。通常一個角色動做會拆成上千個片斷,可是當角色進入遊戲的非互動部分是例外。這部分稱爲非交互連續鏡頭(NIS)或全動視頻(FMV)。

局部時間線

每一個動畫片斷各自有一條局部時間線,使用自變量t表示時間線。t的每一個值爲時間索引。

片斷中一些時間點上有一些重要的姿式,這些姿式稱爲關鍵姿式關鍵幀,而後計算機會採用線性或基於曲線的插值計算中間的姿式。動畫片斷的時間是連續的,在計算機中時間變量是實數(浮點數)而非整數。這樣能夠有足夠的分辨率度量時間,並且能夠計算幀之間的結果或改變更畫播放速率。並且它的比例是可變的。典型的幀持續時間是1/30s或1/60s。

動畫片斷中的一個時間點稱爲一個採樣。採樣的數目和幀數目的關係:

  • 若片斷非循環,N個幀的動畫有N+1個採樣;
  • 若片斷是循環,N個幀的動畫有N個採樣,最後一個採樣是冗餘的。

歸一化時間有時稱爲動畫的相位。歸一化的時間單位u的範圍始終是[0,1];當動畫在循環時,u有如正弦波的相位。當要同步兩個或以上的動畫片斷,而它們的持續時間有不一樣,歸一化時間就很適合。

全局時間線

遊戲中每一個角色的全局時間線是從角色在遊戲世界中誕生開始。播放一個動畫能夠當作動畫的局部時間映射到角色的全局時間中。能夠經過天正時間比例,把片斷播放的更快或更慢。只須要把片斷映射到全局時間線以前縮放它的比例便可。這稱爲播放速率R(片斷縮小一半時,R = 2;R = -1時,片斷倒轉播放)。

動畫片斷映射到全局時間線和下面的相關:

  • 全局起始時間τstart
  • 播放速率R
  • 持續時間T
  • 循環次數N

局部時間t和全局時間τ的映射關係:t = R(τ - τstart)    τ = τstart + t/R

  • 若動畫是非循環的(N = 1),應該吧t裁剪到合法的範圍[0,T]中:t = clamp[R(τ - τstart)]|0T
  • 若動畫是無限循環(N = ∞),則  t = (R(τ - τstart))modT
  • 若動畫是有限循環(1 < N < ∞),則  t = (clamp[R(τ - τstart)]|0NT)modT

若要使用局部時間同步動畫,必須保證在徹底相同的遊戲時間播放它們。實際上這是很困難的,由於動畫播放命令可能來自多個不一樣的子系統,要保證它們的徹底同步很是困難。

若使用全局時間同步動畫,只須要片斷的全局開始時間數值上相同,它們的播放就是徹底同步的,若片斷的播放速率相同,則會已知同步下去。

動畫數據格式

一些關節姿式一般存儲爲SQT格式。動畫的表示方法:

struct JointPose{...};//前面已定義了SQT

struct AnimationSample
{
    JointPose* m_aJointPose;//關節姿式數組
};

struct AnimationClip
{
    Skeleton*        m_pSkeleton;//因爲每一個動畫片斷是爲特定的骨骼而設計的,這個多是骨骼標識,而不是指針
    F32              m_framesPerSecond;
    U32              m_frameCount;
    AnimationSample* m_aSamples;//採樣數組
    bool             m_isLooping;
};

連續通道函數

動畫的採樣可看作隨時間而改變的連續函數;對於採樣間的值,許多遊戲引擎採用線性插值,這其實是對連續進行分段線性逼近

許多遊戲容許在動畫中加入額外的「元通道」數據。常見的是在多個時間點上存儲事件觸發器,當動畫的局部時間索引進過這些觸發器,觸發器的事件就會交到遊戲引擎,遊戲引擎處理這些事件。事件觸發器經常使用於記錄在動畫的某個時間點播放音效(腳觸地時播放腳步聲),或粒子效果。另外一種是名爲定位器的特殊關節,它和骨骼關節一塊兒設置動畫。典型的能夠吧攝像機和定位器綁定,這樣就能獲得位置和角度。

蒙皮及生成矩陣調色板

把三維網格頂點聯繫至骨骼的過稱稱爲蒙皮。蒙皮用的網格是經過其頂點聯繫上骨骼的。每一個頂點可綁定到一個或多個關節。若綁定至多個頂點,該頂點的位置爲把它綁定到單一關節後的位置,再求其加權平均。

每一個頂點須要信息:

  • 該頂點綁定的(一個或多個)關節的索引;
  • 對於綁定的每一個關節,提供一個權重因子。

遊戲引擎會限制每一個頂點能綁定的關節數目,典型的限制爲每頂點4個關節。
緣由以下:首先,4個8位關節索引能方便地包裹爲一個32位字,此外,每頂點使用2個、3個及4個關節所產生的質量很容易區分,但大多數人並不能分辨出每一個頂點4個關節以上的質量差異。

典型的蒙皮頂點數據結構

蒙皮矩陣

以矩陣Bj->M表示關節j在模型空間的綁定姿式,即此矩陣把點或矢量從關節j的空間變換至模型空間;

在綁定姿式時,該頂點的模型空間位置爲VMB,則把此頂點變換相當節j的空間:VjVMBBM->jVMB(BM->j)-1

蒙皮過稱要計算的該頂點在當前姿式的模型空間的位置VMC = VjCj->MVMB(BM->j)-1Cj->M

Kj = 綁定姿式矩陣的逆矩陣 * 當前姿式矩陣 = (BM->j)-1Cj->M

矩陣調色板(Matrix palette)

咱們須計算一組蒙皮矩陣Kj,當中每一個矩陣對應第j個關節。此數組稱爲矩陣調色板。

當要渲染一個蒙皮網格時,矩陣調色板便要傳送至渲染引擎。渲染器會爲每一個頂點查找調色板中合適的關節蒙皮矩陣,並用該矩陣把頂點從綁定姿式變換至當前姿式。

假設角色的姿式隨時間改變,其當前姿式矩陣便須要每幀更新。然而,綁定姿式逆矩陣在整個遊戲中都是常量,由於骨骼的綁定姿式是模型建立時肯定下來的。所以綁定姿式逆矩陣一般會緩存於骨骼,並不須要在運行時計算。

每一個頂點最終會由模型空間變換至世界空間。所以有些引擎會把蒙皮矩陣調色板預先乘以物體的模型-世界變換。這是個頗有用的優化。(Kj)W = (BM->j)-1Cj->MMM->W

頂點蒙皮至多個關節,求加權平均(wij表示權重):

動畫混合(animation blending)

指能令一個以上的動畫片斷對角色最終姿式起做用的技術。混合是把兩個或更多的輸入姿式結合,產生骨骼的輸出姿式。好比,經過混合負傷及無負傷的步行動畫,咱們能夠生成兩者之間不一樣負傷程度的步行動畫。動畫混合可用於對面部表情、身體站姿、運動模式等的極端姿式之間插值。

動畫混合也能夠用於求出不一樣時間點的兩個已知姿式之間的姿式。經過在短期段內把來源動畫逐漸混合至目標動畫,就能把某動畫圓滑地過渡至另外一動畫。

線性混合插值

將兩個骨骼姿式,經過線性插值找到它們中間姿式

則兩個姿式中每一個關節的局部姿式線性插值: 當整個骨骼插值後的姿式:

其中β爲混合百分比混合因子;β∈[0,1]。至於對每一個關節姿式的矩陣插值的處理方法,能夠對它的SQT格式進行插值,這在前面數學基礎中講到。

線性插值混合的應用

時間性混合,即β的值和時間相關,例如要求Δt和2Δt之間的2.18Δt的時間處的姿式,能夠求β = 0.18的線性插值。所以,要求時間點t1和t2之間的某個姿式採樣,β能夠這樣求得:

動做連續性,當動畫從一個片斷過渡到另外一個片斷常常出現跳幀的問題。

爲了在不一樣的動畫片斷之間平滑過渡,這裏有三種級別的動畫連續性:

  • 位置連續 C0連續(continuity):骨骼中每一個關節移動時描繪的三維路徑不含忽然的「跳躍」。
  • 速度連續(位置的導數) C1連續,速度及動量的連續性:路徑的第一導數也是連續的。
  • 加速度連續(速度的導數)C2連續

若使用更高階的連續性,角色的動做會顯得更佳及更真實。可是,一般難以達到嚴格數學上的C1或以上的連續性。一般使用線性插值(LERP)達到C0動做連續性就能夠了。LERP混合即成爲淡入/淡出。

兩種常見的淡入淡出過渡方法

  • 圓滑過渡(smooth transition):播放片斷A和B的同時把β從0增至1。這種作法要求兩片斷都是循環動畫,且兩片斷同步到手腳位置大體匹配。
  • 凍結過渡(frozen transition):片斷A的局部時鐘停頓於片斷B開始播放時。它適合混合兩個不相關且不能再時間上同步的片斷。

β 線性差值中的混合因子

爲達到圓滑的過渡,咱們能夠令β按時間的三次函數變化,例如用一維貝塞爾曲線。當把這些曲線應用正在淡出的當前片斷時,該曲線就稱爲緩出曲線(ease-out curve);當應用到正在淡入的新片斷時,稱爲緩入曲線(ease-in curve)。

Bezier曲線以下:βstart爲混合之始tstart的混合因子,βend爲時間tend的最終混合因子,參數u是tstart和tend之間的歸一化時間,v = 1 - u。

無需混合就能產生連續動做的方法:

動畫師確保其片斷的最後姿式能匹配後續片斷的首個姿式。這種姿式稱爲核心姿式。具體的作法,創做一段圓滑的動畫,而後把它切爲兩個或兩個以上的動畫片斷。

方向性運動

  • 靶向移動:(有助於理解unity中的混合樹(Blend Tree))

動畫師製做3中不一樣的循環動畫片斷,包括向前、向左和向右移動,這些稱爲方向性運動片斷。把它們排在半圓周上,分別對應0o、90o、-90o。角色面向0o方向,而後選擇要求的角度的兩個相鄰的片斷使用LERP方式混合,β是移動角度和相鄰片斷的角度求得。

  • 軸轉移動

簡單的播放向前運動的動畫片斷,同時以垂直軸旋轉整個角色。

複雜的線性插值混合

泛化的一維線性插值混合中,能夠定義一個參數b,它的範圍任意,可是全部片斷對應於該參數的某點上。(上面靶向移動的混合因子就是他的一個特例,b∈[-90o,90o])這樣就能夠經過b求出β:

簡單的二維線性插值混合中,b變成了二維混合矢量b = [bx  by]。若b位於4個片斷包含的正方形中:

  • 利用水平混合因子bx求出兩個中間姿式,一個位於頂邊的兩個片斷之間,一個位於底邊的兩個片斷之間。它們能夠經過一維LERP混合求得。
  • 而後利用垂直混合因子by,把兩個中間姿式用一維LERP求得最終姿式。

如果3個片斷,能夠簡單的使用三個權重:α、β、γ;α + β + γ = 1。

經過下面的公式求得它的姿式:

使用Delaunay三角剖分(Delaunay triangulation)能夠獲得泛化的二維線性插值混合。詳細:http://en.wikipedia.org/wiki/Delaunay_trianglulation

骨骼分部混合

人可獨立控制身體不一樣部位。例如,能夠在步行時揮動右臂,並同時令左臂指着某物。在遊戲中實現這種動做的方法之一是,使用名爲骨骼分部混合(partial-skeleton blending)的技術。

混合遮罩(blend mask)能夠把某些關節的混合百分比設爲0,來掩蓋那些關節。

現實中,在跑步中揮手時,揮手動做比站立時更「晃動」及不受控制,骨骼分部混合沒法實現這樣的真實性。另外一種更天然的技術:加法混合。

加法混合(additive blending)

區別片斷(difference clip)- 表明兩段正常動畫的區別。
考慮兩個輸入片斷 來源片斷(source clip S)、參考片斷(reference clip R);概念上,區別片斷 D = S – R
若區別片斷D加進原來的參考片斷,咱們就會獲得來源片斷。只須要把某百分比的D加進R,咱們也能夠產生介於R和S之間的動畫。就像線性插值。加法混合技術之美在於:製做一個區別片斷以後,能夠把該片斷加進其餘不相關的片斷,而不只限於原來的參考片斷。

實際的數學公式,關節j的區別片斷:   產生新的姿式Aj

僅當輸入片斷S和R的持續時間相同,才能獲得它們的區別動畫。也能夠加上加法混合百分比,修改二者的權重:

加法混合的侷限

  • 在參考片斷中,儘可能減小髖關節的旋轉;
  • 在參考片斷中,肩及肘關節應該一直維持中性姿式;
  • 動畫師應爲每一個核心姿式(站立、蹲下、躺下等)建立新的區別動畫。

加法混合的應用

  • 站姿變化
  • 移動噪聲
  • 瞄準及注視

動畫後期處理(animation post-processing)

經過上面的混合以後,一般在渲染前還要修改姿式,這稱爲動畫後期處理。

  • 程序式動畫:指任何在運行時生成的動畫,這些動畫並不是由動畫工具導出的數據驅動。
  • 逆運動學:與通常的從一組局部姿式推出一個全局姿式相反,逆運動學的輸入是某關節想要的全局姿式,此輸入稱爲末端受動器,要求出其餘關節的局部姿式。
  • 布娃娃物理:它是由一組物理模擬的剛體,這些剛體彼此受限於角色的關節位置,這些受限方式要設置成能產生天然的「無生氣」身體移動。

壓縮技術

通道省略:省略無關的通道。多數角色不須要非統一放縮,所以三個放縮通道減小成一個;人形角色的骨頭不能伸縮,所以關節的平移通道也能夠省略。

量化:動畫片斷常常不須要32爲浮點數的精度,16位編碼的精度足夠。所以,將32位浮點數的量化成整數表示法,或者把整數解碼爲浮點數(損失精度)。

U32 CompressUnitFloatRL(F32 unitFloat, U32 nBits)
{
    //基於要求的輸出位數,判斷區間數量
    U32 nIntervals = 1u << nBits;
    
    //把輸入值從[0,1]範圍放縮至[0,nIntervals - 1]範圍
    //這裏須要減一是因爲但願最大的輸出值能存儲在nBits個位內
    F32 scaled = unitFloat * (F32)(nIntervals - 1u);
    
    //最後,咱們須要加0.5f,在四捨五入至最近的區間中點
    //而後,把該值截尾,取得區間索引(經過轉型至U32)
    U32 rounded = (U32)(scaled + 0.5f);
    
    //爲無效輸入值作出保護
    if (rounded > nIntervals - 1u)
        rounded = nIntervals - 1u;
    
    return rounded;
}

F32 DecompressUnitFloatRL(U32 quantized, U32 nBits)
{
    //基於編碼時的位數,判斷區間數量
    U32 nIntervals = 1u << nBits;
    
    //解碼時只需簡單的把U32轉爲F32,並按區間大小縮放
    F32 intervalSize = 1.0f / (F32)(nIntervals - 1u);
    
    F32 approxUnitFloat = (F32)quantized * intervalSize;
    
    return approxUnitFloat;
}

//處理任意在[min,max]範圍內的輸入值,可使用下面的函數
U32 CompressFloatRL(F32 value, F32 min, F32 max, U32 nBits)
{
    F32 unitFloat = (value - min) / (max - min);
    U32 quantized = CompressUnitFloatRL(unitFloat, nBits);
    return quantized;
}

F32 DecompressFloatRL(U32 quantized, F32 min, F32 max, U32 nBits)
{
    F32 unitFloat = DecompressUnitFloatRL(quantized, nBits);
    F32 value = min + (unitFloat * (max - min));
    return value;
}

若是要把32位壓縮爲16位

inline U16 CompressRotationChannel(F32 qx)
{
    return (U16)CompressFloatRL(qx, -1.0f, 1.0f, 16u);
}

inline F32 CompressRotationChannel(U16 qx)
{
    return (U16)DecompressFloatRL((U32)qx, -1.0f, 1.0f, 16u);
}

下降總體的採樣率和省略一些樣本也是壓縮數據的方法

多數遊戲不須要全部動畫同時載入內存,只會在遊戲開始時載入一組核心動畫片斷。

動畫系統架構

  • 動畫管道 animation pipeline
  • 動做狀態機 action state machine, ASM:遊戲角色的動做一般建模爲有限狀態機,這稱爲動做狀態機
  • 動畫控制器 animation controller:許多引擎中,玩家和非玩家角色的行爲最終由動畫控制器所組成的高級系統控制。

動畫管道

它包含下面的階段:

  • 片斷解壓及姿式提取
  • 姿式混合
  • 全局姿式生成
  • 後期處理
  • 從新計算全局姿式
  • 矩陣調色板生成

遊戲中每一個個別角色或物體有其每實體數據結構,但相同類型的角色或物體會共享一組資源數據。

  • 骨骼
  • 蒙皮網絡
  • 動畫片斷

尚未統一個方法表示每實例數據,可是幾乎全部動畫引擎都有下面記錄:

  • 片斷狀態
    • 局部時鐘
    • 播放速率
  • 混合規格:描述哪些動畫片斷正在播放,以及這些片斷如何混合在一塊兒。
  • 分部骨骼關節權重
  • 局部姿式
  • 全局姿式
  • 矩陣調色盤

扁平加權平均混合表示法

N維的加權平均,淡入淡出時,比較複雜。細節省略。

混合樹

將片斷桉樹形結果混合,淡入淡出時,很簡單。細節省略。

動做狀態機

動畫狀態

ASM中每一個狀態對應一個任意複雜的動畫片斷混合。在混合樹的架構中,每一個狀態對應至某個預先定義的混合樹。在扁平加權平均架構中,一個狀態表明一組片斷及一組相對權重

過渡

ASM中狀態的過渡,須要這些信息:

  • 來源及目標狀態
  • 過渡類型:即時過渡、淡入淡出、其餘
  • 持續時間
  • 緩入/緩出曲線類型
  • 過渡窗口:某些過渡只能在來源動畫的局部時間位於某個窗口內才能進行。

狀態層

控制參數

約束

  • 依附
  • 參考定位器
  • 抓取及手部IK
  • 動做提取及腳部IK
  • 注視(look-at):角色能注視環境中的興趣點。角色能夠僅用眼睛注視,又或者同時用眼和頭,又或加入上半身的扭動。注視約束有時候是以IK或程序式關節偏移實現的,但更天然的觀感可用加法混合實現。
  • 掩護對準(cover registration):角色在掩護時要和掩護物完美對齊,這一般是用參考定位器技術來實現。
  • 進入及離開掩護
  • 通行協助
相關文章
相關標籤/搜索