根據平面的多邊形點的資料,生成簡單的立體網格
- 1.分析
- 2.用耳切法把多邊形三角化
- 3.構造墻壁
最近需要一個功能,就是根據給定的多邊形頂點(按照順時針或者逆時針這樣的順序),然后生成一個具有高度,投影與多邊形一樣的一個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生成,效果就如下了,
手機上播放可能看到的視頻不對,可以到原視頻觀看,
(當輸入的多邊形是一個不正確的形狀的時候,三角計算會為空,然后后面就會報錯,加一個檢測其實就行了)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/241003.html
標籤:其他
