zoukankan      html  css  js  c++  java
  • Unity中动态创建Mesh

    什么是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 
    11             // left  
    12             new Vector3(-5.0f, 10.0f, -5.0f),
    13             new Vector3(-5.0f, 0.0f, -5.0f),
    14             new Vector3(-5.0f, 0.0f, 5.0f),//
    15             new Vector3(-5.0f, 10.0f, 5.0f),
    16 
    17             // back
    18             new Vector3(-5.0f, 10.0f, 5.0f),
    19             new Vector3(-5.0f, 0.0f, 5.0f),
    20             new Vector3(5.0f, 0.0f, 5.0f),
    21             new Vector3(5.0f, 10.0f, 5.0f),
    22 
    23 
    24             // right  
    25             new Vector3(5.0f, 10.0f, 5.0f),
    26             new Vector3(5.0f, 0.0f, 5.0f),
    27             new Vector3(5.0f, 0.0f, -5.0f),
    28             new Vector3(5.0f, 10.0f, -5.0f),
    29 
    30 
    31             // Top
    32             new Vector3(-5.0f, 10.0f, 5.0f),
    33             new Vector3(5.0f, 10.0f, 5.0f),
    34             new Vector3(5.0f, 10.0f, -5.0f),
    35             new Vector3(-5.0f, 10.0f, -5.0f),
    36 
    37            // Bottom
    38             new Vector3(-5.0f, 0.0f, 5.0f),
    39             new Vector3(5.0f, 0.0f, 5.0f),
    40             new Vector3(5.0f, 0.0f, -5.0f),
    41             new Vector3(-5.0f, 0.0f, -5.0f),
    42 
    43         };

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

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

    2.三角面索引

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

    这里设置的原则时外面被渲染里面剔除掉,顺时针构建(注意里外面的区别),还要注意的一个点,如上所写,比如我想生成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             
    11             // Left
    12             new Vector2(1.0f, 1.0f),
    13             new Vector2(0.0f, 1.0f),
    14             new Vector2(0.0f, 0.0f),
    15             new Vector2(1.0f, 0.0f),
    16 
    17             
    18             // Back
    19             new Vector2(1.0f, 0.0f),
    20             new Vector2(1.0f, 1.0f),
    21             new Vector2(1.0f, 0.0f),
    22             new Vector2(0.0f, 0.0f),
    23 
    24             
    25             // Right
    26             new Vector2(1.0f, 1.0f),
    27             new Vector2(0.0f, 1.0f),
    28             new Vector2(0.0f, 0.0f),
    29             new Vector2(1.0f, 0.0f),
    30 
    31             //// Top
    32             //new Vector2(0.0f, 0.0f),
    33             //new Vector2(1.0f, 0.0f),
    34             //new Vector2(1.0f, 1.0f),
    35             //new Vector2(0.0f, 1.0f),
    36 
    37 
    38             // Bottom
    39             new Vector2(0.0f, 0.0f),
    40             new Vector2(1.0f, 0.0f),
    41             new Vector2(1.0f, 1.0f),
    42             new Vector2(0.0f, 1.0f),
    43 
    44         };

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

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

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

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

     1   Vector3 contralPos = (maxPos + minPos) / 2;
     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 = 2;
    14             mesh.SetTriangles(vertexIndexList[0], 0);
    15             mesh.SetTriangles(vertexIndexList[1], 1);
    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[2];
    22             materials[0] = new Material(Resources.Load<Material>("Materials/Mine/MinefieldTexture_Side"));
    23             materials[1] = new Material(Resources.Load<Material>("Materials/Mine/MinefieldTexture_Buttom"));
    24             Box.AddComponent<MeshRenderer>().materials = materials;
     mesh.subMeshCount = 2;
     mesh.SetTriangles(vertexIndexList[0], 0);
     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                2,1,0,
    11                2,0,3,
    12 
    13                //back
    14                4,5,6,
    15                4,6,7,
    16 
    17                //left
    18                8,10,11,
    19                8,9,10,
    20 
    21                //right
    22                13,15,14,
    23                13,12,15,
    24             };
    25             int[] buttomFaceIndexArray =
    26             {
    27                //buttom
    28                17,16,19,
    29                17,19,18
    30             };
    31             indexList.Add(sideIndexArray);
    32             indexList.Add(buttomFaceIndexArray);
    33 
    34             return indexList;
    35         }
    View Code

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

     
  • 相关阅读:
    spring scope 属性的取值
    DefaultTransactionStatus源码
    Spring事务管理接口PlatformTransactionManager的实现类DataSourceTransactionManager
    Spring 框架简介
    PL/SQL游标
    [BC冠军赛(online)]小结
    [hdu5164]ac自动机
    [hdu2222]ac自动机(模板)
    上浮法或漂浮法
    [hdu5213]容斥原理+莫队算法
  • 原文地址:https://www.cnblogs.com/answer-yj/p/11231247.html
Copyright © 2011-2022 走看看