Unity中的网格与材质球合并

https://blog.csdn.net/dardgen2015/article/details/51517860

很多时候我们需要把具有相同shader的材质球合并,从而减少drawcall的产生。

比如九龙战里面,一个人物带有10个部位,10个部位各自来自不同的fbx文件,加上身体,就有11个材质球,占上11个drawcall。如果主城里面跑着10个角色,光人物就占了110个drawcall!所以这种时候材质球合并是必须的。

(下图为九龙战里面的多部位换装效果)

材质球合并,分以下几步走,首先我们讨论普通的MeshRenderer的材质球合并,然后再讨论SkinnedMeshRenderer的材质球合并。

普通的MeshRenderer的材质球合并:

1.合并所有材质球所携带的贴图,新建一个材质球,并把合并好的贴图赋予新的材质球。

2.记录下每个被合并的贴图所处于新贴图的Rect,用一个Rect[]数组存下来。

3.合并网格,并把需要合并的各个网格的uv,根据第2步得到的Rect[]刷一遍。

4.把新的材质球赋予合并好的网格,此时就只占有1个drawcall了。

下面是关键代码:

  1. void CombineMesh()

  2. {

  3. MeshFilter[] mfChildren = GetComponentsInChildren<MeshFilter>();

  4. CombineInstance[] combine = new CombineInstance[mfChildren.Length];

  5. MeshRenderer[] mrChildren = GetComponentsInChildren<MeshRenderer>();

  6. Material[] materials = new Material[mrChildren.Length];

  7. MeshRenderer mrSelf = gameObject.AddComponent<MeshRenderer>();

  8. MeshFilter mfSelf = gameObject.AddComponent<MeshFilter>();

  9. Texture2D[] textures = new Texture2D[mrChildren.Length];

  10. for (int i = 0; i < mrChildren.Length; i++)

  11. {

  12. if (mrChildren[i].transform == transform)

  13. {

  14. continue;

  15. }

  16. materials[i] = mrChildren[i].sharedMaterial;

  17. Texture2D tx = materials[i].GetTexture( "_MainTex") as Texture2D;

  18. Texture2D tx2D = new Texture2D(tx.width, tx.height, TextureFormat.ARGB32, false);

  19. tx2D.SetPixels(tx.GetPixels( 0, 0, tx.width, tx.height));

  20. tx2D.Apply();

  21. textures[i] = tx2D;

  22. }

  23. Material materialNew = new Material(materials[0].shader);

  24. materialNew.CopyPropertiesFromMaterial(materials[ 0]);

  25. mrSelf.sharedMaterial = materialNew;

  26. Texture2D texture = new Texture2D(1024, 1024);

  27. materialNew.SetTexture( "_MainTex", texture);

  28. Rect[] rects = texture.PackTextures(textures, 10, 1024);

  29. for (int i = 0; i < mfChildren.Length; i++)

  30. {

  31. if (mfChildren[i].transform == transform)

  32. {

  33. continue;

  34. }

  35. Rect rect = rects[i];

  36. Mesh meshCombine = mfChildren[i].mesh;

  37. Vector2[] uvs = new Vector2[meshCombine.uv.Length];

  38. //把网格的uv根据贴图的rect刷一遍

  39. for (int j = 0; j < uvs.Length; j++)

  40. {

  41. uvs[j].x = rect.x + meshCombine.uv[j].x * rect.width;

  42. uvs[j].y = rect.y + meshCombine.uv[j].y * rect.height;

  43. }

  44. meshCombine.uv = uvs;

  45. combine[i].mesh = meshCombine;

  46. combine[i].transform = mfChildren[i].transform.localToWorldMatrix;

  47. mfChildren[i].gameObject.SetActive( false);

  48. }

  49. Mesh newMesh = new Mesh();

  50. newMesh.CombineMeshes(combine, true,true);//合并网格

  51. mfSelf.mesh = newMesh;

  52. }

合并前的drawcall:

合并后的drawcall:

合并好的网格:

合并好的贴图:

下面我们将讨论SkinnedMeshRenderer的合并。

SkinnedMeshRenderer比MeshRenderer稍微麻烦一点,因为SkinnedMeshRenderer要处理bones。

以下是步骤:

1.合并所有材质球所携带的贴图,新建一个材质球,并把合并好的贴图赋予新的材质球。

2.记录下每个被合并的贴图所处于新贴图的Rect,用一个Rect[]数组存下来。

3.记录下需要合并的SkinnedMeshRenderer的bones。

4.合并网格,并把需要合并的各个网格的uv,根据第2步得到的Rect[]刷一遍。

5.把合并好的网格赋予新的SkinnedMeshRenderer,并把第3步记录下的bones赋予新的SkinnedMeshRenderer。

