Blender建模与游戏换装(转载文)
本文轉(zhuǎn)載自https://my.oschina.net/huliqing/blog/880113?hmsr=toutiao.io
如果本文涉及侵權(quán)行為,請原作者聯(lián)系博主郵箱,我將及時進行刪除處理
博主郵箱:yibiandao@aliyun.com
前言
本文將詳細講解3D游戲中換裝的原理及換裝中的一些重點問題,先粗略看一下?lián)Q裝的簡單原理:
沒錯,看起來很簡單吧!!!
那么接下來,開始講復(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)備
在整個講解過程中需要以下一些東西來進行說明,我們將通過各種工具一步一步來完成以下物件,并最終使用這些物件在游戲中演示如何進行換裝。
- 一個標(biāo)準(zhǔn)的人物骨骼 - 用于控制角色基本身形、裝備以及角色動畫
- 一套基本身形 - 用于模擬人物角色的皮膚,即脫光裝備后的樣子,這里以內(nèi)衣裝示例,后面我稱為“身形”或“身體”
- 一套角色裝備 - 用于換裝示例,后面我統(tǒng)稱為“裝備”
注:角色基本身形和裝備的本質(zhì)是一樣的,切換原理也是一樣的, 這里先區(qū)分開來,以方便后面進行說明。由于文章的重點是講解“換裝”原理,以及換裝過程中可能遇到的重要問題,所以繁瑣的"建模"過程我可能會簡單略過。
二、確定和建立自己的標(biāo)準(zhǔn)骨骼
首先,你可能也知道一些骨骼動畫的基本知識,我這里不秀概念,盡量講清楚明白。
就像人體的骨骼和皮膚的關(guān)系一樣,當(dāng)人的手在動的時候,實際上就是手上的骨頭帶動了手上的皮膚在動。原理一樣,這里的骨骼和人體的骨骼一樣,3D中的骨骼在動畫過程中通過各個骨頭來帶動皮膚(或裝備)模型中的各個頂點進行運動就形成了骨骼動畫。模型中的每個頂點都可能受一個或多個骨頭影響,而這些影響可能不一樣,有一些影響大一些,有一些影響小一些,這個影響值就是權(quán)重。
因此在開始講解換裝之前,我們需要先建立一個骨骼(或稱骨架),盡量建得標(biāo)準(zhǔn)一些,確定一個標(biāo)準(zhǔn)的骨骼非常重要,因為后面我們的游戲角色動畫都可以使用這一套骨骼,身體模型和裝備也都是要以這一套骨骼來建模的。下面我稱這套骨骼為標(biāo)準(zhǔn)骨骼
關(guān)于標(biāo)準(zhǔn)骨骼你可以自己手動在blender或其它3D建模工具中手動一根一根建立,或使用一些建模插件或是從一些開源軟件中獲得,比如從MakeHuman中建立一個模型,然后給模型配置一個骨骼,如下圖:
根據(jù)自己的需要和模型動畫的復(fù)雜度,選擇不同的骨骼,如果你的模型需要包含眼部、嘴部或手指動畫等,你就可以選擇包含眼部、嘴部和手指等骨頭的骨骼。當(dāng)然,骨骼和模型越復(fù)雜,對后續(xù)游戲性能影響也就越大,根據(jù)需要適當(dāng)選擇。
在MakeHuman中建立了模型之后導(dǎo)出為mhx,再把它導(dǎo)入到blender中,就可以在blender中獲得一套比較標(biāo)準(zhǔn)的骨骼了。
下面的講解我將以我所開發(fā)的游戲落櫻之劍中使用的標(biāo)準(zhǔn)骨骼來進行說明:
骷骼正面(手指處的名稱太雜亂,我沒有顯示出來)
骨骼側(cè)面
注意,給骨骼中的骨頭起個有意義的名稱也很重要。
三、建立基本身形并切割身形
下面是根據(jù)標(biāo)準(zhǔn)骨骼建立的基本身形,你也可以在建立模型后再根據(jù)模型確定自己的標(biāo)準(zhǔn)骨骼,根據(jù)自己需要而定。 你也可以直接把從MakeHuman中導(dǎo)出的模型來作為基本身型使用,但是一般情況還是需要根據(jù)自己的需要調(diào)節(jié)一下,比如導(dǎo)出的模型面數(shù)或頂點數(shù)可能達到1萬多,在大部分情況下對于游戲來說精度太高,會影響游戲性能, 建模是一個繁瑣的過程,也不是我們要講的重點,這里就不詳細介紹了。 下面是落櫻之劍中主角的基本身形,看一下是如何和標(biāo)準(zhǔn)骨骼匹配的。
注:這個基本身形非常重要,因為后面的裝備都是以這個身形為基礎(chǔ)建立的。
現(xiàn)在我們需要把身型切割成一個一個的部分,因為在后面的游戲中我們需要對不同部位進行替換,來達到“換裝”的效果。 在落櫻之劍中我是這樣切割的:
腳(foot),下身(lowerBody), 上身(upperBody), 手部(hand), 臉部(face), 眼睛(eye), 耳朵(ear),頭發(fā)(hair),眼罩(blinder),角(horn),臉罩(mouthmask),...
注:左手和右手是作為一個整體切割出來的。
由于篇幅和模型復(fù)雜度的關(guān)系一部分基本身型我沒有顯示出來,并且也不打算在這里講解如何切割模型,如果你懂得一些建模的知識,你應(yīng)該很清楚如何做,切割模型很簡單,在明白了原理之后你也可以根據(jù)自己實際的需要切割成更多或更細的部分。
為了方便說明,我把切割后的模型各部分都移動了一下,實際切割后的模型應(yīng)該保留原來的位置不變,以便后續(xù)的操作。特別是切割后的模型分界線,
這里以手部的分界線為例,上身和手部的分界線是一樣的,分界線上的這些頂點位置不能移動,特別是在制作裝備的時候,應(yīng)該盡量保持這些分界線上的頂點位置不變動,否則“換裝”后的整個模型就可能出現(xiàn)裂縫, 因為后續(xù)可能會制作很多的裝備,這些裝備需要能夠和身形以及其它裝備進行無縫匹配,那么嚴(yán)格遵守這個規(guī)范就很重要。
四、將身形綁定到骨骼和調(diào)整邊界權(quán)重
在切割了身形之后,我們需要為身形的各個部分分配骨骼權(quán)重,因為身形和裝備最終都是要由標(biāo)準(zhǔn)骨骼來控制的,所以身形的各部分需要各自綁定到骨骼中去,在最終的產(chǎn)出物中身形的各個部分是各自獨立的,并且這些身形和裝備都附帶有骨骼權(quán)重信息,這些信息最終在游戲中會被用到主骨骼(標(biāo)準(zhǔn)骨骼)中去。
現(xiàn)在,假如我把身形切分成“腳”,“下身”,“上身”,“手”,“臉”。 先以“腳”為例來進行說明,其它部分操作原理是一樣的。
首先給“腳”分配骨骼和確定“連接骨骼”
從標(biāo)準(zhǔn)骨骼中復(fù)制1個骨骼出來,記得保留原始標(biāo)準(zhǔn)骨骼,因為后面我們需要用它來作為控制角色的主骨骼,這個臨時復(fù)制出來的骨骼只是為了給“腳”部分配權(quán)重,后續(xù)在游戲中我們是不需要這些臨時骨骼的,如圖所示,
將復(fù)制出來的骨骼刪除掉一些不必要的骨頭,因為腳部非常小,需要綁定的骨骼也很少,所以shin.L和shin.R的父骨骼部分都可以不需要。
然而為什么需要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ù)自己模型的切割方式來定義,下圖是我在游戲“落櫻之劍”中對模型各個部分分配骨骼后的劃分參考!
通常來說,一旦確定了身形各個部分和骨骼各個部分的劃分之后,這些定義(或稱規(guī)范)都可以通用到你后續(xù)裝備的制作部分,所以你可以把身形和相關(guān)骨骼部分備份起來以便后續(xù)制作裝備時使用,特別是在制作裝備的時候要盡量保持“裝備”各部分的分界點與劃分身形時各部分的分界點一致(不移動,不增刪頂點),以做到無縫匹配。
給“腳”分配權(quán)重
身形的其它部分操作與腳相似,劃分及確定連接骨骼請參考上圖。接著需要把身形的各部分分別綁定到各自所分配的骨骼上去,仍然以“腳”為例來說明。
操作: 右鍵選擇“腳” -> 按住Shift不放再選擇腳部骨骼 -> 按Ctr+P -> 選擇“With Automaic Weights”來自動分匹配權(quán)重。如圖:
在綁定后你就可以使用骨骼來控制腳部或制作動畫了,但是在這里我們不需要為特定身形或裝備來制作動畫,我們需要的只是身形(或裝備)與骨骼的權(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.
- 其它部分按默認系統(tǒng)自動分配的權(quán)重或適當(dāng)按需調(diào)整
現(xiàn)在說明為什么要這樣做,我們以“下身”左邊和左“腳”的邊界點來說明,由于兩個邊界都是以連接骨骼"shin.L"進行連接的,shin.L對“下身”和“腳”邊界的權(quán)重影響都是1.0,并且這些邊界點不受其它骨骼的影響,這樣當(dāng)shin.L在運動的時候就能保證對邊界處頂點的完全控制,使它們的運動行為一致而不會出現(xiàn)裂縫。具體參考下圖:
或許對于邊界裂縫的處理方式不同的人會有不同的處理方式,但是遵守一定的規(guī)則是有意義的,特別是在后續(xù)裝備的制作。身形的其它部分對裂縫的處理方式與“腳”和“下身”的處理方式是一樣的,這里就不一一詳細說明。
五、根據(jù)基本身形建立裝備模型
在建立基本身形并綁定了權(quán)重之后,現(xiàn)在我們來制作一套“裝備”,以便后續(xù)在游戲中演示如何進行“換裝”。
裝備的性質(zhì)實際與基本身形是一樣的,只是看起來像是一套裝甲而已,建立過程與基本身形差不多,所以一般也是在基本身形上建模而成的,并且為了確保與基本身形以及其它裝備之間的無縫匹配(不出現(xiàn)邊界處的裂縫現(xiàn)象),我們在制作裝備的時候需要確保邊界處的頂點盡量不要被編輯到,即不移動也不增刪頂點,雖然這并不是絕對必須遵守的原則,比如一些比較寬大的裝備或許可以遮住分界處的裂縫,這個時候邊界處的頂點即使稍微移動或改變也可能不會有什么大的影響,但是始終遵守邊界頂點不被編輯可以減少模型制作時候的很多麻煩。
現(xiàn)在使用我在落櫻之劍中為角色創(chuàng)建的一些“裝備”來示例說明:
這些“裝備”是從基本身形中修改而來的,建模過程就略了,重點注意裝備各部分箭頭所示處的頂點與原始身形的位置是一致的,這些是原始身形的邊界點,為了與原始身形無縫嵌接,這些頂點在建模裝備的時候必須保持與原來的位置一致(這里為了方便示例,我對各個部分做了一些移動)。
在建立了裝備后,同樣需要給裝備綁定骨骼,綁定骨骼和分配權(quán)重的過程與前面“基本身形”的處理過程一樣遵守相同的規(guī)則,即原始邊界點處只接受連接骨骼的影響(權(quán)重1.0),非連接骨骼不影響邊界處的頂點。如下圖:
六、使用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)插件(詳細看說明,注意版本的兼容)。
使用ogre插件分別導(dǎo)出以下相應(yīng)物件:
注:在導(dǎo)出標(biāo)準(zhǔn)骨骼時,ogre3D可能不允許單獨導(dǎo)出骨骼,這時可以隨便找個模型(如簡單的立方體)綁定到骨骼中去,再進行導(dǎo)出。
在使用ogre3D導(dǎo)出后,我們將獲得這些文件(文件名稱依據(jù)物體在blender中的名稱而定):
1.標(biāo)準(zhǔn)骨骼文件:
ske.skeleton.xml, ske.mesh.xml, ske.material2.基本身形各部分:
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七、將ogre3D格式的文件轉(zhuǎn)換為j3O文件
接下來的講解需要你了解過一些JME3游戲引擎的知識,因為這一部分的說明會與特定引擎和環(huán)境有關(guān),并非所有游戲引擎通用,但是了解一下也有益處,畢竟道理和原理是差不多通用的。
從ogre3D導(dǎo)出的文件為ogre格式,現(xiàn)在需要把這些文件轉(zhuǎn)換為j3o格式,j3o是JME3游戲引擎默認使用的模型文件格式。因此在JME3使用這些模型之前需要將它們轉(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)添加上去的。下面對骨骼中的多余模型進行刪除操作:
在SDK中選擇模型(ske.mesh.j3o) -> 右鍵選擇“Edit in SceneComposer” -> 在SceneExplorer Window中將多余的Geometry刪除掉,如圖:
調(diào)整身形及裝備
所有身形和裝備的調(diào)整過程是一樣的,所以這里統(tǒng)一進行說明。
身形和裝備的調(diào)整剛好與骨骼的調(diào)整相反,由于身形和裝備最終是添加(attach)到標(biāo)準(zhǔn)骷髏中去的,由標(biāo)準(zhǔ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ù)雜,在明白原理之后你可以自己進行處理,也可以使用下面我提供的方法來處理骨骼索引的重定向(注:骨骼索引重定向這一步是可以有其它方法繞過的,看附錄)。
package name.huliqing.luoying.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.luoying.LuoYing; import name.huliqing.luoying.utils.GeometryUtils; import name.huliqing.luoying.utils.ModelFileUtils; /** * * @author huliqing */ public class OutfitUtils { private final static Logger logger = Logger.getLogger(OutfitUtils.class.getName()); private final static String REDIRECT_BONE_INDEX_OK = "redirect_bone_index_ok"; private final static String RIG_SKE_PATH = "Models/actor/ske.mesh.j3o"; public static void redirectBoneIndex(String outfitFile, String rigSkeFile) { AssetManager am = LuoYing.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,每一個都要進行處理. List<Geometry> geos = GeometryUtils.findAllGeometry(outfit); Skeleton outfitSke = outfitSC.getSkeleton(); // skin中的骨骼(已丟棄) for (Geometry geo : geos) { // 找到Mesh中的骨骼索引 // 這里需要檢測并初始化一次就可以, 不能重復(fù)做,即使skin是重新load進來的 // 因為geometry或mesh可能進行了緩存,所以即使重新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(); // 從父骨骼中找出與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中的骨骼沒有在父骨骼中找到,則隨便直接綁定到父骨骼的根節(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"); ModelFileUtils.saveTo(outfit, outfitFile); } // 1.注意:如果skin中存在骨骼及動畫信息,這些信息 // 將被丟棄,相應(yīng)的骨骼索引信息會重定向到actor中的主骨骼上.這要求skinNode // 骨骼中的所有骨頭的名稱都應(yīng)該與主rig中骨骼中的骨頭名稱相匹配. // 2.需要重定向skin中的骨骼索引,這里稍微復(fù)雜,需要詳細記住: // 比如:主骨骼存在 root - hips - hand 這三個骨頭, 而skin中的骨骼可能只有 // hand這個骨頭, 為了把skin綁定到主骨骼中,也就是是將skin中的mesh的控制權(quán) // 從skin自身的hand骨頭交由主骨骼中的hand骨頭來控制,這樣skin就不需要AnimControl // 和SkeletonControl. // 因為skin自身的mesh中包含有權(quán)重和骨骼索引的信息. // 但是因為skin自身的骨骼索引和主骨骼索引是不一致的,如上:主骨骼中的hand // 的索引為2, 而skin因為hand只有一個骨頭,索引為0, 所以在丟棄了skin中的 // AnimControl和SkeletonControl之后,需要把skin中hand的骨頭索引重定向到 // 主骨骼中的hand的索引.而權(quán)重信息不需要改變,也就是如上:skin中mesh的關(guān)于 // hand的骨骼索引需要從0重定向到主骨骼中的hand索引2. // 這樣主骨骼中的SkeletonControl在控制skin執(zhí)行變換的時候才能正確從主骨骼 // 中找到hand的骨頭,(否則找到的就是root了). // 關(guān)于骨骼與Mesh的具體邏輯可參考: SkeletonControl.controlRenderSoftware public static void main(String[] args) { // 重要:模型(j3o)只需要重定向一次就可以了,不能多次重定向,除非模型修改后重新轉(zhuǎn)換成j3o. redirectBoneIndex("Models/actor/female/eye.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)該像是下面這樣的:
在處理后,你的模型應(yīng)該像是下面這樣的:
示例圖中的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)在使用一段代碼來示例如何在游戲中進行換裝:
沒錯,跟其它節(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移除即可。
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)載于:https://www.cnblogs.com/xiaoahui/p/11294723.html
總結(jié)
以上是生活随笔為你收集整理的Blender建模与游戏换装(转载文)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Git回滚操作的总结
- 下一篇: 出路在哪里?出路在于思路!智者无敌