关于Unity3D动态生成连续性网格几何体总结【第二部分】(算法篇)
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
关于Unity3D动态生成连续性网格几何体总结【第二部分】
前言
书接上回,第一部分阐述动态生成连续性模型的具体样例,以及生成模型的具体模板数据结构体和相关基础概念。本篇第二部分将会着重讲解动态生成连续性网格的具体逻辑与相关算法内容。本篇可以再第一部分的基础上,动态生成连续性网格,但是由于本篇没有涉及路径的平滑算法内容,因此最终呈现的相关效果会有折扣。但是缺失的部分,我会再后续内容慢慢补全。
一、路径点的构成
一组路径点集合是构成生成连续性网格的必要条件。它需要由空间中一组连续的点构成,且点与点之间,只有一对一的连接关系。
二、路径生成算法
1.路径点集合细分
为了保证路径点之间细节更丰富,且保证路径点线之间更加平顺的过渡,我们将会对路径点进行进一步的细分操作。
如图所示,我们将会从左往右为点进行排序,图中红色竖线代表路径节点,红色节点之间的黑色连线,就是路径,而路径节点之间的绿色小竖线就是我们具体细分节点。图中草图示例总共有5个主节点,每个节点之间总共有3个细分节点。原本5个节点的路径,通过一路细分以后,总共节点数量变成了17个节点(细分粒度*细分路径数+主节点数),即 3 * 4 + 5。当然具体细分粒度要根据使用情况与表现细节来确定,
2.路径与模型模板结合
现在我们再说有的路径节点以及细分节点处放置上模型模板,这样整体上就会出现由模型模板构成的连续整体模型。但是这样的模型确实不完整且没有关联的,因此我们还需要通过固定的算法,把路径节点处与细分节点处的模型模板关联起来。那么问题来了,该怎样进行关联呢。还记得我们在该系列第一部分的时候,我们对已经存在的模型模板文件进行进一步排序么,把无序的定点顺序,组合成有一定规律的定点顺序么。目的就是在关联模型模板文件而使用。
如图所示,模板之间需要重新构建三角面,图中使用的模型模板总共43个顶点。那假设我们路径上总共有两个节点即总共有两个模型模板。按照顶点顺序排序的话,第一个模型模板的顶点顺序区间为0-43,那么第二模型模板的顶点顺序区间为43-86。并且unity是采用的左手坐标系,假设我们要构建顶点序号为5、6、49的三角面,那么按照三角面的构建顺序应该为5-6-49。到这里我们模型模板之间的连接就构建完成了。
三角面构建的顶点公式为:
第一个顶点,
第二个顶点 = 第一个顶点 + 1 ,
第三个顶点 = 第一个顶点 + 模板顶点总数
构件路径Mesh网格的顶点位置:
//伪代码
int meshVerticeIndex = 0;//顶点序号
for (int pathIndex = 0; pathIndex < pathCount; pathIndex++)
{
Vector3 curNode = pathList[pathIndex];//前一个路径点
Vector3 nextNode = pathList[pathIndex + 1];//后一个点路径点
//路径之间生成细分节点
for (int segmentIndex = pathIndex == 0 ? 0 : 1; segmentIndex < refreshMesh.Segment; segmentIndex++)
{
Vector3 segmentPoint = GetSegmentPoint(curNode, nextNode);//获取细分节点位置
//templteVerticesCount 为模型模板的顶点总数
for (int verticesIndex = 0; verticesIndex < templteVerticesCount; verticesIndex++)
{
Vector3 localPos = templeteVertice[verticesIndex];//获取模型模板的顶点坐标
Vector3 worldPos = localToWorld(localPos, segmentPoint);//转化模型坐标为世界坐标系
refreshVertice[verticeIndex] = worldPos - Vector3.zero;//生成模型坐标
meshVerticeIndex++;
}
}
}
构建Mesh网格的三角形:
int triangleIndex = 0;//三角形的序号
//对应每个路径点进行遍历,“pathCount + (pathCount) * (segment - 2)” 为路径上的节点总数
for (int pathIndex = 0; pathIndex < (pathCount + (pathCount) * (segment - 2)); pathIndex++)
{
//遍历模型模板顶点
for (int pointIndex = 0; pointIndex < templteVerticesCount; pointIndex++)
{
//前一个节点模型模板的第一个顶点序号
int currentOne = templteVerticesCount * pathIndex + pointIndex;
//前一个节点模型模板的第二个顶点序号
int currentTwo = templteVerticesCount * pathIndex + (pointIndex + 1) % (templteVerticesCount);
//后一个节点模型模板的对应currentOne的顶点序号
int nextOne = templteVerticesCount * (pathIndex + 1) + pointIndex;
//后一个节点模型模板的对应currentTwo的顶点序号
int nextTwo = templteVerticesCount * (pathIndex + 1) + (pointIndex + 1) % (templteVerticesCount);
//记录下当前构成的三角面
tunnelShell.AddTriangles(triangleIndex,currentOne, currentTwo, nextOne);
triangleIndex += 3;
//记录下当前构成的三角面
tunnelShell.AddTriangles(triangleIndex,nextOne, currentTwo, nextTwo);
triangleIndex += 3;
}
}
到此为止整个路径模型的网格就可以自动生成啦!但是有一个小细节很容易被忽视,那就是整个路径模型的前后横截面模型怎么办呢?很简单,下面就简绍如何生成构成横截面模型。
3.生成路径模型的截面
截面模型有几个问题需要提前解决,第一如果把截面模型与路径生成主模型归成一个mesh网格的话,他们的UV值与贴图都会相互影响。第二如果把截面模型单独生成一个mesh网格的话,又不方便网格的位置与旋转变化。那么经过苦苦思索与查找资料以后,终于寻找到一个完美的方案,那就是使用Mesh网格的子网格SubMesh功能。
public sealed class Mesh : Object{
......
public void SetTriangles(List<int> triangles, int submesh);
........
}
下面是生成截面模型的代码:
//伪代码
//生成路径首部截面
List<int> forwardSubMeshTriangels = GetSubTriangelsList(tempVerticeCount, meshTemplet, templteTriangleCapcity);//子网格三角面序号集合
Vector3 firstGradient = -GetGradient(pathList[0], pathList[1]);//获取截面的旋转朝向
AddMaskTemplet(meshTemplet, tunnelShell, pathList[0], firstGradient,ref tempVerticeCount);//添加子网格截面的顶点信息
tunnelShell.AddSubTriangles(0,forwardSubMeshTriangels);//添加序号为0的子网格
//生成路径尾部截面
List<int> backSubMeshTriangels = GetSubTriangelsList(tempVerticeCount, meshTemplet, templteTriangleCapcity);//子网格三角面序号集合
Vector3 backGradient = GetGradient(pathList[pathList.Count - 2], pathList[pathList.Count - 1]);//获取截面的旋转朝向
AddMaskTemplet(meshTemplet, tunnelShell, pathList[pathList.Count - 1], backGradient,ref tempVerticeCount);//添加子网格截面的顶点信息
tunnelShell.AddSubTriangles(1,backSubMeshTriangels);//添加序号为1的子网格
下面是子网格的顶点与三角面的构建主要方法:
/// <summary>
/// 添加子网格顶点信息
/// </summary>
/// <param name="meshTemplet">模型模板对象</param>
/// <param name="tunnelShell">生成Mesh网格的对象</param>
/// <param name="curPosition">节点的世界坐标位置</param>
/// <param name="firstGradient">截面的旋转朝向角度</param>
/// <param name="verticeIndex">顶点序号</param>
private static void AddMaskTemplet(MeshTemplet meshTemplet, MeshShell tunnelShell, Vector3 curPosition, Vector3 firstGradient,ref int verticeIndex)
{
for (int verticesIndex = 0; verticesIndex < meshTemplet.Vertices.Length; verticesIndex++)
{
Vector3 localPos = meshTemplet.Vertices[verticesIndex];
Vector3 worldPos = localToWorld(localPos, curPosition, firstGradient);
//添加定点坐标
tunnelShell.AddVertices(verticeIndex,worldPos);
//添加法线
tunnelShell.AddNormals(verticeIndex,meshTemplet.Normals[verticesIndex]);
//添加UV
tunnelShell.AddUV(verticeIndex,meshTemplet.UV[verticesIndex]);
verticeIndex++;
}
}
/// <summary>
/// 添加子网格的三角面序号信息
/// </summary>
/// <param name="verticeCount">顶点总数</param>
/// <param name="meshTemplet">模型模板对象</param>
/// <param name="tCapacity">集合容积大小</param>
/// <returns></returns>
private static List<int> GetSubTriangelsList(int verticeCount, MeshTemplet meshTemplet, int tCapacity)
{
List<int> subMeshTriangels = new List<int>(tCapacity);
//遍历模型模板的三角序号数量
for (int triangelIndex = 0; triangelIndex < meshTemplet.Triangels.Length; triangelIndex += 3)
{
//三角形的第一个顶点
subMeshTriangels.Add(verticeCount + meshTemplet.Triangels[triangelIndex]);
//三角形的第二个顶点
subMeshTriangels.Add(verticeCount + meshTemplet.Triangels[triangelIndex + 1]);
////三角形的第三个顶点
subMeshTriangels.Add(verticeCount + meshTemplet.Triangels[triangelIndex + 2]);
}
return subMeshTriangels;
}
总结
那么本篇的主要内容就已经完成了,连续性网格的主要生成算法与技巧,在这里就已经完结了,下一个部分,我们将简绍如何生成动态模型的UV与法线信息以及材质相关联的内容。
传送门
上一篇 :关于Unity3D动态生成连续性网格几何体总结【第一部分】(基础篇)
下一篇 :关于Unity3D动态生成连续性网格几何体总结【第三部分】(贴图篇)