6.把新的材质球赋予合并好的网格,此时就只占有1个drawcall了。

上面红色部分的步骤就是与MeshRenderer不同之处。

下面是关键代码:

  1. void CombineMesh()

  2. {

  3. SkinnedMeshRenderer[] smrs = GetComponentsInChildren<SkinnedMeshRenderer>();

  4. CombineInstance[] combine = new CombineInstance[smrs.Length];

  5. Material[] materials = new Material[smrs.Length];

  6. Texture2D[] textures = new Texture2D[smrs.Length];

  7. SkinnedMeshRenderer smrCombine = combineMesh.gameObject.AddComponent<SkinnedMeshRenderer>();

  8. smrCombine.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;

  9. smrCombine.receiveShadows = false;

  10. for (int i = 0; i < smrs.Length; i++)

  11. {

  12. materials[i] = smrs[i].sharedMaterial;

  13. Texture2D tx = materials[i].GetTexture( "_MainTex") as Texture2D;

  14. Texture2D tx2D = new Texture2D(tx.width, tx.height, TextureFormat.ARGB32, false);

  15. tx2D.SetPixels(tx.GetPixels( 0, 0, tx.width, tx.height));

  16. tx2D.Apply();

  17. textures[i] = tx2D;

  18. }

  19. Material materialNew = new Material(materials[0].shader);

  20. materialNew.CopyPropertiesFromMaterial(materials[ 0]);

  21. Texture2D texture = new Texture2D(1024, 1024);

  22. Rect[] rects = texture.PackTextures(textures, 10, 1024);

  23. materialNew.SetTexture( "_MainTex", texture);

  24. List<Transform> boneTmp = new List<Transform>();

  25. for (int i = 0; i < smrs.Length; i++)

  26. {

  27. if (smrs[i].transform == transform)

  28. {

  29. continue;

  30. }

  31. Rect rect = rects[i];

  32. Mesh meshCombine = CreatMeshWithMesh(smrs[i].sharedMesh);

  33. Vector2[] uvs = new Vector2[meshCombine.uv.Length];

  34. for (int j = 0; j < uvs.Length; j++)

  35. {

  36. uvs[j].x = rect.x + meshCombine.uv[j].x * rect.width;

  37. uvs[j].y = rect.y + meshCombine.uv[j].y * rect.height;

  38. }

  39. boneTmp.AddRange(smrs[i].bones);

  40. meshCombine.uv = uvs;

  41. combine[i].mesh = meshCombine;

  42. combine[i].transform = smrs[i].transform.localToWorldMatrix;

  43. GameObject.Destroy(smrs[i].gameObject);

  44. }

  45. Mesh newMesh = new Mesh();

  46. newMesh.CombineMeshes(combine, true, true);

  47. smrCombine.bones = boneTmp.ToArray();

  48. smrCombine.rootBone = rootBone;

  49. smrCombine.sharedMesh = newMesh;

  50. smrCombine.sharedMaterial = materialNew;

  51. }

  52. Mesh CreatMeshWithMesh(Mesh mesh)

  53. {

  54. Mesh mTmp = new Mesh();

  55. mTmp.vertices = mesh.vertices;

  56. mTmp.name = mesh.name;

  57. mTmp.uv = mesh.uv;

  58. mTmp.uv2 = mesh.uv2;

  59. mTmp.uv2 = mesh.uv2;

  60. mTmp.bindposes = mesh.bindposes;

  61. mTmp.boneWeights = mesh.boneWeights;

  62. mTmp.bounds = mesh.bounds;

  63. mTmp.colors = mesh.colors;

  64. mTmp.colors32 = mesh.colors32;

  65. mTmp.normals = mesh.normals;

  66. mTmp.subMeshCount = mesh.subMeshCount;

  67. mTmp.tangents = mesh.tangents;

  68. mTmp.triangles = mesh.triangles;

  69. return mTmp;

  70. }

未合并前,有两个网格,占用2drawcall,分别为人物和刀的网格和材质球。

合并后,只占用 1 drawcall了,而且AnimationController以及其动画能够正常工作:

合并好的网格:

合并好的贴图:

以上便是合并Mesh和Material的内容,目前只关注实现,还可以进一步优化,有以下几点要注意的:

1.合并的材质球需要使用同一个shader,如果多个材质球使用了不同shader,就要做进一步的出分类处理了。

2.材质球所用的Texture文件的Read/Write Enable选项要打上勾。

项目的github地址:  https://github.com/arBao/MeshMaterialCombine

原创文章,转载请注明出处:   http://blog.csdn.net/dardgen2015/article/details/51517860

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Unity中的网格与材质球合并 - HelloWorld