<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    3D游戲中角色的換裝原理-落櫻之劍實例圖文詳細(xì)剖析(JME3,JMonkeyEngine游戲開發(fā))

    *******************************************************************************************************

    聲明:轉(zhuǎn)載請包含 * 號內(nèi)這段聲明(含 * 號)

    作者: huliqing

    Email: 31703299@qq.com

    原文鏈接:http://www.huliqing.name/article/articleId=41

    *******************************************************************************************************

    前言

    本文將詳細(xì)講解3D游戲中換裝的原理及換裝中的一些重點問題,先粗略看一下?lián)Q裝的簡單原理:

    change1.jpg

    change2.jpg

    沒錯,看起來很簡單吧!!!

    Orz,說真的,要不是開發(fā)了“落櫻之劍”這個游戲,順便想打一下軟廣告,我還真不想去寫這么長一篇文章,去貼這么多圖!因為寫清楚、說明白、外加圖文并茂,還是要花不少心思的,并且我相信大部分程序員是不會建模的,很多美工也不會去寫代碼的,因為這篇文章會同時涉及到編程和建模,所以可能有很多人看明白了但是卻很難做出來。如果你還有興趣那就看下去吧,如果有錯誤,也希望有朋友幫我指出來。

    那么接下來,開始講復(fù)雜的地方!

    在閱讀本文之前如果你能夠了解或知道以下一些基礎(chǔ)知識,可以幫助你更好的了解3D換裝原理,因為后面的講解或多或少會引用或涉及到這些內(nèi)容。

    • Blender - 開源的3D建模工具,或了解過其它如:3dmax,maya之類的建模工具都可以。了解一些其中的基礎(chǔ)建模、骨骼動畫和蒙皮知識
    • JME3 - 開源3D游戲引擎,全名:JMonkeyEngine,基于Java。了解一些Java基礎(chǔ)編程,文章后面將在這個游戲引擎中用代碼示例如何換裝。
    • MakeHuman - 開源的人物建模工具,可幫你快速建立人物模型及標(biāo)準(zhǔn)骨骼.
    • ogre3D - blender導(dǎo)出插件,我們將用它來導(dǎo)出ogre格式的模型文件
    • 落櫻之劍 - 我開發(fā)的免費Android游戲,沒錯,打了個軟廣告,還加粗了呢! :) 用于換裝演示,含換裝脫裝、換武器、換眼鏡、換面罩、換角、換耳朵、換發(fā)形,...

    版本要求: Blender2.69, JME3.0, ogre插件v0.6.0

    你可以償試更新的版本來學(xué)習(xí)這個教程,但建議使用與我一樣的版本。

    一、準(zhǔn)備

    在整個講解過程中需要以下一些東西來進(jìn)行說明,我們將通過各種工具一步一步來完成以下物件,并最終使用這些物件在游戲中演示如何進(jìn)行換裝。

    • 一個標(biāo)準(zhǔn)的人物骨骼 - 用于控制角色基本身形、裝備以及角色動畫
    • 一套基本身形 - 用于模擬人物角色的皮膚,即脫光裝備后的樣子,這里以內(nèi)衣裝示例,后面我稱為“身形”或“身體”
    • 一套角色裝備 - 用于換裝示例,后面我統(tǒng)稱為“裝備”

    注:角色基本身形和裝備的本質(zhì)是一樣的,切換原理也是一樣的, 這里先區(qū)分開來,以方便后面進(jìn)行說明。由于文章的重點是講解“換裝”原理,以及換裝過程中可能遇到的重要問題,所以繁瑣的"建模"過程我可能會簡單略過。

    二、確定和建立自己的標(biāo)準(zhǔn)骨骼

    首先,你可能也知道一些骨骼動畫的基本知識,我這里不秀概念,盡量講清楚明白。

    就像人體的骨骼和皮膚的關(guān)系一樣,當(dāng)人的手在動的時候,實際上就是手上的骨頭帶動了手上的皮膚在動。原理一樣,這里的骨骼和人體的骨骼一樣,3D中的骨骼在動畫過程中通過各個骨頭來帶動皮膚(或裝備)模型中的各個頂點進(jìn)行運動就形成了骨骼動畫。模型中的每個頂點都可能受一個或多個骨頭影響,而這些影響可能不一樣,有一些影響大一些,有一些影響小一些,這個影響值就是權(quán)重。

    因此在開始講解換裝之前,我們需要先建立一個骨骼(或稱骨架),盡量建得標(biāo)準(zhǔn)一些,確定一個標(biāo)準(zhǔn)的骨骼非常重要,因為后面我們的游戲角色動畫都可以使用這一套骨骼,身體模型和裝備也都是要以這一套骨骼來建模的。下面我稱這套骨骼為標(biāo)準(zhǔn)骨骼

    關(guān)于標(biāo)準(zhǔn)骨骼你可以自己手動在blender或其它3D建模工具中手動一根一根建立,或使用一些建模插件或是從一些開源軟件中獲得,比如從MakeHuman中建立一個模型,然后給模型配置一個骨骼,如下圖:

    mh_skeleton.jpg

    根據(jù)自己的需要和模型動畫的復(fù)雜度,選擇不同的骨骼,如果你的模型需要包含眼部、嘴部或手指動畫等,你就可以選擇包含眼部、嘴部和手指等骨頭的骨骼。當(dāng)然,骨骼和模型越復(fù)雜,對后續(xù)游戲性能影響也就越大,根據(jù)需要適當(dāng)選擇。

    在MakeHuman中建立了模型之后導(dǎo)出為mhx,再把它導(dǎo)入到blender中,就可以在blender中獲得一套比較標(biāo)準(zhǔn)的骨骼了。

    mh_export.jpg

    下面的講解我將以我所開發(fā)的游戲落櫻之劍中使用的標(biāo)準(zhǔn)骨骼來進(jìn)行說明:

    blender_ske.jpg

    骷骼正面(手指處的名稱太雜亂,我沒有顯示出來)

    blender_ske2.jpg

    骨骼側(cè)面

    注意,給骨骼中的骨頭起個有意義的名稱也很重要。

    三、建立基本身形并切割身形

    下面是根據(jù)標(biāo)準(zhǔn)骨骼建立的基本身形,你也可以在建立模型后再根據(jù)模型確定自己的標(biāo)準(zhǔn)骨骼,根據(jù)自己需要而定。 你也可以直接把從MakeHuman中導(dǎo)出的模型來作為基本身型使用,但是一般情況還是需要根據(jù)自己的需要調(diào)節(jié)一下,比如導(dǎo)出的模型面數(shù)或頂點數(shù)可能達(dá)到1萬多,在大部分情況下對于游戲來說精度太高,會影響游戲性能, 建模是一個繁瑣的過程,也不是我們要講的重點,這里就不詳細(xì)介紹了。 下面是落櫻之劍中主角的基本身形,看一下是如何和標(biāo)準(zhǔn)骨骼匹配的。

    base1.jpg base2.jpg base3.jpg

    注:這個基本身形非常重要,因為后面的裝備都是以這個身形為基礎(chǔ)建立的。

    現(xiàn)在我們需要把身型切割成一個一個的部分,因為在后面的游戲中我們需要對不同部位進(jìn)行替換,來達(dá)到“換裝”的效果。 在落櫻之劍中我是這樣切割的:

    base4.jpg

    腳(foot),下身(lowerBody), 上身(upperBody), 手部(hand), 臉部(face), 眼睛(eye), 耳朵(ear),頭發(fā)(hair),眼罩(blinder),角(horn),臉罩(mouthmask),...

    注:左手和右手是作為一個整體切割出來的。

    由于篇幅和模型復(fù)雜度的關(guān)系一部分基本身型我沒有顯示出來,并且也不打算在這里講解如何切割模型,如果你懂得一些建模的知識,你應(yīng)該很清楚如何做,切割模型很簡單,在明白了原理之后你也可以根據(jù)自己實際的需要切割成更多或更細(xì)的部分。

    為了方便說明,我把切割后的模型各部分都移動了一下,實際切割后的模型應(yīng)該保留原來的位置不變,以便后續(xù)的操作。特別是切割后的模型分界線,

    line1.jpg line2.jpg

    這里以手部的分界線為例,上身和手部的分界線是一樣的,分界線上的這些頂點位置不能移動,特別是在制作裝備的時候,應(yīng)該盡量保持這些分界線上的頂點位置不變動,否則“換裝”后的整個模型就可能出現(xiàn)裂縫, 因為后續(xù)可能會制作很多的裝備,這些裝備需要能夠和身形以及其它裝備進(jìn)行無縫匹配,那么嚴(yán)格遵守這個規(guī)范就很重要。

    四、將身形綁定到骨骼和調(diào)整邊界權(quán)重

    在切割了身形之后,我們需要為身形的各個部分分配骨骼權(quán)重,因為身形和裝備最終都是要由標(biāo)準(zhǔn)骨骼來控制的,所以身形的各部分需要各自綁定到骨骼中去,在最終的產(chǎn)出物中身形的各個部分是各自獨立的,并且這些身形和裝備都附帶有骨骼權(quán)重信息,這些信息最終在游戲中會被用到主骨骼(標(biāo)準(zhǔn)骨骼)中去。

    現(xiàn)在,假如我把身形切分成“腳”,“下身”,“上身”,“手”,“臉”。 先以“腳”為例來進(jìn)行說明,其它部分操作原理是一樣的。

    首先給“腳”分配骨骼和確定“連接骨骼”

    從標(biāo)準(zhǔn)骨骼中復(fù)制1個骨骼出來,記得保留原始標(biāo)準(zhǔn)骨骼,因為后面我們需要用它來作為控制角色的主骨骼,這個臨時復(fù)制出來的骨骼只是為了給“腳”部分配權(quán)重,后續(xù)在游戲中我們是不需要這些臨時骨骼的,如圖所示,

    link1.jpg

    將復(fù)制出來的骨骼刪除掉一些不必要的骨頭,因為腳部非常小,需要綁定的骨骼也很少,所以shin.L和shin.R的父骨骼部分都可以不需要。

    link2.jpg

    然而為什么需要shin.L和shin.R部分的骨頭呢?因為腳很小,所以不是只要foot.L和foot.R及其子骨骼部分就可以了嗎? 因為需要一些有特殊意義的骨頭,為了處理角色在“換裝”后可能出現(xiàn)的“裂縫”現(xiàn)象,需要使用這些特殊骨頭,我把這些為了處理模型邊界裂縫、在兩個連接模型中都出現(xiàn)、同時連接兩個身體模型的骨頭稱為“連接骨骼”。 比如(參考下圖):

    • 連接“腳”和“下身”的骨頭: shin.L,shin.R
    • 連接“下身”和“上身”的骨頭: hips
    • 連接“手”和“上身”的骨頭: hand.L, hand.R
    • 連接“上身”和“臉”的骨頭: neck

    一般情況下,這些“連接骨骼”需要根據(jù)自己模型的切割方式來定義,下圖是我在游戲“落櫻之劍”中對模型各個部分分配骨骼后的劃分參考!

    link3.jpg

    通常來說,一旦確定了身形各個部分和骨骼各個部分的劃分之后,這些定義(或稱規(guī)范)都可以通用到你后續(xù)裝備的制作部分,所以你可以把身形和相關(guān)骨骼部分備份起來以便后續(xù)制作裝備時使用,特別是在制作裝備的時候要盡量保持“裝備”各部分的分界點與劃分身形時各部分的分界點一致(不移動,不增刪頂點),以做到無縫匹配。

    給“腳”分配權(quán)重

    身形的其它部分操作與腳相似,劃分及確定連接骨骼請參考上圖。接著需要把身形的各部分分別綁定到各自所分配的骨骼上去,仍然以“腳”為例來說明。

    操作: 右鍵選擇“腳” -> 按住Shift不放再選擇腳部骨骼 -> 按Ctr+P -> 選擇“With Automaic Weights”來自動分匹配權(quán)重。如圖:

    weight1.jpgweight2.jpg

    在綁定后你就可以使用骨骼來控制腳部或制作動畫了,但是在這里我們不需要為特定身形或裝備來制作動畫,我們需要的只是身形(或裝備)與骨骼的權(quán)重信息就可以,所有的動畫我們將在“標(biāo)準(zhǔn)骨骼”中制作。現(xiàn)在我們還有一個更重要的問題要處理 - 處理“裂縫”

    處理“裂縫”

    在給“腳”綁定了骨骼之后,由于是使用自動分配權(quán)重(With Automatic Weight), 自動分配權(quán)重是個好東西,但是可能在“換裝”游戲中由于模型邊界處頂點權(quán)重的分布不一致而導(dǎo)致角色皮膚或裝備出現(xiàn)裂縫的現(xiàn)象。現(xiàn)在我們需要為這些身形確定一些權(quán)重分配規(guī)則,以及通過這些規(guī)則來調(diào)整一下權(quán)重分配,有可能你的規(guī)范與我的不一樣,下面是我在落櫻之劍中定義的規(guī)則。

    • 身形邊界點(邊界處的頂點)只接受連接骨骼的影響,并且影響權(quán)重是1.0
    • 身形邊界點不接受其它骨骼的權(quán)重影響,所有其它非連接骷髏對于邊界點的權(quán)重都要清0.
    • 其它部分按默認(rèn)系統(tǒng)自動分配的權(quán)重或適當(dāng)按需調(diào)整

    現(xiàn)在說明為什么要這樣做,我們以“下身”左邊和左“腳”的邊界點來說明,由于兩個邊界都是以連接骨骼"shin.L"進(jìn)行連接的,shin.L對“下身”和“腳”邊界的權(quán)重影響都是1.0,并且這些邊界點不受其它骨骼的影響,這樣當(dāng)shin.L在運動的時候就能保證對邊界處頂點的完全控制,使它們的運動行為一致而不會出現(xiàn)裂縫。具體參考下圖:

    weight3.jpg weight4.jpg weight5.jpg

    或許對于邊界裂縫的處理方式不同的人會有不同的處理方式,但是遵守一定的規(guī)則是有意義的,特別是在后續(xù)裝備的制作。身形的其它部分對裂縫的處理方式與“腳”和“下身”的處理方式是一樣的,這里就不一一詳細(xì)說明。

    五、根據(jù)基本身形建立裝備模型

    在建立基本身形并綁定了權(quán)重之后,現(xiàn)在我們來制作一套“裝備”,以便后續(xù)在游戲中演示如何進(jìn)行“換裝”。

    裝備的性質(zhì)實際與基本身形是一樣的,只是看起來像是一套裝甲而已,建立過程與基本身形差不多,所以一般也是在基本身形上建模而成的,并且為了確保與基本身形以及其它裝備之間的無縫匹配(不出現(xiàn)邊界處的裂縫現(xiàn)象),我們在制作裝備的時候需要確保邊界處的頂點盡量不要被編輯到,即不移動也不增刪頂點,雖然這并不是絕對必須遵守的原則,比如一些比較寬大的裝備或許可以遮住分界處的裂縫,這個時候邊界處的頂點即使稍微移動或改變也可能不會有什么大的影響,但是始終遵守邊界頂點不被編輯可以減少模型制作時候的很多麻煩。

    現(xiàn)在使用我在落櫻之劍中為角色創(chuàng)建的一些“裝備”來示例說明:

    border.jpg

    這些“裝備”是從基本身形中修改而來的,建模過程就略了,重點注意裝備各部分箭頭所示處的頂點與原始身形的位置是一致的,這些是原始身形的邊界點,為了與原始身形無縫嵌接,這些頂點在建模裝備的時候必須保持與原來的位置一致(這里為了方便示例,我對各個部分做了一些移動)。

    在建立了裝備后,同樣需要給裝備綁定骨骼,綁定骨骼和分配權(quán)重的過程與前面“基本身形”的處理過程一樣遵守相同的規(guī)則,即原始邊界點處只接受連接骨骼的影響(權(quán)重1.0),非連接骨骼不影響邊界處的頂點。如下圖:

    border2.jpg border3.jpg border4.jpg

    六、使用Ogre3D插件導(dǎo)出骨骼、基本身形、裝備等物件

    在經(jīng)過前面的過程后,現(xiàn)在我們在blender中獲得了這些東西:

    • 一套標(biāo)準(zhǔn)骨骼(可帶動畫,也可不帶)
    • 一套基本身形(包含身形的各部分,并已經(jīng)全部綁定了骨骼)
    • 一套可換的裝備(包含裝備各部分,并已經(jīng)全部綁定了骨骼)

    現(xiàn)在我們需要把這些東西從blender中導(dǎo)出,我這里使用ogre3D插件來導(dǎo)出,你可以從ogre3D官網(wǎng)中找到相關(guān)插件(詳細(xì)看說明,注意版本的兼容)。

    export1.jpg

    使用ogre插件分別導(dǎo)出以下相應(yīng)物件:

    1. 導(dǎo)出標(biāo)準(zhǔn)骨骼
    2. 導(dǎo)出身形的各個部分,“腳”,“下身”,“上身”,“手”,“臉”...等
    3. 導(dǎo)出裝備的各個部分,“腳”,“下身”,“上身”,“手” 裝備部分我們只要這一些就可以。

    注:在導(dǎo)出標(biāo)準(zhǔn)骨骼時,ogre3D可能不允許單獨導(dǎo)出骨骼,這時可以隨便找個模型(如簡單的立方體)綁定到骨骼中去,再進(jìn)行導(dǎo)出。

    在使用ogre3D導(dǎo)出后,我們將獲得這些文件(文件名稱依據(jù)物體在blender中的名稱而定):

    1.標(biāo)準(zhǔn)骨骼文件:

    ske.skeleton.xml, ske.mesh.xml, ske.material
    

    2.基本身形各部分:

    foot0.skeleton.xml, foot0.mesh.xml, foot0.material
    lowerBody0.skeleton.xml, lowerBody0.mesh.xml, lowerBody0.material
    upperBody0.skeleton.xml, upperBody0.mesh.xml, upperBody0.material
    hand0.skeleton.xml, hand0.mesh.xml, hand0.material
    face0.skeleton.xml, face0.mesh.xml, face0.material
    ...
    

    3.裝備各部分:

    foot1.skeleton.xml, foot1.mesh.xml, foot1.material
    lowerBody1.skeleton.xml, lowerBody1.mesh.xml, lowerBody1.material
    upperBody1.skeleton.xml,upperBody0.mesh.xml, upperBody1.material
    hand1.skeleton.xml, hand1.mesh.xml, hand1.material
    

    export2.jpg

    七、將ogre3D格式的文件轉(zhuǎn)換為j3O文件

    接下來的講解需要你了解過一些JME3游戲引擎的知識,因為這一部分的說明會與特定引擎和環(huán)境有關(guān),并非所有游戲引擎通用,但是了解一下也有益處,畢竟道理和原理是差不多通用的。

    從ogre3D導(dǎo)出的文件為ogre格式,現(xiàn)在需要把這些文件轉(zhuǎn)換為j3o格式,j3o是JME3游戲引擎默認(rèn)使用的模型文件格式。因此在JME3使用這些模型之前需要將它們轉(zhuǎn)為j3o格式。

    操作

    1. 打開JME3的SDK,然后創(chuàng)建或打開你的JME項目.(JME3官網(wǎng)下載SDK,請使用JME3.0穩(wěn)定版)
    2. 將所有剛導(dǎo)出的ogre格式的模型文件(標(biāo)準(zhǔn)骨骼,身形,裝備)一起拷貝到JME項目的asset目錄下,如: "assets/Textures/demo"
    3. 在SDK中,將所有xxx.mesh.xml文件一個一個轉(zhuǎn)換為j3o文件,操作: 右鍵選擇xxx.mesh.xml文件 -> 轉(zhuǎn)換為j3o

    現(xiàn)在我們將得到這些文件(省略號部分自己腦補):

    標(biāo)準(zhǔn)骨骼: ske.mesh.j3o
    基本身形: foot0.mesh.j3o, lowerBody0.mesh.j3o, upperBody0.mesh.j3o, ...
    裝 備: foot1.mesh.j3o, lowerBody1.mesh.j3o, upperBody1.mesh.j3o, ...
    

    八、調(diào)整標(biāo)準(zhǔn)骨骼,身形和裝備

    在把標(biāo)準(zhǔn)骨骼、身形和裝備轉(zhuǎn)換為j3o格式的模型之后我們還需要對其做一些調(diào)整,以便在游戲中使用。

    調(diào)整標(biāo)準(zhǔn)骨骼

    如果從blender中導(dǎo)出標(biāo)準(zhǔn)骷髏的時候我們給骨骼綁定了任何模型,這時轉(zhuǎn)換后的ske.mesh.j3o文件中可能包含這些模型,然而我們并不需要它們, 因為標(biāo)準(zhǔn)骨骼只包含骨骼就可以,在游戲中“換裝”的時候模型(身形或裝備)是動態(tài)添加上去的。下面對骨骼中的多余模型進(jìn)行刪除操作:

    在SDK中選擇模型(ske.mesh.j3o) -> 右鍵選擇“Edit in SceneComposer” -> 在SceneExplorer Window中將多余的Geometry刪除掉,如圖:

    model1.jpg

    調(diào)整身形及裝備

    所有身形和裝備的調(diào)整過程是一樣的,所以這里統(tǒng)一進(jìn)行說明。

    身形和裝備的調(diào)整剛好與骨骼的調(diào)整相反,由于身形和裝備最終是添加(attach)到標(biāo)準(zhǔn)骷髏中去的,由標(biāo)準(zhǔn)骨骼進(jìn)行控制,所以身形和裝備自身就不需要包含骨骼。但是從blender中導(dǎo)出身形和裝備的時候,由于我們需要骨骼與權(quán)重的相關(guān)信息,所以導(dǎo)出并轉(zhuǎn)換為j3o后的模型中包含了骨骼,所以現(xiàn)在需要把這些東西處理掉,只保留權(quán)重信息就可以,這里稍微有一些復(fù)雜。

    由于在blender中我們使用了不完整的骨骼去綁定身形和裝備的各個部分。 所以在導(dǎo)出并轉(zhuǎn)換后的身形或裝備(如:foot0.mesh.j3o)中所包含的骨骼索引和標(biāo)準(zhǔn)骨骼中的索引是有可能不一樣的。舉個例子,標(biāo)準(zhǔn)骨骼(ske.mesh.j3o)中名稱為foot.L的骨頭的索引值可能是10,而腳(foot0.mesh.j3o)自身骨骼中的foot.L的骨頭的索引值可能是其它值,這種不一致最終會導(dǎo)致在執(zhí)行角色動畫的時候,無法從“標(biāo)準(zhǔn)骨骼”中找到正確的用于控制“腳”部動畫的骨頭。

    現(xiàn)在我們需要執(zhí)行一些操作來重定向“腳”(foot0.mesh.j3o)中頂點和骨骼之間的權(quán)重信息中的骨骼索引,使它指向標(biāo)準(zhǔn)骨骼中正確的骨頭,然后“腳”中的骨骼就可以不要了,順便各種Control也可以丟掉,只保留網(wǎng)格(Geometry)就行,因為權(quán)重等信息保存在Geometry的mesh中,所以丟掉其它東西是沒有問題的。這一段說得有點繞,操作也有些復(fù)雜,在明白原理之后你可以自己進(jìn)行處理,也可以使用下面我提供的方法來處理骨骼索引的重定向(注:骨骼索引重定向這一步是可以有其它方法繞過的,看附錄)。

    package name.huliqing.fighter.utils.modifier;
    import com.jme3.animation.AnimControl;
    import com.jme3.animation.Skeleton;
    import com.jme3.animation.SkeletonControl;
    import com.jme3.asset.AssetManager;
    import com.jme3.scene.Geometry;
    import com.jme3.scene.Mesh;
    import com.jme3.scene.Spatial;
    import com.jme3.scene.VertexBuffer;
    import java.nio.ByteBuffer;
    import java.util.List;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import name.huliqing.fighter.Common;
    import name.huliqing.fighter.utils.GeometryUtils;
    import name.huliqing.fighter.utils.ModelFileUtils;
    /**
    * @author huliqing
    */
    public class OutfitUtils {
    private final static Logger logger = Logger.getLogger(OutfitUtils.class.getName());
    // 標(biāo)記是否已經(jīng)進(jìn)行過重定向
    private final static String REDIRECT_BONE_INDEX_OK = "redirect_bone_index_ok";
    // 標(biāo)準(zhǔn)骨骼
    private final static String RIG_SKE_PATH = "Models/actor/ske.mesh.j3o";
    // 將裝備或身形中的骨骼索引進(jìn)行重定向
    public static void redirectBoneIndex(String outfitFile, String rigSkeFile) {
    AssetManager am = Common.getAssetManager();
    Spatial outfit = am.loadModel(outfitFile);
    if (outfit.getUserData(REDIRECT_BONE_INDEX_OK) != null) {
    logger.log(Level.WARNING, "Outfit name={0} has already redirect bone index!", outfit.getName());
    return;
    }
    AnimControl outfitAC = outfit.getControl(AnimControl.class);
    SkeletonControl outfitSC = outfit.getControl(SkeletonControl.class);
    if (outfitSC == null) {
    return;
    }
    // 移除control
    outfit.removeControl(outfitAC);
    outfit.removeControl(outfitSC);
    // 重定向boneIndex
    Skeleton rigSke = am.loadModel(rigSkeFile).getControl(SkeletonControl.class).getSkeleton();
    // skin中可能存在多個Geometry,每一個都要進(jìn)行處理.
    List<Geometry> geos = GeometryUtils.findAllGeometry(outfit);
    Skeleton outfitSke = outfitSC.getSkeleton();
    for (Geometry geo : geos) {
    // 找到Mesh中的骨骼索引
    // 這里需要檢測并初始化一次就可以, 不能重復(fù)做,即使skin是重新load進(jìn)來的
    // 因為geometry或mesh可能進(jìn)行了緩存,所以即使重新Loader.loadSkin(),可能
    // 載入的對象仍然引用相同的mesh.所以這里需要通過判斷,避免對skin mesh
    // 中的骨骼索引重定向多次,只有第一次是正確的,第二次及后續(xù)一定錯誤,因為數(shù)據(jù)覆蓋了.
    Mesh mesh = geo.getMesh();
    logger.log(Level.INFO, "==MaxNumWeights={0}", mesh.getMaxNumWeights());
    VertexBuffer indices = mesh.getBuffer(VertexBuffer.Type.BoneIndex);
    if (!indices.getData().hasArray()) {
    // Prepares the mesh for software skinning by converting the bone index and weight buffers to heap buffers.
    // 另參考: SkeletonControl => void resetToBind()
    mesh.prepareForAnim(true);
    }
    // 重定向
    ByteBuffer ib = (ByteBuffer) indices.getData();
    ib.rewind();
    byte[] fib = ib.array();
    for (int i = 0; i < fib.length; i++) {
    int bIndex = fib[i] & 0xff; // bIndex是skin中子骨骼
    // 這里一般不會發(fā)生, 除非做了第二次骨骼索引的重定向,
    // 否則skin中的初始骨骼索引不可能會大于或等于它的骨骼數(shù)(最大索引為BoneCount-1)
    if (bIndex >= outfitSke.getBoneCount()) {
    logger.log(Level.WARNING, "SkinSke bone index big than boneCount, bIndex={0}, totalBone={1}"
    , new Object[] {bIndex, outfitSke.getBoneCount()});
    continue;
    }
    String boneName = outfitSke.getBone(bIndex).getName();
    // 從標(biāo)準(zhǔn)骨骼中找出與skin中當(dāng)前骨頭相同名稱的骨頭.
    int rootBoneIndex = rigSke.getBoneIndex(boneName);
    if (rootBoneIndex != -1) {
    logger.log(Level.INFO, "update bone index, skin={0}, index update: {1} to {2}"
    , new Object[]{outfit.getName(), fib[i], rootBoneIndex});
    fib[i] = (byte) rootBoneIndex;
    } else {
    // 如果skinNode中的骨骼沒有在標(biāo)準(zhǔn)骨骼中找到,則隨便直接綁定到父骨骼的根節(jié)點中.
    // 出現(xiàn)這種情況主要是skin中存在額外的骨骼,這個骨頭不知道要綁定到哪里?!!?
    fib[i] = 0;
    logger.log(Level.WARNING, "SkinSke found a extra bone, but not know where to bind to! boneName={0}"
    , boneName);
    }
    }
    indices.updateData(ib);
    }
    outfit.setUserData(REDIRECT_BONE_INDEX_OK, "1");
    // 把處理后的j3o文件保存起來
    ModelFileUtils.saveTo(outfit, outfitFile);
    }
    // 使用示例
    public static void main(String[] args) {
    redirectBoneIndex("Models/actor/female/foot.000.mesh.j3o", RIG_SKE_PATH);
    }
    }
    

    redirectBoneIndex中有幾個比較簡單的外部引用,由于文章篇幅關(guān)系我沒有列出代碼.

    AssetManager am = Common.getAssetManager(); // 獲取資源管理器,可以自己從application中獲得引用。
    GeometryUtils.findAllGeometry(outfit); // 找出一個節(jié)點中的所有子"Geometry"
    ModelFileUtils.saveTo(outfit, outfitFile); // 保存文件
    

    這幾個方法很簡單,請自行實現(xiàn)。

    下面是調(diào)用這個方法來處理身形和裝備中骨骼索引的示例(你可以在自己的代碼中動態(tài)調(diào)用):

    public static void main(String[] args) {
    redirectBoneIndex("xxx/foot0.mesh.j3o", "xxx/ske.mesh.j3o");
    redirectBoneIndex("xxx/lowerBody0.mesh.j3o", "xxx/ske.mesh.j3o");
    redirectBoneIndex("xxx/upperBody0.mesh.j3o", "xxx/ske.mesh.j3o");
    redirectBoneIndex("xxx/hand0.mesh.j3o", "xxx/ske.mesh.j3o");
    ...
    redirectBoneIndex("xxx/foot1.mesh.j3o", "xxx/ske.mesh.j3o");
    redirectBoneIndex("xxx/lowerBody1.mesh.j3o", "xxx/ske.mesh.j3o");
    redirectBoneIndex("xxx/upperBody1.mesh.j3o", "xxx/ske.mesh.j3o");
    redirectBoneIndex("xxx/hand1.mesh.j3o", "xxx/ske.mesh.j3o");
    }
    

    xxx代表你的資源路徑,請自行腦補。在處理前你的模型應(yīng)該像是下面這樣的:

    model2.jpg

    在處理后,你的模型應(yīng)該像是下面這樣的:

    model3.jpg

    示例圖中的foot.000名稱根據(jù)你的模型在blender中的名稱不同而有所區(qū)別。身形和裝備中的其它部分處理過程都是一樣的,這里不一一詳述。

    九、在游戲中載入,并演示換裝

    在經(jīng)過前面的處理后,剩下的問題已經(jīng)不是問題了,我們獲得了這些東西:

    標(biāo)準(zhǔn)骨骼: ske.mesh.j3o (已經(jīng)去掉了多余的模型)
    基本身形: foot0.mesh.j3o, lowerBody0.mesh.j3o, upperBody0.mesh.j3o, ...(已經(jīng)重定向骨骼索引,并去掉了Control和骨骼)
    裝 備: foot1.mesh.j3o, lowerBody1.mesh.j3o, upperBody1.mesh.j3o, ...(同身形一樣)
    

    現(xiàn)在使用一段代碼來示例如何在游戲中進(jìn)行換裝:

    @Override
    public void simpleInitApp() {
    // 載入標(biāo)準(zhǔn)骨骼
    Node ske = (Node) getAssetManager().loadModel("Models/actor/ske.mesh.j3o");
    rootNode.attachChild(ske);
    rootNode.addLight(new AmbientLight());
    // 載入基本皮膚
    Spatial foot = getAssetManager().loadModel("Models/actor/female/foot.000.mesh.j3o");
    Spatial lowerBody = getAssetManager().loadModel("Models/actor/female/lowerBody.000.mesh.j3o");
    Spatial upperBody = getAssetManager().loadModel("Models/actor/female/upperBody.000.mesh.j3o");
    Spatial hand = getAssetManager().loadModel("Models/actor/female/hand.000.mesh.j3o");
    Spatial face = getAssetManager().loadModel("Models/actor/female/face.000.mesh.j3o");
    // 組裝角色(基本皮膚)
    ske.attachChild(foot);
    ske.attachChild(lowerBody);
    ske.attachChild(upperBody);
    ske.attachChild(hand);
    ske.attachChild(face);
    // 換裝示例,比如換上“腳”裝備
    Spatial footOutfit = getAssetManager().loadModel("Models/actor/female/foot.001.mesh.j3o");
    ske.detachChild(foot); // 移除基本皮膚
    ske.attachChild(footOutfit);// 換上裝備
    }
    

    沒錯,跟其它節(jié)點的切換一樣簡單,只是attach和detach而已。當(dāng)標(biāo)準(zhǔn)骨骼(上例中的ske節(jié)點)在執(zhí)行動畫的時候,它的SkeletonControl會從子節(jié)點(皮膚或裝備)中獲取頂點、頂點與骨骼索引、權(quán)重的相關(guān)信息來計算動畫(注:皮膚和裝備中的Mesh中保存了頂點與骨骼索引和權(quán)重的關(guān)系)。

    好了,文章就寫到這里,下面是一些額外補充!

    十、附錄

    1.上下連身裝備的處理

    有時候我們可能會需要一些上下連身的裝備,如法師或牧師的長袍,這些沒有什么特別的,只是把lowerBody和upperBody合起來(在blender中不要切割開來就可以),在游戲中換上長袍的時候同時把基本皮膚中的lowerBody和upperBody移除即可。

    model4.jpg

    2.不需要蒙皮的皮膚或裝備

    有一些皮膚或裝備是可以不需要蒙皮的,比如頭發(fā),手里的武器(如劍),或一些靜態(tài)飾品(如眼鏡),這些直接導(dǎo)出成完全靜態(tài)(不需要骨骼和動畫)的物體即可,然后在游戲中(這里以JME3示例)直接掛接到某塊骨頭上就可以,比如頭發(fā):

    // 載入標(biāo)準(zhǔn)骨骼
    Node ske = (Node) getAssetManager().loadModel("Models/actor/ske.mesh.j3o");
    SkeletonControl skeControl = ske.getControl(SkeletonControl.class);
    // 獲得“頭骨”的節(jié)點
    Node headNode = skeControl.getAttachmentsNode("headBone")
    // 把頭發(fā)添加到頭骨的節(jié)點下(注意與上面的差別)
    headNode.attachChild(hairNode);
    

    這可以節(jié)省性能,因為蒙皮的物體在動畫過程中每個頂點都需要經(jīng)過骨骼和權(quán)重來計算頂點的位置,一個頂點還可能受多個骨頭的影響,所以計算量比一般動畫要大得多。

    3.不需要骨骼索引重定向的方法

    在前面我們使用了一些特殊代碼來重定向皮膚和裝備中權(quán)重信息中的骨骼索引,但是這一步是可以超過的,只要在blender中為皮膚或裝備(如腳)綁定骨骼的時候使用復(fù)制出來的完整的標(biāo)準(zhǔn)骨骼就可以,這樣導(dǎo)出blender并轉(zhuǎn)為j3o后的foot0.mesh.j3o中的骨骼索引就和標(biāo)準(zhǔn)骨骼中的完全一樣(同樣的要刪除掉皮膚和裝備中的Control). 只不過使用這種方法后,以后調(diào)整標(biāo)準(zhǔn)骨骼的時候(比如添加或減少某些骨頭)你的皮膚和裝備可能需要重新使用新的標(biāo)準(zhǔn)骨骼來綁定并重新導(dǎo)出。

    *******************************************************************************************************

    聲明:轉(zhuǎn)載請包含 * 號內(nèi)這段聲明(含 * 號)

    作者: huliqing

    Email: 31703299@qq.com

    原文鏈接:http://www.huliqing.name/article/articleId=41

    *******************************************************************************************************



    - huliqing@huliqing.name
    - http://www.huliqing.name

    posted on 2016-04-03 20:36 huliqing 閱讀(3414) 評論(1)  編輯  收藏 所屬分類: JavaAndroid3DJME3JMonkeyEngine落櫻之劍Game

    評論

    # re: 3D游戲中角色的換裝原理-落櫻之劍實例圖文詳細(xì)剖析(JME3,JMonkeyEngine游戲開發(fā))[未登錄] 2016-04-21 22:20 linda

    3D游戲換裝 很實用  回復(fù)  更多評論   

    導(dǎo)航

    統(tǒng)計

    公告

    文章原創(chuàng),歡迎轉(zhuǎn)載
    ——轉(zhuǎn)載請注明出處及原文鏈接

    隨筆分類(60)

    隨筆檔案(33)

    最新評論

    評論排行榜

    主站蜘蛛池模板: 日本高清免费网站| 老司机精品视频免费| 久久久精品国产亚洲成人满18免费网站| **一级一级毛片免费观看| 99re8这里有精品热视频免费| 亚洲AV永久无码天堂影院 | 黄页视频在线观看免费| 亚洲乱码一二三四五六区| 亚洲A∨无码无在线观看| 国产亚洲AV手机在线观看| 免费大黄网站在线观| 日韩精品视频免费在线观看| 114一级毛片免费| 亚洲一区二区三区免费观看| 久久精品无码精品免费专区| 99久久99这里只有免费的精品| 日本高清不卡中文字幕免费| 久久久久久亚洲精品无码| 亚洲男人的天堂网站| 国产成人亚洲综合一区| 亚洲av无码国产综合专区| 亚洲视频在线观看地址| 久久精品九九亚洲精品| 亚洲AV无码一区二区二三区入口| 中文字幕在线亚洲精品| 国产偷国产偷亚洲清高动态图| 久久久久噜噜噜亚洲熟女综合| 亚洲av午夜精品一区二区三区| 免费国产怡红院在线观看| 国产免费小视频在线观看| 国产成人高清精品免费软件| 日韩伦理片电影在线免费观看| 全免费一级午夜毛片| 日韩一区二区在线免费观看| 免费无遮挡无码视频网站| 美女被免费视频网站a国产| 国产免费人成视频在线观看| 国产公开免费人成视频| 亚洲国产精品一区二区三区久久| 亚洲人成色77777在线观看大| 亚洲熟妇无码乱子AV电影|