Blender建模与游戏换装(转载文)

本文转载自https://my.oschina.net/huliqing/blog/880113?hmsr=toutiao.io

如果本文涉及侵权行为,请原作者联系博主邮箱,我将及时进行删除处理

博主邮箱:yibiandao@aliyun.com

前言

本文将详细讲解3D游戏中换装的原理及换装中的一些重点问题,先粗略看一下换装的简单原理:

change1

change2

没错,看起来很简单吧!!!

那么接下来,开始讲复杂的地方!

在阅读本文之前如果你能够了解或知道以下一些基础知识,可以帮助你更好的了解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中建立一个模型,然后给模型配置一个骨骼,如下图:

mh_skeleton.jpg

根据自己的需要和模型动画的复杂度,选择不同的骨骼,如果你的模型需要包含眼部、嘴部或手指动画等,你就可以选择包含眼部、嘴部和手指等骨头的骨骼。当然,骨骼和模型越复杂,对后续游戏性能影响也就越大,根据需要适当选择。

在MakeHuman中建立了模型之后导出为mhx,再把它导入到blender中,就可以在blender中获得一套比较标准的骨骼了。

mh_export.jpg

下面的讲解我将以我所开发的游戏落樱之剑中使用的标准骨骼来进行说明:

骷骼正面(手指处的名称太杂乱,我没有显示出来)

骨骼侧面

注意,给骨骼中的骨头起个有意义的名称也很重要。

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

下面是根据标准骨骼建立的基本身形,你也可以在建立模型后再根据模型确定自己的标准骨骼,根据自己需要而定。 你也可以直接把从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插件分别导出以下相应物件:

  1. 导出标准骨骼
  2. 导出身形的各个部分,“脚”,“下身”,“上身”,“手”,“脸”...等
  3. 导出装备的各个部分,“脚”,“下身”,“上身”,“手” 装备部分我们只要这一些就可以。

注:在导出标准骨骼时,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格式。

操作

  1. 打开JME3的SDK,然后创建或打开你的JME项目.(JME3官网下载SDK,请使用JME3.0稳定版)
  2. 将所有刚导出的ogre格式的模型文件(标准骨骼,身形,装备)一起拷贝到JME项目的asset目录下,如: "assets/Textures/demo"
  3. 在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.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中的骨骼索引 // 这里需要检测并初始化一次就可以, 不能重复做,即使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"); ModelFileUtils.saveTo(outfit, outfitFile); } // 1.注意:如果skin中存在骨骼及动画信息,这些信息 // 将被丢弃,相应的骨骼索引信息会重定向到actor中的主骨骼上.这要求skinNode // 骨骼中的所有骨头的名称都应该与主rig中骨骼中的骨头名称相匹配. // 2.需要重定向skin中的骨骼索引,这里稍微复杂,需要详细记住: // 比如:主骨骼存在 root - hips - hand 这三个骨头, 而skin中的骨骼可能只有 // hand这个骨头, 为了把skin绑定到主骨骼中,也就是是将skin中的mesh的控制权 // 从skin自身的hand骨头交由主骨骼中的hand骨头来控制,这样skin就不需要AnimControl // 和SkeletonControl. // 因为skin自身的mesh中包含有权重和骨骼索引的信息. // 但是因为skin自身的骨骼索引和主骨骼索引是不一致的,如上:主骨骼中的hand // 的索引为2, 而skin因为hand只有一个骨头,索引为0, 所以在丢弃了skin中的 // AnimControl和SkeletonControl之后,需要把skin中hand的骨头索引重定向到 // 主骨骼中的hand的索引.而权重信息不需要改变,也就是如上:skin中mesh的关于 // hand的骨骼索引需要从0重定向到主骨骼中的hand索引2. // 这样主骨骼中的SkeletonControl在控制skin执行变换的时候才能正确从主骨骼 // 中找到hand的骨头,(否则找到的就是root了). // 关于骨骼与Mesh的具体逻辑可参考: SkeletonControl.controlRenderSoftware public static void main(String[] args) { // 重要:模型(j3o)只需要重定向一次就可以了,不能多次重定向,除非模型修改后重新转换成j3o. redirectBoneIndex("Models/actor/female/eye.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). 只不过使用这种方法后,以后调整标准骨骼的时候(比如添加或减少某些骨头)你的皮肤和装备可能需要重新使用新的标准骨骼来绑定并重新导出。

