流星

坚持住才有机会.

网格及材质合并 有更新!

    最近看Combine Children脚本
    发现他合并静态场景可以降低大量的同一个材质的多处MeshRenderer引用
    他已经做了我想做的事

    分析所有MeshRenderer引用到的所有材质,列一个相同材质列表
    把全部网格,按照某一批网格使用一个材质的方式
    将多个使用同一个材质的网格合并后与这个材质关联,这样,DrawCall被大幅降低
    他的合并并非单单的合并到一个GameObject里

    而我所想要的合并,是把全部网格和材质放到一个游戏对象里
    因为pmd文件好像只支持一个游戏对象。
    其实按原理应该是一样的
    区别在
    一系列网格合并成为一个mesh的submesh,然后每个submesh对应一个材质。

    最近弄unity地图导出pmd,发现好多网格合并代码用不了
    我无法理解那些合并代码里的网格合并到底是什么意思.
    我说说我理解的网格合并
    一般一个地图场景,有很多个gameobject,每个含有网格的gameobject上都有一个meshfilter组件和一个meshrenderer组件
    而对meshrenderer组件来说,他可能具有一系列材质来对应渲染meshfilter里的每个submesh
    而每个材质都有自己的shader参数等.但是使用同一材质的,这些shader参数都一样
    而对于每个meshfilter组件来说,他拥有顶点缓冲区,索引缓冲区,uv,color,normal
    以及每个submesh对应了哪些顶点,以及这个meshfilter所在的gameobject的坐标(世界变换矩阵)
    所以要合并,应该是先要统计这些信息,然后把所有信息存储到一个在原点,无旋转,无缩放的gameobject上.
    把所有网格信息,为其添加meshfilter组件放到mesh里,把所有材质放到meshrenderer里的materials里
    而这个主要麻烦,就是网格和材质的对应关系,以及网格顶点信息的重新计算(要把原来meshfilter里的顶点算出世界坐标然后放到这里).
    这整个流程只要心里有数了,就可以写代码来实现了.
    最后放一个组件代码,用于合并一系列的gameobject到一个gameobject里,主要是合并网格信息,材质等
    这个组件没有合并同材质的submesh,事实上,使用了同一材质的不同网格,可以合并到一个submesh里.
    这个点可以继续优化,但是现在是没处理的,我只是想把地图场景保存为pmd的,所以这部分忽略了
    如果有不对的请指教

    在一个Editor类里添加一个类
    [CustomEditor(typeof(PMDSave))]
    public class PMDSaveInspector : Editor
    {
    public override void OnInspectorGUI()
    {
    base.OnInspectorGUI();
    if (GUILayout.Button(“Combine”))
    {
    var obj = (PMDSave)target;
    obj.CombineMapMesh();
    }
    }
    }

    然后在Asset目录下新增此组件
    然后在地图的根节点处,挂入此脚本组件,点组件Inspector栏下的CombineMesh按钮,这个单网格就可以替代全部地图下的所有gameobejct了。

    public class PMDSave : MonoBehaviour {
    void CombineMapMesh()
    {
    Transform GenMapRoot = new GameObject(“GenMapRoot”).transform;
    GenMapRoot.position = Vector3.zero;
    GenMapRoot.localScale = Vector3.one;
    GenMapRoot.rotation = Quaternion.identity;
    MeshFilter mf = GenMapRoot.gameObject.AddComponent();
    MeshRenderer mr = GenMapRoot.gameObject.AddComponent();
    MeshFilter[] mfs = GetComponentsInChildren();
    MeshRenderer[] mrs = GetComponentsInChildren();
    //统计材质
    List mat = new List();
    for (int i = 0; i < mrs.Length; i++)
    {
    if (!mrs[i].enabled)
    continue;
    mat.AddRange(mrs[i].sharedMaterials);
    }
    mr.sharedMaterials = mat.ToArray();
    int subMeshIndex = 0;
    List vertex = new List();
    List uv = new List();
    List normal = new List();
    List co = new List();
    Dictionary<int, int[]> subMeshIndices = new Dictionary<int, int[]>();
    //统计顶点.
    for (int i = 0; i < mfs.Length; i++)
    {
    //隐藏了的不要
    if (!mrs[i].enabled)
    continue;
    //推入顶点
    int vertexIndex = vertex.Count;//记录推入之前的索引起始
    for (int j = 0; j < mfs[i].sharedMesh.vertexCount; j++)
    vertex.Add(mfs[i].transform.localToWorldMatrix * mfs[i].sharedMesh.vertices[j]);

    	  //推入颜色,UV,法线
    	  int uv_len = mfs[i].sharedMesh.uv.Length;
    	  for (int j = 0; j < uv_len; j++)
    		  uv.Add(mfs[i].sharedMesh.uv[j]);
    	  int normal_len = mfs[i].sharedMesh.normals.Length;
    	  for (int j = 0; j < normal_len; j++)
    		  normal.Add(mfs[i].sharedMesh.normals[j]);
    	  int color_len = mfs[i].sharedMesh.colors.Length;
    	  for (int j = 0; j < color_len; j++)
    		  co.Add(mfs[i].sharedMesh.colors[j]);
    	  for (int j = 0; j < mfs[i].sharedMesh.subMeshCount; j++)
    	  {
    		  int[] indices = mfs[i].sharedMesh.GetIndices(j);
    		  //把这些索引,都加上全局索引下标
    		  for (int k = 0; k < indices.Length; k++)
    			  indices[k] += vertexIndex;
    		  subMeshIndices.Add(subMeshIndex, indices);
    		  subMeshIndex++;
    	  }
      }
      mf.sharedMesh = new Mesh();
      mf.sharedMesh.SetVertices(vertex);
      mf.sharedMesh.SetColors(co);
      mf.sharedMesh.uv = uv.ToArray();
      mf.sharedMesh.SetNormals(normal);
      mf.sharedMesh.subMeshCount = subMeshIndices.Count;
      vertexCnt = vertex.Count;
      foreach (var each in subMeshIndices)
    	  mf.sharedMesh.SetIndices(each.Value, MeshTopology.Triangles, each.Key);
    

    }
    }

    之前做这个PMDSave主要是来保存和官方PMD骨骼结构调整一致的流星.net人物角色.
    因为想让.net流星角色也跳极乐净土,后来朋友想把场景也弄进去,就让我导出下场景.
    其实导出角色的时候,要导出骨骼结构,以及部分的腿部IK设置,以及蒙皮数据,所以导出角色,是比导出地图更麻烦的.
    而一个pmd貌似只能保存一个meshfilter里的顶点和材质.
    所以后来就找代码合并网格和材质到一个物件上,找了半天都是些不能用的,我就想不通到底哪里理解不一样.
    于是自己折腾了一个,过几天朋友就会发出一个流星蝴蝶剑.net角色跳舞蹈的视频.
    其实转换骨骼结构为官方mmd的标准骨架后,舞蹈动作都可以通用了,想想一群.net的角色一起跳舞
    就觉得好滑稽.

    最后合并网格是用来减少drawcall的,相同材质的网格合并能减少drawcall,所以在这个组件基础上,只要把材质相同的submesh合并到一个submesh里,drawcall也就会被优化掉
    不过合并后,成为一个整体,是无法控制地图上的任何元素的,因为都在一起了,要显示就一起显示,要隐藏就一起隐藏

    validate