http://blog.csdn.net/pizi0475/article/details/48286579 -------------(Collada 快速入門)

http://blog.csdn.net/zhouhangjay/article/details/8469085html

說明:Collada的文件格式,中文版的不多,在csdn上看到了一個Sleepy的,感受也不是全面特別是沒有圖讓我很傷感,因此我在這裏加上了圖,但願對你們有幫助。node

一步一步的使用C++和OPENGL實現COLLADA骨骼動畫編程

第一部分數組

 

英文原做者:waZim數據結構

原文標題:Step by Step Skeletal Animation in C++ and OpenGL, Using COLLADAapp

原文地址:http://www.wazim.com/Collada_Tutorial_1.htm編程語言

 

Sleepy譯編輯器

譯註:ide

這是一篇詳細介紹COLLADA文件(也就是DAE文件,3D模型文件的一種)格式的文章。之因此翻譯這篇文章的緣由,一是這篇文章的確寫得很好很詳細,另外一方面關於DAE文件格式的中文資料很是的少,每次看E文的也累,因此正好翻譯出來一了百了。工具

我是從看dancingwind(周煒)與AKER翻譯的NEHE Opengl教程開始學習Opengl的,對這些將外國的優秀文章和教程漢化的人,我向他們致以由衷的感謝,同時也以此譯文向他們致敬。

另外,本人E文水平有限,有些詞翻譯得不是很準(但我相信應該不會對閱讀的人形成誤導),若是發現錯誤和不完善的地方(估計會有不少),你們能夠經過郵件與我交流,我會在第一時間更正錯誤。

謝謝。 

                                                                                              Sleepy

本次修改:2011-09-08

介紹:HI,我是waZim,歡迎來到個人第一篇骨骼動畫的教程。這一系列教程由兩部分組成:

1.       瞭解如何讀取COLLADA文件(歸納的介紹COLLADA文件)。

2.       用C++和OPENGL去真正實現第一部分所講的內容。

第一部份:

 

閱讀與理解COLLADA文件

       正如在前面的介紹部分裏所說,這篇教程分爲兩個部份。第一部分的通常性的講解並不考慮和涉及任何編程語言。可是若是你想直接跳到第二部分去看程序實現的話,你很是可能會感到徹底沒法理解從而沒法繼續下去。因此強烈建議對於COLLADA文件一無所知的初學者來講,仍是耐心看完第一部分的介紹再去看第二部分的實現。

       廢話很少說,讓咱們開始吧。

 

COLLADA文件

       在咱們準備開始深刻挖掘COLLADA文件的意義以前,我但願大家先下載一個實例文件,這個文件咱們將作爲此教程從頭至尾討論的對象(因此你們仍是下載回來對照參看吧)。你們能夠在COLLADA模型中心中找到它。它的名字叫「astroBoy_walk.dae」,若是你處處都找不到這個文件,那麼好吧,你能夠到這篇教程所在網頁的「下載」部分找到它。(我怎麼找不到)

       就像咱們以前所說的,COLLADA文件以XML的形式存儲。如今你們能夠打開前面所說的示例文件看看,你能夠用你最喜歡的文本編輯器打開這個文件(IE就不錯)。你會看到一個根結點名爲「COLLADA」,若是你所用的文本編輯器支持展開與摺疊XML結點的話(IE就能夠),你能夠經過點擊+-號把各個結點展開收起來成這個樣子:

圖1:COLLADA文件的概覽

在.dae文件或.xml文件的根結點<COLLADA>下你會找到不少library這樣的東西,它們就是用來存儲模型中各類不一樣各種的信息的。好比<library_geometries>就是用來存儲幾何數據的(就是三角形啊,還有所謂的mesh啊 – 另外mesh這個詞好像你們叫成英文的比較多,下面遇到這個詞就不譯成中文了);<library_lights>則是用來存儲光照和場景數據的。你們看看圖1,並非什麼製造火箭般的高科技是否是,經過這些叫library_xx的東西咱們能找到模型實際的各類數據。而像如幾何數據的存儲區會有<geometry>名字的結點,而光照數據的存儲區會有叫<light>的結點,這代表這些數據存儲區裏存儲的模型或光照數據經常不止一組。如今,讓咱們來一個一個地分析每一個數據區,按照每一個數據區的重要性不一樣,我會將它們合理的排列在這篇教程的不一樣位置。

首先,爲了讓問題變得簡單,正如我說的這是篇入門教程,因此咱們不會討論COLLADA文件的每個方面,爲了在教程中除去其中的複雜的部分,咱們來設定幾個前提條件。

 

前提條件:

1.              雖然不管COLLADA文件從Max中導出仍是從Maya中導出照理說應該是同樣的,但實際上在某些狀況下總會有那麼一點不一樣。咱們只討論從Max中導出的COLLADA文件,固然這並非說用Maya的人就杯具了。由於我仍能夠確定的是,若是COLLADA從Maya中導出時,在彈出的COLLADA導出選項對話框中將「triangulate」這個選項鉤上,而且以「背向矩陣」(backed matrices,我沒用過Maya,也不知道是什麼)方式導出的話,則與Max導出的是同樣的。可是由於我有用過Maya,因此不知道個人導出器載入Maya導出的文件時會失敗在什麼地方。

