Unity中根据平面的多边形点的数据,生成简单的立体网格

根据平面的多边形点的数据,生成简单的立体网格

最近需要一个功能,就是根据给定的多边形顶点(按照顺时针或者逆时针这样的顺序),然后生成一个具有高度,投影与多边形一样的一个Mesh。
效果如下,比如给定以下的一些顶点的位置,
按顺序给定顶点顺序
生成一个这样的Mesh。
在这里插入图片描述


1.分析

首先肯定就是要生成mesh的这样一个问题。mesh的主要内容包括顶点、三角形、法线(uv的话我用不上,所以就不考虑了,而且感觉不好弄)。
顶点,这个很简单,地面的一圈顶点是给定好的,然后上层的顶点只要加上一个高度就能算出来。
三角形,这个尤为不好弄,如果是一个规规矩矩的矩形,三角形的关系很容易就算出来了。但是问题现在给的多边形可以是任意的。我是真的想不到什么方法。然后经过一番查找,终于找到了我想要的东西——多边形三角化(wiki)
在这里插入图片描述
里面提到了很多方法,然后在github上找了一下,有同志已经把耳切法的算法实现了,并且也是在Unity里面——耳切法(github)。那我就不客气了。虽然耳切法不适用于带孔的多边形,但是能够处理凹凸多边形对我来说已经足够了。
法线,法线其实也还好,直接用向量差乘算出来就好了。

2.用耳切法把多边形三角化

具体算法我暂时没有去深究,现在的先试着用前面yiwei151写好的代码生成一下。主要的东西就是在Triangulation.cs中。
仿照他原来的测试代码我也写了一个生成mesh的代码。
这个函数是传入一个多边形顶点的List (按照我的需求多边形顶点给的是逆时针为顺序,并且是一个位于xOz平面上的多边形,所以比较轴我也设置的Y轴),然后返回对应三角形的索引。
如果不把三角形反转的话,是会算的z轴的反向为正向,与我的需求不服,所以我这里默认反转以下三角形的顺序。

        public static int[] PolygonToTriangles(List<Vector3> polygonVertex, bool filpTriangle = true)
        {
            // 存结果的列表
            var resultVertexes = new List<Vector3>();
            
            // 转化器
            var triangulation = new Triangulation(polygonVertex);
            
            // 设置比较轴
            triangulation.SetCompareAxle(CompareAxle.Y);

            // 取得三角形
            var triangles = triangulation.GetTriangles();

            // 反转三角形
            int tempInt;
            if (filpTriangle)
            {
                for (int i = 0; i < triangles.Length; i+=3)
                {
                    // 交换两个顶点的顺序令其三角形的顺序相反
                    tempInt = triangles[i + 1];
                    triangles[i + 1] = triangles[i + 2];
                    triangles[i + 2] = tempInt;
                }
            }
            
            return triangles;
        }

再调用上面的算法,顺便把mesh也一并生成了

        public static Mesh GenPolyMesh(List<Vector3> polyVerts)
        {
            var resMesh = new Mesh();

            // 把多边形数据转换为三角形
            var triangles = PolygonToTriangles(polyVerts);

            // 设置顶点
            resMesh.vertices = polyVerts.ToArray();
            // 设置三角形
            resMesh.triangles = triangles;
            // 计算法线
            resMesh.RecalculateNormals();
            
            return resMesh;
        }

然后也学着yiwei151用物体的坐标计算几个顶点位置,方便调试。
写了个测试脚本。

using System.Collections.Generic;
using UnityEngine;
using SimpleObject;
using UnityEngine.PlayerLoop;

namespace Test
{
    public class TestGenerateSimpleMesh : MonoBehaviour
    {
        // 是否实时更新mesh
        public bool updateMesh = false;
        // 生成物体的材质
        public Material mat;
        // 生成的高度
        public float height = 3;
        // 用物体的坐标来代替点
        public List<Transform> tList;
        
        private GameObject _targetObj;
        private MeshFilter _targetMeshFilter;
        private List<Vector3> posList = new List<Vector3>();

        private void Start()
        {
            // 获取多边形顶点
            for (int i = 0; i < tList.Count; i++)
                posList.Add(tList[i].position);
        
            // 创建物体
            if(_targetObj) Destroy(_targetObj);
            _targetObj = new GameObject("Target");
        
            // 网格 
            _targetMeshFilter = _targetObj.AddComponent<MeshFilter>();
            _targetObj.AddComponent<MeshRenderer>();
            _targetMeshFilter.mesh = MeshGenerator.GenPolyMesh(posList);
            //_targetMeshFilter.mesh = MeshGenerator.GenBuildingMesh(posList,height);

            // 材质
            if(mat)
                _targetObj.GetComponent<Renderer>().material = mat;
            else
                _targetObj.GetComponent<Renderer>().material = new Material(Shader.Find("Diffuse"));

        }

