什么是Mesh?

Mesh是指的模型的网格,3D模型是由多边形拼接而成,而多边形实际上又是由多个三角形拼接而成的。即一个3D模型的表面其实是由多个彼此相连的三角面构成。三维空间中,构成这些三角形的点和边的集合就是Mesh。

原理

即动态创建一个Mesh,设置三角形和顶点数据,然后赋值给MeshFilter(增加mesh属性),通过MeshRenderer(增加材质并渲染出Mesh)绘制出来

理论基础:

1、左手坐标系和右手坐标系

我们的三维坐标系,在3dmax里是右手坐标系,而在Unity里是左手坐标系。

左手坐标系和右手坐标系的区别 http://www.cnblogs.com/mythou/p/3327046.html

2、三边面如何组成四边面

如图,左边是Unity里的左手坐标系,右边是在此坐标系里生成的一个面以及它的各个点坐标。

012和230这两个三边面就组成了一个四边面。

如果我问这个四边面有几个顶点,想必大家都会回答4个,实际上是6个,012和230这是6个顶点,不同面的顶点不公用。

要组成2个三边面可以有很多种顺序,例如012和320、012和032、023和012等等等

但是我们一般都是按照4个点的顺序来画2个三边面组成四边面,所以可选的只有【012和230、230和012】,以及【032和210、210和032】这两大类

这两类画法有什么区别呢?细心的童鞋应该已经发现,这两种方式前者是逆时针,后者是顺时针。

这种循环的方向会导致面的法线方向不同,而这个法线方向会决定这个面的朝向。

我们要确定这个法线方向其实很简单,上面说了,Unity里是左手坐标系,拿出左手,伸直,拇指与其他四个指头垂直,然后四指弯曲,指尖朝向循环的方向,拇指就指向法线的方向。

由此我们得出结论,要想生成正确的面(法线指向我们),我们只能用【032和210、210和032】

这里需要注意的一点是,我们确定4个点的循环方向,和生成三边面时的循环方向无关,只要生成三边面时,用到的前4个点的index顺序没错就行了。

Mesh的组成部分

1.vertices(顶点数据数组Vector3[])

2.triangles(三角形顶点索引数组,int[])

3.normals(法线向量数组,Vector3[])

4.uv(纹理坐标数组,Vector2[])

顶点坐标:顶点坐标数组存放Mesh的每个顶点的空间坐标,假设某mesh有n个顶点,则vertex的size为n

法线:法线数组存放mesh每个顶点的法线,大小与顶点坐标对应,normal[i]对应顶点vertex[i]的法线

      法线详解:

法线就是垂直于面的一条线,它有方向,没有大小。

法线的方向就是面朝外的方向。比如我们现在盯着显示器看,从显示器的正中心会有一条法线垂直于屏幕指向我们。

法线向外的面就是正面,相反的就是背面,一般来讲,从正面看才能看到面,背面看面是看不到的。

纹理坐标:它定义了图片上每个点的位置的信息. 这些点与3D模型是相互联系的, 以决定表面纹理贴图的位置. UV就是将图像上每一个点精确对应到模型物体的表面. uv[i]对应vertex[i]

三角形序列:每个mesh都由多个三角面组成,而三角面的三个点就是顶点坐标里的点,三角形的数组的size = 三角形个数 * 3

      三边面和四边面:

三边面就是三条边组成的面,四边面就是四条边组成的面。

三边面在三维空间中是不可扭曲的,而四边面在三维空间中可以扭曲。所以Unity里只支持三边面。其他支持四边面的软件例如3dmax在导出fbx的时候,会把四边面转换成三边面。

创建一个立方体

1.定顶点坐标

一般我们会以立方体几何中心为坐标原点。

