*******************************************************************************************************
聲明:轉載請包含 * 號內這段聲明(含 * 號)
作者: huliqing
Email: 31703299@qq.com
原文鏈接:http://www.huliqing.name/article/articleId=41
*******************************************************************************************************
前言
本文將詳細講解3D游戲中換裝的原理及換裝中的一些重點問題,先粗略看一下換裝的簡單原理:


沒錯,看起來很簡單吧!!!
Orz,說真的,要不是開發了“落櫻之劍”這個游戲,順便想打一下軟廣告,我還真不想去寫這么長一篇文章,去貼這么多圖!因為寫清楚、說明白、外加圖文并茂,還是要花不少心思的,并且我相信大部分程序員是不會建模的,很多美工也不會去寫代碼的,因為這篇文章會同時涉及到編程和建模,所以可能有很多人看明白了但是卻很難做出來。如果你還有興趣那就看下去吧,如果有錯誤,也希望有朋友幫我指出來。
那么接下來,開始講復雜的地方!
在閱讀本文之前如果你能夠了解或知道以下一些基礎知識,可以幫助你更好的了解3D換裝原理,因為后面的講解或多或少會引用或涉及到這些內容。
- Blender - 開源的3D建模工具,或了解過其它如:3dmax,maya之類的建模工具都可以。了解一些其中的基礎建模、骨骼動畫和蒙皮知識
- JME3 - 開源3D游戲引擎,全名:JMonkeyEngine,基于Java。了解一些Java基礎編程,文章后面將在這個游戲引擎中用代碼示例如何換裝。
- MakeHuman - 開源的人物建模工具,可幫你快速建立人物模型及標準骨骼.
- ogre3D - blender導出插件,我們將用它來導出ogre格式的模型文件
- 落櫻之劍 - 我開發的免費Android游戲,沒錯,打了個軟廣告,還加粗了呢! :) 用于換裝演示,含換裝脫裝、換武器、換眼鏡、換面罩、換角、換耳朵、換發形,...
版本要求: Blender2.69, JME3.0, ogre插件v0.6.0
你可以償試更新的版本來學習這個教程,但建議使用與我一樣的版本。
一、準備
在整個講解過程中需要以下一些東西來進行說明,我們將通過各種工具一步一步來完成以下物件,并最終使用這些物件在游戲中演示如何進行換裝。
- 一個標準的人物骨骼 - 用于控制角色基本身形、裝備以及角色動畫
- 一套基本身形 - 用于模擬人物角色的皮膚,即脫光裝備后的樣子,這里以內衣裝示例,后面我稱為“身形”或“身體”
- 一套角色裝備 - 用于換裝示例,后面我統稱為“裝備”
注:角色基本身形和裝備的本質是一樣的,切換原理也是一樣的, 這里先區分開來,以方便后面進行說明。由于文章的重點是講解“換裝”原理,以及換裝過程中可能遇到的重要問題,所以繁瑣的"建模"過程我可能會簡單略過。
二、確定和建立自己的標準骨骼
首先,你可能也知道一些骨骼動畫的基本知識,我這里不秀概念,盡量講清楚明白。
就像人體的骨骼和皮膚的關系一樣,當人的手在動的時候,實際上就是手上的骨頭帶動了手上的皮膚在動。原理一樣,這里的骨骼和人體的骨骼一樣,3D中的骨骼在動畫過程中通過各個骨頭來帶動皮膚(或裝備)模型中的各個頂點進行運動就形成了骨骼動畫。模型中的每個頂點都可能受一個或多個骨頭影響,而這些影響可能不一樣,有一些影響大一些,有一些影響小一些,這個影響值就是權重。
因此在開始講解換裝之前,我們需要先建立一個骨骼(或稱骨架),盡量建得標準一些,確定一個標準的骨骼非常重要,因為后面我們的游戲角色動畫都可以使用這一套骨骼,身體模型和裝備也都是要以這一套骨骼來建模的。下面我稱這套骨骼為標準骨骼
關于標準骨骼你可以自己手動在blender或其它3D建模工具中手動一根一根建立,或使用一些建模插件或是從一些開源軟件中獲得,比如從MakeHuman中建立一個模型,然后給模型配置一個骨骼,如下圖:

根據自己的需要和模型動畫的復雜度,選擇不同的骨骼,如果你的模型需要包含眼部、嘴部或手指動畫等,你就可以選擇包含眼部、嘴部和手指等骨頭的骨骼。當然,骨骼和模型越復雜,對后續游戲性能影響也就越大,根據需要適當選擇。
在MakeHuman中建立了模型之后導出為mhx,再把它導入到blender中,就可以在blender中獲得一套比較標準的骨骼了。

下面的講解我將以我所開發的游戲落櫻之劍中使用的標準骨骼來進行說明:

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

骨骼側面
注意,給骨骼中的骨頭起個有意義的名稱也很重要。
三、建立基本身形并切割身形
下面是根據標準骨骼建立的基本身形,你也可以在建立模型后再根據模型確定自己的標準骨骼,根據自己需要而定。 你也可以直接把從MakeHuman中導出的模型來作為基本身型使用,但是一般情況還是需要根據自己的需要調節一下,比如導出的模型面數或頂點數可能達到1萬多,在大部分情況下對于游戲來說精度太高,會影響游戲性能, 建模是一個繁瑣的過程,也不是我們要講的重點,這里就不詳細介紹了。 下面是落櫻之劍中主角的基本身形,看一下是如何和標準骨骼匹配的。

注:這個基本身形非常重要,因為后面的裝備都是以這個身形為基礎建立的。
現在我們需要把身型切割成一個一個的部分,因為在后面的游戲中我們需要對不同部位進行替換,來達到“換裝”的效果。 在落櫻之劍中我是這樣切割的:

腳(foot),下身(lowerBody), 上身(upperBody), 手部(hand), 臉部(face), 眼睛(eye), 耳朵(ear),頭發(hair),眼罩(blinder),角(horn),臉罩(mouthmask),...
注:左手和右手是作為一個整體切割出來的。
由于篇幅和模型復雜度的關系一部分基本身型我沒有顯示出來,并且也不打算在這里講解如何切割模型,如果你懂得一些建模的知識,你應該很清楚如何做,切割模型很簡單,在明白了原理之后你也可以根據自己實際的需要切割成更多或更細的部分。
為了方便說明,我把切割后的模型各部分都移動了一下,實際切割后的模型應該保留原來的位置不變,以便后續的操作。特別是切割后的模型分界線,

這里以手部的分界線為例,上身和手部的分界線是一樣的,分界線上的這些頂點位置不能移動,特別是在制作裝備的時候,應該盡量保持這些分界線上的頂點位置不變動,否則“換裝”后的整個模型就可能出現裂縫, 因為后續可能會制作很多的裝備,這些裝備需要能夠和身形以及其它裝備進行無縫匹配,那么嚴格遵守這個規范就很重要。
四、將身形綁定到骨骼和調整邊界權重
在切割了身形之后,我們需要為身形的各個部分分配骨骼權重,因為身形和裝備最終都是要由標準骨骼來控制的,所以身形的各部分需要各自綁定到骨骼中去,在最終的產出物中身形的各個部分是各自獨立的,并且這些身形和裝備都附帶有骨骼權重信息,這些信息最終在游戲中會被用到主骨骼(標準骨骼)中去。
現在,假如我把身形切分成“腳”,“下身”,“上身”,“手”,“臉”。 先以“腳”為例來進行說明,其它部分操作原理是一樣的。
首先給“腳”分配骨骼和確定“連接骨骼”
從標準骨骼中復制1個骨骼出來,記得保留原始標準骨骼,因為后面我們需要用它來作為控制角色的主骨骼,這個臨時復制出來的骨骼只是為了給“腳”部分配權重,后續在游戲中我們是不需要這些臨時骨骼的,如圖所示,

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