        private void FixedUpdate()
        {
            if (updateMesh)
            {
                // 获取多边形顶点
                posList.Clear();
                for (int i = 0; i < tList.Count; i++)
                    posList.Add(tList[i].position);
                
                _targetMeshFilter.mesh = MeshGenerator.GenPolyMesh(posList);
                //_targetMeshFilter.mesh = MeshGenerator.GenBuildingMesh(posList,height);
            }
        }
    }
}

场景里稍微弄成这样,运行就能看见结果了。
在这里插入图片描述
测试得这个用法没问题。

3.构造墙壁

这边先说一下我的思路,假设给的是一个四边形的话(四边形比较简单,边数更多的也是一样的道理)。四边形的顶点编号分别是0、1、2、3,然后根据设定的高度,算出顶层的顶点4、5、6、7。然后应该如下图所示。
在这里插入图片描述
现在我需要做的就是以此把垂直的四个面构造出来,构造一个四边形至少需要两个三角形(这个三角形怎么分就看你怎么写了,其实都是可以的)。所以这四个墙壁应该要有8个三角面组成。
我打算按照如下顺序遍历一圈依次生成四个面。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
遍历顶点0、1、2、3,在到点0的时候计算面0154点1的时候计算面1265点2的时候计算面2376点3的时候计算面3047

在计算一个面的时候,把这个面当作是面向自己的。然后三角形的顺序是顺时针为正向。
在这里插入图片描述

在这里插入图片描述
如果想要这个面为正面,需要存的数据应该是ADCACB(字母要换成顶点对应的索引),这样子才是一个面向自己的面。

法线的计算的话,这里同一个面的四个顶点的法线都是相同方向的,所以只要算一次就好了,算法就用一个向量的叉乘就好了,根据右手定则可以算出垂直于两个向量的向量。
在这里插入图片描述
如用向量12叉乘向量15就可以得到点1、2、5、6的法线了。

实现成具体代码如下。

public static Mesh GenBuildingMesh(List<Vector3> polyVerts, float height)
        {
            // 结果Mesh
            var resMesh = new Mesh();
            
            // 所有顶点的集合
            var allVerts = new List<Vector3>();
            var upperVerts = new List<Vector3>();

            // 所有法线的集合
            var allNormals = new List<Vector3>();
            var upperNormals = new List<Vector3>();

            // 所有三角形的集合
            var allTriangles = new List<int>();
            var upperTriangles = new List<int>();

            // 计算顶部的顶点位置
            for (var i = 0; i < polyVerts.Count; i++)
                upperVerts.Add(polyVerts[i] + Vector3.up * height);
            
            // 计算墙壁的顶点、法线、三角
            var wallVerts = new List<Vector3>();
            var wallNormals = new List<Vector3>();
            var wallTriangles = new List<int>();
            // 遍历一边多边形的所有顶点
            var counter = 0;
            for (var i = 0; i < polyVerts.Count; i++)
            {
                // 先添加这个面的四个顶点(顺时针)
                wallVerts.Add(polyVerts[i]);
                wallVerts.Add(upperVerts[i]);
                wallVerts.Add(upperVerts[(i + 1) % polyVerts.Count]);
                wallVerts.Add(polyVerts[(i + 1) % polyVerts.Count]);
                
                // 利用两个向量差乘计算法线
                var normal = Vector3.Cross(upperVerts[i] - polyVerts[i], polyVerts[(i + 1) % polyVerts.Count] - polyVerts[i]).normalized;
                wallNormals.Add(normal);
                wallNormals.Add(normal);
                wallNormals.Add(normal);
                wallNormals.Add(normal);
                
                // 计算三角
                // 第一个三角
                wallTriangles.Add(counter);
                wallTriangles.Add(counter + 1);
                wallTriangles.Add(counter + 2);
                // 第二个三角
                wallTriangles.Add(counter);
                wallTriangles.Add(counter + 2);
                wallTriangles.Add(counter + 3);

                // 自增
                counter += 4;
            }
            
            // 计算顶部的顶点、法线、三角
            // 法线
            for (var i = 0; i < upperVerts.Count; i++)
                upperNormals.Add(Vector3.up);
            // 三角
            upperTriangles = PolygonToTriangles(upperVerts).ToList();
            // 延后三角的索引
            for (var i = 0; i < upperTriangles.Count; i++)
                upperTriangles[i] += wallVerts.Count;
            
            // 合并数据
            allVerts.AddRange(wallVerts);
            allVerts.AddRange(upperVerts);
            allNormals.AddRange(wallNormals);
            allNormals.AddRange(upperNormals);
            allTriangles.AddRange(wallTriangles);
            allTriangles.AddRange(upperTriangles);
            
            // 设置顶点
            resMesh.vertices = allVerts.ToArray();
            // 设置三角形 
            resMesh.triangles = allTriangles.ToArray();
            // 计算法线
            resMesh.normals = allNormals.ToArray();
            
            return resMesh;
        }

(因为我不需要底面的,所以就没有生成底面需要信息,要生成的话按照顶面照猫画虎的生成一遍,记得反转三角形就是了。)
有一个需要注意的点,就是三角形存的是顶点的索引,而我这里的顶面和墙面是分开计算三角形的(也就是他们都是以0为开始的索引)。这样子直接把两个三角形的列表加起来肯定是错误的结果。
因为我是把顶部的顶点加载墙面顶点的后面,所以顶部的三角形的所有索引值都应该加上墙面顶点的个数,这就是代码里面// 延后三角的索引所做的事情。

