在前置篇中,基本上梳理了一下换装功能背后涉及到的美术工作流。但程序员嘛,功能终归是要落到代码上的。本文中会结合Unity提供的API及之前提到的内容来实现一个简单的换装功能。效果如下:
  

           (图1:最终效果展示)


资源导出规则


所有的换装实现都是和导出规则相对应的。先说一下我这个小例子的导出规则。

1.角色的主干部分,包括头,胳膊,大腿。整体导出作为一个基础蒙皮。

2.其他部分的蒙皮,手套,下装,衣服,头发。每一种样式都一个个单独导出。

3.从MAX中导出FBX资源时,要注意导出蒙皮时候,骨骼也要选上,否则导出的就是普通mesh,而不是蒙皮了。

          (图2:角色基础部分的导出内容,左侧为主干部分,右侧为一个头发部件.都要带上骨骼)            


基本流程


如图3,将max导出的所有fbx放入Unity后,每个部件都是单独的,我们要做的就是把这些分散的部件攒在一起,让他们正确的显示并响应动画。

        

(图3:Unity中显示的所有导出身体部件,Girl为主干模型)

  写具体代码之前,我们先说一下几个关键的Unity组件,Animator,SkinnedMeshRenderer.Animator会读取动画信息,我们在前置篇提到,max只制作动画的关键帧,而游戏渲染是一帧一帧的,关键帧之间的动画如何过渡,就是引擎自己负责的,也就是Animator做的事,Animator计算好当前帧的骨骼姿态后。会根据结果去改变Animator组件所在节点下的骨骼结构节点,只有我们在max里将骨骼正确导出,才会出现这些节点。SkinnedMeshRenderer则负责蒙皮计算,在每一帧中根据Animator计算出来后的骨骼位置,找到自己关联了哪些骨骼及权重,然后进行变换和插值,计算出mesh顶点的正确位置。再将这些顶点信息传入对应的材质球中进行渲染。


实现代码


下面是一个简单的实现代码,我会对一些关键代码进行说明。这个脚本是挂在角色主干部分的Prefab上。

 public class SkinTest : MonoBehaviour
{ public GameObject[] Hairs;
public GameObject[] Clothes;
public GameObject[] Gloves;
public GameObject[] Unders; private int hairIndex = ;
private int clothesIndex = ;
private int glovesIndex = ;
private int underIndex = ; private List<Transform> bones;
private GameObject rootBone;
void Start ()
{
rootBone = gameObject.transform.FindChild("Bip001").gameObject;
bones = new List<Transform>(); BuildPlayer();
} public void BuildPlayer()
{
bones.Clear();
List<CombineInstance> combineInstances = new List<CombineInstance>();
List<Material> materials = new List<Material>();
List<SkinnedMeshRenderer> smrList = new List<SkinnedMeshRenderer>();
Transform[] transforms = rootBone.GetComponentsInChildren<Transform>(true); if(Hairs!=null && Hairs.Length > hairIndex && Hairs[hairIndex]!=null)
{
SkinnedMeshRenderer smr = Hairs[hairIndex].GetComponentInChildren<SkinnedMeshRenderer>();
if (smr != null)
{
smrList.Add(smr);
}
} if (Clothes != null && Clothes.Length > clothesIndex && Clothes[clothesIndex] != null)
{
SkinnedMeshRenderer smr = Clothes[clothesIndex].GetComponentInChildren<SkinnedMeshRenderer>();
if (smr != null)
{
smrList.Add(smr);
}
} if (Gloves != null && Gloves.Length > glovesIndex && Gloves[glovesIndex] != null)
{
SkinnedMeshRenderer smr = Gloves[glovesIndex].GetComponentInChildren<SkinnedMeshRenderer>();
if (smr != null)
{
smrList.Add(smr);
}
} if (Unders != null && Unders.Length > underIndex && Unders[underIndex] != null)
{
SkinnedMeshRenderer smr = Unders[underIndex].GetComponentInChildren<SkinnedMeshRenderer>();
if (smr != null)
{
smrList.Add(smr);
}
} for(int i =;i<smrList.Count;i++)
{
SkinnedMeshRenderer smr = smrList[i];
if(smr)
{
for(int sub =;sub<smr.sharedMesh.subMeshCount;sub++)
{
for (int j = ; j < smr.bones.Length; j++)
{
for (int index = ; index < transforms.Length; index++)
{
if (smr.bones[j].name.Equals(transforms[index].name))
{
bones.Add(transforms[index]);
break;
}
}
} CombineInstance ci = new CombineInstance(); ci.mesh = smr.sharedMesh;
ci.subMeshIndex = sub;
combineInstances.Add(ci);
}
materials.AddRange(smr.sharedMaterials); }
} SkinnedMeshRenderer oldSkin = GetComponent<SkinnedMeshRenderer>();
if(oldSkin!=null)
{
GameObject.DestroyImmediate(oldSkin);
} SkinnedMeshRenderer newSmr = gameObject.AddComponent<SkinnedMeshRenderer>();
newSmr.sharedMesh = new Mesh();
newSmr.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false);
newSmr.bones = bones.ToArray();
newSmr.rootBone = rootBone.transform;
newSmr.materials = materials.ToArray();
}
}