代码:

  1. //顶点数组
  2. Vector3[] _vertices =
  3. {
  4. // front
  5. new Vector3(-5.0f, 10.0f, -5.0f),
  6. new Vector3(-5.0f, 0.0f, -5.0f),
  7. new Vector3(5.0f, 0.0f, -5.0f),
  8. new Vector3(5.0f, 10.0f, -5.0f),
  9.  
  10. // left
  11. new Vector3(-5.0f, 10.0f, -5.0f),
  12. new Vector3(-5.0f, 0.0f, -5.0f),
  13. new Vector3(-5.0f, 0.0f, 5.0f),//
  14. new Vector3(-5.0f, 10.0f, 5.0f),
  15.  
  16. // back
  17. new Vector3(-5.0f, 10.0f, 5.0f),
  18. new Vector3(-5.0f, 0.0f, 5.0f),
  19. new Vector3(5.0f, 0.0f, 5.0f),
  20. new Vector3(5.0f, 10.0f, 5.0f),
  21.  
  22. // right
  23. new Vector3(5.0f, 10.0f, 5.0f),
  24. new Vector3(5.0f, 0.0f, 5.0f),
  25. new Vector3(5.0f, 0.0f, -5.0f),
  26. new Vector3(5.0f, 10.0f, -5.0f),
  27.  
  28. // Top
  29. new Vector3(-5.0f, 10.0f, 5.0f),
  30. new Vector3(5.0f, 10.0f, 5.0f),
  31. new Vector3(5.0f, 10.0f, -5.0f),
  32. new Vector3(-5.0f, 10.0f, -5.0f),
  33.  
  34. // Bottom
  35. new Vector3(-5.0f, 0.0f, 5.0f),
  36. new Vector3(5.0f, 0.0f, 5.0f),
  37. new Vector3(5.0f, 0.0f, -5.0f),
  38. new Vector3(-5.0f, 0.0f, -5.0f),
  39.  
  40. };

这里有人会有疑问,正方体6个面,每个面由2个三角形组成,所以共需要36个三角形顶点索引。但是正方体只有8个顶点,为什么需要24个顶点坐标数据呢?

答案是:Unity3D的Mesh.triangles是三角形索引数组,不仅依靠这个索引值索引三角形顶点坐标,而且索引纹理坐标,索引法线向量。即正方体的每个顶点都参与了3个平面,而这3个平面的法线向量是不同的,该顶点在渲染这3个平面的时候需要索引到不同的法线向量。而由于顶点坐标和法线向量是由同一个索引值triangles[Index]取得的,例如,有三个点在vertices中索引到的顶点都为(0,0,0),但是在normals中索引到的法向量值各不相同。这就决定了在正方体中一个顶点,需要有3份存储。(如果你需要创建其它模型,需要根据实际情况决定顶点坐标的冗余度。实质上顶点坐标的冗余正是方便了法线坐标、纹理坐标的存取。),一般不共点。还有就是Unity中是左手坐标系,一定记好,因为在绘制三角面时很重要。

2.三角面索引

  1. //索引数组
  2. int[] _triangles =
  3. {
  4. //front
  5. ,,,
  6. ,,,
  7. //left
  8. ,,,
  9. ,,,
  10. //back
  11. ,,,
  12. ,,,
  13. //right
  14. ,,,
  15. ,,,
  16. ////up
  17. //16,17,18,
  18. //16,18,19,
  19. ////buttom
  20. //21,23,22,
  21. //21,20,23,
  22.  
  23. //不可跳跃设置索引值(否则会提示一些索引超出边界顶点 15直接20不可,要连续15-16)
  24. ,,,
  25. ,,,
  26. };

这里设置的原则时外面被渲染里面剔除掉,顺时针构建(注意里外面的区别),还要注意的一个点,如上所写,比如我想生成5个面,那你的索引值也要是连续的,不可16直接蹦到20。这里立法体面的绘制顺序是(即绘制三角面的面与上面顶点顺序要一致)设置顶点的顺序

3.UV坐标

