关于Unity3D动态生成连续性网格几何体总结【最终章】(高性能篇)
文章目录
- 关于Unity3D动态生成连续性网格几何体总结【最终章】(高性能篇)
- 前言
- 一、高性能生成方案
- 二、具体实现细节
- 1.多线程划分结构
- 2.代码执行主体
- 总结
前言
前面部分已经对动态生成连续性网格进行了单独的简绍。包含模型模板、模型动态生成算法、贴图的UV值、以及曲线曲率值等等。本篇将进一步对算法与生成逻辑进行更新优化,采用更加高性能的方案来动态生成连续性模型。
一、高性能生成方案
为了实现高性能生成动态模型的算法,我们需要借助Unity提供的JobSystem多线程与Burst这两个工具,借助多线程并发计算,用于节省算法计算的耗时,再通过Burst进行底层优化,可以大大降低生成耗时。例如,我们要生成一个接近30w三角面的隧道模型,普通模式将会耗时大约46毫秒,但是使用高性能的生成动态模型方式只需要仅仅的25毫秒,整体效率提升了1倍多。
普通模式
高性能模式
二、具体实现细节
1.多线程划分结构
/// <summary>
/// 多线程计算网格顶点位置数据
/// </summary>
[BurstCompile]
public struct MeshVerticesCaculate : IJobParallelForBatch
{
/// <summary>
/// 路径点集合
/// </summary>
[ReadOnly]
public NativeArray<float3> PathPointArray;
/// <summary>
/// 曲线点集合
/// </summary>
[ReadOnly]
public NativeArray<float3> BeizerPointArray;
/// <summary>
/// 模型模板顶点结合
/// </summary>
[ReadOnly]
public NativeArray<float3> TempletVerticeArray;
/// <summary>
/// 细分数量
/// </summary>
[ReadOnly]
public int SegmentCount;
/// <summary>
/// 固定向量s
/// </summary>
[ReadOnly]
public float3 DIRECTIONUP;
/// <summary>
/// U总长度
/// </summary>
[ReadOnly]
public float UTotalLength;
/// <summary>
/// 网格的顶点集合
/// </summary>
[NativeDisableParallelForRestriction]
[WriteOnly]
public NativeArray<float3> VeticesArray;
/// <summary>
/// 网格的法线集合
/// </summary>
[NativeDisableParallelForRestriction]
[WriteOnly]
public NativeArray<float3> NormalArray;
/// <summary>
/// 网格的UV集合
/// </summary>
[NativeDisableParallelForRestriction]
[WriteOnly]
public NativeArray<float2> UVArray;
public void Execute(int startIndex, int count)
{
int templetVerticeCount = TempletVerticeArray.Length;
int indexCount = count + startIndex;
for (int pathIndex = startIndex; pathIndex < indexCount; pathIndex++)
{
int nextPathIndex = pathIndex + 1;
if (nextPathIndex >= PathPointArray.Length) break;
float3 curNode = PathPointArray[pathIndex];
float3 nextNode = PathPointArray[nextPathIndex];
float3 bezierNode = BeizerPointArray[pathIndex];
for (int segmentIndex = pathIndex == 0 ? 0 : 1; segmentIndex < SegmentCount; segmentIndex++)
{
float t = segmentIndex * 1.0f / (SegmentCount - 1);
float3 segmentPoint = BeizerUilityTools.BezierCurve(curNode, nextNode, bezierNode, t);
float3 gradient = BeizerUilityTools.BezierCurveGradient(curNode, nextNode, bezierNode, t);
float tempLength = 0;
for (int verticesIndex = 0; verticesIndex < templetVerticeCount; verticesIndex++)
{
int tempVerticeCount = (pathIndex * (SegmentCount - 1) + segmentIndex) * templetVerticeCount + verticesIndex + 1;
float3 localPos = TempletVerticeArray[verticesIndex];
float3 worldPos = localToWorld(localPos, segmentPoint, gradient, DIRECTIONUP);
VeticesArray[tempVerticeCount] = worldPos;
NormalArray[tempVerticeCount] = math.normalize(TempletVerticeArray[verticesIndex] - segmentPoint);
if (verticesIndex > 0) tempLength += Vector3.Distance(TempletVerticeArray[verticesIndex], TempletVerticeArray[verticesIndex - 1]);
UVArray[tempVerticeCount] = new Vector2(tempLength / UTotalLength, 0);
}
}
}
}
}
[BurstCompile]
public struct BeizerCurveLength : IJob
{
/// <summary>
/// 路径点集合
/// </summary>
[ReadOnly]
public NativeArray<float3> PathPointArray;
/// <summary>
/// 曲线点集合
/// </summary>
[ReadOnly]
public NativeArray<float3> BeizerPointArray;
/// <summary>
/// 顶点总数
/// </summary>
[ReadOnly]
public int PathCount;
/// <summary>
/// 贝塞尔曲线总长度
/// </summary>
public NativeArray<float> BeizerTotalLength;
public void Execute()
{
for (int pathIndex = 0; pathIndex < PathCount; pathIndex++)
{
Vector3 curNode = PathPointArray[pathIndex];
Vector3 nextNode = PathPointArray[pathIndex + 1];
Vector3 bezierNode = BeizerPointArray[pathIndex];
float length = BeizerUilityTools.BezierLength(curNode, nextNode, bezierNode,50);
BeizerTotalLength[0] = BeizerTotalLength[0] + length;
}
}
}
/// <summary>
/// 计算UV值中V值的总长度
/// </summary>
[BurstCompile]
public struct MeshVTotalLength : IJobParallelFor
{
/// <summary>
/// 模型模板顶点结合
/// </summary>
[ReadOnly]
public NativeArray<float3> TempletVerticeArray;
/// <summary>
/// 网格的顶点集合
/// </summary>
[NativeDisableParallelForRestriction]
[ReadOnly]
public NativeArray<float3> VeticesArray;
/// <summary>
/// 顶点数上限
/// </summary>
[ReadOnly]
public int CurrentVerticeCount;
/// <summary>
/// 网格的V值的顶点集合
/// </summary>
[NativeDisableParallelForRestriction]
[WriteOnly]
public NativeArray<float> VTotalLength;
/// <summary>
/// 每个顶点的距离集合
/// </summary>
[NativeDisableParallelForRestriction]
[WriteOnly]
public NativeArray<float> EachVDistanceLength;
public void Execute(int templteIndex)
{
//计算V值方向的距离总长度
float totalDistance = 0;
EachVDistanceLength[templteIndex] = 0;
for (int vIndex = templteIndex; vIndex < CurrentVerticeCount; vIndex += TempletVerticeArray.Length)
{
int nextIndex = vIndex + TempletVerticeArray.Length;
if (nextIndex > (CurrentVerticeCount - 1)) break;
float3 curPoint = VeticesArray[vIndex];
float3 nextPoint = VeticesArray[nextIndex];
totalDistance += CaculateSqrDistance(curPoint, nextPoint);
EachVDistanceLength[nextIndex] = totalDistance;
}
VTotalLength[templteIndex] = totalDistance;
}
}
/// <summary>
/// 动态网格UV值计算
/// </summary>
[BurstCompile]
public struct MeshUVCaculate : IJobParallelForBatch
{
/// <summary>
/// 网格的顶点集合
/// </summary>
[NativeDisableParallelForRestriction]
[ReadOnly]
public NativeArray<float3> VeticesArray;
/// <summary>
/// 网格的V值的顶点集合
/// </summary>
[ReadOnly]
public NativeArray<float> VTotalLength;
/// <summary>
/// 每个顶点的距离集合
/// </summary>
[NativeDisableParallelForRestriction]
[ReadOnly]
public NativeArray<float> EachVDistanceLength;
/// <summary>
/// 网格模板顶点总数
/// </summary>
[ReadOnly]
public int TemplteVerticesCount;
/// <summary>
/// 网格的UV集合
/// </summary>
[NativeDisableParallelForRestriction]
public NativeArray<float2> UVArray;
public void Execute(int startIndex, int count)
{
for (int verticeIndex = startIndex; verticeIndex < (startIndex + count - 1); verticeIndex++)
{
int index = verticeIndex % TemplteVerticesCount;
float t = EachVDistanceLength[verticeIndex] / VTotalLength[index];
UVArray[verticeIndex] = new float2(UVArray[verticeIndex].x, t);
}
}
}
/// <summary>
/// 动态网格三角面计算
/// </summary>
[BurstCompile]
public struct MeshTriangleCaculate : IJobParallelFor
{
/// <summary>
/// 顶点总数
/// </summary>
[ReadOnly]
public int TemplteVerticesCount;
/// <summary>
/// 三角形序号集合
/// </summary>
[NativeDisableParallelForRestriction]
[WriteOnly]
public NativeArray<int> TriangleArray;
public void Execute(int pathIndex)
{
for (int pointIndex = 0; pointIndex < TemplteVerticesCount - 1; pointIndex++)
{
int triangleIndex = TemplteVerticesCount * pathIndex * 6 + pointIndex * 6;
int currentOne = TemplteVerticesCount * pathIndex + pointIndex;
int currentTwo = TemplteVerticesCount * pathIndex + (pointIndex + 1) % (TemplteVerticesCount);
int nextOne = TemplteVerticesCount * (pathIndex + 1) + pointIndex;
int nextTwo = TemplteVerticesCount * (pathIndex + 1) + (pointIndex + 1) % (TemplteVerticesCount);
TriangleArray[triangleIndex] = currentOne;
TriangleArray[triangleIndex + 1] = currentTwo;
TriangleArray[triangleIndex + 2] = nextOne;
TriangleArray[triangleIndex + 3] = nextOne;
TriangleArray[triangleIndex + 4] = currentTwo;
TriangleArray[triangleIndex + 5] = nextTwo;
}
}
}
多线程结构
整体结构主要划分MeshVerticesCaculate 、MeshUVCaculate 、MeshTriangleCaculate 这几个运算多线程结构体,并且他们都是继承于IJobParallelFor,都有多并发执行能力。主要功能是分别计算顶点的位置,网格的UV值,以及网格的三角面的顺序。
2.代码执行主体
/// <summary>
/// 多线程版本生成动态连续网格
/// </summary>
/// <param name="TemplteMesh"></param>
/// <param name="pathList"></param>
/// <param name="beizerList"></param>
/// <param name="finishCallback"></param>
/// <param name="textrueType"></param>
/// <param name="segment"></param>
public static void DynamicSpwanModelWithHighPerformance(TempletObjectBase TemplteMesh, List<Vector3> pathList, List<Vector3> beizerList, Action<MeshPlus, Material[], Vector2> finishCallback, TextureType textrueType = TextureType.Texture, int segment = 10)
{
HighPerformanceMeshTemplet meshTemplet = HighEffectParseModelTemplet(TemplteMesh);
NativeArray<float3> pathNatvieArray;
NativeArray<float3> beizerNativeArray;
if (!TranslationPathListAndBeizerList(pathList, beizerList, out pathNatvieArray, out beizerNativeArray))
{
Debug.LogError("路径点集合与曲线集合不能空!");
return;
}
int templteVerticesCount = meshTemplet.Vertices.Length;
int templteTriangleCapcity = meshTemplet.Triangels.Length;
int pathCount = pathList.Count - 1;
int vCapacity = templteVerticesCount * (pathCount * (segment - 1) + 3);
int tCapacity = templteVerticesCount * (pathCount * (segment - 2) + pathCount) * 6;
int vCurrentCapacity = templteVerticesCount * (pathCount * (segment - 1) + 1);
int sCapacity = 2;
int pathNodeCount = pathCount + (pathCount) * (segment - 2);
Material surfaceMat = TemplteMesh.SurfaceMaterial;
Material sectionMat = TemplteMesh.SectionMaterial;
float UTotalLength = BeizerUilityTools.GetTotalLength(meshTemplet.Vertices);
MeshShell tunnelShell = new MeshShell(vCapacity, tCapacity, sCapacity);
NativeArray<float3> verticesNativeArray = new NativeArray<float3>(vCapacity, Allocator.TempJob);
NativeArray<float3> normalNativeArray = new NativeArray<float3>(vCapacity, Allocator.TempJob);
NativeArray<float2> UVNativeArray = new NativeArray<float2>(vCapacity, Allocator.TempJob);
NativeArray<int> triangleNativeArray = new NativeArray<int>(tCapacity, Allocator.TempJob);
NativeArray<float> VTotalLengthNativeArray = new NativeArray<float>(templteVerticesCount, Allocator.TempJob);
NativeArray<float> EachVDistanceLength = new NativeArray<float>(vCurrentCapacity, Allocator.TempJob);
NativeArray<float> bezierLength = new NativeArray<float>(1, Allocator.TempJob);
MeshVerticesCaculate meshVerticesCaculateJob = new MeshVerticesCaculate()
{
PathPointArray = pathNatvieArray,
BeizerPointArray = beizerNativeArray,
TempletVerticeArray = meshTemplet.Vertices,
SegmentCount = segment,
UTotalLength = UTotalLength,
VeticesArray = verticesNativeArray,
NormalArray = normalNativeArray,
UVArray = UVNativeArray,
DIRECTIONUP = new float3(0,1,0)
};
BeizerCurveLength curveLengthJob = new BeizerCurveLength()
{
PathPointArray = pathNatvieArray,
BeizerPointArray = beizerNativeArray,
PathCount = pathCount,
BeizerTotalLength = bezierLength
};
MeshVTotalLength VTotalLengthJob = new MeshVTotalLength()
{
TempletVerticeArray = meshTemplet.Vertices,
VeticesArray = verticesNativeArray,
CurrentVerticeCount = vCurrentCapacity,
VTotalLength = VTotalLengthNativeArray,
EachVDistanceLength = EachVDistanceLength
};
MeshUVCaculate UVCaculateJob = new MeshUVCaculate()
{
VeticesArray = verticesNativeArray,
VTotalLength = VTotalLengthNativeArray,
TemplteVerticesCount = templteVerticesCount,
UVArray = UVNativeArray,
EachVDistanceLength = EachVDistanceLength
};
MeshTriangleCaculate meshTriangleCaculateJob = new MeshTriangleCaculate()
{
TemplteVerticesCount = templteVerticesCount,
TriangleArray = triangleNativeArray,
};
JobHandle meshVerticeHandle = meshVerticesCaculateJob.ScheduleBatch(pathNatvieArray.Length, pathNatvieArray.Length > 4 ? pathNatvieArray.Length / 4 : pathNatvieArray.Length);
JobHandle cureLengthHandle = curveLengthJob.Schedule(meshVerticeHandle);
JobHandle VToatalLengthHandle = VTotalLengthJob.Schedule(templteVerticesCount, templteVerticesCount > 4 ? templteVerticesCount / 4 : templteVerticesCount, meshVerticeHandle);
JobHandle dependencies = JobHandle.CombineDependencies(meshVerticeHandle, VToatalLengthHandle);
JobHandle UVCaculateHandle = UVCaculateJob.ScheduleBatch(vCurrentCapacity, vCurrentCapacity > 4 ? vCurrentCapacity / 4 : vCurrentCapacity, dependencies);
JobHandle meshTriangleHandle = meshTriangleCaculateJob.Schedule(pathNodeCount, pathNodeCount > 4 ? pathNodeCount / 4 : pathNodeCount);
JobHandle.CompleteAll(ref UVCaculateHandle, ref meshTriangleHandle, ref cureLengthHandle);
//Debug.Log("<color=green>"+ curveLengthJob.BeizerTotalLength[0]+" vs "+UTotalLength+"</color>");
tunnelShell.AddNativeArray(verticesNativeArray, normalNativeArray, UVNativeArray, triangleNativeArray);
List<int> forwardSubMeshTriangels = GetSubTriangelsList(vCurrentCapacity, meshTemplet, templteTriangleCapcity);
Vector3 firstGradient = -BeizerUilityTools.BezierCurveGradient(pathList[0], pathList[1], beizerList[0], 0);
AddMaskTemplet(meshTemplet, tunnelShell, pathList[0], firstGradient, ref vCurrentCapacity);
tunnelShell.AddSubTriangles(0, forwardSubMeshTriangels);
List<int> backSubMeshTriangels = GetSubTriangelsList(vCurrentCapacity, meshTemplet, templteTriangleCapcity);
Vector3 backGradient = BeizerUilityTools.BezierCurveGradient(pathList[pathList.Count - 2], pathList[pathList.Count - 1], beizerList[pathList.Count - 2], 1);
AddMaskTemplet(meshTemplet, tunnelShell, pathList[pathList.Count - 1], backGradient, ref vCurrentCapacity);
tunnelShell.AddSubTriangles(1, backSubMeshTriangels);
MeshPlus mesh = tunnelShell.GetMesh(meshTemplet, segment);
Material[] materials = new Material[] {
surfaceMat,
sectionMat,
sectionMat
};
Vector2 textureTile = Vector2.one;
if (textrueType == TextureType.Tile)
textureTile = new Vector2(UTotalLength, curveLengthJob.BeizerTotalLength[0]);
else
textureTile = new Vector2(1, curveLengthJob.BeizerTotalLength[0] / UTotalLength);
if (finishCallback != null)
finishCallback(mesh, materials, textureTile);
//释放内存
meshTemplet.Dispose();
pathNatvieArray.Dispose();
beizerNativeArray.Dispose();
verticesNativeArray.Dispose();
normalNativeArray.Dispose();
UVNativeArray.Dispose();
VTotalLengthNativeArray.Dispose();
EachVDistanceLength.Dispose();
triangleNativeArray.Dispose();
bezierLength.Dispose();
}
除了UV的计算需要依赖顶点位置计算与拉伸方向总长度计算,其余计算都是分布并发执行的。最后通过Burst的进一步优化,以达到大大减少耗时的目的。