4~15行,一些基本变量,存放用于换装的Prefab的引用,以及索引下标,bones用来存储Skin合并后的骨骼引用,rootBone用来存储根骨骼。

18行,找到根骨的节点,此处的Bip001是3Dmax中Bip结构的默认根节点。主干部分的蒙皮导出时带有骨骼,所以可以在Prefab的子节点上找到。

27~30行,建立一些List用来存储SkinMeshRenderer合并过程中所用到的一些中间内容。这里再提一下SkinMeshRenderer,我们会发现一个SkinMeshRenderer一般都只包含一个Material,但它是可以包含多个的。当我们的SkinMeshRenderer里对应的Mesh是包含多个subMesh的时候,那么他们需要多个材质球来对应每个SubMesh。我们导出的各个部件里都有自己的SkinMeshRenderer,我们要做的是把他们合为一个整体,这样做会对计算性能上有提升,逻辑处理上也更统一。后面我们再细说。

32~66行,这部分是根据各个部位的索引号去配置好的Prefab数组中查找到对应配件,只获取SkinMeshRenderer组件就够了,因为他里面包含了我们所需的蒙皮的所有信息。把他们放到List中后面统一处理。

68~96行,循环遍历处理我们前面获取的各个部件的SkinMeshRenderer.这里要说一下关于SkinMeshRenderer的Bones变量,它返回的是这个Skin绑定了哪些骨骼,Unity是以Transform引用数组的形式返回的,引用的是原来每个部件Prefab下自己的Bip下的骨骼节点,当我们把这些SkinMeshRenderer整合成一个的时候,就需要把引用重新指定成主体模型上的相应骨骼节点,这正是73~85行做的事。注意我这里根据部位里是否有多个subMesh来重复添加多次骨骼,这是必须的,而且顺序也是一定要保证的。在FBX上的Optimize Mesh选项可以解决这个问题,不过会引入其它问题,这里不展开了。每个部件Skin对应的材质球也都按顺序放到List中。89~91行的CombineInstance是Unity用来进行Mesh合并的一个数据结构,我们最终是需要把每个部件Skin对应的Mesh合并到一起,这里注意,合并到一起,并不一定是真的变成了一个Mesh,因为部件和部件之间的材质不一定完全一致,这时候的Mesh合并实际上只是一种逻辑上的合并,真正渲染时各个部件的Mesh顶点数据还是各走一个DrawCall。即使是这样,逻辑上的这种整合对于Unity的性能也是有好处的,这涉及到渲染层面节省顶点Buffer的问题,也涉及到提高Unity引擎一些自身逻辑效率的问题,这里不展开。subMeshIndex这个变量,对于普通的部件Skin里只包含一个subMesh,所以一般指定0,但有时候会包含多个,如果在max里部件本身就由多个材质构成,那么每个材质负责的Mesh部分到了Unity里就变成一个SubMesh了。93行我们把材质球也按顺序(顺序很重要),放到了List里,你也许会问为什么不合并呢?理论上如果所有部件用的都是统一材质,或者材质基本相似的话是可以通过合并贴图,重新赋值UV来让所有部件正真的合并在一起,只用一个Mesh。

104~106行,我们最终要把所有分散的SkinMeshRenderer合并到一起,添加一个SkinnedMeshRenderer组件,但是这个组件的所有变量都是默认空的。所以105行我们给这个Renderer新建一个空的Mesh。106行通过CombineMesh来利用我们前面创建的CombineInstance数据把Mesh合并。这里说明一下后两个参数,第一个参数如果为true,则表示会把所有Mesh真的合并到一起,也就是合并之后subMeshCount为1。这一搬是与我前面提到的材质合并配合使用的。第三个参数为true的话我们需要给每个CombineInstance提供一个变换矩阵,在它们被合并之前,它们会先利用这个矩阵进行一次空间变换。

107行,将前面骨骼节点集合传递给前面新建的SkinMeshRenderer,必须保证顺序。

108行,rootBone习惯性的赋值为骨骼结构的根节点,这里设为空也没问题。

109行,同骨骼节点一样集合一样,材质球集合传递给SkinMeshRenderer,保证顺序与部件合并的顺序相同。


总结


  换装功能的实现代码并没有统一规范,这跟部件的设计规则有很大关系,所以本文只提供一种最简单基本的思路。还可以在这个基础上继续展开,深入优化。有些地方我没有深入去剖析,一笔带过。一方面是有些内容我也并不深入了解,另一方面是怕大家过于纠结细节,迷失方向。对于一些更深入的内容,我计划有时间再写一篇来分享。上面的实现在实际项目中也有很多问题,比如每个部件的Fbx导出都需要带全套骨骼。这造成一些数据上的冗余,如果要是在资源打包上依然没有办法去掉冗余的话,就会造成运行时内存的浪费,希望大家来一起讨论。   

  尊重他人智慧成果,若要转载,请注明作者esfog,原文地址http://www.cnblogs.com/Esfog/p/EquipChange_SimpleArchive.html