代码:

  1. //UV数组
  2. Vector2[] uvs =
  3. {
  4. // Front
  5. new Vector2(1.0f, 0.0f),
  6. new Vector2(1.0f, 1.0f),
  7. new Vector2(1.0f, 0.0f),
  8. new Vector2(0.0f, 0.0f),
  9.  
  10. // Left
  11. new Vector2(1.0f, 1.0f),
  12. new Vector2(0.0f, 1.0f),
  13. new Vector2(0.0f, 0.0f),
  14. new Vector2(1.0f, 0.0f),
  15.  
  16. // Back
  17. new Vector2(1.0f, 0.0f),
  18. new Vector2(1.0f, 1.0f),
  19. new Vector2(1.0f, 0.0f),
  20. new Vector2(0.0f, 0.0f),
  21.  
  22. // Right
  23. new Vector2(1.0f, 1.0f),
  24. new Vector2(0.0f, 1.0f),
  25. new Vector2(0.0f, 0.0f),
  26. new Vector2(1.0f, 0.0f),
  27.  
  28. //// Top
  29. //new Vector2(0.0f, 0.0f),
  30. //new Vector2(1.0f, 0.0f),
  31. //new Vector2(1.0f, 1.0f),
  32. //new Vector2(0.0f, 1.0f),
  33.  
  34. // Bottom
  35. new Vector2(0.0f, 0.0f),
  36. new Vector2(1.0f, 0.0f),
  37. new Vector2(1.0f, 1.0f),
  38. new Vector2(0.0f, 1.0f),
  39.  
  40. };

UV坐标从左上角开始(想象摄像机在立方体内部去判断),

开始的即(0,0),一般是在0-1之间,一些比较大的面为防止纹理被拉伸马赛克,我们会重复贴纹理,会有大于1的情况,这里的点要与顶点坐标一一对应。重复贴纹理时需要将重复帖的贴图的Wrap Mode设为Repeat(重复)。
即:
4.构建mesh
代码:
  1. Mesh mesh = new Mesh()
  2. {
  3. vertices = _vertices,
  4. uv = uvs,
  5. triangles = _triangles,
  6. };
  7.  
  8. //重新计算网格的法线
  9. //在修改完顶点后,通常会更新法线来反映新的变化。法线是根据共享的顶点计算出来的。
  10. //导入到网格有时不共享所有的顶点。例如:一个顶点在一个纹理坐标的接缝处将会被分成两个顶点。
  11. //因此这个RecalculateNormals函数将会在纹理坐标接缝处创建一个不光滑的法线。
  12. //RecalculateNormals不会自动产生切线,因此bumpmap着色器在调用RecalculateNormals之后不会工作。然而你可以提取你自己的切线。
  13. mesh.RecalculateNormals();

给mesh属性赋值。

