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

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 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// 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 meshesfor (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;}}}}// merge materialsif (combine){Shader tmpShader = Shader.Find("E3D/Actor/PBR-MaskRG-Normal");newMaterial = new Material(tmpShader);oldUV = new List<Vector2[]>();// merge the textureList<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 uvVector2[] 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 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(), combine, false);// Combine meshesr.bones = bones.ToArray();// Use new bonesif (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.mzph.cn/news/236870.shtml

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

相关文章

HarmonyOS引导页登陆页以及tabbar的代码说明1

效果 以下代码是东拼西凑出来的。只是为了个人熟悉一下相关模块的使用&#xff1a; 用的知识点&#xff1a; Resouces 此部分分内容可以在项目中找到&#xff1a; resources/base/element/color.json 为项目着色配置&#xff0c;当然也可以正接在代码里写 float.json 为相关…

IPv6路由基础-理论与配置

在企业网络中&#xff0c;IPv6技术的应用越来越普及。IETF组织针对IPv6网络制定了路由协议OSPFv3。 OSPFv3 ff02::5是为OSPFv3路由协议预留的IPv6组播地址。OSPFv3中的路由条目下一跳地址是链路本地地址。OSPFv3是运行在IPv6网络的OSPF协议。运行OSPFv3的路由器使用物理接口的…

华为云Stack 8.X 流量模型分析(一)

一、基础知识 1.tap与tun ​ tap与tun都是操作系统&#xff08;Linux&#xff09;内核中的虚拟网络设备&#xff0c;等同于一个以太网设备&#xff0c;可以收发数据报文包。 ​ tap与tun的定义相同&#xff0c;两者仅仅是通过一个Flag来区分。但二者所承担的功能差别较大&am…

centos磁盘扩容

[rootlocalhost ~]# fdisk -l磁盘 /dev/sda&#xff1a;429.5 GB, 429496729600 字节&#xff0c;838860800 个扇区 Units 扇区 of 1 * 512 512 bytes 扇区大小(逻辑/物理)&#xff1a;512 字节 / 4096 字节 I/O 大小(最小/最佳)&#xff1a;4096 字节 / 4096 字节 磁盘标签类…

浅析 fuse kernel mmap write 过程及性能问题

前言 最近在项目里面用到了fuse文件系统&#xff0c;在使用过程中遇到了一个内核在做mmap write的一个bug&#xff0c;目前并没有从根本上解决这个bug&#xff0c;而是通过修改fuse kernel module的一些参数&#xff0c;绕开了这个bug。这里记录一下这个问题&#xff0c;并顺便…

信息收集 - 网站敏感信息

CMS指纹信息: CMS (内容管理系统)又称为整站系统或文章系统,用于网站内容管理。用户只需要下载对应的CMS软件包,就能部署搭建,并直接利用CMS。但是各种CMS都具有其独特的结构命名规则和特定的文件内容,因此可以利用这些内容来获取CMS站点的具体软件CMS与版本。通过识别CMS…

手把手教你在windows上安装Portainer

前言 大家好&#xff0c;我是潇潇雨声。在之前的文章中&#xff0c;我们探讨了在 Windows 上安装 Docker 的方法。今天&#xff0c;我将简要介绍一个开源的轻量级容器管理工具——Portainer&#xff0c;它类似于 navicat。Portainer 是一个与 navicat 类似的工具&#xff0c;但…

facebook广告怎么效果好

要提高Facebook广告的效果&#xff0c;可以尝试以下策略&#xff1a; 明确广告目标&#xff1a;在制定广告计划之前&#xff0c;需要明确广告的目标。这可能包括增加网站流量、提高品牌知名度、推广新产品或提高现有产品的销售量。明确目标后&#xff0c;可以制定相应的广告策…

Amazon Toolkit — CodeWhisperer 使用

tFragment--> 官网&#xff1a;https://aws.amazon.com/cn/codewhisperer/?trkcndc-detail 最近学习了亚马逊云科技的 代码工具&#xff0c;感慨颇多。下面是安装 和使用的分享。 CodeWhisperer&#xff0c;亚马逊推出的实时 AI 编程助手&#xff0c;是一项基于机器学习…

前端八股文(vue篇)