浅谈角色换装功能--Unity简单例子实现的更多相关文章

  1. unity 角色换装

    unity角色换装的关键是更改角色部位上的物体的SkinnedMeshRenderer组件的属性: 更改mesh:mesh决定了部位的物体的外形,是主要的数据. 刷新骨骼:同一个部位下,不同的mesh ...

  2. 【Unity3D】3D角色换装++ Advance

    http://www.cnblogs.com/dosomething/archive/2012/12/15/2818897.html 本文在之前的文章Unity3D角色换装的原理 基础上做一个补充 给 ...

  3. 浅谈测试rhel7新功能时的感受及遇到的问题【转载】

    半夜起来看世界杯,没啥激情,但是又怕错误意大利和英格兰的比赛,就看了rhel7 相关新功能的介绍. rhel7的下载地址: https://access.redhat.com/site/downloa ...

  4. Unity3d 3d角色换装实现原理及步骤

    http://www.cnblogs.com/dosomething/archive/2012/04/15/2450526.html 1.角色模型制作 unity3d支持Skin动画  但是不支持Ph ...

  5. 【Unity3d】3d角色换装实现原理及步骤

    http://www.cnblogs.com/dosomething/archive/2012/04/15/2450526.html 1.角色模型制作 unity3d支持Skin动画  但是不支持Ph ...

  6. 浅谈vuex使用方法(vuex简单实用方法)

    Vuex 是什么? Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex 也集成到 Vu ...

  7. 浅谈APP的分享功能,有时候社交裂变形式比内容更“重要”

    回顾2018年的移动互联网,“社交裂变”“下沉”等成为年度关键词.一方面我们可以看到社交裂变助推用户增长,另一方面我们也看到了以拼多多.趣头条为代表的互联网企业对于社交裂变模式表现出的空前关注度.作为 ...

  8. Unity-ECS(一)浅谈CPU缓存命中和Unity面向数据技术栈(DOTS)--笔记

    一,缓存类型 概念:局部性. 时间局部性:当前用到的一个存储器位置,不久的将来会被用到. 空间局部性:当前用到的一个存储器位置,附近的位置会被用到. 那么在CPU的层面,这两个局部性的特性就会被Cac ...

  9. unity简单例子

    1. https://www.cnblogs.com/chengxuzhimei/p/4992106.html 2.https://www.cnblogs.com/GreenLeaves/p/7086 ...

随机推荐

  1. Python3.7和数据库MySQL交互(二)SQLyog安装教程

    首先安装MySQL数据库,初学者建议选择图形化客户端. Toad for MySQL.MySQL-Front.Navicat for MySQL.SQLyog. 官方下载链接: Toad for My ...

  2. 在LINUX(Ubuntu 18.04.x、CentOS)下配置MySQL8.0.x

    安装教程:Installing MySQL on Unix/Linux Using Generic Binaries MySQL下载链接:https://dev.mysql.com/downloads ...

  3. UVa 712

    这个题根本不用建树,因为是完全二叉树,可以把这个想成二进制.对于根是二进制数的首位,之后依次类推.到最后的叶子节点就是从0到pow(2,n)-1. 关键在于在第一次输入的不是按照x1,x2,x3,x4 ...

  4. VMware Ubuntu安装过程

    一.下载Ubuntu镜像文件 1.到官网下载:http://www.ubuntu.com 2.百度云(16.4.6): 链接:https://pan.baidu.com/s/14IlVP--D5mtZ ...

  5. 03MYSQL数据库

    mySQL 数据库 储存数据,属于中小型数据库   默认端口号 3306  密码root sql是一门编程语言 结构化查询语言  是强类型语言(定义变量时要指定变量类型) 字符串有两种类型:  定长: ...

  6. 20164322 韩玉婷-----Exp6 信息搜索与漏洞扫描

    1.实践目标 掌握信息搜集的最基础技能与常用工具的使用方法. 2.实践内容 (1)各种搜索技巧的应用 (2)DNS IP注册信息的查询 (3)基本的扫描技术:主机发现.端口扫描.OS及服务版本探测.具 ...

  7. html5 知识点简单总结03

    table表格 ----基本结构 table默认无边框(border) <table border="数值"> <tr> <th>表头</ ...

  8. 《构建之法》chapter5,6 读书心得

    <构建之法>第五章用体育运动等团队例子引出软件开发团队的形式,用更加生活化.形象化的例子让读者更能理解软件开发团队的形式.软件团队形式多样,适用于不同的人员与需求.团队可能会演变的模式有: ...

  9. 最详细最权威的Android 编码规范

    1. 前言 这份文档参考了 Google Java 编程风格规范和 Google 官方 Android 编码风格规范.该文档仅供参考,只要形成一个统一的风格,见量知其意就可. 1.1 术语说明 在本文 ...

  10. 4. Traffic monitoring tools (流量监控工具 10个)

    4. Traffic monitoring tools (流量监控工具 10个)EttercapNtop SolarWinds已经创建并销售了针对系统管理员的数十种专用工具. 安全相关工具包括许多网络 ...