转载于:https://www.cnblogs.com/xiaoahui/p/11294723.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/262977.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

出路在哪里?出路在于思路!智者无敌

有人工作&#xff0c;有人继续上学&#xff0c;大家千万不要错过这篇文章&#xff0c;能看到这篇文章也是一种幸运&#xff0c;真的受益匪浅&#xff0c;对我有很大启迪&#xff0c;这篇文章将会改变我的一生&#xff0c;真的太好了&#xff0c;希望与有缘人分享&#xff0c;也…

xml02 XML编程(CRUD)增删查改

XML解析技术概述 Demo2.java import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; public class Demo2 { public static void main(String args[])throws Exception { //1.创建工程 DocumentBuilderFac…

ASP.NET Web Game 架构设计1--服务器基本结构

ASP.NET Web Game 架构设计1--服务器基本结构 1. 基本结构图 2. 系统组成与角色 整个系统大体上分为三个部分&#xff1a;1.网页客户端。2.IIS Web服务器。3.数据库及逻辑服务器。其中Web服务器不处理任何逻辑&#xff0c;它的作用只有两点&#xff1a;1.承载用户。…

人人网 Windows Phone 7 应用开发起步

目前&#xff0c;人人网在国内高校学生中的普及率非常高。前段时间&#xff0c;大概是11月下旬的样子&#xff0c;人人网发布了Windows Phone 7客户端的公测版。我想&#xff0c;Windows Phone 7本地化的优劣&#xff0c;直接关系到其将来在国内的市场份额。而诸如人人等针对学…

XP Sp2下双机通过无线网卡实现Internet共享

两台均有无线网卡、装有XP SP2系统的计算机如何实现共享Internet上网呢&#xff0c;请参考一下步骤&#xff1a; 1、打开两台计算机的无线网络连接属性&#xff0c;并切换至“无线网络配置”页签。2、点中右下角的高级按钮设置&#xff0c;选中最下面的“仅计算机到计算机” 和…

C#面向对象设计模式第九讲:Composite 组合模式(结构型模式)

&#xff08;根据MSDN Webcast相关课程整理&#xff09; 由俄罗斯套娃讲起。娃娃里又包含另一个娃娃&#xff0c;最后那个不包含任何娃娃。 组合模式&#xff0c;采用树型结构来实现普遍存在的对象容器&#xff0c;将本原一对多的复杂的关系&#xff0c;转换成一对一的简单关系…

Docker for Windows

安装条件&#xff1a;必须是 Win10 Pro 或者 Enterprise version. 转载于:https://www.cnblogs.com/qijiage/p/9261258.html

《火影忍者:究级风暴》渲染技术究极解析!

http://www.opengpu.org/forum.php?modviewthread&tid6609 与Takara Tomy公司的《火影忍者》系列游戏不同&#xff0c;初次登陆PS3平台的本作是由日本CyberConnect2制作的对战格斗游戏《火影忍者&#xff1a;终极英雄》系列的最新作。虽然游戏的开发商仍然是CyberConnect2…

工程中新增Properties

如一开始工程中是没有Properties文件夹的&#xff01; 但工程目录文件夹下却有一个Properties&#xff1a; 现在要向这个Properties文件夹中添资源文件。操作步骤&#xff1a; [添加新项]->[资源文件] 再将Resource.resx文件拉到Properties DONE!!!

Django:序列化的几种方法

前言 关于序列化操作&#xff0c;就是将一个可迭代的数据结构&#xff0c;通过便利的方式进行我们所需要的操作。 今天历来归纳一下&#xff0c;Django中的几种不同得分方法&#xff0c;已经Django-restframework提供的方法 创建一个Django的项目 再新建一个app 创建一个模型&a…

c#通过app.manifest使程序 右键 以管理员身份运行

c#通过app.manifest使程序以管理员身份运行 时间:2013-06-27 22:47来源:网络收集本站整理 作者:jtydl 点击: 1175 次微软在Windows Vista开始引入了UAC&#xff08;用户帐户控制&#xff09;新技术&#xff08;点击这儿了解什么是UAC&#xff09;。当程序执行时需要权限的话&am…

25款操作系统全面接触 [2]

Sun Solaris Sun Microsystems公司早期的操作系统版本Sun OS是基于BSD的。在1993年&#xff0c;他们与AT&T合作&#xff0c;转向了UNIX System V&#xff0c;并发布了称作Solaris.System V release 4的系统&#xff0c;这是一个UNIX System V和BSD的整合体。Solaris系统主…

Nuget发布Dll

今天要开始写ViewModel了&#xff0c;写完之后系统里的ViewModel都汇总到我这里&#xff0c;然后由我负责ViewModel的发布跟维护&#xff0c;所以Nuget发布Dll就要熟练啦~ 一&#xff0c;安装工具 1&#xff0c;Nuget Package Manager 2,NuGet.exe 下载地址为&#xff1a;http:…

技巧/诀窍:在ASP.NET中重写URL(转)

技巧/诀窍&#xff1a;在ASP.NET中重写URL 【原文地址】Tip/Trick: Url Rewriting with ASP.NET 【原文发表日期】 Monday, February 26, 2007 9:27 PM 经常有人请我指导应该如何动态地“重写”URL&#xff0c;以在他们的ASP.NETweb应用中发布比较干净的URL端点。这个博客帖…

妙趣横生算法 3:寻找相同元素的指针

实例说明 在已知两个从小到大的有序的数表中寻找出现的相同元素在第一个数表中的指针。 运行结果 实例解析 设两个数表的首元素指针分别为pa和pb,两个数表分别有元素an和bn个。另外&#xff0c;引入两个指针变量ca和cb,分别指向两个数表的当前访问元素。由于两个数表从小到大有…

Nginx 笔记与总结(14)expires 缓存设置

设置缓存&#xff0c;可以提高网站性能。 当网站的部分内容&#xff0c;比如新闻站的图片&#xff0c;一旦发布就不太可能发生更改&#xff0c;此时需要用户在访问一次页面之后&#xff0c;把该页面的图片缓存在用户的浏览器端一段时间&#xff0c;就可以用到 nginx 的 expires…

WP7应用开发笔记(8) IP输入框控件

因为需要在手机上配置IP&#xff0c;我需要一个界面输入IP地址&#xff0c;虽然直接使用TextBox&#xff0c;但是这样不太友好&#xff0c;我希望能够有和Windows网络设置上一样的IP输入框。所以决定写一个自定义控件。 设计控件外观 4个TextBox和3个显示“.”的TextBlock就可以…

C#并发实战Parallel.ForEach使用

前言&#xff1a;最近给客户开发一个伙食费计算系统&#xff0c;大概需要计算2000个人的伙食。需求是按照员工的预定报餐计划对消费记录进行检查&#xff0c;如有未报餐有刷卡或者有报餐没刷卡的要进行一定的金额扣减等一系列规则。一开始我的想法比较简单&#xff0c;直接用一…

[MSDN]ASP.NET MVC2(5)MVCRoute和urls

说明&#xff1a;本内容来自微软的webcast&#xff0c;讲师为苏鹏。视频没有书方便(想看哪页就看哪页)&#xff0c;所以抄录要点和老师语录。 内容介绍 - url和routes介绍 - routes匹配url的工作方式 - 使用routes Router对象mvc运转的核心。 预备知…

一个、说到所有的扩展指标

版权声明&#xff1a;本文博客原创文章&#xff0c;博客&#xff0c;未经同意&#xff0c;不得转载。