3D模型人物换装系统

3D模型人物换装系统

  • 介绍
  • 遇到的问题
  • 问题修复
  • 具体实现换装
    • 1.准备所有模型部位和模型骨骼
      • 部位准备
      • 材质准备
      • 模型根骨骼准备
      • 创建文件夹将上述模型拖成预制体
      • 创建一个动画状态机给他们附上待机动画
    • 2.脚本驱动
      • Mesh合并代码 UCombineSkinnedMgr.cs
      • 创建Mesh以及实例化对象的代码 UCharacterController.cs
      • 测试换装调用 UCharacterManager.cs
    • 3.测试
  • 总结

介绍

本文使用2018.4.4和2020.3.26进行的测试

人物换装系统

人物换装系统下

人物换装对模型的还是有一定要求的,首先换装是要有多套模型的,通用做法是将人物的身体(包含头),衣服、裤子、鞋子、头发分别作为一个部位,然后将这几个部位动态合批成一个人物模型。有的人身体和头也是分开的部位这个看做模型那边的情况具体定。还有的不要身体直接分成衣服、裤子、双手、双脚、头(包含头发),我这里介绍的也是这种方法这样也能避免很多蒙皮问题。

遇到的问题

之前我们在做换装人物合批的时候也遇到过几个影响比较大的问题这里我拿出来说一下。

  1. 蒙皮外露穿模情况:这个也比较好理解其实就是人物在做动作的时候可能把人身体的一部分露出来了,比如说肩膀衣服露肉了这种穿模情况。
  2. 人物合批材质也需要合批那么身上的贴图合批会乱(这种情况我会在下面代码讲一下这里不详细说了)。

问题修复

针对蒙皮穿模的这种情况其实有很多办法

  1. 协商模型制作去掉能够被衣服挡住的蒙皮(但是这里要考虑所有的衣服,但凡有一个衣服是需要露这块的蒙皮那么就不能使用这个方法)
  2. 针对不同的衣服使用不同程度的蒙皮,比如A衣服要用露胳膊但是B衣服不露胳膊,那么做一个是删除胳膊的身体蒙皮A1和一个是不删除胳膊的身体蒙皮B1,这样A-A1绑定 B-B1绑定,需要穿A衣服的时候绑定A1身体,需要穿B衣服的时候绑定B1身体。
  3. 不要身体,身体直接和衣服裤子绑定在一起做,去掉身体部位(这个也是本文的做法)。

具体实现换装

1.准备所有模型部位和模型骨骼

部位准备

如下所示把其中一个赋值好材质的模型预制体拖上来,红框是每个需要合批的部位。
在这里插入图片描述

选中每一个部位然后Ctrl + D拷贝一份
在这里插入图片描述

将上述拷贝的部位改名如下
在这里插入图片描述
全部拷贝出来如下所示,这样的对象是包含根节点信息的
在这里插入图片描述
改名如下
眉毛和脸模型都是一样的就不需要合批处理只用一个即可
在这里插入图片描述

材质准备

模型材质赋值到上述对象上
在这里插入图片描述
在这里插入图片描述

模型根骨骼准备

使用刚才上面的那个白膜拖拽到场景上
在这里插入图片描述
解除预制体绑定先
在这里插入图片描述

删除下面其他的Mesh模型
在这里插入图片描述
在这里插入图片描述

修改名称如下
在这里插入图片描述

这里我只是用了一个模型来讲,换装肯定有多个模型,其他模型也是这样处理,第一个模型名字我后缀都是1,后面的模型依次后缀是2、3、4…

创建文件夹将上述模型拖成预制体

创建文件如下,并且将上面的拷贝的对象分别放到对应文件夹下做预制体
在这里插入图片描述

其他所有的需要换装的模型都这样处理放到对应文件夹下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上图中脸和眉毛只有一个是因为都是一样的只保留了一份

创建一个动画状态机给他们附上待机动画

创建一个动画控制器并双击打开
在这里插入图片描述
将待机动画拖拽上来
在这里插入图片描述
将之前的所有模型根节点中的动画控制器换成刚才我们生成的动画控制器
在这里插入图片描述
在这里插入图片描述

2.脚本驱动

Mesh合并代码 UCombineSkinnedMgr.cs