5.增加MeshFilter组件,网格过滤。以及增加MeshRenderer组件添加材质实现渲染。OK!!!到这基本已经绘制完了,Mesh已经出来了。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. public class ShaderBase : MonoBehaviour
  6. {
  7.  
  8. void Start()
  9. {
  10. GameObject gameObject = new GameObject("Cube");
  11. gameObject.transform.position = Vector3.zero;
  12.  
  13. //顶点数组
  14. Vector3[] _vertices =
  15. {
  16. // front
  17. new Vector3(-5.0f, 10.0f, -5.0f),
  18. new Vector3(-5.0f, 0.0f, -5.0f),
  19. new Vector3(5.0f, 0.0f, -5.0f),
  20. new Vector3(5.0f, 10.0f, -5.0f),
  21.  
  22. // left
  23. new Vector3(-5.0f, 10.0f, -5.0f),
  24. new Vector3(-5.0f, 0.0f, -5.0f),
  25. new Vector3(-5.0f, 0.0f, 5.0f),//
  26. new Vector3(-5.0f, 10.0f, 5.0f),
  27.  
  28. // back
  29. new Vector3(-5.0f, 10.0f, 5.0f),
  30. new Vector3(-5.0f, 0.0f, 5.0f),
  31. new Vector3(5.0f, 0.0f, 5.0f),
  32. new Vector3(5.0f, 10.0f, 5.0f),
  33.  
  34. // right
  35. new Vector3(5.0f, 10.0f, 5.0f),
  36. new Vector3(5.0f, 0.0f, 5.0f),
  37. new Vector3(5.0f, 0.0f, -5.0f),
  38. new Vector3(5.0f, 10.0f, -5.0f),
  39.  
  40. // Top
  41. new Vector3(-5.0f, 10.0f, 5.0f),
  42. new Vector3(5.0f, 10.0f, 5.0f),
  43. new Vector3(5.0f, 10.0f, -5.0f),
  44. new Vector3(-5.0f, 10.0f, -5.0f),
  45.  
  46. // Bottom
  47. new Vector3(-5.0f, 0.0f, 5.0f),
  48. new Vector3(5.0f, 0.0f, 5.0f),
  49. new Vector3(5.0f, 0.0f, -5.0f),
  50. new Vector3(-5.0f, 0.0f, -5.0f),
  51.  
  52. };
  53. //索引数组
  54. int[] _triangles =
  55. {
  56. //front
  57. ,,,
  58. ,,,
  59. //left
  60. ,,,
  61. ,,,
  62. //back
  63. ,,,
  64. ,,,
  65. //right
  66. ,,,
  67. ,,,
  68. ////up
  69. //16,17,18,
  70. //16,18,19,
  71. ////buttom
  72. //21,23,22,
  73. //21,20,23,
  74.  
  75. //不可跳跃设置索引值(否则会提示一些索引超出边界顶点 15直接20不可,要连续15-16)
  76. ,,,
  77. ,,,
  78. };
  79.  
  80. //UV数组
  81. Vector2[] uvs =
  82. {
  83. // Front
  84. new Vector2(1.0f, 0.0f),
  85. new Vector2(1.0f, 1.0f),
  86. new Vector2(1.0f, 0.0f),
  87. new Vector2(0.0f, 0.0f),
  88.  
  89. // Left
  90. new Vector2(1.0f, 1.0f),
  91. new Vector2(0.0f, 1.0f),
  92. new Vector2(0.0f, 0.0f),
  93. new Vector2(1.0f, 0.0f),
  94.  
  95. // Back
  96. new Vector2(1.0f, 0.0f),
  97. new Vector2(1.0f, 1.0f),
  98. new Vector2(1.0f, 0.0f),
  99. new Vector2(0.0f, 0.0f),
  100.  
  101. // Right
  102. new Vector2(1.0f, 1.0f),
  103. new Vector2(0.0f, 1.0f),
  104. new Vector2(0.0f, 0.0f),
  105. new Vector2(1.0f, 0.0f),
  106.  
  107. //// Top
  108. //new Vector2(0.0f, 0.0f),
  109. //new Vector2(1.0f, 0.0f),
  110. //new Vector2(1.0f, 1.0f),
  111. //new Vector2(0.0f, 1.0f),
  112.  
  113. // Bottom
  114. new Vector2(0.0f, 0.0f),
  115. new Vector2(1.0f, 0.0f),
  116. new Vector2(1.0f, 1.0f),
  117. new Vector2(0.0f, 1.0f),
  118.  
  119. };
  120.  
  121. Mesh mesh = new Mesh()
  122. {
  123. vertices = _vertices,
  124. uv = uvs,
  125. triangles = _triangles,
  126. };
  127.  
  128. //重新计算网格的法线
  129. //在修改完顶点后,通常会更新法线来反映新的变化。法线是根据共享的顶点计算出来的。
  130. //导入到网格有时不共享所有的顶点。例如:一个顶点在一个纹理坐标的接缝处将会被分成两个顶点。
  131. //因此这个RecalculateNormals函数将会在纹理坐标接缝处创建一个不光滑的法线。
  132. //RecalculateNormals不会自动产生切线,因此bumpmap着色器在调用RecalculateNormals之后不会工作。然而你可以提取你自己的切线。
  133. mesh.RecalculateNormals();
  134. gameObject.AddComponent<MeshFilter>().mesh=mesh;
  135. //Material/New Material 1
  136. gameObject.AddComponent<MeshRenderer>().material = Resources.Load<Material>("Material/New Material");
  137.  
  138. }
  139.  
  140. }

这不是上述代码的结果图片,这是动态创建外围盒的图片,做法一样。

最新:这个立方体,我想底面和侧面贴不同贴图,如何实现?

