关于Unity3D动态生成连续性网格几何体总结【最终章】(高性能篇)

关于Unity3D动态生成连续性网格几何体总结【最终章】(高性能篇)


前言

前面部分已经对动态生成连续性网格进行了单独的简绍。包含模型模板、模型动态生成算法、贴图的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的进一步优化,以达到大大减少耗时的目的。

总结

以上就是今天要讲的内容,本文仅仅简单介绍了高性能方案,随文会附送下载链接。

项目工程下载链接:https://gitee.com/Emerson92/auto-create-tunnel.git

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值