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

相关文章

Three.js构建一个 3D 商品展示空间完整实战项目

《Three.js构建一个3D商品展示空间完整实战项目》Three.js是一个强大的JavaScript库,专用于在Web浏览器中创建3D图形,:本文主要介绍Three.js构建一个3D商品展... 目录引言项目核心技术1. 项目架构与资源组织2. 多模型切换、交互热点绑定3. 移动端适配与帧率优化4. 可

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

基于Python实现自动化邮件发送系统的完整指南

《基于Python实现自动化邮件发送系统的完整指南》在现代软件开发和自动化流程中,邮件通知是一个常见且实用的功能,无论是用于发送报告、告警信息还是用户提醒,通过Python实现自动化的邮件发送功能都能... 目录一、前言:二、项目概述三、配置文件 `.env` 解析四、代码结构解析1. 导入模块2. 加载环

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Java实现复杂查询优化的7个技巧小结

《Java实现复杂查询优化的7个技巧小结》在Java项目中,复杂查询是开发者面临的“硬骨头”,本文将通过7个实战技巧,结合代码示例和性能对比,手把手教你如何让复杂查询变得优雅,大家可以根据需求进行选择... 目录一、复杂查询的痛点:为何你的代码“又臭又长”1.1冗余变量与中间状态1.2重复查询与性能陷阱1.

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变

Python多线程应用中的卡死问题优化方案指南

《Python多线程应用中的卡死问题优化方案指南》在利用Python语言开发某查询软件时,遇到了点击搜索按钮后软件卡死的问题,本文将简单分析一下出现的原因以及对应的优化方案,希望对大家有所帮助... 目录问题描述优化方案1. 网络请求优化2. 多线程架构优化3. 全局异常处理4. 配置管理优化优化效果1.

MySQL中优化CPU使用的详细指南

《MySQL中优化CPU使用的详细指南》优化MySQL的CPU使用可以显著提高数据库的性能和响应时间,本文为大家整理了一些优化CPU使用的方法,大家可以根据需要进行选择... 目录一、优化查询和索引1.1 优化查询语句1.2 创建和优化索引1.3 避免全表扫描二、调整mysql配置参数2.1 调整线程数2.