2.              COLLADA文件中必須僅僅只有一個mesh,這意味着任何在max文件中有用的數據都已經記錄下來了(原文:which means, anything in the asset's Max file, should be attached.不知道該怎麼譯,不過好像對文章的內容並沒影響)。因此咱們在COLLADA文件中的<library_geometries>結點裏不會看到多於1個的<mesh>結點。但若是咱們能讀取1個<mesh>,那咱們一樣的也能讀取成千上萬個<mesh>不是嗎。

3.              COLLADA裏的幾何圖形是以三角形的方式記錄的,由於這即便不是最好的,也是比較好的記錄方式,咱們能夠直接提供三角形數據給OPENGL,因此咱們讓Max幫咱們將圖形導出爲三角形記錄的方式。

4.              在稍後的實現部分,咱們還假定咱們所分析的COLLADA中只包含一個貼圖文件。

5.              COLLADA中的動畫至少含有一個骨骼—--至少一個根骨骼(這是很典型的)。嗯,我想,咱們能實現骨骼動畫,咱們簡直是英雄般的人物。(原文:And I think that’s why we are here, to implement skeletal animation.)

6.              導出到COLLADA中的硬動畫必須以矩陣的形式保存,從本質上來講,在某些狀況下這個造成一個動畫的通道而其它狀況下則會生成16個動畫通道(什麼是通道,咱們稍後解釋)。(原文:Animation exported to COLLADA must be baked in matrices, which essentially in some cases makes 1 channel of animation and in others 16 channels of animation (Now what is channel? It should be explained later).)

7.              動畫只在通道向對象實體施加變化影響時纔有效,請把它們相像得儘可能簡單和清晰。若是你固化了矩陣,那麼前面所說的事就理所固然的被完成了,因此不用擔憂這些。(原文:Animations can only be valid if the channel targets the "Transform" of the targeted entity, just to keep things clear and easy. When you will bake matrices, then you will have this automatically, so don't need to worry about that.)

8.              動畫不能包含嵌套的動畫。

9.              只支持骨骼動畫(沒有硬動畫)(譯註:那你前面說一大堆硬動畫的事幹毛啊。)

10.          層次中的每一個骨頭都必須對某些皮膚產生影響,換句話說,它們都必須關聯到皮膚上。

 

請你們在腦中從頭至尾一直保持上面所列的這些假設,讓咱們開始一個一個部分爲你講解。你會以爲一切都很容易,若是你當即跳到實現部分去看你也會發現這些原來並不難。下面的每一節中都會給出相應實現代碼的連接

從COLLADA文件中讀取幾何數據

<library_geometries>

這是COLLADA文件中最重要的一個library了,若是你須要一個繪製一個角色動畫,在這裏你能找到它的幾何數據。

這個library中包含許多<geometry>類型的結點,它們分別存儲了場景中的各類幾何數據,別忘了COLLADA只有一個文件(也就是沒有其它的附屬數據文件,貼圖的圖片除外),因此只能把全部的大量的數據全放在一個文件裏面。但正如咱們假設的,咱們只考慮這個文件中只有一個<geometry> node結點,這個結點下也只有一個<mesh>結點的狀況。好了,咱們找到它了,如今讓咱們開始分析它。

 

<mesh>

       咱們會在這個被稱爲「網格」的結點中找到咱們想要的幾何數據。若是你試着分析這一結點,你會看到至少1到2個<source>結點,它的意義決定於它的類型,它能夠存儲頂點、法線、紋理座標等信息。在示例文件中(若是你是從COLLADA.org這個網站下載的話,這個文件裏面不會包含反向動畫,因此你必須將它從新導入Max而且而且以「背向矩陣」從新導出,導出時還要將導出對話框中的「triangulate」這個選項鉤上,如圖2所示),你會找到3個<source>結點,你會發現很是幸運的是在COLLADA中每一個source結點都是以一樣的形式來定義的。

圖2:Max導出COLLADA時的設置

<souce>

       請記住,咱們所討論的全部的XML結點都有一個相應的ID號,這個ID用來定位這個結點在COLLADA文件中的位置,當其它地方須要引用這個結點時,就須要使用這個ID。Source這個結點也並不例外。如今<source>這個結點擁有許多的子結點,但最重要的幾個就是<float_array>或<NAME_array>還有 <technique_common>。

       正如它們的名字所描述的,<float_array>包含了許多的浮點數數據,它們能夠用於各類不一樣用途,在同一個source結點下的<technique_common>指出了它們的具體用途。而<float_array>與<NAME_array>的不一樣之處僅僅在於前者存儲的是一系列浮點數然後者存儲的是一系列字符串。

       如今咱們來看看<technique_common>下的<accessor>子結點是怎麼指明各類array如<float_array>, <NAME_array>或其它各類名字的array結點它們的用途的。<accessor>結點有一個叫作「source」的屬性,它說明的是「這個array究竟是什麼意思,它究竟是用來幹什麼的」;另外一個叫作「count」的屬性說明了這個array有幾組數據;還有一個叫"stride"的屬性則是說明間隔多少數據開始下一個數據(說的複雜了,也就是一組有多少個數據,好比是2個數字一組仍是3個數字一組,你懂的)。

好了我但願我不是在講天書,咱們來直接看圖表吧,這個圖表解釋了COLLADA的source指示的意義。(在此吐糟一下,原文:hope I am not talking Chinese but let's explain it with a figure and example COLLADA source. 嗯,沒錯,如今大家如今看到的就是Chinese)

[html]  copy
 

< = => < = =>> <> < = = => < = =/> < = =/> < = =/> </> </> </>    


 

 

正如大家在圖3中所看到的,這是浮點型的數組(注:數據組,不是C語言的數組哈),其中數量是6個 (count="6")(注:仔細看float_array),其中每三個一組(stride="3")共有2組(count="2")(注:仔細看accessor),分別表示XYZ,它們的類型都是浮點型(注:仔細看param)。如今咱們看到在<accessor>下有3個<param>子結點,因此咱們每一個頂點數據是3個一組(x, y, z)(一樣的法線和紋理座標也多是3個一組)。理解這些信息很是的重要,由於在我沒有COLLADA的文件說明的時候,只是理解這些就花費了我大量的時間(也許我比較笨吧),因此若是你仍沒理解的話,請再仔細讀一遍。

       簡單來講,這個source說明了如下的意思:「我有2組頂點數據,其中每3個一組,它們都存在<floats_array>裏,總共有6個數字。每一組頂點數據由名爲 「X」,」Y」,」Z」的成份組成,它們都是浮點形的數字」。若是咱們讀到一個<source>結點,裏面存放的是紋理貼圖座標的話,那麼每一組數據則是由名爲「S」,」T」,」P」的成份組成。(注:做爲天朝人,理解這些應該無壓力吧)

       好了,這就是source裏面的全部東西了。這個示例文件共有3個<source>結點,在咱們分析另外2個以前,或許咱們已經猜到了,它們應該存的是法線座標和紋理映射座標。若是你導出模型時還有其它的屬性,那麼你會獲得一個有更多<source>結點的文件,好比雙切線(bitangents)和切線(注:tangents,原文是tangets,我猜是筆誤)等等。

       如今咱們能夠對<source>進行解碼了,可是咱們仍是不知道哪些source是頂點哪些source是法線等等。咱們還要讀取<mesh>下的<vertices>結點來找到存儲頂點數據的source,儘管我實在是想不通他們這麼作的緣由,但爲了完整起見,你必須讀這個結點(注:指<vertices>結點),它至少有一個子結點名爲<input>,而且其屬性」semantic」的值是「POSITIONS」,它以另外一個名字/ID引用了了頂點的source(注:相似於定義別名,下面有詳細解釋)。而後當你須要頂點的source時,你就會引用到這個新的ID。若是你不明白這一節的內容,那麼請直接跳到下一節,而後你頗有可能就明白了。

<triangles>

       如今正如咱們所假設的同樣,咱們只考慮由三角形幾何元素組成的COLLADA文件,因此你在<mesh>下只會看到<triangles>類型的子結點,不然你可能還會看到好比<polylist>這樣的結點,這咱們儘可能不去考慮它。

       這個<triangles>結點可以告訴咱們所須要的全部構造模型的三角形數據,這些數據在咱們以前讀出的3個source裏面(只針對這個文件來講)。在<triangles>裏,"count"屬性告訴咱們在這個結點下到底有多少個三角形,而"material"屬性則告訴咱們如何從<library_material>下找到相應的材質數據,咱們使用這相應的材質數據來渲染對應的三角形。因此你會看到不少的<triangles>節點,它們是根據材質來劃分的(注:也就是說每一類材質的表面與它對應的材質信息一塊兒記錄爲一個<triangles>節點)。因此咱們必須讀取全部的<triangles>節點。

       要解碼<triangles>節點咱們必須讀取它們的子結點,其中的<input>和<p>結點是最重要的。<input>結點的數量代表每一個頂點所具備的屬性的個數。而<p>結點則是頂點相應屬性在相應的<source>結點中的索引(注,不是值,是索引,真正的值請根據索引到相應的source裏面查)。讓咱們來看看這個例子。

[html]  copy
 

<> < =/> < =/> < =/> < => < = =/> </> < = => < = = =/> < = = =/> < = = = =   

  •   
  • <> </> </> </>  


  正如你從上面的例子中看到的,<vertices>結點將名爲"position"的<source>結點重命名爲"verts",而後以"verts」的名字來定義頂點的source(原文:As you can see from the above example <vertices> node is renaming the "position" source with "verts" and then defining the triangles vertices source with "verts" name.)。這就是咱們須要讀取<vertices>結點的緣由,只有這樣,咱們才能從一堆<source>中找到咱們須要的<source>的實際位置。

       若是你讀取<triangles>的子結點,你會讀到3個<input>結點,它們的」semantic」屬性的值分別是"VERTEX" "NORMAL" 和 "TEXCOORD"。這其實是說,咱們三角形數據每一個頂點有一個值,第一個是頂點的位置(注:座標),第二個是頂點的法線,第三個是頂點的紋理映射座標。咱們怎麼知道在<p>裏面哪一個是哪一個呢,咱們來看看:

 

<input> 結點有semantic屬性= "VERTEX" 它的偏移是 offset = "0",

<input> 結點有 semantic屬性 = "NORMAL" 它的偏移是offset = "1",

<input> 結點有 semantic屬性 = "TEXCOORD" 它的偏移是offset = "2".

 

因此咱們從<p>中爲每一個三角形每一個頂點讀值的時候:

 

第一個值是"VERTEX"也就是三角形頂點位置在名爲"positions"的<source>結點中的索引,

第二個值是"NORMAL"也就是三角形頂點法線的值在名爲"normal"的<source>結點中的索引,

第三個值是"TEXCOORD"也就是紋理映射座標的值在名爲"textureCoords"的<source>結點中的索引。

    好,如今有一件事我必須講清楚,全部從<p>這個結點下讀出來的值都是「索引」而不是實際的數據值,全部三角形的全部數據的值都以索引的形式保存是爲了在有重複屬性的狀況下節省存儲空間。爲了找到真實的數據值咱們必須引用相關的<source>結點,將它們的數據按索引指示的一個一個對應取出來使用。

       構造三角形如今變得很是容易了。你要作的事情就是從<p>這個結點下一次讀取3 * (<triangles>結點下<input>結點的數量)個值,而後以這些值爲索引從相應的<source>結點中讀出真正的數據。若是對於每一個頂點只有一個屬性的三角形來講,咱們會看到像下面這樣的<triangles>結點,它只有一個<input>子節點。這種狀況下你就必須一次從<p>中讀取三個數字做爲索引,而後在相應的名爲"verts"的<source>中根據索引讀取其真正的值。

 

<triangles count="2" material="Bricks">

<input semantic="VERTEX" source="#verts" offset="0"/>

<p>

0 3 2

0 2 1

</p>

</triangles>

 

還有一件咱們須要知道的事情就是<triangles>結點的"material"屬性,這個屬性引用了<library_materials>裏面的材質數據,這一個library咱們將在稍後的教程中討論。

本節的介紹所實現的代碼

從COLLADA文件中讀取貼圖文件名

正如你們所知道了,咱們在開始作了一些假設,其中之一就是一個COLLADA只對應着一張紋理貼圖,這讓尋找貼圖的文件名變成很是容易。

咱們所須要作的一切就是讀取<library_images>下「惟一」的<image>結點中的"id"屬性。通常來講它會是COLLADA中使用的紋理貼圖的文件名。不過它可能並非正確的文件名,由於COLLADA可能會建立一個與它文件名不一樣的ID。因此爲了可以正確的讀取文件名,咱們必須讀取<image>結點下的<init_from>子結點,它給出了完整的路徑,其中也包括文件名。對於咱們的目的而言,咱們只關心它的文件名,而不是完整的絕對路徑,因此咱們讀取完整路徑後僅保存文件名而已。

因此咱們保存下<material>的」url」屬性,而後去<library_effects>尋找,但杯具的地方又出現了,<library_effects>能夠說是COLLADA中迄今爲止我所知道的最複雜的一個library。特別是當陰影效果和一些根本在COLLADA文檔中找不到說明的內容被添加進去後,這個<library_effects>會變得異常的複雜和難解。可是我答應過我會讓咱們所講的東西簡單明瞭,因此咱們不會隨便讀取這裏面的數據,除非是它對於定義材質來講很是重要。

若是咱們找到任意材質的<material>裏」url」所對應的<effect>結點,咱們須要尋找一個名爲<phong>或<blin>的結點,這個結點在<profile_COMMON>結點中,<profile_COMMON>又是<effects>的子結點。<phong>或<blin>結點通常位於<profile_COMMON>的子結點<technique>的裏面。一旦咱們找到了<phong>或<blin>,咱們繼續看它們下面的關於材質的各類參數,好比"ambient" "diffuse" "specular" "emission" "shininess" 和 "transparency"等等(注:這些都是<phong>或<blin>的子結點)。若是你但願你的模型看起來感受很是的好,通常來講, "diffuse" "shininess" 和 "transparency"這三組參數足夠你創造出一個有良好觀感的材質了。

咱們怎樣才能用簡單的方法從這些結點中讀取數據呢?通常來講,ambient(環境光), emission(輻射光), diffuse(漫反射光) 和 specular(鏡面光) 結點包含4個浮點數,這4個浮點數在它們的<color>子結點裏,這4個浮點數分別表示材質相應顏色屬性的RGBA組分;而reflectivity(反射)和 transparency(透明度)等只包含一個浮點數。

 

[html]  copy
 

<> <>> </> <> <></> </>  


 

若是咱們把一張紋理貼在物體的表面,那麼物體的漫反射光將不是簡單的顏色,而是一張貼圖,那麼<diffuse>將不會有名爲<color>的子結點。但爲了簡單起見,咱們沒必要擔憂這一點,而是認爲貼圖是貼在物體的漫反射光上,也就是說咱們不用從COLLADA中讀取漫反射光的值。可是咱們須要使用一個OPENGL中定義的默認的漫反射光的值

這就是咱們要實現任何靜態模型所須要的一切了。因此若是你只關心如何從COLLADA中讀取一個靜態的模型,那麼你能夠不用讀下面的部分而直接跳到實現部分。若是你的目的不只僅是這樣,那麼請繼續看咱們是怎樣從COLLADA文件中提取動畫數據的。

本節所用到的代碼

 

場景組織和模型節點組織

這部分並非Sleepy翻譯的,是本文連接最後另外一種介紹中的內容,寫的很好就放到這裏。

這樣想象一下,3d建模的時候,可能場景有多個物件,比喻一個場景有房子、人物。房子是一個模型,可能有窗戶、門、牆等組成。而人由頭、身子、手、腳等組成。scene節點至關於場景的全部物件的根節點。以下圖,根節點id值爲demo_rigged.max。拿着這個id去library_visual_scenes去找,整個物件的節點信息均可見了。

如今問題是如何組織一個物件的結構?爲了簡化這個問題,換一個模型實例。以下圖。物件的根節點是VisualSceneNode。其下面有一個node節點。一個node節點能夠想象爲物件的孩子,好比人的一部分手。顯然手有本身的位置、旋轉屬性,因而matrix節點出現了,是一個16個數據的字符串,其實就是一個Matrix3D。有時候matrix不存在,就是默認單位矩陣吧!注意看子節點instance_geometry,這個是關鍵。表示該node引用這個幾何模型,能夠理解爲這個手的具體幾何模型信息就是連接幾何id= MeshShape的幾何模型。這個id就是library_geometries中出現的geometry節點的id。以下圖:

須要說明的是,這是模型節點結構中最簡單的一種方式,複雜的狀況是visual_scene下面有多個node,而且node又嵌套node。以下圖:

本實例中 visual_scene節點node的子節點使用instance_node引用了一個node,其屬性url=WALL-E_mark_2。這個引用的node在library_nodes能夠看作一根藤,經過這個藤把整個模型串起來。找到library_nodes下面node對應的id=WALL-E_mark_2的節點看其子節點,能夠看到id=mesh27的node是直接引用一個幾何模型url=mesh27-geometry,而後就完結了。對應節點id=wall_e_leg1的節點,引用了一個node節點id=wall_e_leg,這時候咱們要去上一個層次去查找了。總的講,dae的模型結構是嵌套的,使用引用的層次結構。

分析到如今,對應解析靜態模型,應該沒有大問題了。經過遞歸library_visual_scenes和library_nodes(若是有)的Node層次結構,很容易創建整個模型的結構。記得將Node的id設置成對象的name屬性,以便於調試。在個人實現代碼中,使用YObject3DContainer對象對應一個Node,其子Node將被addChild進來。Node及子Node經過matrix的連乘實現位置、旋轉、縮放屬性的傳遞。

 

讀取COLLADA文件中的骨骼數據

咱們假定咱們讀取的是COLLADA文件中的骨骼動畫而不是硬動畫,因此咱們須要讀取COLLADA中的骨骼數據。所謂骨骼,個人意思是讀取關節(骨頭)數據。咱們一樣必須讀取關節的層次關係數據。這會告訴咱們,誰是誰的子關節或誰是某個關節的父關節等等。下面這張圖解釋了關於骨骼的一些術語。記住骨頭和關節其實是同一個東西,它們只是爲了方便闡述而起的名字,咱們從COLLADA文件中讀取的數據其實是關節數據,骨頭只不過是咱們假想的鏈接兩個關節的線。

在下面這張圖裏你會看到咱們的示例文件中的骨骼,還有附在骨骼上的皮膚。

圖6左邊部分的紅點就是咱們從COLLADA中讀出來的關節,鏈接這些點的線是假想的骨頭,它們可使皮膚運動起來。在圖的右邊你能夠看到另外一幀皮膚附着在骨骼上的圖像。

你可能還記得咱們的一些假設,其中之一就是,全部的關節都關聯到皮膚上,這樣會使得<library_visual_scenes>變得很是簡單易讀。你所須要作的一切就是在<visual_scenes>找到骨骼根關節(骨頭)的<node>結點,而後讀出整個關節樹。這麼作的缺點之一是,你將會考慮到有不少的影響皮膚的關節,而事實上它們不會對皮膚形成任何影響。但若是你不將全部的骨頭都附加到皮膚上的話,你會看到類型爲"JOINT" 和"NODE"的<node>結點在骨骼層次中混合出現。但若是你將全部的骨頭附加到皮膚上你就會擁有隻有"JOINT"類型的骨骼樹。這也是不少引擎模型導入的默認處理方法。若是在骨骼層次中類型爲"JOINT" 和"NODE"的<node>結點混合出現,你就必須得讀取<library_visual_scenes>下的<instance_controller>結點,而後每一次讀取<skeleton>的時候你都必須的再讀取一個關節數據。那些類型不是"JOINT"的<node>結點實際上仍然是關節,只不過它們沒有任何效果而已,也就是說它們不會對皮膚形成影響。這就是爲何咱們假設全部骨頭都必須附着在皮膚上,從而使事情容易和簡單。(注:簡單來講,就是將全部類型的接點,不管是在邊界的仍是在中間的,都統一考慮,從而使問題處理起來簡單化,若是對其做區分,則會增長不少諸如邊界等的斷定條件)

爲了讀取骨骼的層次,你須要一個數據結構,它能夠保存同種類型數據結構的大量的子數據和它的父數據的引用(這在實現部分有很清楚的描述)。你還須要保存<node>結點的」SID」屬性。一旦咱們創建了這樣的數據結構,咱們就要找到根骨骼的結點而且遞歸讀取它們的子骨骼與子骨骼的子骨骼……等等,而後將它們保存在上面所說的數據結構中。當你完成了這些工做,你的數據結構能夠清楚的指示出好比:哪一個關節是哪一個關節的子關節而哪一個關節又是哪一個關節的父關節。

       那麼咱們怎麼能找到根骨骼呢?由於咱們假定一個COLLADA文件中只有一個模型,因此咱們不用去讀<scene>結點來查找哪一個場景是被實例化的。咱們能夠當即跳到<library_visual_scenes>裏面惟一的子結點<visual_scene>中去看下面的<node>結點,其<node>結點下有子結點有叫做<instance_controller>的就是咱們想要的,咱們讀取<instance_controller>下的<skeleton>子結點,它會告訴你根結點的ID。由於咱們將全部的骨骼都關聯到了皮膚上,因此在<instance_controller>下只會有一個<skeleton>子結點,這個結點中記錄的就是咱們要找的根骨骼,鏈接在它上面的全部東西都是骨骼的一部分。

若是你看了COLLADA文件中的<node>結點,你會看到全部的<node>結點的第一個子結點都是一個叫做<matrix>的結點。<matrix>包含了16個浮點數,這夠構成了關節矩陣。這也被稱爲局部骨骼轉換矩陣(the local bone transformation matrix)。當咱們將全部關節鏈接起來,咱們須要將它父關節的世界矩陣與子關節的局部矩陣相乘做爲子關節的世界矩陣。對於根關節來講,它沒有父關節,因此它的關節矩陣也就是世界變換矩陣。(注:若是由於我譯得太差你們不理解的話,我簡要說明以下。所謂的骨骼動畫,骨骼控制皮膚,說白了就是所謂骨骼的變換矩陣影響所謂皮膚的那一部分幾何圖形的繪製,也就是繪製表明「皮膚」的網格以前先用它相關的「骨骼」的變換矩陣來變換一下,從而獲得網格繪製的正確位置,這就是所謂骨骼動畫的控制原理。而骨骼是有層次結構的,越是上層的受到別的骨骼影響越少,越是下層的受到別的骨骼的影響就越多,好比你活動一下肘部,雖然你的腕部關節沒動,但你的手掌位置也改變了有木有,而這所謂的影響反應到3D世界也就是它們的變換矩陣的疊加)

到如今爲止,你應該可以讀取骨骼和經過從每一個<node>結點讀出的關節矩陣計算出整個設計好的造型了。在下一節中,咱們將讀取與骨架相關聯的蒙皮信息。

 本節所用代碼

從COLLADA文件中讀取蒙皮信息

迄今爲止咱們已經完成了讀取了幾何數據(頂點信息、材質、紋理貼圖文件名)甚至是模型的骨骼數據。咱們還須要知道的就是骨骼是怎麼關聯皮膚(幾何數據)的。咱們已經讀取了骨骼中的許多關節。但咱們仍然不知道哪一個關節關聯哪一個頂點。一些關節可能根本不關聯任何的頂點。但若是大家還記得我曾經做過的假設,那就是全部的關節必須附加到皮膚上的話,那麼咱們討論的狀況的前提是全部的關節都必須關聯到皮膚上。

爲了正確的關聯全部的皮膚(幾何數據),咱們須要皮膚的數據,這一節中我會試着讓你瞭解咱們從COLLADA文件中的什麼地方能獲取皮膚數據。

再咱們進一步的說明以前,有件事情我必須解釋一下。若是咱們的人物模型每一個頂點只關聯到一個關節上的話,當這個關節移動那麼這部分皮膚固然也相應的會移動,只不過這樣的動畫效果看起來很是的僵硬。這並非咱們實際中所採用的方法,幾乎全部的頂點都會關聯到不止一個關節上。咱們經過所謂的「權值」來表達每一個關聯的關節對相應皮膚的影響。每一個關節對一個頂點有必定百分比的影響,總量是100%。因此權值在皮膚的信息來講是很是重要的的一個。

<library_controllers>

<library_controllers>包含了整個模型中全部的關節各自所關聯的頂點和關聯的頂點的權值信息。依照咱們的假設,咱們只有一組網格和一組骨骼(注:也就是隻1個<mesh>結點和一個<skeleton>結點),因此<library_controllers>下.只有一個<controller>結點。一旦咱們找到了這個僅有的<controller>結點,咱們繼續找到它的<skin>子結點。在<skin>結點中,找到一個<source>結點,這個<source>結點它的子結點<technique_common>下的子結點<accessor>下的子結點<param>中名爲」name」屬性值是"JOINT"(我不作過多的解釋了,由於咱們在前面讀取幾何數據的時候已經分析過<source>結點了),這個結點的<NAME_array>會給你骨骼中全部關節的名字。如今你懂的,你能夠從這個<source>下的<NAME_array>裏的"count"屬性中獲知全部的關節數量。一個<source>的例子以下所示:

[html]  copy
 

< => < = =>   

  •   
  • <> < = = =>  = =   
  •   
  • </> </> </>    


 

若是你回頭看看<library_visual_scene>中<skeleton>下的<node>結點,你就會看到你從<NAME_array>中讀到的全部關節的名字其實是前面<node>結點的SID。

要徹底讀取皮膚數據,首先咱們得先讀取<bind_shape_matrix>,這每每是<skin>的第一個子結點,若是不是的話,那麼遍歷它的全部子結點找到它,而後讀取並保存下來。而後咱們開始讀名爲<vertex_weights>的結點了,它的"count"屬性給出了權值的數量,至今爲止我所知道的是,這個值應該等於模型頂點的數量,這個數量咱們以前讀取幾何數據時已經讀出來了,由於咱們必須爲每一個頂點定義一份權重數據。(注:是一份,不是一個,千萬不要看錯,高潮在後面)

若是你看看<vertex_weights>結點的結構,你會看到至少2個<input>結點,一個的<semantic>屬性爲"JOINT"而另外一個的<semantic>屬性爲"WEIGHT";除此以外還有一個<vcount>結點和一個<v>結點。

當咱們須要讀取每個頂點的權值的時候,咱們循環N次(N = <vertex_weight>的"count"屬性)讀取<vcount>中的每個值。每個值都是影響咱們當前正在讀取的頂點的關節數量。因此咱們必須嵌套的以一對爲一組(在這裏咱們假定在<vertex_weight>中只有兩個<input>結點)讀取M(M = 當前<vcount>的值)組<v>中的索引值。

讀出的每組索引值中,

第一個是以前讀出的名爲"JOINT"的<source>裏<NAME_array>裏面的值的索引(在此假設屬性semantic="JOINT"的<input>它的"offset"屬性值是0)。咱們以前提過怎麼樣尋找對應的source了,不過這個的<input>裏面的"source"屬性也給出了對應的source的ID了(因此不管怎麼說都能找到吧)。

第二個是」semantic」="WEIGHT"的那隻<input>中"source"屬性指出的<source>結點裏的索引了(好繞口)(假設這隻<input>的"offset"屬性值是1)。

 

(注:若是我翻譯得你實在看不懂的話,我用純正的中文來解釋一下:咱們把<vcount>裏面的值一個一個依次取出來,假設當前取出的值是M,而<input>的數量是C(上面假設的是隻有2個),而後咱們得從<v>中一次讀取M*C個值,其中,以C個值爲一組,共有M組數據。爲何有M組數據呢,由於對這個頂點來講,有M個關節能影響它;爲何是以C個值爲一組呢,多的我不知道,但就你所看到的當前例子而言,C=2,第一個是影響它的關節的名字的索引值,第二個是這個關節對它影響的權值。關節+權值,兩者組合起來就是一個完整的數據了。這麼說應該能明白了吧。)

[html]  copy
 

< => < = = =/> < = = =/> <>> <> </> </>  


 

在這個例子裏你能夠看到<vertex_weight>結點爲4個頂點定義了它們的權值(關聯),第一個頂點有3個關聯的關節,第一個頂點的第一個關聯的關節的序號1,這個序號是用在Ssemantic="JOINT"的那個input指明的source中的<NAME_array>裏的。一樣的它的權值在semantic="WEIGHT"的那個input指明的source中的<float_array>裏,序號是0。

 

<skin>下還有另外一個很是重要的子結點,它的名字是<joints>。它通常有兩個<input>子結點:其中一個的屬性semantic="JOINT",它經過"source"屬性引用了一個含「joint」這樣名字的<source>結點;另外一個的屬性semantic="INV_BIND_MATRIX",它也經過"source"屬性引用了一個<source>結點,這個引用的結點爲每一個關節都定義一個反向綁定矩陣(注:原文with inverse bind matrices for each Joint,全文是And the second <input> with semantic="INV_BIND_MATRIX" references the source with inverse bind matrices for each Joint through the attribute "source")。這個包含了反向綁定矩陣的<source>含有 關節數量*16 個值用以記錄與關節數量同樣的那麼多個反向綁定矩陣。這個矩陣是蒙皮所須要的,你們讀了實現部分後就知道了。

一旦咱們讀完<controller>結點,咱們會有一個動做綁定矩陣(Bind shape matrix)及不少的關節及它們的反向綁定矩陣(Inverse bind matrices),還有就是咱們早先從<visual_scene>中讀取的關節矩陣。每一個頂點都受到一個或多個骨骼的影響(記住這個條件的反面就是:每一個關節必須至少對一個或多個頂點形成影響,實際上這是不對的,由於他們多是端點(注:原文since their might be Joints我想應該正好相反),不影響任何頂點)。所以咱們必須擁有它們的權值信息。

到了如今這一步爲止,你應該可以讀取COLLADA文件中的幾何數據、骨骼數據和蒙皮數據。而且你可以以原始三角形繪製模型甚至可以繪製出它的骨骼。儘管我尚未討論你怎樣能夠爲每一個關節疊加它們的世界矩陣而後將其以世界座標的形式來繪製從而方便調試使用。但我想我能夠給你一個提示,咱們必須將父關節的世界矩陣乘以當前關節的矩陣而後將它做爲固然關節的世界矩陣保存起來。咱們必須從根關節開始作這件事。從而咱們不會從父節點中獲取污染了的矩陣,並且根關節的世界矩陣同時也是根關節自己的變換矩陣,由於根關節沒有任何的父關節(注:也就是說把開始繪製固然模型時的世界矩陣看成根關節的矩陣,而不要從新的維護一個本身的,整個骨骼每次都從根關節的矩陣也就是當前模型的世界矩陣開始從新計算一遍,這樣也不會形成矩陣重複疊加的錯誤。儘管這裏作了一個很複雜的解釋,但我想實際上他不說你們也都是這麼作的不是嗎)。若是你同時還在讀COLLADA的1.5版規範說明,你能夠找到蒙皮的公式,因此你也能夠本身將模型擺成文件中定義好的各個形狀(注:動畫數據其實就是一個一個的POSE和擺出這個POSE的時間,只不過按時間的流逝不停的擺出POSE而且還計算出兩個時間點之間的中間POSE從而讓動畫看起來更平滑而已,這是後文)。到如今咱們還沒討論到怎麼讓這個模型動起來,咱們會在下一節討論這點。

點擊打開本節代碼

讀取COLLADA文件中的動畫數據

       迄今爲止咱們已經能夠讀取靜態模型的全部數據了,還剩下的惟一的事情就是理解和讀取動畫部分的數據。COLLADA的動畫並非很是成熟,能夠說它還處在幼年時期,過一段時間後說不定它的動畫會變得更成熟更好。但就從實現咱們的目的這點來看,咱們還有許多值得擔憂的地方。

      

<library_animations>

       在這個library裏保存了全部的動畫數據。對於每一個關節的動畫,你會看到一個<animation>結點,它包含了相關關節的詳細動畫數據。請記住,一個<animation>通道(注:也就是它下面所關聯的一系列數據)會改變它所做用的目標原來的形狀,它的做用目標通常而言是關節(注:而不是所謂的骨骼,骨骼是假想的東西)。

       在<animation>下有三種類型的子結點,第一種一般是一系列的記錄數據的<source>,第二種是<sampler>,第三種是<channel>。你須要<sampler>和<channel>結點來得到動畫數據關聯的目標。

       在<channel>結點裏你會得到這個動畫數據做用的對象的ID。(注:這極難翻譯的原文是From <channel> node you pick the target which gives you the ID of the Object on which the Animation data will be applied. And you also get the Sampler ID from where you will pick the sources from which you will pick the animation Data.)

       下面的例子是不不會出如今咱們的示例COLLADA文件中的,由於咱們假定文件記錄用的是背向矩陣(backing matrices)。但這樣的例子比較容易理解。

[html]  copy
 

Example:  

  •   
  • < => < = =>> <> < = = => < = =/> </> </   
  •   
  • </> < => < = =>> <> < = = => < = =/> </> </> </> < => < = =>> <> < = = => < = =/> </   
  •   
  • </> </> < => < = =/> < = =/> < = =/> </> < = =/>    


 

如今咱們從底部的<channel>結點開始分析。

這表示在場景中有一個叫作"astroBoy_Skeleton"的實體(對咱們來講這實體就是關節),它的動畫其中的「X方向變換」(trans.X)是由叫作"astroSkeleton-sampler"的採樣器控制的。

       因此咱們須要知道"astroSkeleton-sampler"採樣器是怎樣對實體座標的進行X變換的,咱們須要讀取<sampler>結點,它會告訴咱們這一點。

       爲了得到動畫數據,你須要讀取3種輸入信息(也就是<input>)結點。

 

       第一種<intput>結點是:INPUT

       第二種<intput>結點是:OUTPUT

       第三種<intput>結點是:INTERPOLATION

 

       當咱們開始讀取<sampler>下的<input>結點。

       屬性」semantic」 = "INPUT"的的告訴咱們動畫的輸入 <source>

       屬性」semantic」 = "OUTPUT"的告訴咱們動畫的輸出 <source>

       屬性」semantic」 = "INTERPOLATON"的告訴咱們動畫的插值<source>

 

       當咱們讀取這一堆<source>時,咱們看到<sampler>下屬性」semantic」爲"INPUT"的<input>子結點所引用的<source>結點,其子結點<technique_common>下的子結點<accessor>下的子節點<param>的名字爲」TIME」,簡單來講這個source包含了動畫的一系列類型爲浮點型的時間信息。

       而<sampler>下屬性」semantic」爲"OUTPUT"的<input>子結點所引用的<source>結點,其子結點<technique_common>下的子結點<accessor>下的子節點<param>的名字爲」 TRANSFORM」。這說明了這個source所包含的一系列浮點型的值爲X座標變換,這些變換的值與上面讀取的時間相對應。(爲何是X座標的變換呢,由於<channel>中指明瞭是關於X軸的變換,它所屬的一系列數據天然也是一樣的意義了)

      

       <sampler>下屬性」semantic」爲"INTERPOLATION"的<input>子結點所引用的<source>結點,其子結點<technique_common>下的子結點<accessor>下的子節點<param>的名字爲」INTERPOLATION」。這個source以字符串的方式說明了前面咱們讀取的OUPUT中的值所應採起的插值方式(在Max中它的插值方式一般都是」LINERA」(線性插值),因此咱們能夠不讀這個source而直接默認所有采用線性插值)。

       最後一個source(注:就是插值的那個)是什麼意思呢,好比對應兩個時間點,咱們能夠相應的從OUTPUT中取出兩個值。那麼若是這個時間正好落在這兩個時間點之間呢,咱們怎麼作它的動畫?因而咱們經過插值來獲得那個中間時間的OUTPUT值。如以前所說的,咱們能夠用簡單的線性插值來實現。

       你所看到的名爲TIME的source,其實是動畫的關鍵幀。OUTPUT中所對應的數據,就是關鍵幀的數據。具體來講在這裏就是控制實體的X座標變換的關鍵幀數據了。

       因此在你的代碼中不斷的獲取時間相應的OUTPUT值,並將其做爲X變換因子做用於實體上,那麼你的模型的動畫就實現了。用線性插值計算關鍵幀之間的插值數據,會讓你的動畫看起來更加的平滑。

插值是什麼意思?

插值就是計算一個值或多個值間的任意中間值。

 

       好比咱們有值X和Y,咱們要計算它們兩個的「中間」的值(注:也就是1/2處的值),咱們使用0.5做爲插值因數,這個插值因數咱們稱之爲「T」。若是咱們要找到X和Y間3/4處的值,咱們使用的插值因數T=0.75,以此類推(注:原文3-Quater應爲3-Quarter即四分之三)。

       你可讓T以不一樣增量好比0.00一、0.0一、0.05等等作一個從0.0到1.0的循環,而後你就能夠獲得它們之間的不少不少插值。

       線性插值是一種很簡單的插值方法,它的公式以下所示:

float Interpolate( float a_Value1, float a_Value2, float a_T)

{

return ((a_Value2 * a_T) + ((1 - a_T) * a_Value1));

}          

 

這個公式代表,若是"a_T"爲0,那麼它會返回給你的值;若是它是1,那麼會返回給你的值;若是它是0到1之間的值,那麼它會返回一個a_Value1到a_Value2之間的值。

實際上還有其它更好的插值方法。好比貝塞爾插值,三次方插值等。它們有更爲複雜的公式,並且它們的插值是基於多於兩個值的狀況。但咱們只使用線性插值,這也是爲了簡單考慮。

如今正如咱們以前所說的,這個例子並非咱們的示例文件中所出現的實際內容,因此讓咱們來看看實際內容是怎麼樣的。

謹記咱們的假設,咱們只有兩種類型的<animation>結點,同時咱們有16*3=48個<source>和16個<sampler>與16個<channel>結點,或者咱們有3個<source>、1個<sampler>和1個<channel>。在第一種狀況下"target"屬性在最後的「/」以前含有"transform (X) (Y)"這樣的記錄;而在第二種狀況下,"target"屬性在最後的「/」以前則只含有"transform"這樣的記錄。

 

這種狀況:

<channel source="#astroSkeleton-sampler" target="astroBoy_Skeleton/transform (0) (0)"/>

或是這種狀況:

<channel source="#astroSkeleton-sampler" target="astroBoy_Skeleton/transform"/>

 

第二種狀況,咱們得到的矩陣的值,是不屬於那三種source之中任意一種的,這和咱們在控制器的反向矩陣中遇到的狀況是同樣的。而第一種狀況下組成4X4矩陣的每個值來自不一樣的source,所以當咱們讀數據的時候,必須把它們組合起來。

若是你記得咱們從<visual_scene>中讀取每個關節的矩陣時,這些咱們從<animation>結點中讀出的值(它們應該是矩陣,由於咱們將它們背向了 原文:(which will be matrices, since we backed matrices) 什麼玩意),它們經過子結點<channel>的"target"屬性來指明做用的對象(target,其實是關節joint),它會替換它所做用的關節的矩陣,咱們早前從<visual_scene>讀出來的每一個關鍵幀在這裏的animation中被定義。咱們爲每一個關節計算它們的世界矩陣,咱們用新的關節矩陣乘以它的父關節世界矩陣

好了,這就是全部的東西了(原文:And that’s all pretty much it.這是什麼鳥語)。若是你從頭至尾讀完了這篇教程,我猜你已經能夠寫出你本身的COLLADA文檔導出工具了。並且如今你能夠準備去讀這篇教程的下一部分了,若是你以前尚未看過的話。

 

一些關於Collada的鏈接:

原文:http://www.wazim.com/Collada_Tutorial_1.htm

另外一種介紹:http://www.the3frames.com/?p=788

感謝原做者:http://blog.csdn.net/qyfcool/article/details/6775309

相關文章
相關標籤/搜索