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 skeleton
        List<Transform> transforms = new List<Transform>();
        transforms.AddRange(skeleton.GetComponentsInChildren<Transform>(true));

        List<Material> materials = new List<Material>();//the list of materials
        List<CombineInstance> combineInstances = new List<CombineInstance>();//the list of meshes
        List<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 meshes
            for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
            {
                CombineInstance ci = new CombineInstance();
                ci.mesh = smr.sharedMesh;
                ci.subMeshIndex = sub;
                combineInstances.Add(ci);
            }
            // Collect bones
            for (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 SkinnedMeshRenderer
        SkinnedMeshRenderer 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 meshes
        r.bones = bones.ToArray();// Use new bones

        r.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,材质球是叠加的并没有合批材质
在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文带你了解栈的基本概念以及栈的实现

✏️✏️✏️今天给大家分享一下栈的基本概念、线性栈的自定义实现&#xff0c;以及栈的应用题目。 清风的CSDN博客 &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01…

阿里云配置ECS实例的IPv6地址,开通公网IPv6

1.阿里云ECS服务器开通IPv6地址&#xff0c;开通公网IPv6 1.1.官网教程 配置ECS实例的IPv6地址 1.2.相关截图 1 2 3 4 5 6

ElasticSearch中常见的分词器介绍

文章目录 ElasticSearch中常见的分词器介绍前言分词器的作用如何指定分词器分词器的组成分词器的类型标准分词器空格分词器简单分词器关键词分词器停用词分词器IK分词器NGram分词器正则匹配分词器语言分词器自定义分词器 ElasticSearch中常见的分词器介绍 前言 ElasticSearch是…

抖音小程序开发:探索技术创新的代码之旅

随着抖音小程序的兴起&#xff0c;企业纷纷将目光投向这个充满活力的平台。抖音小程序开发不仅为品牌提供了更广泛的曝光机会&#xff0c;更是技术创新的舞台。本文将带领读者深入探索抖音小程序开发的技术要点&#xff0c;探讨如何通过代码实现个性化、高效的小程序。 1. 小…

【2】Gradle-快速入门使用【Gradle项目结构概念】

目录 【2】Gradle-快速入门使用【Gradle项目结构概念】安装本地安装先决条件 官网安装教程 Gradle 快速指南初始化项目查看Gradle的项目结构了解Gradle Wrapper调用Gradle包装器了解Gradle的项目结构了解settings文件了解构建脚本 IDEA中使用Gradle创建一个新项目创建一个Sprin…

【STM32】

STM32 1 CMSIS1.1 概述1.2 CMSIS 应用程序文件描述 2 库2.1 简介2.2 标准外设库&#xff08;standrd Peripheral Libraries&#xff09;2.3 HAL 库2.3.1 目录结构2.3.2 HAL库API函数和变量的命名规则2.3.3 HAL库对寄存器位操作的相关宏定义2.3.4 HAL库回调函数2.3.5 HAL使用注意…

6.存储器概述,主存储器

目录 一. 存储系统基本概念 &#xff08;1&#xff09;存储系统的层次结构 &#xff08;2&#xff09;分类 &#xff08;3&#xff09;存储器的性能指标 二. 主存储器的基本组成 三. SRAM和DRAM 四. 只读存储器ROM 五. 提升主存速度的方法 &#xff08;1&#xff09;双…

【tg】 5 :线程切换

manager 可以切到 其他类的其他线程去执行。线程切换 先通过 networkmgr 线程 执行 ,但是传递了Manager 自己的线程 进去。在networkmgr 的network线程中,获取到stats数据,然后扔给 manager的线程thread ,去posttask 还行这个task里调用了mediamanager 的perform ,在media…

U盘不可以访问的维护

u盘打不开&#xff0c;可按下图&#xff0c;设置&#xff1a;winR→gpedit.msc&#xff1b;配置“管理模板”→“系统”→“可移动存储访问”→“所有可移动存储类”。 然后&#xff0c;选择“未配置”&#xff0c;如下图

环形处理习题,举例:约瑟夫环,魔方阵

目录 约瑟夫环 魔方阵 约瑟夫环 题目描述&#xff1a;有n 个人围成一圈,顺序排号。从第1个人开始报数从1到3报数凡是报到3 的人退出圈子,问最后留下的是原来的第几号? 环形处理:依次遍历数据集的每个元素&#xff08;每个人依次报号&#xff09;&#xff0c;直到遍历到最后…

xlua游戏热更新(lua访问C#)

CS.UnityEngine静态方法访问unity虚拟机 创建游戏物体 CS.UnityEngine.GameObject(new by lua);静态属性 CS.UnityEngine.GameObject(new by lua); -- 创建 local camera CS.UnityEngine.GameObject.Find(Main Camera); --查找 camera.name Renamed by Lua;访问组件 loca…

思维模型 斯金纳箱原理

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。通过合理奖惩&#xff0c;塑造行为&#xff0c;此名为“学习”。 1 斯金纳箱原理的应用 1.1 斯金纳箱在游戏设计中的应用-《糖果传奇》 《糖果传奇》是一款由 King 开发的三消游戏&#x…

C语言--定义一个包含年月日的结构体Day,实现一个函数,根据传入的结构体指针计算,该日期是当年的第几天?

一.题目要求 输入2000年6月5日&#xff0c;输出&#xff1a;这是2000年的第157天。 二.思路分析 首先定义一个包含年月日的结构体 年份&#xff1a;要判断是否是闰年&#xff0c;闰年的二月有29天&#xff0c;平年的二月有28天。 月份&#xff1a;一个月份分大月和小月&#…

leetCode 493 翻转对

给定一个数组 nums &#xff0c;如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。你需要返回给定数组中的重要翻转对的数量。 未完待续~

IDEA的优化配置教程

前言 IDEA 全称 IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以…

Win11专业版安装Docker Desktop,并支持映射主机的gpu

一、Windows环境下安装 Docker 必须满足: 1. 64位Windows 11 Pro(专业版和企业版都可以) 2. Microsoft Hyper-V,Hyper-V是微软的虚拟机,在win11上是自带的,我们只需要启动就可以了 二、下载Docker Desktop安装包 方式一:进入官网下载 https://docs.docker.com/desktop…

基于VSCode + PlatformIO创建运行第一个esp32程序

文章目录 使用VSCode创建项目安装驱动下载驱动安装驱动连接开发板电脑识别开发板 编写程序烧录程序第一步、编译程序第二步、烧录程序第三步、开发板观察效果 原理讲解项目源码 在之前的课程&#xff0c;我们已经介绍了ESP32单片机&#xff0c;并且也已经安装好了开发环境&…

matplotlib 创建图和子图

Matplotlib 可能是 Python 2D-绘图领域使用最广泛的套件。它能让使用者很轻松地将数据图形化&#xff0c;并且提供多样化的输出格式。这里将会探索 matplotlib 的常见用法。 plt方式是先生成了一个画布&#xff0c;然后在这个画布上隐式的生成一个画图区域来进行画图&#xff1…

yum工具的使用

yum工具的使用 rpm的弊端 前面我们讲了下rpm&#xff0c;那么rpm有什么弊端呢&#xff1f;其弊端是显而易见的&#xff0c;当用rpm安装软件时&#xff0c;若遇到有依赖关系的软件&#xff0c;必须先安装依赖的软件才能继续安装我们要安装的软件&#xff0c;当依赖关系很复杂的…

drawio连接线的样式设置

drawio是一款强大的图表绘制软件&#xff0c;支持在线云端版本以及windows, macOS, linux安装版。 如果想在线直接使用&#xff0c;则直接输入网址draw.io或者使用drawon(桌案), drawon.cn内部完整的集成了drawio的所有功能&#xff0c;并实现了云端存储&#xff0c;以及在线共…