3D模型人物换装系统二(优化材质球合批降低DrawCall)

news2024/11/17 14:48:46

3D模型人物换装系统

  • 介绍
  • 原理
  • 合批材质对比没有合批材质
  • 核心代码
  • 完整代码修改
  • 总结

介绍

本文使用2018.4.4和2020.3.26进行的测试
本文没有考虑法线贴图合并的问题,因为生成法线贴图有点问题,放在下一篇文章解决在进行优化
如果这里不太明白换装的流程可以参考我之前3D模型人物换装系统
请添加图片描述

原理

原理其实很简单,其实就是将原来没有合批的材质进行了一个合批我下面截图给大家演示一下
下面的图你可以看到只有一个合并贴图的材质球,而且DrawCall也很低,仔细看下面的贴图你能看到其实是好几张贴图合并成了一个
在这里插入图片描述
下面这个是没有合批材质的模型,能看出来Draw Call其实很高,已经到了37,而且材质球也是很多个但是贴图是一个对应一个
在这里插入图片描述

合批材质对比没有合批材质

  1. 合批材质的DrawCall会降低很多有多少个材质就降低多少倍

  2. 合批材质需要贴图有要求,这里我所有用到的贴图大小都是一致的256当然大小多少都可以,前提是需要贴图大小一致
    在这里插入图片描述

  3. 合批贴图需要设置成可读可写
    在这里插入图片描述

  4. 缺点是你合并材质的时候生成图片是需要消耗一定的CPU的,最好做预加载不然会卡顿

  5. 注意:需要身上所有的贴图是相同的shader才能使用,并且每个材质球贴图也要数量相同

综合上面考虑其实合并材质是必然要用的,只要做好预加载其实都不是问题

核心代码

这里面的核心主要就是重新生成新的拼接的贴图,对模型的纹理做一下调整
uv调整代码如下

// reset uv
Vector2[] uva, uvb;
for (int i = 0; i < combineInstances.Count; i++)
{
    uva = combineInstances[i].mesh.uv;
    uvb = new Vector2[uva.Length];
    for (int k = 0; k < uva.Length; k++)
    {
        uvb[k] = new Vector2((uva[k].x * uvs[i].width) + uvs[i].x, (uva[k].y * uvs[i].height) + uvs[i].y);
    }
    oldUV.Add(uva);
    combineInstances[i].mesh.uv = uvb;
}

完整代码修改

这里我就不放资源了(上一篇文章有之前的资源,但是之前资源头发和眉毛跟人物身上材质不同,无法合批仅供参考,本文的模型是公司未上线游戏模型不能发布公开出来希望理解),但是注意我上面写的需要人物身上模型的材质shader都必须相同的才能完成合批

UCombineSkinnedMgr.cs

using UnityEngine;
using System.Collections.Generic;
using System.IO;

public class UCombineSkinnedMgr
{

    /// <summary>
    /// Only for merge materials.
    /// </summary>
	private const int COMBINE_TEXTURE_MAX = 256;
    private const string COMBINE_ALBEDOMAP_TEXTURE = "_AlbedoMap";
    //private const string COMBINE_NORMALMAP_TEXTURE = "_NormalMap";
    private const string COMBINE_MASKMAP_TEXTURE = "_MaskMap";