是否有人有疑问,为什么每一个四边面创建三角形的时候都要新建四个新的顶点呢?这样子在同一个位置不就有好几个重复的顶点了吗?
这是因为我需要的是一个硬边的mesh,而这样就需要每个点的法线都是垂直于这个面的。
然而一个顶点只有一个法线,如果每个角都公用一个顶点的话,那在渲染的时候他周围的法线都会被插值计算为一个平滑的法线。所以如果共用顶点的话,光照看起来会很奇怪,如下图。
在这里插入图片描述
下图才是我想要的效果。
在这里插入图片描述
而且unity自带的cube也是24个顶点,6个面中每个面都是四个顶点,不是8个。虽然这样子在三个面的交点会有三个位置一样的点,但是他们的法线各不相同。
在blender里面弄了个立方体也是24个顶点。
在这里插入图片描述
之前就是没考虑到这一个,先写了一个生成方法是共用顶点的,但是看了效果才知道不行。
但是我还是把代码留着了,毕竟这个mesh用来做碰撞体也许会更省性能?(我猜的,毕竟顶点更少,但是我不知道)计算共用顶点的mesh代码如下。

public static Mesh GenBuildingMeshSimple(List<Vector3> polyVerts, float height)
        {
            var resMesh = new Mesh();

            // verts作为存放最终顶点的容器
            var verts = polyVerts;
            
            // 计算顶层顶点
            var upperVerts = new List<Vector3>();
            for (var i = 0; i < polyVerts.Count; i++)
            {
                upperVerts.Add(polyVerts[i] + Vector3.up * height);
            }

            // 计算顶层三角形
            var upperTriangle = PolygonToTriangles(upperVerts);
            // 给每一个顶点索引加上长度
            /* 因为加上顶层顶点之后,相应的索引应该延后 */
            for (var i = 0; i < upperTriangle.Length; i++)
            {
                upperTriangle[i] += polyVerts.Count;
            }
            
            // 计算墙壁的三角形
            var wallTriangle = new List<int>();
            int j;
            for (var i = 0; i < verts.Count; i++)
            {
                // 四边形中第一个三角形
                wallTriangle.Add(i);    
                wallTriangle.Add(i + verts.Count);
                wallTriangle.Add(i + 1);

                // 计算四边形中第二个三角形
                j = (i + 1) % verts.Count;
                wallTriangle.Add(j);    
                wallTriangle.Add(j + verts.Count - 1);
                wallTriangle.Add(j + verts.Count);
            }
            
            // 将顶点相加
            verts.AddRange(upperVerts);
            // 将三角相加
            var triangleList = wallTriangle.ToList();
            triangleList.AddRange(upperTriangle);

            // 设置顶点
            resMesh.vertices = verts.ToArray();
            // 设置三角形 
            resMesh.triangles = triangleList.ToArray();
            // 计算法线
            resMesh.RecalculateNormals();
            
            return resMesh;
        }

把前面测试生成多边面的代码中的mesh生成改成用GenBuildingMesh生成,效果就如下了。
手机上播放可能看到的视频不对,可以到原视频观看。

(当输入的多边形是一个不正确的形状的时候,三角计算会为空,然后后面就会报错,加一个检测其实就行了)

Unity可以使用Mesh生成多边形,可以通过给定一组的坐标来创建一个多边形。 首先,需要定义一个的数组,这些将用于构建多边形。假设我们有一个名为“points”的Vector3数组,其包含了多边形的各个顶的坐标。 接着,我们需要为多边形创建一个Mesh,这可以通过使用新建一个GameObject并添加一个MeshFilter和一个MeshRenderer来完成。然后,我们可以将数组传递给Mesh的vertices属性,以指定多边形的顶。 接下来,我们需要定义多边形三角形。由于多边形的每个面都是由三角形组成的,因此我们需要定义哪些将形成每个三角形。这可以通过将三角形的顶索引指定给Mesh的triangles属性来完成。对于简单的凸多边形,可以使用以下代码: ``` Mesh mesh = new Mesh(); mesh.vertices = points; int[] triangles = new int[(points.Length - 2) * 3]; for (int i = 0; i < triangles.Length; i += 3) { triangles[i] = 0; triangles[i + 1] = i / 3 + 1; triangles[i + 2] = i / 3 + 2; } mesh.triangles = triangles; // Assign the mesh to the MeshFilter and MeshRenderer components MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>(); MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer>(); meshFilter.mesh = mesh; ``` 这段代码假设数组的第一个将作为多边形。然后,对于每个顶,我们使用该和前一个和下一个形成的三角形来填充三角形数组。最后,我们将三角形数组指定给Mesh的triangles属性。 注意,这段代码仅适用于简单的凸多边形,对于复杂的多边形,可能需要使用其他算法来计算三角形。
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值