using UnityEngine;
using System.Collections.Generic;public class UCombineSkinnedMgr
{/// <summary>/// Combine SkinnedMeshRenderers together and share one skeleton./// Merge materials will reduce the drawcalls, but it will increase the size of memory. /// </summary>/// <param name="skeleton">combine meshes to this skeleton(a gameobject)</param>/// <param name="meshes">meshes need to be merged</param>/// <param name="combine">merge materials or not</param>public void CombineObject(GameObject skeleton, SkinnedMeshRenderer[] meshes){// Fetch all bones of the skeletonList<Transform> transforms = new List<Transform>();transforms.AddRange(skeleton.GetComponentsInChildren<Transform>(true));List<Material> materials = new List<Material>();//the list of materialsList<CombineInstance> combineInstances = new List<CombineInstance>();//the list of meshesList<Transform> bones = new List<Transform>();//the list of bones// Collect information from meshes//获取所有for (int i = 0; i < meshes.Length; i++){SkinnedMeshRenderer smr = meshes[i];materials.AddRange(smr.materials); // Collect materials// Collect meshesfor (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++){CombineInstance ci = new CombineInstance();ci.mesh = smr.sharedMesh;ci.subMeshIndex = sub;combineInstances.Add(ci);}// Collect bonesfor (int j = 0; j < smr.bones.Length; j++){int tBase = 0;for (tBase = 0; tBase < transforms.Count; tBase++){if (smr.bones[j].name.Equals(transforms[tBase].name)){bones.Add(transforms[tBase]);break;}}}}// Create a new SkinnedMeshRendererSkinnedMeshRenderer oldSKinned = skeleton.GetComponent<SkinnedMeshRenderer>();if (oldSKinned != null){GameObject.DestroyImmediate(oldSKinned);}SkinnedMeshRenderer r = skeleton.AddComponent<SkinnedMeshRenderer>();r.sharedMesh = new Mesh();r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false);// Combine meshesr.bones = bones.ToArray();// Use new bonesr.materials = materials.ToArray();}
}

创建Mesh以及实例化对象的代码 UCharacterController.cs