然而為什么需要shin.L和shin.R部分的骨頭呢?因為腳很小,所以不是只要foot.L和foot.R及其子骨骼部分就可以了嗎? 因為需要一些有特殊意義的骨頭,為了處理角色在“換裝”后可能出現的“裂縫”現象,需要使用這些特殊骨頭,我把這些為了處理模型邊界裂縫、在兩個連接模型中都出現、同時連接兩個身體模型的骨頭稱為“連接骨骼”。 比如(參考下圖):
- 連接“腳”和“下身”的骨頭: shin.L,shin.R
- 連接“下身”和“上身”的骨頭: hips
- 連接“手”和“上身”的骨頭: hand.L, hand.R
- 連接“上身”和“臉”的骨頭: neck
一般情況下,這些“連接骨骼”需要根據自己模型的切割方式來定義,下圖是我在游戲“落櫻之劍”中對模型各個部分分配骨骼后的劃分參考!

通常來說,一旦確定了身形各個部分和骨骼各個部分的劃分之后,這些定義(或稱規范)都可以通用到你后續裝備的制作部分,所以你可以把身形和相關骨骼部分備份起來以便后續制作裝備時使用,特別是在制作裝備的時候要盡量保持“裝備”各部分的分界點與劃分身形時各部分的分界點一致(不移動,不增刪頂點),以做到無縫匹配。
給“腳”分配權重
身形的其它部分操作與腳相似,劃分及確定連接骨骼請參考上圖。接著需要把身形的各部分分別綁定到各自所分配的骨骼上去,仍然以“腳”為例來說明。
操作: 右鍵選擇“腳” -> 按住Shift不放再選擇腳部骨骼 -> 按Ctr+P -> 選擇“With Automaic Weights”來自動分匹配權重。如圖:


在綁定后你就可以使用骨骼來控制腳部或制作動畫了,但是在這里我們不需要為特定身形或裝備來制作動畫,我們需要的只是身形(或裝備)與骨骼的權重信息就可以,所有的動畫我們將在“標準骨骼”中制作。現在我們還有一個更重要的問題要處理 - 處理“裂縫”
處理“裂縫”
在給“腳”綁定了骨骼之后,由于是使用自動分配權重(With Automatic Weight), 自動分配權重是個好東西,但是可能在“換裝”游戲中由于模型邊界處頂點權重的分布不一致而導致角色皮膚或裝備出現裂縫的現象。現在我們需要為這些身形確定一些權重分配規則,以及通過這些規則來調整一下權重分配,有可能你的規范與我的不一樣,下面是我在落櫻之劍中定義的規則。
- 身形邊界點(邊界處的頂點)只接受連接骨骼的影響,并且影響權重是1.0
- 身形邊界點不接受其它骨骼的權重影響,所有其它非連接骷髏對于邊界點的權重都要清0.
- 其它部分按默認系統自動分配的權重或適當按需調整
現在說明為什么要這樣做,我們以“下身”左邊和左“腳”的邊界點來說明,由于兩個邊界都是以連接骨骼"shin.L"進行連接的,shin.L對“下身”和“腳”邊界的權重影響都是1.0,并且這些邊界點不受其它骨骼的影響,這樣當shin.L在運動的時候就能保證對邊界處頂點的完全控制,使它們的運動行為一致而不會出現裂縫。具體參考下圖:

或許對于邊界裂縫的處理方式不同的人會有不同的處理方式,但是遵守一定的規則是有意義的,特別是在后續裝備的制作。身形的其它部分對裂縫的處理方式與“腳”和“下身”的處理方式是一樣的,這里就不一一詳細說明。
五、根據基本身形建立裝備模型
在建立基本身形并綁定了權重之后,現在我們來制作一套“裝備”,以便后續在游戲中演示如何進行“換裝”。
裝備的性質實際與基本身形是一樣的,只是看起來像是一套裝甲而已,建立過程與基本身形差不多,所以一般也是在基本身形上建模而成的,并且為了確保與基本身形以及其它裝備之間的無縫匹配(不出現邊界處的裂縫現象),我們在制作裝備的時候需要確保邊界處的頂點盡量不要被編輯到,即不移動也不增刪頂點,雖然這并不是絕對必須遵守的原則,比如一些比較寬大的裝備或許可以遮住分界處的裂縫,這個時候邊界處的頂點即使稍微移動或改變也可能不會有什么大的影響,但是始終遵守邊界頂點不被編輯可以減少模型制作時候的很多麻煩。
現在使用我在落櫻之劍中為角色創建的一些“裝備”來示例說明:

這些“裝備”是從基本身形中修改而來的,建模過程就略了,重點注意裝備各部分箭頭所示處的頂點與原始身形的位置是一致的,這些是原始身形的邊界點,為了與原始身形無縫嵌接,這些頂點在建模裝備的時候必須保持與原來的位置一致(這里為了方便示例,我對各個部分做了一些移動)。
在建立了裝備后,同樣需要給裝備綁定骨骼,綁定骨骼和分配權重的過程與前面“基本身形”的處理過程一樣遵守相同的規則,即原始邊界點處只接受連接骨骼的影響(權重1.0),非連接骨骼不影響邊界處的頂點。如下圖:

六、使用Ogre3D插件導出骨骼、基本身形、裝備等物件
在經過前面的過程后,現在我們在blender中獲得了這些東西:
- 一套標準骨骼(可帶動畫,也可不帶)
- 一套基本身形(包含身形的各部分,并已經全部綁定了骨骼)
- 一套可換的裝備(包含裝備各部分,并已經全部綁定了骨骼)
現在我們需要把這些東西從blender中導出,我這里使用ogre3D插件來導出,你可以從ogre3D官網中找到相關插件(詳細看說明,注意版本的兼容)。

使用ogre插件分別導出以下相應物件:
- 導出標準骨骼
- 導出身形的各個部分,“腳”,“下身”,“上身”,“手”,“臉”...等
- 導出裝備的各個部分,“腳”,“下身”,“上身”,“手” 裝備部分我們只要這一些就可以。
注:在導出標準骨骼時,ogre3D可能不允許單獨導出骨骼,這時可以隨便找個模型(如簡單的立方體)綁定到骨骼中去,再進行導出。
在使用ogre3D導出后,我們將獲得這些文件(文件名稱依據物體在blender中的名稱而定):
1.標準骨骼文件:
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

七、將ogre3D格式的文件轉換為j3O文件
接下來的講解需要你了解過一些JME3游戲引擎的知識,因為這一部分的說明會與特定引擎和環境有關,并非所有游戲引擎通用,但是了解一下也有益處,畢竟道理和原理是差不多通用的。
從ogre3D導出的文件為ogre格式,現在需要把這些文件轉換為j3o格式,j3o是JME3游戲引擎默認使用的模型文件格式。因此在JME3使用這些模型之前需要將它們轉為j3o格式。
操作
- 打開JME3的SDK,然后創建或打開你的JME項目.(JME3官網下載SDK,請使用JME3.0穩定版)
- 將所有剛導出的ogre格式的模型文件(標準骨骼,身形,裝備)一起拷貝到JME項目的asset目錄下,如: "assets/Textures/demo"
- 在SDK中,將所有xxx.mesh.xml文件一個一個轉換為j3o文件,操作: 右鍵選擇xxx.mesh.xml文件 -> 轉換為j3o
現在我們將得到這些文件(省略號部分自己腦補):
標準骨骼: ske.mesh.j3o
基本身形: foot0.mesh.j3o, lowerBody0.mesh.j3o, upperBody0.mesh.j3o, ...
裝 備: foot1.mesh.j3o, lowerBody1.mesh.j3o, upperBody1.mesh.j3o, ...
八、調整標準骨骼,身形和裝備
在把標準骨骼、身形和裝備轉換為j3o格式的模型之后我們還需要對其做一些調整,以便在游戲中使用。
調整標準骨骼
如果從blender中導出標準骷髏的時候我們給骨骼綁定了任何模型,這時轉換后的ske.mesh.j3o文件中可能包含這些模型,然而我們并不需要它們, 因為標準骨骼只包含骨骼就可以,在游戲中“換裝”的時候模型(身形或裝備)是動態添加上去的。下面對骨骼中的多余模型進行刪除操作:
在SDK中選擇模型(ske.mesh.j3o) -> 右鍵選擇“Edit in SceneComposer” -> 在SceneExplorer Window中將多余的Geometry刪除掉,如圖:

調整身形及裝備
所有身形和裝備的調整過程是一樣的,所以這里統一進行說明。
身形和裝備的調整剛好與骨骼的調整相反,由于身形和裝備最終是添加(attach)到標準骷髏中去的,由標準骨骼進行控制,所以身形和裝備自身就不需要包含骨骼。但是從blender中導出身形和裝備的時候,由于我們需要骨骼與權重的相關信息,所以導出并轉換為j3o后的模型中包含了骨骼,所以現在需要把這些東西處理掉,只保留權重信息就可以,這里稍微有一些復雜。
由于在blender中我們使用了不完整的骨骼去綁定身形和裝備的各個部分。 所以在導出并轉換后的身形或裝備(如:foot0.mesh.j3o)中所包含的骨骼索引和標準骨骼中的索引是有可能不一樣的。舉個例子,標準骨骼(ske.mesh.j3o)中名稱為foot.L的骨頭的索引值可能是10,而腳(foot0.mesh.j3o)自身骨骼中的foot.L的骨頭的索引值可能是其它值,這種不一致最終會導致在執行角色動畫的時候,無法從“標準骨骼”中找到正確的用于控制“腳”部動畫的骨頭。
現在我們需要執行一些操作來重定向“腳”(foot0.mesh.j3o)中頂點和骨骼之間的權重信息中的骨骼索引,使它指向標準骨骼中正確的骨頭,然后“腳”中的骨骼就可以不要了,順便各種Control也可以丟掉,只保留網格(Geometry)就行,因為權重等信息保存在Geometry的mesh中,所以丟掉其它東西是沒有問題的。這一段說得有點繞,操作也有些復雜,在明白原理之后你可以自己進行處理,也可以使用下面我提供的方法來處理骨骼索引的重定向(注:骨骼索引重定向這一步是可以有其它方法繞過的,看附錄)。
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());
// 標記是否已經進行過重定向
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 = 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,每一個都要進行處理.
List<Geometry> geos = GeometryUtils.findAllGeometry(outfit);
Skeleton outfitSke = outfitSC.getSkeleton();
for (Geometry geo : geos) {
// 找到Mesh中的骨骼索引
// 這里需要檢測并初始化一次就可以, 不能重復做,即使skin是重新load進來的
// 因為geometry或mesh可能進行了緩存,所以即使重新Loader.loadSkin(),可能
// 載入的對象仍然引用相同的mesh.所以這里需要通過判斷,避免對skin mesh
// 中的骨骼索引重定向多次,只有第一次是正確的,第二次及后續一定錯誤,因為數據覆蓋了.
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中子骨骼
// 這里一般不會發生, 除非做了第二次骨骼索引的重定向,
// 否則skin中的初始骨骼索引不可能會大于或等于它的骨骼數(最大索引為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中當前骨頭相同名稱的骨頭.
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中的骨骼沒有在標準骨骼中找到,則隨便直接綁定到父骨骼的根節點中.
// 出現這種情況主要是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
中有幾個比較簡單的外部引用,由于文章篇幅關系我沒有列出代碼.
AssetManager am = Common.getAssetManager(); // 獲取資源管理器,可以自己從application中獲得引用。
GeometryUtils.findAllGeometry(outfit); // 找出一個節點中的所有子"Geometry"
ModelFileUtils.saveTo(outfit, outfitFile); // 保存文件
這幾個方法很簡單,請自行實現。
下面是調用這個方法來處理身形和裝備中骨骼索引的示例(你可以在自己的代碼中動態調用):
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代表你的資源路徑,請自行腦補。在處理前你的模型應該像是下面這樣的:

在處理后,你的模型應該像是下面這樣的:

示例圖中的foot.000名稱根據你的模型在blender中的名稱不同而有所區別。身形和裝備中的其它部分處理過程都是一樣的,這里不一一詳述。
九、在游戲中載入,并演示換裝
在經過前面的處理后,剩下的問題已經不是問題了,我們獲得了這些東西:
標準骨骼: ske.mesh.j3o (已經去掉了多余的模型)
基本身形: foot0.mesh.j3o, lowerBody0.mesh.j3o, upperBody0.mesh.j3o, ...(已經重定向骨骼索引,并去掉了Control和骨骼)
裝 備: foot1.mesh.j3o, lowerBody1.mesh.j3o, upperBody1.mesh.j3o, ...(同身形一樣)
現在使用一段代碼來示例如何在游戲中進行換裝:
@Override
public void simpleInitApp() {
// 載入標準骨骼
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);// 換上裝備
}
沒錯,跟其它節點的切換一樣簡單,只是attach和detach而已。當標準骨骼(上例中的ske節點)在執行動畫的時候,它的SkeletonControl會從子節點(皮膚或裝備)中獲取頂點、頂點與骨骼索引、權重的相關信息來計算動畫(注:皮膚和裝備中的Mesh中保存了頂點與骨骼索引和權重的關系)。
好了,文章就寫到這里,下面是一些額外補充!
十、附錄
1.上下連身裝備的處理
有時候我們可能會需要一些上下連身的裝備,如法師或牧師的長袍,這些沒有什么特別的,只是把lowerBody和upperBody合起來(在blender中不要切割開來就可以),在游戲中換上長袍的時候同時把基本皮膚中的lowerBody和upperBody移除即可。