一.vue中的key的作用 key的作用主要是为了高效的更新虚拟dom。另外vue中在使用相同标签名元素的过渡切换时&#xff0c;也会使用到key属性&#xff0c;其目的也是为了让vue可以区分它们&#xff0c;否则vue只会替换内部属性而不会触发过渡效果。 二.接口请求一般放在哪个生命周…

详解数据科学自动化与机器学习自动化

过去十年里&#xff0c;人工智能&#xff08;AI&#xff09;构建自动化发展迅速并取得了多项成就。在关于AI未来的讨论中&#xff0c;您可能会经常听到人们交替使用数据科学自动化与机器学习自动化这两个术语。事实上&#xff0c;这些术语有着不同的定义&#xff1a;如今的自动…

【QT Visual Studio环境配置】error MSB8020: 无法找到 v141/v142 的生成工具(完整版)

首先要了解V**平台工具集根据你安装的Visual Studio版本不同而有所区别&#xff0c;知道这个就容易解决问题了&#xff0c;确定你安装的那个版本&#xff0c;需要使用哪个工具集。 v143–>VS2022v142–>VS2019v141–>VS2017v140–>VS2015v120–>VS2013 一、解决…

[kubernetes]基于版本v1.28.5+containerd + helm 搭建集群

0 环境准备 节点数量: 3 台虚拟机 centos7硬件配置: 2G或更多的RAM&#xff0c;2个CPU或更多的CPU&#xff0c;硬盘至少30G 以上网络要求: 多个节点之间网络互通&#xff0c;每个节点能访问外网 1 集群规划 k8s-node1&#xff1a;10.0.0.32k8s-node2&#xff1a;10.0.3.231k…

TypeScript 中的 interface 和 type 有什么区别?应该如何选择?

背景 TypeScript中的 interface 和 type 都是声明自定义类型的方式&#xff0c;但它们有一些区别&#xff0c;适用于不同的使用场景。 两者使用案例 interface interface 主要用于描述对象的形状或者类的结构&#xff0c;这是它最经常的应用场景。 interface使用示例&…

机器学习之线性回归(Linear Regression)附代码

概念 线性回归(Linear Regression)是机器学习中的一种基本的监督学习算法,用于建立输入变量(特征)与输出变量(目标)之间的线性关系。它假设输入变量与输出变量之间存在线性关系,并试图找到最佳拟合线来描述这种关系。 在简单线性回归中,只涉及两个变量:一个是自变量…

MySQL:通过官方mysql server,搭建绿色版mysql服务器(Windows)

1. 官网下载mysql server 下载mysql server的zip文件&#xff0c;地址&#xff1a; https://downloads.mysql.com/archives/community/ 解压后 2. 初始化数据库 运行cmd&#xff0c; 进入bin目录&#xff0c;运行 mysqld --initialize-insecureinitialize-insecure说明如…

python面向对象反射

就是通过字符串的形式操作对象相关属性 反射基本语法 from django.test import TestCase# Create your tests here. class Students:def __init__(self, name, gender):self.name nameself.gender genderdef play(self, game, novel):print(game, novel)s Students(lyz, …

Jenkins 插件下载速度慢安装失败?这篇文章可能解决你头等难题!

Jenkins部署完毕&#xff0c;如果不安装插件的话&#xff0c;那它就是一个光杆司令&#xff0c;啥事也做不了&#xff01; 所以首先要登陆管理员账号然后点击系统管理再点击右边的插件管理安装CI/CD必要插件。 但是问题来了&#xff0c;jenkins下载插件速度非常慢&#xff0c…

Educational Codeforces Round 160 (Div. 2) A~E

A.Rating Increase&#xff08;思维&#xff09; 题意&#xff1a; 给出一个仅包含数字的字符串 s s s&#xff0c;要求将该字符串按以下要求分成左右两部分 a , b a,b a,b&#xff1a; 两个数字均不包含前导 0 0 0 两个数字均大于 0 0 0 b > a b > a b>a 如果…

算法基础之快速幂

快速幂 核心思想&#xff1a;logk的复杂度求出ak mod p 将k拆成若干个2的n之和 (二进制) #include<iostream>#include<algorithm>using namespace std;typedef long long LL;LL qmi(int a,int k,int p){LL res 1 % p;while(k) //k转为二进制 还有正数 就进行…