    /// <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, bool combine = false)
    {

        // 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

        // Below informations only are used for merge materilas(bool combine = true)
        //老UV坐标
        List<Vector2[]> oldUV = null;

        //新材质球
        Material newMaterial = null;

        //创建新Albedo贴图
        Texture2D newAlbedoMapTex = null;
        //创建新Normal贴图
        //Texture2D newNormalMapTex = null;
        //创建新Mask贴图
        Texture2D newMaskMapTex = null;

        // 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;
                    }
                }
            }
        }

        // merge materials
        if (combine)
        {
            Shader tmpShader = Shader.Find("E3D/Actor/PBR-MaskRG-Normal");
            newMaterial = new Material(tmpShader);
            oldUV = new List<Vector2[]>();

            // merge the texture
            List<Texture2D> AlbedoTextures = new List<Texture2D>();
            List<Texture2D> NormalTextures = new List<Texture2D>();
            List<Texture2D> MaskTextures = new List<Texture2D>();

            for (int i = 0; i < materials.Count; i++)
            {
                AlbedoTextures.Add(materials[i].GetTexture(COMBINE_ALBEDOMAP_TEXTURE) as Texture2D);
                //NormalTextures.Add(materials[i].GetTexture(COMBINE_NORMALMAP_TEXTURE) as Texture2D);
                MaskTextures.Add(materials[i].GetTexture(COMBINE_MASKMAP_TEXTURE) as Texture2D);
            }
            newAlbedoMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);
            //newNormalMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);
            newMaskMapTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true);

            Rect[] uvs = newAlbedoMapTex.PackTextures(AlbedoTextures.ToArray(), 0);
            //newNormalMapTex.PackTextures(NormalTextures.ToArray(), 0);
            newMaskMapTex.PackTextures(MaskTextures.ToArray(), 0);

            newMaterial.SetTexture(COMBINE_ALBEDOMAP_TEXTURE, newAlbedoMapTex);
            //newMaterial.SetTexture(COMBINE_NORMALMAP_TEXTURE, newNormalMapTex);
            newMaterial.SetTexture(COMBINE_MASKMAP_TEXTURE, newMaskMapTex);

            newMaterial.SetFloat("_SideLightScale", 0);

            //#region 导出图片

            //WriteIntoPic(TextureToTexture2D(newMaterial.GetTexture(COMBINE_ALBEDOMAP_TEXTURE)), "albedo");
            //WriteIntoPic(TextureToTexture2D(newMaterial.GetTexture(COMBINE_NORMALMAP_TEXTURE)), "normal");
            //WriteIntoPic(TextureToTexture2D(newMaterial.GetTexture(COMBINE_MASKMAP_TEXTURE)), "mask");

            //#endregion

            // reset uv
            Vector2[] uva, uvb;
            for (int i = 0; i < combineInstances.Count; i++)
            {
                uva = combineInstances[i].mesh.uv;
                uvb = new Vector2[uva.Length];
                for (int k = 0; k < uva.Length; k++)
                {
                    uvb[k] = new Vector2((uva[k].x * uvs[i].width) + uvs[i].x, (uva[k].y * uvs[i].height) + uvs[i].y);
                }
                oldUV.Add(uva);
                combineInstances[i].mesh.uv = uvb;
            }
        }

        // 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(), combine, false);// Combine meshes
        r.bones = bones.ToArray();// Use new bones
        if (combine)
        {
            r.material = newMaterial;
            for (int i = 0; i < combineInstances.Count; i++)
            {
                combineInstances[i].mesh.uv = oldUV[i];
            }
        }
        else
        {
            r.materials = materials.ToArray();
        }
    }

    #region 导出图片

    private Texture2D TextureToTexture2D(Texture texture)
    {
        Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
        RenderTexture currentRT = RenderTexture.active;
        RenderTexture renderTexture = RenderTexture.GetTemporary(texture.width, texture.height, 32);
        Graphics.Blit(texture, renderTexture);

        RenderTexture.active = renderTexture;
        texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
        texture2D.Apply();

        RenderTexture.active = currentRT;
        RenderTexture.ReleaseTemporary(renderTexture);

        return texture2D;
    }

    public void WriteIntoPic(Texture2D tex, string name)
    {
        //编码纹理为PNG格式 
        var bytes = tex.EncodeToPNG();
        File.WriteAllBytes(Application.dataPath + "/" + name + ".png", bytes);
    }

    #endregion
}

UCharacterController.cs

using UnityEngine;

public class UCharacterController
{
    /// <summary>
    /// GameObject reference
    /// </summary>
	public GameObject Instance = null;

    /// <summary>
    /// 换装总组装数量
    /// </summary>
    public int m_MeshCount = 7;

    public string Role_Skeleton;
    public string Role_Body;
    public string Role_Clothes;
    public string Role_Hair;
    public string Role_Head;
    public string Role_Pants;
    public string Role_Shoes;
    public string Role_Socks;

    /// <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>
    /// <param name="combine"></param>
    public UCharacterController(string job, string skeleton, string body, string clothes, string hair, string head, string pants, string shoes, string socks, bool combine = false)
    {
        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_Clothes = clothes;
        this.Role_Hair = hair;
        this.Role_Head = head;
        this.Role_Pants = pants;
        this.Role_Shoes = shoes;
        this.Role_Socks = socks;

        string[] equipments = new string[m_MeshCount];
        equipments[0] = "Body/" + Role_Body;
        equipments[1] = "Clothes/" + Role_Clothes;
        equipments[2] = "Hair/" + Role_Hair;
        equipments[3] = "Head/" + Role_Head;
        equipments[4] = "Pants/" + Role_Pants;
        equipments[5] = "Shoes/" + Role_Shoes;
        equipments[6] = "Socks/" + Role_Socks;

        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, combine);

