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生成,效果就如下了。
手机上播放可能看到的视频不对,可以到原视频观看。

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

  • 17
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 34
    评论
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、付费专栏及课程。

余额充值