3D模型人物换装系统

news2024/10/6 4:09:19

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

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

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

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

相关文章

CSS特效第一弹:右上角tag标志纯代码前端实现(非图片)

&#x1f60e;效果&#xff1a; &#x1f937;‍♂️思路&#xff1a; 分为2个部分&#xff1a; 1.文字方块右下角折角 文字方块用绝对定位z-index让文字方块悬浮在右上角的位置 2.右下角折角通过before伪元素border属性实现(三角形实现方法&#xff09; &#x1f44d;核心代…

人工智能基础_机器学习024_梯度下降进阶_L1正则可视化图形---人工智能工作笔记0064

然后我们就来用代码实现一下L1正则的可视化,我们来看看 首先导入 import numpy as np 数学计算 import matplotlib.pyplot as plt 画图用的 然后我们把L1正则的公式写出来 可以看到L1的正则 其实就是w1和w2的绝对值相加对吧 然后这里我们写一个公式: f(x,y) = |x|+|y| …

12. 定时器按键消抖

12. 定时器按键消抖 定时器按键消抖简介定时器消抖配置步骤程序编写bsp_keyfilter.hbsp_keyfilter.cmain 定时器按键消抖简介 使用延时函数消抖会浪费 CPU 性能&#xff0c;因为延时函数就是空跑。如果按键是使用中断的方式实现的&#xff0c;就更不能在中断服务函数中使用延时…

【基础算法模板梳理】再也不想学算法了!(待更新)

目录 1、【二分】 &#xff08;1&#xff09;rmid —— 大于等于某数的最小值 &#xff08;2&#xff09;lmid —— 小于等于某数的最大值 2、【前缀和】 &#xff08;1&#xff09;一维前缀和 &#xff08;2&#xff09;二维前缀和 3、【差分】 &#xff08;1&#x…

嵌入式中常见的显示屏接口有哪些?

显示屏接口一般有I2C、SPI、UART、RGB、LVDS、MIPI、EDP和DP等。下面简要总结一下。 01 中小屏接口I2C、SPI、UAR 一般3.5寸以下的小尺寸LCD屏&#xff0c;显示数据量比较少&#xff0c;普遍采用低速串口&#xff0c;如I2C、SPI、UART。 I2C&#xff1a; I2C总线是半双工&…

游戏公司数据分析师必备知识(持续补充中...)

1.如何撰写专题报告&#xff1f; ①原则 只有一个主题&#xff1a;即使不讲ppt&#xff0c;业务方也能看得懂行文通俗简单易懂&#xff1a;学习产品经理平常是如何写报告的明确的数据结论和落地项先行&#xff1a;跟业务方多沟通数据结论&#xff0c;让他们给出落地项 ②结构…

BMVC 23丨多模态CLIP:用于3D场景问答任务的对比视觉语言预训练

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/abs/2306.02329 摘要&#xff1a; 训练模型将常识性语言知识和视觉概念从 2D 图像应用到 3D 场景理解是研究人员最近才开始探索的一个有前景的方向。然而&#xff0c…

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

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

如何用sklearn对随机森林调参

文章目录 一、概述二、实操1、导入相关包2、导入乳腺癌数据集&#xff0c;建立模型3、调参 三、总结 Link&#xff1a;https://zhuanlan.zhihu.com/p/126288078 Author&#xff1a;陈罐头 一、概述 sklearn是目前python中十分流行的用来实现机器学习的第三方包&#xff0c;其中…

一篇文章初步学习Python基础知识+结构+数据类型,Python零基础入门~

文章目录 前言一、编程基础1.基本的输入输出2.变量3.基本运算符 二、控制流程1.选择结构2.循环结构 三、数据类型1.字符串关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战…

传奇GOM引擎微端连接不上如何解决

Gom传奇引擎的微端连不上的原因可能有很多&#xff0c;比如网络问题、服务器配置问题、版本兼容性问题等。1.检查网络连接&#xff1a;首先要确保你的网络连接稳定。如果遇到网络问题&#xff0c;比如网络延迟过高&#xff0c;可能会导致你无法连接到服务器。建议使用稳定的网络…

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…

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

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

matplotlib 设置标签和图例

常用标签 xlabel&#xff1a;x轴标签名称。 ylabel&#xff1a;y轴标签名称。 title&#xff1a;图像标题。 设置x和y轴的刻度&#xff1a;xticks和yticks。 nums np.arange(0, 1.3, 0.01)# 设置标题 plt.title("title") # 设置横坐标信息 plt.xlabel("x-…

Spark的转换算子和操作算子

1 Transformation转换算子 1.1 Value类型 1&#xff09;创建包名&#xff1a;com.shangjack.value 1.1.1 map()映射 参数f是一个函数可以写作匿名子类&#xff0c;它可以接收一个参数。当某个RDD执行map方法时&#xff0c;会遍历该RDD中的每一个数据项&#xff0c;并依次应用f函…

中低收入群体能在“双十一”购物狂欢吗?

今天这个“双十一”购物狂欢节&#xff0c;在各大网站的报道的确蜂拥而上&#xff0c;显得很有点儿“狂欢”的景象&#xff0c;可读罢内容却听到哀鸿遍野。 笔者仅只接力“腾迅新闻”和“今日头条”几小时前分别发表的《 双11十五年&#xff0c;价格战还能打多久&#xff1f;》…

乡村振兴 品牌引领 “盘锦碱地柿子”亮相第二十届中国国际农产品交易会

2023年11月9日&#xff0c;为期4天的第二十届中国国际农产品交易会在山东青岛成功举办。本次大会以“奋进新征程强农促振兴”为主题。农交会是经党中央、国务院批准&#xff0c;农业农村部主办的大型农业行业盛会&#xff0c;在宣传“三农”政策、展示农业农村发展成就、活跃农…

58基于matlab的采样的运动规划算法-RRT(Rapidly-exploring Random Trees)

基于matlab的采样的运动规划算法-RRT(Rapidly-exploring Random Trees)&#xff0c;3D和2D,原始的RRT算法中将搜索的起点位置作为根节点&#xff0c;然后通过随机采样增加叶子节点的方式&#xff0c;生成一个随机扩展树&#xff0c;当随机树的叶子节点进入目标区域&#xff0c;…

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

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

13. 高精度延时

13. 高精度延时 GPT 定时器简介GPT 定时器结构GPT 定时器工作模式 GPT 定时器相关寄存器GPTx_CRGPTx_PRGPTx_SRGPTx_CNTGPTx_OCR GPT 配置步骤程序编写bsp_delay.hbsp_delay.cmain GPT 定时器简介 GPT 定时器是一个 32 位向上定时器&#xff0c;也就是从0x00000000 开始向上递…