2.不需要蒙皮的皮膚或裝備
有一些皮膚或裝備是可以不需要蒙皮的,比如頭發,手里的武器(如劍),或一些靜態飾品(如眼鏡),這些直接導出成完全靜態(不需要骨骼和動畫)的物體即可,然后在游戲中(這里以JME3示例)直接掛接到某塊骨頭上就可以,比如頭發:
// 載入標準骨骼
Node ske = (Node) getAssetManager().loadModel("Models/actor/ske.mesh.j3o");
SkeletonControl skeControl = ske.getControl(SkeletonControl.class);
// 獲得“頭骨”的節點
Node headNode = skeControl.getAttachmentsNode("headBone")
// 把頭發添加到頭骨的節點下(注意與上面的差別)
headNode.attachChild(hairNode);
這可以節省性能,因為蒙皮的物體在動畫過程中每個頂點都需要經過骨骼和權重來計算頂點的位置,一個頂點還可能受多個骨頭的影響,所以計算量比一般動畫要大得多。
3.不需要骨骼索引重定向的方法
在前面我們使用了一些特殊代碼來重定向皮膚和裝備中權重信息中的骨骼索引,但是這一步是可以超過的,只要在blender中為皮膚或裝備(如腳)綁定骨骼的時候使用復制出來的完整的標準骨骼就可以,這樣導出blender并轉為j3o后的foot0.mesh.j3o中的骨骼索引就和標準骨骼中的完全一樣(同樣的要刪除掉皮膚和裝備中的Control). 只不過使用這種方法后,以后調整標準骨骼的時候(比如添加或減少某些骨頭)你的皮膚和裝備可能需要重新使用新的標準骨骼來綁定并重新導出。
*******************************************************************************************************
聲明:轉載請包含 * 號內這段聲明(含 * 號)
作者: huliqing
Email: 31703299@qq.com
原文鏈接:http://www.huliqing.name/article/articleId=41
*******************************************************************************************************
- huliqing@huliqing.name
- http://www.huliqing.name