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

2023-12-21 20:44

本文主要是介绍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
}

总结

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

这篇关于3D模型人物换装系统(二 优化材质球合批降低DrawCall)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/521452

相关文章

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

MySQL中慢SQL优化的不同方式介绍

《MySQL中慢SQL优化的不同方式介绍》慢SQL的优化,主要从两个方面考虑,SQL语句本身的优化,以及数据库设计的优化,下面小编就来给大家介绍一下有哪些方式可以优化慢SQL吧... 目录避免不必要的列分页优化索引优化JOIN 的优化排序优化UNION 优化慢 SQL 的优化,主要从两个方面考虑,SQL 语

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Linux系统之dns域名解析全过程

《Linux系统之dns域名解析全过程》:本文主要介绍Linux系统之dns域名解析全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、dns域名解析介绍1、DNS核心概念1.1 区域 zone1.2 记录 record二、DNS服务的配置1、正向解析的配置

MySQL中慢SQL优化方法的完整指南

《MySQL中慢SQL优化方法的完整指南》当数据库响应时间超过500ms时,系统将面临三大灾难链式反应,所以本文将为大家介绍一下MySQL中慢SQL优化的常用方法,有需要的小伙伴可以了解下... 目录一、慢SQL的致命影响二、精准定位问题SQL1. 启用慢查询日志2. 诊断黄金三件套三、六大核心优化方案方案

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Linux系统中配置静态IP地址的详细步骤

《Linux系统中配置静态IP地址的详细步骤》本文详细介绍了在Linux系统中配置静态IP地址的五个步骤,包括打开终端、编辑网络配置文件、配置IP地址、保存并重启网络服务,这对于系统管理员和新手都极具... 目录步骤一:打开终端步骤二:编辑网络配置文件步骤三:配置静态IP地址步骤四:保存并关闭文件步骤五:重