using UnityEngine;public class UCharacterController
{/// <summary>/// GameObject reference/// </summary>public GameObject Instance = null;/// <summary>/// 换装总组装数量/// </summary>public int m_MeshCount = 9;public string Role_Skeleton;public string Role_Body;public string Role_Cloak;public string Role_Face;public string Role_Hair;public string Role_Hand;public string Role_Leg;public string Role_MainWeapon;public string Role_Retina;public string Role_SubWeapon;/// <summary>/// 创建对象/// </summary>/// <param name="job"></param>/// <param name="skeleton"></param>/// <param name="body"></param>/// <param name="cloak"></param>/// <param name="face"></param>/// <param name="hair"></param>/// <param name="hand"></param>/// <param name="leg"></param>/// <param name="mainweapon"></param>/// <param name="retina"></param>/// <param name="subweapon"></param>public UCharacterController(string job, string skeleton, string body, string cloak, string face, string hair, string hand, string leg, string mainweapon, string retina, string subweapon){Object res = Resources.Load("RoleMesh/" + job + "/" + job + "/" + skeleton);this.Instance = GameObject.Instantiate(res) as GameObject;this.Role_Skeleton = skeleton;this.Role_Body = body;this.Role_Cloak = cloak;this.Role_Face = face;this.Role_Hair = hair;this.Role_Hand = hand;this.Role_Leg = leg;this.Role_MainWeapon = mainweapon;this.Role_Retina = retina;this.Role_SubWeapon = subweapon;string[] equipments = new string[m_MeshCount];equipments[0] = "Body/" + Role_Body;equipments[1] = "Cloak/" + Role_Cloak;equipments[2] = "Face/" + Role_Face;equipments[3] = "Hair/" + Role_Hair;equipments[4] = "Hand/" + Role_Hand;equipments[5] = "Leg/" + Role_Leg;equipments[6] = "Mainweapon/" + Role_MainWeapon;equipments[7] = "Retina/" + Role_Retina;equipments[8] = "Subweapon/" + Role_SubWeapon;SkinnedMeshRenderer[] meshes = new SkinnedMeshRenderer[m_MeshCount];GameObject[] objects = new GameObject[m_MeshCount];for (int i = 0; i < equipments.Length; i++){res = Resources.Load("RoleMesh/" + job + "/" + equipments[i]);objects[i] = GameObject.Instantiate(res) as GameObject;meshes[i] = objects[i].GetComponentInChildren<SkinnedMeshRenderer>();}UCharacterManager.Instance.CombineSkinnedMgr.CombineObject(Instance, meshes);for (int i = 0; i < objects.Length; i++){GameObject.DestroyImmediate(objects[i].gameObject);}}public void Delete(){GameObject.Destroy(Instance);}/// <summary>/// 部位换装/// </summary>/// <param name="path">路径</param>/// <param name="index">切换部位</param>/// <param name="equipment"></param>/// <param name="combine"></param>public void ChangeEquipments(string path, int index, int equipmentId){switch (index){case 0:Role_Body = "Body" + equipmentId;break;case 1:Role_Cloak = "Cloak" + equipmentId;break;case 2:Delete();this.Instance = GameObject.Instantiate(Resources.Load("RoleMesh/" + path + "/" + path + "/" + path + equipmentId)) as GameObject;Role_Hair = "Hair" + equipmentId;break;case 3:Role_Hand = "Hand" + equipmentId;break;case 4:Role_Leg = "Leg" + equipmentId;break;case 5:Role_MainWeapon = "Mainweapon" + equipmentId;Role_SubWeapon = "Subweapon" + equipmentId;break;}string[] equipments = new string[m_MeshCount];equipments[0] = "Body/" + Role_Body;equipments[1] = "Cloak/" + Role_Cloak;equipments[2] = "Face/" + Role_Face;equipments[3] = "Hair/" + Role_Hair;equipments[4] = "Hand/" + Role_Hand;equipments[5] = "Leg/" + Role_Leg;equipments[6] = "Mainweapon/" + Role_MainWeapon;equipments[7] = "Retina/" + Role_Retina;equipments[8] = "Subweapon/" + Role_SubWeapon;Object res = null;SkinnedMeshRenderer[] meshes = new SkinnedMeshRenderer[m_MeshCount];GameObject[] objects = new GameObject[m_MeshCount];for (int i = 0; i < equipments.Length; i++){res = Resources.Load("RoleMesh/" + path + "/" + equipments[i]);objects[i] = GameObject.Instantiate(res) as GameObject;meshes[i] = objects[i].GetComponentInChildren<SkinnedMeshRenderer>();}UCharacterManager.Instance.CombineSkinnedMgr.CombineObject(Instance, meshes);for (int i = 0; i < objects.Length; i++){GameObject.DestroyImmediate(objects[i].gameObject);}}}

测试换装调用 UCharacterManager.cs

using UnityEngine;
using System.Collections.Generic;/// <summary>
/// 换装管理器
/// </summary>
public class UCharacterManager : MonoBehaviour
{public static UCharacterManager Instance;private UCombineSkinnedMgr skinnedMgr = null;public UCombineSkinnedMgr CombineSkinnedMgr { get { return skinnedMgr; } }private int characterIndex = 0;private Dictionary<int, UCharacterController> characterDic = new Dictionary<int, UCharacterController>();public UCharacterManager(){skinnedMgr = new UCombineSkinnedMgr();}private void Awake(){Instance = this;}public UCharacterController mine;private void Start(){mine = Generatecharacter("Axceler", "Axceler1", "Body5", "Cloak5", "Face", "Hair1", "Hand5", "Leg5", "Mainweapon5", "Retina", "Subweapon5");}private void Update(){if (Input.GetKeyDown(KeyCode.Space)){ChangeRole();}//衣服if (Input.GetKeyDown(KeyCode.Q)){mine.ChangeEquipments("Axceler", 0, Random.Range(1, 8));}//飘带if (Input.GetKeyDown(KeyCode.W)){mine.ChangeEquipments("Axceler", 1, Random.Range(1, 7));}//头发if (Input.GetKeyDown(KeyCode.E)){mine.ChangeEquipments("Axceler", 2, Random.Range(1, 8));}//手套if (Input.GetKeyDown(KeyCode.A)){mine.ChangeEquipments("Axceler", 3, Random.Range(1, 8));}//腿饰if (Input.GetKeyDown(KeyCode.S)){mine.ChangeEquipments("Axceler", 4, Random.Range(1, 8));}//武器if (Input.GetKeyDown(KeyCode.D)){mine.ChangeEquipments("Axceler", 5, Random.Range(1, 8));}}public void ChangeRole(){if (mine != null){mine.Delete();}int a = Random.Range(1, 8);int b = Random.Range(1, 8);int c = Random.Range(1, 7);int d = Random.Range(1, 8);int e = Random.Range(1, 8);int f = Random.Range(1, 8);int g = Random.Range(1, 8);mine = Generatecharacter("Axceler", "Axceler" + a, "Body" + b, "Cloak" + c, "Face", "Hair" + a, "Hand" + d, "Leg" + e, "Mainweapon" + f, "Retina", "Subweapon" + g);}#region 创建人物模型骨骼public UCharacterController Generatecharacter(string job, string skeleton, string body, string cloak, string face, string hair, string hand, string leg, string mainweapon, string retina, string subweapon){UCharacterController instance = new UCharacterController(job, skeleton, body, cloak, face, hair, hand, leg, mainweapon, retina, subweapon);characterDic.Add(characterIndex, instance);characterIndex++;return instance;}#endregion
}

3.测试

随便创建一个场景然后在随便一个对象上面挂载UCharacterManager.cs脚本运行测试在这里插入图片描述

总结

这个方案是将所有的Mesh合并成一个Mesh,材质球是叠加的并没有合批材质
在这里插入图片描述

注意:如果要想将模型完全合批需要将所有图都设置可读可写,这里放到另一篇文章去讲,这里先不讲合批材质的方法在这里插入图片描述
本文资源
如果文章对你有帮助的话留一个免费的关注和点赞吧

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

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

相关文章

Kotlin文件和类为什么不是一对一关系

在Java中&#xff0c;一个类文件的public类名必须和文件名一致&#xff0c;如何不一致就会报异常&#xff0c;但是在kotlin的文件可以和类名一致&#xff0c;也可以不一致。这种特性&#xff0c;就跟c有点像&#xff0c;毕竟c的.h 和 .cpp文件是分开的。只要最终编译的时候对的…

threejs(11)-shader着色器打造漫天飞舞孔明灯

src/main/main.js import * as THREE from "three";import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import gsap from "gsap"; // 动画库 import vertexShader from "../shaders/flylight/vertex.glsl"…

不可否认程序员的护城河已经越来越浅了

文章目录 那些在冲击程序员护城河低代码/无代码开发平台自动化测试和部署工具AI辅助开发工具在线学习和教育平台 面临冲击&#xff0c;程序员应该怎么做深入专业知识&#xff1a;不断学习全栈技能开发解决问题的能力建立人际网络管理和领导技能 推荐阅读 技术和应用的不断发展对…

用于图像处理的高斯滤波器 (LoG) 拉普拉斯

一、说明 欢迎来到拉普拉斯和高斯滤波器的拉普拉斯的故事。LoG是先进行高斯处理&#xff0c;继而进行拉普拉斯算子的图像处理算法。用拉普拉斯具有过零功能&#xff0c;实现边缘岭脊提取。 二、LoG算法简述 在这篇博客中&#xff0c;让我们看看拉普拉斯滤波器和高斯滤波器的拉普…

Docker快速入门

Docker是一个用来快速构建、运行和管理应用的工具。 Docker技术能够避免对服务器环境的依赖&#xff0c;减少复杂的部署流程&#xff0c;有了Docker以后&#xff0c;可以实现一键部署&#xff0c;项目的部署如丝般顺滑&#xff0c;大大减少了运维工作量。 即使你对Linux不熟…

Learn runqlat in 5 minutes

内容预告 learn X in 5 系列第一篇. 本篇主要介绍进程时延统计方式和 rawtracepoint. runqlat "高负载场景下应用为何卡顿", "进程 A 为什么得不到调度". 当我们在工作生活中产生这样的疑问, 目标进程的调度时延是一个不错的观测切入点. runqlat 可以帮…

【网络编程】网络层——IP协议

文章目录 基本概念路径选择主机和路由器 IP协议格式分片与组装网段划分IP地址的数量限制私网IP地址和公网IP地址深入认识局域网路由 基本概念 TCP作为传输层控制协议&#xff0c;其保证的是数据传输的可靠性和传输效率&#xff0c;但TCP提供的仅仅是数据传输的策略&#xff0c…

C语言 每日一题 PTA 11.7 day13

1.求e的近似值 自然常数 e 可以用级数 1 1 / 1! 1 / 2! ⋯ 1 / n! ⋯ 来近似计算。 本题要求对给定的非负整数 n&#xff0c;求该级数的前 n 1 项和。 代码实现 #include<stdio.h> void main() {int a, i, j; double b 1; double c 1;printf("请输入一个数\n…

考研数据结构单链表的增删改查看这一篇就够了

目录 一. 单链表的特点 1.1 解引用拓展 &#x1f916; 二. 单链表的操作 2.1不带头节点的操作 2.1.1 打印 2.1.1.1 创建结点 2.1.2 尾插&#xff08;需要二级指针&#xff09; 注意形参的值不改变实参&#xff1a;&#xff08;精髓部分&#xff09; 2.1.3 头插 2.1.4…

苹果手机安装未上架APP应用测试教程

STEP 2&#xff1a;找到下载的描述文件&#xff08;如果没有找到&#xff0c;请到 设置 - 通用 - 描述文件 中查看&#xff09; STEP 3&#xff1a;安装描述文件 STEP 4&#xff1a;输入解锁密码安装描述文件 STEP 5&#xff1a;同意免责声明&#xff0c;安装描述文件 STEP 6…

单词规律问题

给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配&#xff0c;例如&#xff0c; pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。 示例1: 输入: pattern “abba”, s “dog cat cat d…

一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?

目录 1解题思路&#xff1a; 2代码如下&#xff1a; 3运行结果&#xff1a; 4总结&#xff1a; 5介绍&#xff1a; 1解题思路&#xff1a; 利用循环&#xff08;穷举法&#xff09;来 对 所 需要的数 进行确定 2代码如下&#xff1a; #include <stdio.h>int main() …