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

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

          聲明:轉(zhuǎn)載請(qǐng)包含 * 號(hào)內(nèi)這段聲明(含 * 號(hào))

          作者: huliqing

          Email: 31703299@qq.com

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

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

          前言

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

          change1.jpg

          change2.jpg

          沒(méi)錯(cuò),看起來(lái)很簡(jiǎn)單吧!!!

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

          那么接下來(lái),開(kāi)始講復(fù)雜的地方!

          在閱讀本文之前如果你能夠了解或知道以下一些基礎(chǔ)知識(shí),可以幫助你更好的了解3D換裝原理,因?yàn)楹竺娴闹v解或多或少會(huì)引用或涉及到這些內(nèi)容。

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

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

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

          一、準(zhǔn)備

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

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

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

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

          首先,你可能也知道一些骨骼動(dòng)畫(huà)的基本知識(shí),我這里不秀概念,盡量講清楚明白。

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

          因此在開(kāi)始講解換裝之前,我們需要先建立一個(gè)骨骼(或稱骨架),盡量建得標(biāo)準(zhǔn)一些,確定一個(gè)標(biāo)準(zhǔn)的骨骼非常重要,因?yàn)楹竺嫖覀兊挠螒蚪巧珓?dòng)畫(huà)都可以使用這一套骨骼,身體模型和裝備也都是要以這一套骨骼來(lái)建模的。下面我稱這套骨骼為標(biāo)準(zhǔn)骨骼

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

          mh_skeleton.jpg

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

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

          mh_export.jpg

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

          blender_ske.jpg

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

          blender_ske2.jpg

          骨骼側(cè)面

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

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

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

          base1.jpg base2.jpg base3.jpg

          注:這個(gè)基本身形非常重要,因?yàn)楹竺娴难b備都是以這個(gè)身形為基礎(chǔ)建立的。

          現(xiàn)在我們需要把身型切割成一個(gè)一個(gè)的部分,因?yàn)樵诤竺娴挠螒蛑形覀冃枰獙?duì)不同部位進(jìn)行替換,來(lái)達(dá)到“換裝”的效果。 在落櫻之劍中我是這樣切割的:

          base4.jpg

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

          注:左手和右手是作為一個(gè)整體切割出來(lái)的。

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

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

          line1.jpg line2.jpg

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

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

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

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

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

          從標(biāo)準(zhǔn)骨骼中復(fù)制1個(gè)骨骼出來(lái),記得保留原始標(biāo)準(zhǔn)骨骼,因?yàn)楹竺嫖覀冃枰盟鼇?lái)作為控制角色的主骨骼,這個(gè)臨時(shí)復(fù)制出來(lái)的骨骼只是為了給“腳”部分配權(quán)重,后續(xù)在游戲中我們是不需要這些臨時(shí)骨骼的,如圖所示,

          link1.jpg

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

          link2.jpg

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

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

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

          link3.jpg

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

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

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

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

          weight1.jpgweight2.jpg

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

          處理“裂縫”

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

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

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

          weight3.jpg weight4.jpg weight5.jpg

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

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

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

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

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

          border.jpg

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

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

          border2.jpg border3.jpg border4.jpg

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

          在經(jīng)過(guò)前面的過(guò)程后,現(xiàn)在我們?cè)赽lender中獲得了這些東西:

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

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

          export1.jpg

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

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

          注:在導(dǎo)出標(biāo)準(zhǔn)骨骼時(shí),ogre3D可能不允許單獨(dú)導(dǎo)出骨骼,這時(shí)可以隨便找個(gè)模型(如簡(jiǎn)單的立方體)綁定到骨骼中去,再進(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文件

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

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

          操作

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

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

          標(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格式的模型之后我們還需要對(duì)其做一些調(diào)整,以便在游戲中使用。

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

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

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

          model1.jpg

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

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

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

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

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

          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)行過(guò)重定向
          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中可能存在多個(gè)Geometry,每一個(gè)都要進(jìn)行處理.
          List<Geometry> geos = GeometryUtils.findAllGeometry(outfit);
          Skeleton outfitSke = outfitSC.getSkeleton();
          for (Geometry geo : geos) {
          // 找到Mesh中的骨骼索引
          // 這里需要檢測(cè)并初始化一次就可以, 不能重復(fù)做,即使skin是重新load進(jìn)來(lái)的
          // 因?yàn)間eometry或mesh可能進(jìn)行了緩存,所以即使重新Loader.loadSkin(),可能
          // 載入的對(duì)象仍然引用相同的mesh.所以這里需要通過(guò)判斷,避免對(duì)skin mesh
          // 中的骨骼索引重定向多次,只有第一次是正確的,第二次及后續(xù)一定錯(cuò)誤,因?yàn)閿?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中子骨骼
          // 這里一般不會(huì)發(fā)生, 除非做了第二次骨骼索引的重定向,
          // 否則skin中的初始骨骼索引不可能會(huì)大于或等于它的骨骼數(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中的骨骼沒(méi)有在標(biāo)準(zhǔn)骨骼中找到,則隨便直接綁定到父骨骼的根節(jié)點(diǎn)中.
          // 出現(xiàn)這種情況主要是skin中存在額外的骨骼,這個(gè)骨頭不知道要綁定到哪里?!!?
          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文件保存起來(lái)
          ModelFileUtils.saveTo(outfit, outfitFile);
          }
          // 使用示例
          public static void main(String[] args) {
          redirectBoneIndex("Models/actor/female/foot.000.mesh.j3o", RIG_SKE_PATH);
          }
          }
          

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

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

          這幾個(gè)方法很簡(jiǎn)單,請(qǐng)自行實(shí)現(xiàn)。

          下面是調(diào)用這個(gè)方法來(lái)處理身形和裝備中骨骼索引的示例(你可以在自己的代碼中動(dòng)態(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代表你的資源路徑,請(qǐng)自行腦補(bǔ)。在處理前你的模型應(yīng)該像是下面這樣的:

          model2.jpg

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

          model3.jpg

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

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

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

          標(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)在使用一段代碼來(lái)示例如何在游戲中進(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);// 換上裝備
          }
          

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

          好了,文章就寫(xiě)到這里,下面是一些額外補(bǔ)充!

          十、附錄

          1.上下連身裝備的處理

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

          model4.jpg

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

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

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

          這可以節(jié)省性能,因?yàn)槊善さ奈矬w在動(dòng)畫(huà)過(guò)程中每個(gè)頂點(diǎn)都需要經(jīng)過(guò)骨骼和權(quán)重來(lái)計(jì)算頂點(diǎn)的位置,一個(gè)頂點(diǎn)還可能受多個(gè)骨頭的影響,所以計(jì)算量比一般動(dòng)畫(huà)要大得多。

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

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

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

          聲明:轉(zhuǎn)載請(qǐng)包含 * 號(hào)內(nèi)這段聲明(含 * 號(hào))

          作者: 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 閱讀(3432) 評(píng)論(1)  編輯  收藏 所屬分類: Java 、Android3D 、JME3 、JMonkeyEngine 、落櫻之劍 、Game

          評(píng)論

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

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

          導(dǎo)航

          統(tǒng)計(jì)

          公告

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

          隨筆分類(60)

          隨筆檔案(33)

          最新評(píng)論

          評(píng)論排行榜

          主站蜘蛛池模板: 土默特右旗| 象山县| 亳州市| 安福县| 班戈县| 吉首市| 温州市| 乌什县| 溆浦县| 公主岭市| 岐山县| 嘉善县| 南昌市| 同德县| 吉水县| 潮安县| 祁连县| 伽师县| 东乌珠穆沁旗| 安远县| 常德市| 色达县| 金昌市| 改则县| 和平县| 衡阳县| 东平县| 肥城市| 根河市| 洪洞县| 罗甸县| 濮阳市| 稷山县| 达日县| 青阳县| 阳泉市| 延川县| 临猗县| 襄樊市| 板桥市| 苍梧县|