3D模型人物换装系统三(优化合批处理,提取合批配置可,封装)
- 介绍
- 法线贴图问题
- 规划以及封装
- 缺陷
- 修改整理
- 整合
- 总结
介绍
本文使用2018.4.4和2020.3.26进行的测试
这里先说一下我上一篇没有解决的问题,法线贴图不正确,如果按照我之前的文章创建其他贴图的方式去创建法线贴图,你会发现法线贴图不正确。
如果这里不太明白换装的流程可以参考我之前
3D模型人物换装系统一
3D模型人物换装系统二
法线贴图问题
之前创建贴图的方式如下所示
这里能看到我创建所有图片的时候的格式都是使用的RGBA32,但是后来我在网上查了以后才发现,其实法线贴图是不带A通道的图片,所以这里应该使用的是RGB24而不是使用的RGBA32,前后对比图我就不放了,因为我对这里的方法进行了封装,已经不是原来的方式创建和合并贴图了。
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);
规划以及封装
这里我大概讲一下我之前脚本设计的缺陷,以及我想修改成的样子,在文章最后面我会把我修改完成的脚本和资源都放过来,如果大家在后面使用脚本的过程中遇到问题可以在询问我。
缺陷
- 原来合并设计的是太过于死板,材质球的属性是直接提取出来的并没有动态去做,也就是说我这里只能手动修改,如果换一个四个贴图的材质还需要修改下面的代码,这样太不方便,需要封装一下这里
private const string COMBINE_ALBEDOMAP_TEXTURE = "_AlbedoMap";private const string COMBINE_NORMALMAP_TEXTURE = "_NormalMap";private const string COMBINE_MASKMAP_TEXTURE = "_MaskMap";
- 创建贴图的大小也是固定死的现在,这里也是有缺陷,没法动态的调整
/// <summary>/// Only for merge materials./// </summary>private const int COMBINE_TEXTURE_MAX = 256;
- 合并这里或多或少有点缺陷,但是不算是有问题,这里传入的如果是true则就是需要合并,但是如果合并不成功的话就直接报错
if (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);// 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;}}if (combine){r.material = newMaterial;for (int i = 0; i < combineInstances.Count; i++){combineInstances[i].mesh.uv = oldUV[i];}}
- Shader也是写死的,不是动态的
Shader tmpShader = Shader.Find("E3D/Actor/PBR-MaskRG-Normal");
修改整理
- 创建一个配置类,当创建模型的时候根据自己需要传入定制化配置文件,这样可以动态调整创建贴图的大小、动态调整合并贴图的数量、动态修改想要合并成的材质Shader。
/// <summary>
/// 贴图属性
/// </summary>
public class CombineClass
{/// <summary>/// 新Shader属性名/// </summary>public string NewShaderPropertiesName = "_AlbedoMap";/// <summary>/// 原始Shader属性名/// </summary>public string OriginalPropertiesName = "_AlbedoMap";/// <summary>/// 图片格式/// </summary>public TextureFormat Format = TextureFormat.RGBA32;/// <summary>/// /// </summary>public bool MipChain = true;/// <summary>/// 实例化/// </summary>/// <param name="propertiesName"></param>/// <param name="format"></param>/// <param name="mipChain"></param>public CombineClass(string newPropertiesName, string orinalPropertiesName, TextureFormat format, bool mipChain){this.NewShaderPropertiesName = newPropertiesName;this.OriginalPropertiesName = orinalPropertiesName;this.Format = format;this.MipChain = mipChain;}
}public class CombineConfig
{/// <summary>/// Only for merge materials./// 仅适用于合并材质./// </summary>private int COMBINE_TEXTURE_MAX = 256;/// <summary>/// Shader名称/// </summary>private string ShaderName = "";/// <summary>/// 贴图设置集合/// </summary>private List<CombineClass> combineClasses = new List<CombineClass>();/// <summary>/// 实例化/// </summary>/// <param name="COMBINE_TEXTURE_MAX"></param>/// <param name="combineClasses"></param>public CombineConfig(int COMBINE_TEXTURE_MAX, string ShaderName, List<CombineClass> combineClasses){this.COMBINE_TEXTURE_MAX = COMBINE_TEXTURE_MAX;this.ShaderName = ShaderName;this.combineClasses = combineClasses;}/// <summary>/// 获取合并贴图大小/// </summary>/// <returns></returns>public int CombineTextureMax() { return this.COMBINE_TEXTURE_MAX; }/// <summary>/// 获取Shader名/// </summary>/// <returns></returns>public string GetShaderName() { return this.ShaderName; }/// <summary>/// 获取贴图设置集合数量/// </summary>/// <returns></returns>public int GetCombineCount() { return this.combineClasses.Count; }/// <summary>/// 获取贴图设置集合/// </summary>/// <returns></returns>public List<CombineClass> GetCombineList() { return combineClasses; }
}
- 获取需要合并的模型的所有的所有Shader,如果是只有一个Shader则你combine为true时会进行合批处理,如果Shader数量多了哪怕你combine为true则也不进行合批。
- 加入了一个材质回调,这样在调用的时候可以对材质球进行设置
整合
CombineClass.cs
/// <summary>
/// 贴图属性
/// </summary>
public class CombineClass
{/// <summary>/// 新Shader属性名/// </summary>public string NewShaderPropertiesName = "_AlbedoMap";/// <summary>/// 原始Shader属性名/// </summary>public string OriginalPropertiesName = "_AlbedoMap";/// <summary>/// 图片格式/// </summary>public TextureFormat Format = TextureFormat.RGBA32;/// <summary>/// /// </summary>public bool MipChain = true;/// <summary>/// 实例化/// </summary>/// <param name="propertiesName"></param>/// <param name="format"></param>/// <param name="mipChain"></param>public CombineClass(string newPropertiesName, string orinalPropertiesName, TextureFormat format, bool mipChain){this.NewShaderPropertiesName = newPropertiesName;this.OriginalPropertiesName = orinalPropertiesName;this.Format = format;this.MipChain = mipChain;}
}
CombineConfig.cs
public class CombineConfig
{/// <summary>/// Only for merge materials./// 仅适用于合并材质./// </summary>private int COMBINE_TEXTURE_MAX = 256;/// <summary>/// Shader名称/// </summary>private string ShaderName = "";/// <summary>/// 贴图设置集合/// </summary>private List<CombineClass> combineClasses = new List<CombineClass>();/// <summary>/// 实例化/// </summary>/// <param name="COMBINE_TEXTURE_MAX"></param>/// <param name="combineClasses"></param>public CombineConfig(int COMBINE_TEXTURE_MAX, string ShaderName, List<CombineClass> combineClasses){this.COMBINE_TEXTURE_MAX = COMBINE_TEXTURE_MAX;this.ShaderName = ShaderName;this.combineClasses = combineClasses;}/// <summary>/// 获取合并贴图大小/// </summary>/// <returns></returns>public int CombineTextureMax() { return this.COMBINE_TEXTURE_MAX; }/// <summary>/// 获取Shader名/// </summary>/// <returns></returns>public string GetShaderName() { return this.ShaderName; }/// <summary>/// 获取贴图设置集合数量/// </summary>/// <returns></returns>public int GetCombineCount() { return this.combineClasses.Count; }/// <summary>/// 获取贴图设置集合/// </summary>/// <returns></returns>public List<CombineClass> GetCombineList() { return combineClasses; }
}
UCombineSkinnedMgr.cs
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System;public class UCombineSkinnedMgr
{/// <summary>/// Combine SkinnedMeshRenderers together and share one skeleton./// 将蒙皮网格渲染器组合在一起并共享一个骨架.(优化合批报错,如果无法合批但是combine为true,则进行不分批处理)/// Merge materials will reduce the drawcalls, but it will increase the size of memory./// 合并材质会减少drawcalls,但会增加内存大小./// </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(CombineConfig config, GameObject skeleton, SkinnedMeshRenderer[] meshes, bool combine = false, Action<Material> action = null){//Fetch all bones of the skeleton//获取所有的骨骼List<Transform> transforms = new List<Transform>();transforms.AddRange(skeleton.GetComponentsInChildren<Transform>(true));//the list of materialsList<Shader> shaders = new List<Shader>();//the list of materials//所有材质球List<Material> materials = new List<Material>();//the list of meshes//所有网格List<CombineInstance> combineInstances = new List<CombineInstance>();//the list of bones//所有骨骼节点List<Transform> bones = new List<Transform>();#region 合批使用// Below informations only are used for merge materilas(bool combine = true)//以下信息仅用于合并材料(bool-combine=true)原始UV坐标List<Vector2[]> oldUV = null;//合并之后得新材质球Material newMaterial = null;//创建新贴图集合List<Texture2D> MapTex = new List<Texture2D>();for (int i = 0; i < config.GetCombineCount(); i++){MapTex.Add(null);}#endregion// Collect information from meshes and shader// 获取网格和shader信息for (int i = 0; i < meshes.Length; i++){SkinnedMeshRenderer smr = meshes[i];materials.AddRange(smr.materials);for (int j = 0; j < smr.materials.Length; j++){if (!shaders.Contains(smr.materials[j].shader)){shaders.Add(smr.materials[j].shader);}}// 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 materials//合并材质if (combine && shaders.Count == 1){if (config.GetShaderName() == "") newMaterial = new Material(shaders[0]);else newMaterial = new Material(Shader.Find(config.GetShaderName()));oldUV = new List<Vector2[]>();// merge the texture//合并贴图List<List<Texture2D>> texture2Ds = new List<List<Texture2D>>();for (int i = 0; i < config.GetCombineCount(); i++){texture2Ds.Add(new List<Texture2D>());}for (int i = 0; i < materials.Count; i++){Material mat = materials[i];for (int j = 0; j < config.GetCombineCount(); j++){texture2Ds[j].Add(mat.GetTexture(config.GetCombineList()[j].OriginalPropertiesName) as Texture2D);}//int ind = 0;//texture2Ds.ForEach((txtLst) =>//{// txtLst.Add(mat.GetTexture(config.GetCombineList()[ind].OriginalPropertiesName) as Texture2D);// ind++;//});}Rect[] uvs = new Rect[config.GetCombineCount()];for (int i = 0; i < config.GetCombineCount(); i++){MapTex[i] = new Texture2D(config.CombineTextureMax(), config.CombineTextureMax(), config.GetCombineList()[i].Format, config.GetCombineList()[i].MipChain);uvs = MapTex[i].PackTextures(texture2Ds[i].ToArray(), 0);newMaterial.SetTexture(config.GetCombineList()[i].NewShaderPropertiesName, MapTex[i]);}action?.Invoke(newMaterial);#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 && shaders.Count == 1, false);// Combine meshesr.bones = bones.ToArray();// Use new bonesif (combine && shaders.Count == 1){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(CombineConfig config, string job, string skeleton, string body, string clothes, string hair, string head, string pants, string shoes, string socks, bool combine = false, System.Action<Material> action = null){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(config, Instance, meshes, combine, action);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(new CombineConfig(256, "", new List<CombineClass> {// new CombineClass("_AlbedoMap","_AlbedoMap",TextureFormat.RGBA32,true),// new CombineClass("_NormalMap","_NormalMap",TextureFormat.RGB24,true),// new CombineClass("_MaskMap","_MaskMap", TextureFormat.RGBA32,true),//}), "MaTa", "MaTa", "Body1", "Clothes1", "Hair1", "Head1", "Pants1", "Shoes1", "Socks1", true, (mat) =>//{// mat.SetFloat("_SideLightScale", 0);//});mine = Generatecharacter(new CombineConfig(256, "Standard", new List<CombineClass> {new CombineClass("_MainTex","_AlbedoMap",TextureFormat.RGBA32,true),new CombineClass("_BumpMap","_NormalMap",TextureFormat.RGB24,true),new CombineClass("_DetailMask","_MaskMap", TextureFormat.RGBA32,true),}), "MaTa", "MaTa", "Body1", "Clothes1", "Hair1", "Head1", "Pants1", "Shoes1", "Socks1", true, (mat) =>{mat.SetFloat("_SideLightScale", 0);});}private void Update(){if (Input.GetKeyDown(KeyCode.Space)){ChangeRole();}}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(CombineConfig config, string job, string skeleton, string body, string clothes, string hair, string hand, string pants, string shoes, string socks, bool combine = false, System.Action<Material> action = null){UCharacterController instance = new UCharacterController(config, job, skeleton, body, clothes, hair, hand, pants, shoes, socks, combine, action);characterDic.Add(characterIndex, instance);characterIndex++;return instance;}#endregion
}
完整资源
总结
人物换装的优化这篇文章后面应该不会在出了,预计得优化大概就是这样,如果有别的好点子可以私信我交流一下。希望这篇文章能给大家带来帮助,感谢大家的支持和关注,感谢点赞。