        for (int i = 0; i < objects.Length; i++)
        {
            GameObject.DestroyImmediate(objects[i].gameObject);
        }
    }

    public void Delete()
    {
        GameObject.Destroy(Instance);
    }
}

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("MaTa", "MaTa", "Body1", "Clothes1", "Hair1", "Head1", "Pants1", "Shoes1", "Socks1", true);
        //mine = Generatecharacter("MaTa", "MaTa", "Body2", "Clothes2", "Hair2", "Head2", "Pants2", "Shoes2", "Socks2", true);
        //mine = Generatecharacter("MaTa", "MaTa", "Body3", "Clothes3", "Hair3", "Head3", "Pants3", "Shoes3", "Socks3", true);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            ChangeRole();
        }
    }
    int Index = 2;
    public void ChangeRole()
    {
        if (mine != null)
        {
            mine.Delete();
        }


        int a = Random.Range(1, 4);

        mine = Generatecharacter("MaTa", "MaTa", "Body" + a, "Clothes" + a, "Hair" + a, "Head" + a, "Pants" + a, "Shoes" + a, "Socks" + a, true);
    }

    #region 创建人物模型骨骼

    public UCharacterController Generatecharacter(string job, string skeleton, string body, string clothes, string hair, string hand, string pants, string shoes, string socks, bool combine = false)
    {

        UCharacterController instance = new UCharacterController(job, skeleton, body, clothes, hair, hand, pants, shoes, socks, combine);
        characterDic.Add(characterIndex, instance);
        characterIndex++;

        return instance;
    }

    #endregion
}

总结

法线贴图的合并博主在研究一下在下一篇文章在解决一下,希望这篇文章对大家有帮助,感谢大家的支持关注和点赞。

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

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

相关文章

基于物理的多偏置射频大信号氮化镓HEMT建模和参数提取流程

标题&#xff1a;Physics-Based Multi-Bias RF Large-Signal GaN HEMT Modeling and Parameter Extraction Flow 来源&#xff1a;JOURNAL OF THE ELECTRON DEVICES SOCIETY 摘要 本文展示了一种一致的Al镓氮化物&#xff08;AlGaN&#xff09;/氮化镓&#xff08;GaN&#x…

CSS省略号n行公式

记得改图中的n&#xff0c;这是你需要的几行省略号&#xff01;复制中间的5行就行了。 .text {overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: n; //n为你想省略的行数&#xff0c;需要改-webkit-box-orient: vertical; } 这是…

卷积神经网络(1)

目录 卷积 1 自定义二维卷积算子 2 自定义带步长和零填充的二维卷积算子 3 实现图像边缘检测 4 自定义卷积层算子和汇聚层算子 4.1 卷积算子 4.2 汇聚层算子 5 学习torch.nn.Conv2d()、torch.nn.MaxPool2d()&#xff1b;torch.nn.avg_pool2d()&#xff0c;简要介绍使用方…

时间序列预测实战(十三)定制化数据集FNet模型实现滚动长期预测并可视化结果

论文地址->官方论文代码地址 官方代码地址->官方下载地址Github 个人修改代码地址-> 个人修改版本呢的下载地址CSDN 一、本文介绍 本博客将介绍一种新的时间序列预测模型——FNet&#xff0c;它通过使用傅里叶变换代替自注意力机制&#xff0c;旨在解决传统Transf…

瑞吉外卖Day03