使用  mesh.subMeshCount = X;即subMesh,子网格,具体使用如下:

  1. Vector3 contralPos = (maxPos + minPos) / ;
  2. float boxHight = Mathf.Abs(maxPos.y - minPos.y);
  3. float boxLength = Mathf.Abs(maxPos.x - minPos.x);
  4. float boxWidth = Mathf.Abs(maxPos.z - minPos.z);
  5. vertexPosArray = AddVertexPos(1.2f * boxLength, 1.2f * boxWidth, 1.4f * boxHight);
  6. vertexIndexList = AddVertexIndex();
  7. uvArr = SetUVPos(GetIntValue(boxLength / (textureSizeL * uvNorm)), GetIntValue(boxWidth / (textureSizeL * uvNorm)), GetIntValue(boxHight / (textureSizeW * uvNorm)));
  8. Mesh mesh = new Mesh()
  9. {
  10. vertices = vertexPosArray,
  11. uv = uvArr,
  12. };
  13. mesh.subMeshCount = ;
  14. mesh.SetTriangles(vertexIndexList[], );
  15. mesh.SetTriangles(vertexIndexList[], );
  16. mesh.RecalculateNormals();
  17. GameObject Box = new GameObject(name);
  18. // Box.transform.localPosition = contralPos;
  19. Box.transform.localPosition = new Vector3(contralPos.x, minPos.y, contralPos.z);
  20. Box.AddComponent<MeshFilter>().mesh = mesh;
  21. Material[] materials = new Material[];
  22. materials[] = new Material(Resources.Load<Material>("Materials/Mine/MinefieldTexture_Side"));
  23. materials[] = new Material(Resources.Load<Material>("Materials/Mine/MinefieldTexture_Buttom"));
  24. Box.AddComponent<MeshRenderer>().materials = materials;
  1. mesh.subMeshCount = 2;
  2. mesh.SetTriangles(vertexIndexList[0], 0);
  3. mesh.SetTriangles(vertexIndexList[1], 1);
    这是指定子网格对应的索引集合,在设置索引时,应该这样分开存储:
  1. /// <summary>
  2. /// 添加索引
  3. /// </summary>
  4. private List<int[]> AddVertexIndex()
  5. {
  6. List<int[]> indexList = new List<int[]>();
  7. int[] sideIndexArray =
  8. {
  9. //front
  10. ,,,
  11. ,,,
  12.  
  13. //back
  14. ,,,
  15. ,,,
  16.  
  17. //left
  18. ,,,
  19. ,,,
  20.  
  21. //right
  22. ,,,
  23. ,,,
  24. };
  25. int[] buttomFaceIndexArray =
  26. {
  27. //buttom
  28. ,,,
  29. ,,
  30. };
  31. indexList.Add(sideIndexArray);
  32. indexList.Add(buttomFaceIndexArray);
  33.  
  34. return indexList;
  35. }

即这样完成分开了Mesh,分别使用不同的材质。

  1.  

