• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            永遠也不完美的程序

            不斷學習,不斷實踐,不斷的重構……

            常用鏈接

            統計

            積分與排名

            好友鏈接

            最新評論

            人物動畫系統(轉)

            我最近在學習人物動畫的方面,做個總結,由于剛剛接觸這個方面,所以有什么問題請大家指出。

            在這篇日志里面,你可以獲得這些信息:

            1 人物動畫的框架
            2 骨骼動畫及蒙皮技術
            3 doom 3和quake 4中模型和動畫格式md5及原理
            4 可能的擴展


            先來看一下人物動畫的幾種方法:

            一、簡單關鍵禎的動畫

              像quake3中就是用的這樣的方法。這種方法最簡單,缺點就是空間上的浪費。由于每個關鍵禎中都要儲存整個網格的幾何信息,所以用這種方法生成的動畫文件相當的龐大,在游戲進行中也會占用大量的內存。現在的游戲,一般都不用了。

            二、簡單的骨骼動畫及蒙皮技術

              現在的很多游戲,都是用的這種方法,具體的原理后面再解釋。這種方法可以節省大量的空間,對于美工來說,工作量也相對較小(可以利用動作捕捉的數據),真實性方面,簡單的應用中也表現得比較好。

            三、改進的蒙皮方法和基于物理的骨骼動畫

              改進的蒙皮方法可以避免簡單的蒙皮中產生的“糖紙失真現象”;

              基于物理的骨骼動畫,已經有很多的游戲、物理引擎支持這一特性了,但是還有很多的技術問題需要處理。這是次時代游戲引擎必須很好實現技術之一。



            基本的蒙皮原理

            拿md5格式為例,來簡單的解釋一下蒙皮的原理。在doom3和quake4中md5mesh文件,用來記錄一個人物的靜態模型。有這樣幾個結構:

            Joint: 用來記錄骨骼的關節的信息;
            Weight: 用來記錄頂點相對于關節的權值;
            Vertex: 頂點信息,和一般的頂點不同,這里的頂點不直接的包含幾何坐標信息,而是記錄了對應的Weight;

            現在就來解釋一下這三者之間的關系:

              Joint(關節)是會動的,而皮膚上的頂點是會隨著頂點做相應的運動。我們保持皮膚上面的各個頂點和它相對應的關節的“位置關系”,就可以通過旋轉關節,使得真個皮膚跟著旋轉。這個“位置關系”,就是Weight。在運動的過程中,我們獲得當前的骨骼的幾何信息,也就是每個關節的幾何位置,然后在根據每個頂點對于這些關節的權值,分別計算每個頂點的實際幾何位置,這樣,整個人物網格就計算出來了。

              很顯然,這其中有一個預處理過程和兩個關鍵的步驟。預處理就是需要由靜態的模型(美工做出的人物模型)和骨骼來計算得到一組Weight;兩個關鍵的步驟是,1、獲得的整個骨骼的幾何信息(有可能從關鍵禎混合得到);2、由頂點對應的Weight來計算出每個頂點的實際幾何信息。

              了解這些基本的概念,下面就來介紹人物動畫系統的框架。


            骨骼蒙皮基本框架

            基本的類型:

            關節信息:

            typedef struct _CharJoint
            {
                Vector3 pos;
                Vector4 startPoint;
                int parentID;
                char name[32];
            } CharJoint;

            其中,parentID為父關節,pos為對應父關節的偏移值,startPoint為旋轉角度;

            權值信息:

            typedef struct _CharWeight
            {
                Vector3 pos;
                int jointID;
                float bias;
            } CharWeight;

            其中,pos為偏移量,jiontID為對應的joint,bias偏向值;


            頂點信息:

            typedef struct _CharVert
            {
                float u, v;
                int startWeight;
                int weightCount;
            } CharVert;

              其中,startWeight為該頂點對應的Weight在Weight列表中的偏移地址,weightCount記錄該頂點對應多少個權值;對于簡單的頂點,比如頭頂上的某個點,動畫的時候涉及到的變化并不多,所以,對應的權值數也就少,可以只有一個;對于動畫中涉及變化比較復雜的點,比如手肘區域的頂點,可能由較多的權值(4個或更多),這樣才能夠很好的表示運動中對于多個關節的相對位置。


            大概還涉及到這樣一些類:

            CharSkeleton: 記錄整個骨骼的信息,包含了關節的鏈表;
            CharMesh: 記錄整個人物模型的靜態信息,包括頂點,權值,關節等;
            CharBlender: 基類,根據CharMesh和CharSkeleton來計算出實際的網格,基本成員函數為Blender,用CPU來計算蒙皮,可以被子類Blend覆蓋(比如可以寫一個用Vertex Shader實現的Blender);
            CharAnimation: 每個CharAnimation實例對應一個動作序列,比如“人物蹲下動作”;動作序列保存的是人物骨骼動畫的關鍵禎,也就是在某一禎時,骨骼中各個關節的幾何信息;注意這里的禎的概念并不是平常說的渲染的禎,在動畫中,為了進一步節省空間,一般設定了一個動作為幾個格,就像動漫制作過程中的“故事板”,只是整個過程中的幾個縮略圖,在后期制作過程中,在“填滿”中間缺省的圖片;這里的骨骼動畫關鍵禎也是如此,文件中只保存了間斷的幾個狀態,在渲染的時候,還是要實時的生成中間的某個狀態,來把整個動作序列“填滿”;
            CharAnimCtrl: 這個類的作用就是完成上面所說的,將動作序列“填滿”的功能,輸入是CharAnimation和時間,輸出是一個基本的骨架,也就是CharSkeleton(當然這是靠傳引用參數進行輸出);


            解決關鍵問題

              剛才提到了,整個系統中由三個關鍵的問題:一個預處理過程,關鍵禎混合以及從權值計算出實際頂點。預處理過程,基本上是編寫一個建模工具導出插件的工作,這里就不討論了。

            關鍵禎混合:

              簡單的辦法,就是直接用線性的方法混合,比如現在的動畫時間標識為40,而我只有標識為20和50的兩個關鍵禎,于是:40-20 = 20,50-40 = 10;而20:10 = 2:1;所以,我們現在的狀態離關鍵禎20的差異,以及離關鍵禎50的差異,這兩個差異的比,就是2:1;好了,所以現在很自然地,我們取倒數,1:2;于是,我們做混合的時候,用“1份”關鍵禎20的骨骼,和“2份”50關鍵禎的骨骼,然后相加兩者的結果(也就是“混合”過程)最后,除以3,得到最終的“1份”關鍵禎為40的骨骼。恩,就這么簡單。(不過注意不要把這個比值的含義搞反了);

              際應用中還有其他的混合形式,后面再來介紹。


            計算實際頂點:

            我們看一下軟件的(用CPU做蒙皮)Blend過程:

            void CharBlender::Blend( Mesh &outputMesh, PE::CharMesh &inputMesh, PE::CharSkeleton &inputSk )
            {
                if ( outputMesh.GetNumVertices() < inputMesh.GetNumVerts() )
                    return;

                CharOutVert *pOutVerts = ( CharOutVert* )outputMesh.LockVertexBuffer();
                int numVerts = inputMesh.GetNumVerts();
                int numTris = inputMesh.GetNumTris();

                for ( int i = 0; i < numVerts; i++ )
                {
                    const CharVert *pVert = inputMesh.GetVertAt( i );
                    pOutVerts->x = pOutVerts->y = pOutVerts->z = 0.0f;

                    /* u v initial */
                    pOutVerts->u0 = pOutVerts->u1 = pOutVerts->u2 = pVert->u;
                    pOutVerts->v0 = pOutVerts->v1 = pOutVerts->v2 = pVert->v;

                    for ( int j = 0; j < pVert->weightCount; j++ )
                    {
                        const CharWeight *pWeight = inputMesh.GetWeightAt( pVert->startWeight + j );
                        int index = pWeight->jointID;
                        const CharJoint *pJoint = & ( inputSk.GetJointAt( pWeight->jointID ) );
                        vec3_t wv;
                        Quat_rotatePoint( &pJoint->startPoint.x, &pWeight->pos.x, wv );
                        pOutVerts->x += ( pJoint->pos[0] + wv[0] ) * pWeight->bias;
                        pOutVerts->y += ( pJoint->pos[1] + wv[1] ) * pWeight->bias;
                        pOutVerts->z += ( pJoint->pos[2] + wv[2] ) * pWeight->bias;
                    }
                }

                outputMesh.UnlockVertexBuffer();

                CharTri *pOutTri = ( CharTri* )outputMesh.LockIndexBuffer();

                for ( int i = 0; i < numTris; i++ )
                {
                    const CharTri *pTri = inputMesh.GetTriAt( i );
                    pOutTri->index[0] = pTri->index[0];
                    pOutTri->index[1] = pTri->index[1];
                    pOutTri->index[2] = pTri->index[2];
                }

                outputMesh.UnlockIndexBuffer();
            }

            其中黑體的部分,就是關鍵的代碼,應該很容易看懂。其中,Quat_rotatePoint函數的作用就是將點進行旋轉,得到新的坐標。


            關于md5anim文件

              Doom3和Quake4中的動畫文件都是用md5anim文件保存的。md5anim文件只含有該動作所涉及到的骨骼關節的動畫信息。也就是所,文件中關鍵禎的關節列表,是它所對應的md5mesh文件中基本關節列表的一個子集;這樣做當然是有道理的,因為,有些動作,可能只涉及到身體的一個部分,比如眨眼,換彈夾等等,那么,把一個完整的骨骼框架放在mesh文件中,把若干不同的局部或者整體的關節序列放在不同的動畫文件中,這樣,可以最大限度的節省空間。


            可能的擴展

            一、復雜動作的混合

              有時候,我們需要將兩個動作混合,比如,一個人物同時的在做兩種動作,一邊向左平移,一邊向右方開槍;不可能為每種可能的混合動作做大量的美工工作,而且空間上,我們也不允許這樣做;可行的辦法是,混合兩個不同的動作序列,比如上半身動作和下半身動作的混合,這當然是最簡單的方式。還有很多比較麻煩的混合方式,比如,人物在行走時中了槍,需要混合“行走”和“中槍”兩個動作,而簡單的線性混合是無法真實模擬的。

            二、基于物理的動畫

              這不再僅是圖形方面的問題了,這其中涉及到了大量的物理模型,這個,我也不懂。。。可以從第三方的物理引擎獲得幫助,ODE好像就支持了;

            三、基于GPU的蒙皮

              原理和CPU蒙皮的原理一致,只是用了Shader,會比CPU蒙皮的效率快很多。在前面的代碼中,只需實現CharBlender的子類就可以了。

            四、非常流行的“換裝”系統

              這在RPG游戲里面簡直就是不可少的一條。就現在的框架來說,還不能達到隨意“換裝”的要求。修改CharMesh以及Character的底層,需要能夠添加和刪除基本的骨架,支持多層皮膚(衣服)(多個Mesh的開關)。還可以更換不同的武器(底層實現還是通過添加骨架完成)。。。

            posted on 2008-11-12 11:39 狂爛球 閱讀(636) 評論(0)  編輯 收藏 引用 所屬分類: 圖形編程

            久久精品无码专区免费东京热 | 亚洲日韩欧美一区久久久久我| 国产日韩久久久精品影院首页| 激情久久久久久久久久| 亚洲午夜久久久久妓女影院| 久久久久免费精品国产| 亚洲中文久久精品无码| 国产激情久久久久影院老熟女免费| 人人狠狠综合久久亚洲高清| 99久久99久久久精品齐齐| 欧美久久久久久午夜精品| 久久不见久久见免费视频7| 久久伊人影视| 一本伊大人香蕉久久网手机| 精品一二三区久久aaa片| 久久久无码精品亚洲日韩软件| 国产精品久久久久久福利漫画| 精品综合久久久久久97| 久久久久久毛片免费看| 国产精品伦理久久久久久| aaa级精品久久久国产片| 狠狠精品久久久无码中文字幕| 久久免费99精品国产自在现线| 一级做a爱片久久毛片| 国产精品久久久久久| 久久国产色AV免费看| 亚洲va国产va天堂va久久| 狠狠综合久久AV一区二区三区| 久久亚洲国产最新网站| 久久国产亚洲精品| 久久国产AVJUST麻豆| 欧美日韩精品久久久久| 久久天天躁夜夜躁狠狠躁2022| 国产成人精品综合久久久| 亚洲va久久久噜噜噜久久狠狠| 久久亚洲国产成人精品性色| 久久狠狠高潮亚洲精品| 久久96国产精品久久久| 国产成人无码精品久久久久免费| 国产精品伦理久久久久久| 人妻中文久久久久|