小张推荐:瑞吉外卖Day02 1.启用/禁用员工账号 1.1 思路分析 1.2Controller层 PutMapping()public R<String> update(RequestBody Employee employee, HttpServletRequest request) {log.info(employee.toString());Long emp (Long) request.getSession().getAttribu…

AI毕业设计生成器(基于AI大模型技术开发)

这是一个辅助生成计算机毕业设计的工具&#xff0c;可以自动完成毕业设计的源码。它基于几百个github上面开源的java和python项目&#xff0c;运用tengsorflow技术&#xff0c;训练出了AI大模型。基本实现了计算机毕业设计生成器&#xff0c;能够初步生成Java或python基本源码。…

遇到问题,我该如何提问?

作为IT行业的从业者&#xff0c;我们深知程序员在保障系统安全、数据防护以及网络稳定方面所起到的重要作用。他们是现代社会的护城河&#xff0c;用代码构筑着我们的未来。那程序员的护城河又是什么呢&#xff1f;是技术能力的深度&#xff1f;是对创新的追求&#xff1f;还是…

一些分享| 在线笔记、GIF图片生成方法

文章目录 在线笔记视频转GIF 本片博客旨在挖掘一些好用且免费的在线平台&#xff0c;持续更新中~ 正所谓科技解放双手&#xff0c;使用在线平台可以方便快捷地学习办公&#xff0c;节省时间。 在线笔记 语雀 https://www.yuque.com/dashboard 语雀是笔者用得最长最久的平台了…

Pandas教程(非常详细)(第五部分)

接着Pandas教程&#xff08;非常详细&#xff09;&#xff08;第四部分&#xff09;&#xff0c;继续讲述。 二十五、Pandas sample随机抽样 随机抽样&#xff0c;是统计学中常用的一种方法&#xff0c;它可以帮助我们从大量的数据中快速地构建出一组数据分析模型。在 Pandas…

2.5 CE修改器:寻找数值指针

上一步阐述了如何使用代码替换功能对付变化位置的数据地址&#xff0c;但这种方法往往不能达到预期的效果&#xff0c;所以我们需要学习如何利用指针&#xff0c;在本关的Tutorial.exe窗口下面有两个按钮&#xff0c;一个会改变数值&#xff0c;另一个不但能改变数值而且还会改…

初始MySQL(四)(查询加强练习,多表查询)

目录 查询加强 where加强 order by加强 group by 分页查询 总结 多表查询(重点) 笛卡尔集及其过滤 自连接 子查询 子查询当作临时表 all/any 多列子查询 #先创建三张表 #第一张表 CREATE TABLE dept(deptno MEDIUMINT NOT NULL DEFAULT 0,dname VARCHAR(20) NOT …

js随机生成颜色

封装一个函数 返回一个随机颜色 不传参数或者传true返回十六进制&#xff0c; 传false返回rgb模式 script>function Random(n, m) {if (n > m) {let temp nn mm temp}return Math.floor(Math.random() * (m - n 1)) n}function getRandomColor(flag true) {if (fl…

力扣第647题 回文子串 c++ 动态规划 双指针 附Java代码 注释解释版

题目 647. 回文子串 中等 相关标签 字符串 动态规划 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串…

【中国知名企业高管团队】系列66:老板ROBAM

昨天华研荟为您介绍了厨电领域的TOP企业——方太FOTILE的发展历程&#xff0c;以及作为企一代茅理翔和企二代茅忠群的创业故事。 今天为您介绍同处浙江的老板电器。 一、关于老板电器 以下内容来自老板电器官网介绍&#xff1a; 杭州老板电器股份有限公司创立于1979年&…

学校教的Python根本不够!来看看Python学习路线图

如果只靠学校学的东西去找工作&#xff0c;能找到工作吗&#xff1f; 今天给大家看一个粉丝的真实求职案例&#xff0c;想做Python方面的工作&#xff0c;投了二十几个简历却没人要&#xff0c;心态崩了。为什么没人要&#xff1f;我来告诉你答案。 然后我还会结合我的这些年的…

软件开发项目文档系列之十六如何撰写系统运维方案

前言 项目运维方案是为了确保项目的稳定运行和可持续发展而制定的指导性文档。本文将详细介绍项目运维方案的各个方面&#xff0c;包括硬件和软件基础设施、监控和警报、备份和恢复、安全性、团队组织和沟通等方面。本博客将提供示例和最佳实践&#xff0c;以帮助您更好地理解…

AIGC实战——自编码器(Autoencoder)

AIGC实战——自编码器 0. 前言1. 自编码器原理2. 数据集与模型分析2.1 Fashion-MNIST 数据集2.2 自编码器架构 3. 去噪自编码器3.1 编码器3.2 解码器3.3 连接编码器和解码器3.4 训练自编码器3.5 重建图像 4. 可视化潜空间5. 生成新图像小结系列链接 0. 前言 自编码器 (Autoenc…

【Recap教程】autodesk recap软件的安装、认识与使用

一、autodesk recap概述 1. recap介绍 Autodesk Recap是一款由Autodesk公司推出的三维扫描软件,它能够转换多种数据源(如点云、激光雷达、照片)为可视的三维模型。该软件的使用使得用户可以更容易地生成高质量、完整的三维模型。Autodesk Recap通常用于建筑、土木工程、汽…

④【数据查询】MySQL查询语句,拿来即用。

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ MySQL查询操作 ④【数据查询】MySQL查询语句&a…

【框架篇】统一异常处理

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 1&#xff0c;统一异常处理的介绍 统⼀异常处理使⽤的是 ControllerAdvice ExceptionHandler 来…