Unity中动态创建Mesh的更多相关文章

  1. Unity中一键创建常用文件夹

    Unity中一键创建常用文件夹 说明 项目测试版本Unity5.3. 这个一个小工具:功能非常简单,就是一键给新建工程添加所有文件夹.到此结束. 但是具体咋操作呢? 与把大象装进冰箱一样,三步,下载代 ...

  2. Delphi中动态创建窗体有四种方式

    Delphi中动态创建窗体有四种方式,最好的方式如下: 比如在第一个窗体中调用每二个,主为第一个,第二个设为动态创建 Uses Unit2; //引用单元文件 procedure TForm1.But ...

  3. WPF 中动态创建和删除控件

    原文:WPF 中动态创建和删除控件 动态创建控件 1.容器控件.RegisterName("Name",要注册的控件)   //注册控件 2.容器控件.FindName(" ...

  4. unity, 用脚本创建mesh

    创建一个空gameObject,添加Mesh Filter和Mesh Renderer两个component,再添加一个脚本createMeshScript: using UnityEngine;us ...

  5. Unity3D中动态创建编辑轴(点,线,圆,圆锥)

    问题分析: 最近在搞软件底层开发,将一些工具或者底层脚本打成dll导入unity使用,有这样一需求,就是编辑功能,需要像Scene场景一样,实现那种编辑轴 实现方式: 创建Mesh,构建编辑轴,这个地 ...

  6. Unity通过脚本创建Mesh(网格)

    ##1.创建一个带Mesh的物体 Unity中的网格作为组件不能脱离物体单独存在 新建脚本CreateMesh public class CreateMesh: MonoBehaviour { voi ...

  7. WPF 中动态创建、删除控件,注册控件名字,根据名字查找控件

    动态创建控件 1.容器控件.RegisterName("Name",要注册的控件)   //注册控件 2.容器控件.FindName("Name") as  控 ...

  8. BroadcastReceiver接收电量变化的广播-------在代码中动态创建接受者

    本例为动态创建广播接收者即不是在AndroidManifest.xml文件中定义的广播接收着 代码: package com.qf.broadcastreceiver01; import androi ...

  9. Unity 中动态修改碰撞框(位置,大小,方向)

    在Unity中,玩家处于不同的状态,要求的碰撞框的 位置/大小/方向 会有所改变,怎么动态的修改碰撞框呢? 下面是Capsure Collider(胶囊体)的修改: CapsuleCollider.d ...

随机推荐

  1. Python高阶函数及函数柯里化

    1 Python高阶函数 接收函数为参数,或者把函数作为结果返回的函数为高阶函数. 1.1 自定义sort函数 要求:仿照内建函数sorted,自行实现一个sort函数.内建函数sorted函数是返回 ...

  2. Cocos Creator 通用框架设计 —— 资源管理

    如果你想使用Cocos Creator制作一些规模稍大的游戏,那么资源管理是必须解决的问题,随着游戏的进行,你可能会发现游戏的内存占用只升不降,哪怕你当前只用到了极少的资源,并且有使用cc.loade ...

  3. 九、Executor框架

    Executor框架 ​ 我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等.线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始 ...

  4. JVM垃圾回收(上)

    Java 中的垃圾回收,常常是由 JVM 帮我们做好的.虽然这节省了大家很多的学习的成本,提高了项目的执行效率,但是当项目变得越来越复杂,用户量越来越大时,还是需要我们懂得垃圾回收机制,这样也能进行更 ...

  5. firefox 实用插件推荐和使用

    1.firefox安装插件 2.firebug 3.Cookie editor 4.Tamper data 5.user agent switcher 6.hackbar 7.httpfox抓包工具 ...

  6. 手把手教你搭建HEXO免费博客

    一.环境搭建 node安装 百度搜索node,进入官网.下载稳定版: 下载好后直接打开安装 我这里将其安装在D盘(可以自己选择安装位置) 可以看到安装包中已经自带npm包管理工具 等待安装完成后,WI ...

  7. 对新手严重不友好的强者——Nginx那些俯拾皆是的坑

    1.if和后边的括号要隔一个空格,变量后面也要有空格. 2.location / 和location = / 的意味不一样.前面的是通用匹配,后面的匹配根节点访问请求,前面的使用不好很容易引发重定向过 ...

  8. Python开发【第八篇】元组

    元组 元组是不可改变的序列,元组是可以存储任意类型数据的容器 元组和字符串的共同点:它们都是容器,都是不可变的序列 元组和字符串的不同点:元组可以存储任意的数据类型的元素,字符串只能存储字符 元组和列 ...

  9. PageObjec页面对象模式(理论)

    ui自动化测试的分层思想:实现测试数据与业务数据分离 1. 基础层 2. 对象层:每个页面的操作元素封装为一个文件 3.测试用例层:调用对象层封装的方法进行测试用例编写

  10. 在Mac平台用Sublime编辑器使用Git并连接github

    近期闲来无事,学习一下Git版本控制的东西,首先是要在我的pc上学会如何向git上提交我的代码,记录一下过程以及遇到的问题. 一.Mac下Sublime Text 3整合Git 来源于一个技术教程:h ...