在Unity中使用四叉树算法绘制地形

Posted 海洋_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Unity中使用四叉树算法绘制地形相关的知识,希望对你有一定的参考价值。

四叉树算法在游戏中获得了广泛的应用,前几年3D引擎实现的地形绘制大部分都是用四叉树生成的,因为移动端在硬件方面的限制,我们的地形使用的是美术自己制作的地形,对于程序来说省去了不少工作量,但是作为程序开发者尤其是想从事引擎开发的程序员,我们还是要自己实现一遍四叉树算法的,其实网上很多资料都有C++ OpenGL或者C++ DX实现的四叉树地形。很多引擎的地形它也是在四叉树的基础上改进而成,如果我们想优化四叉树,不知道它的算法实现,即看不懂代码何谈优化?所以我们可以尝试自己动手在Unity的基础上实现四叉树算法。
四叉树算法是一个老生常谈的话题了,其实虽然过去了这么多年,作为经典的算法,它的应用还是很广的,比如我们的服务器同步,在某个区域内的玩家是可以同步的,这个区域划分以及区域角色的查找就可以使用四叉树算法,另外我们的动态寻路也可以使用四叉树算法,把地形不断的划分成四叉树的小格子,然后根据格子中填充的数据判断哪些格子是有建筑物?哪些格子没有建筑物,以及格子与格子之间的最短距离计算;再有就是地形的绘制了,四叉树会结合着LOD算法一起使用,四叉树的表述看下图所示:

四叉树算法的实现方式有两种:

  1. 采用盲目搜索,与二叉树的递归遍历类似,可采用后序遍历或前序遍历或中序遍历对其进行搜索某一对象,时间复杂度为O(n)
  2. 根据对象在区域里的位置来搜索,采用分而治之思想,时间复杂度只与四叉树的深度有关。比起盲目搜索,这种搜索在区域里的对象越多时效果越明显
    再介绍一下四叉树实现地形时为了避免出现裂缝,采用四叉树结构,不同级别的分块之间可能会出现裂缝(Cracks),我们采取的解决方式如下所示:
  3. 一般的做法是:主要分为跳点法和加点法。跳点法就是在较高分辨率的分块的边界上跳过一些点不绘制,这样可以保持相邻分块的连续性。加点法就是在较低分辨率的分块边界上新增一些顶点以达到两个分块顶点保持连续的目的;
  4. 另一种方法时采用一种效率更高的消除裂缝的办法,对每个分块的四条边,在现有的顶点的基础上再延伸出一圈,他们和单个分块的边界共享顶点,只是高度值不同,这种延伸出来的一圈叫做“裙子”(Skirts),投影之后只要保证顶点的高度值足够大,两个分块的裙子可以把裂缝遮挡住。当然,这种消除裂缝的方式会增加绘制的三角形绘制数量,但是通过测试发现,对于现在的图形处理器来讲,这种三角形数量的额外增加不会带来多少性能上的下降。
    具体实现方式如下所示:

    本篇博客的实现方式采用的是第一种传统的方法实现,在Unity中实现的地形的四叉树绘制以及LOD算法,生成地形方式很多,比如可以使用高度图用于生成地形,如下所示:

    黑白像素,利用像素的值表示地形的起伏,当然为了省事起见,我们可以自己定义Mesh模拟生成地形,接下来我们先实现四叉树算法。首先定义四叉树节点结构体代码如下:
 class CQuadTreeNode
    
        public CQuadTreeNode mTopLeftNode;
        public CQuadTreeNode mTopRightNode;
        public CQuadTreeNode mBottomRightNode;
        public CQuadTreeNode mBottomLetfNode;

        public bool mbSubdivide;
        public int mIndexX;
        public int mIndexZ;
        public enNodeType mNodeType;

        public CQuadTreeNode(int x, int z)
        
            mIndexX = x;
            mIndexZ = z;
            mbSubdivide = true;
        
    

四叉树算法,网上有很多关于算法的介绍,这里就不给读者介绍了,它有自己的评价公式用于节点的划分,四叉树算法采用的是递归的方式,在Unity中实现的代码如下所示:

public CQuadTreeNode RefineNodeImpl( 
            float x ,
            float z ,
            int curNodeLength ,   //暂时定为节点的个数
            enNodeType nodeType ,
            CQuadTreeNode parentTopNeighborNode ,
            CQuadTreeNode parentRightNeighborNode ,
            CQuadTreeNode parentBottomNeighborNode ,
            CQuadTreeNode parentLeftNeighborNode, 
            Camera viewCamera ,
            Vector3 vectorScale, 
            float desiredResolution,
            float minResolution,
            bool useRoughnessEvalute 
            )
        
            if( null == viewCamera )
            
                Debug.LogError("[RefineNode]View Camera is Null!");
                return null; 
            

            int tX = (int)x;
            int tZ = (int)z;

            CQuadTreeNode qtNode = GetNode(tX, tZ); 
            if( null == qtNode )
            
                Debug.LogError( string.Format("[RefineNode]No Such Node at :0|1",tX,tZ));
                return null ; 
            

            qtNode.mNodeType = nodeType; 

            //评价公式
            ushort nodeHeight = GetTrueHeightAtPoint(qtNode.mIndexX, qtNode.mIndexZ);
            float fViewDistance = Mathf.Sqrt(
                Mathf.Pow(viewCamera.transform.position.x - qtNode.mIndexX * vectorScale.x, 2) +
                Mathf.Pow(viewCamera.transform.position.y - nodeHeight * vectorScale.y, 2) +
                Mathf.Pow(viewCamera.transform.position.z - qtNode.mIndexZ * vectorScale.z, 2)
                  );

            float f = 0; 
            if(useRoughnessEvalute)
            
                float d = (curNodeLength - 1) * vectorScale.x;
                float d2 = mRoughnessData.GetRoughnessValue(tX, tZ);
                float C = minResolution;
                float c = desiredResolution; 
                float fDenominator = vectorScale.y == 0 ?
                   (Mathf.Max(d * Mathf.Max(c, C), 1.0f)) :
                   (d * C * Mathf.Max( c * d2  , 1.0f) );
                f = fViewDistance / fDenominator; 
            
            else
            
                // l / dc < 1
                float d = ( curNodeLength - 1 ) * vectorScale.x;
                float C = Mathf.Max(minResolution, desiredResolution);

                float fDenominator = Mathf.Max(d * C, 1.0f) ;
                f = fViewDistance / fDenominator;
            


            //这里作用是如果预处理之后,还是发生了crack的情况,就强行拆面
            //一个节点是否能够划分
            //1、满足评价
            //2、和相邻的结点不相差两个层级,也就当前结点和父结点的兄弟结点不能相差两个层级
            qtNode.mbSubdivide = f < 1.0f ? true : false;
            switch (qtNode.mNodeType)
            
                case enNodeType.TopLeft:
                    
                        qtNode.mbSubdivide &= CanNodeDivide(qtNode, parentTopNeighborNode, parentLeftNeighborNode);
                        break;
                    
                case enNodeType.TopRight:
                    
                        qtNode.mbSubdivide &= CanNodeDivide(qtNode, parentTopNeighborNode, parentRightNeighborNode);
                        break;
                    
                case enNodeType.BottomRight:
                    
                        qtNode.mbSubdivide &= CanNodeDivide(qtNode, parentRightNeighborNode, parentBottomNeighborNode);
                        break;
                    
                case enNodeType.BottomLeft:
                    
                        qtNode.mbSubdivide &= CanNodeDivide(qtNode, parentLeftNeighborNode, parentBottomNeighborNode);
                        break;
                    
            

            //决定是否画顶点的
            int iNeighborOffset = curNodeLength - 1;
            int iX = (int)x;
            int iZ = (int)z;

            int iZBottom = iZ - iNeighborOffset;
            int iZTop = iZ + iNeighborOffset;
            int iXLeft = iX - iNeighborOffset;
            int iXRight = iX + iNeighborOffset;

            CQuadTreeNode bottomNeighborNode = GetNode(iX, iZBottom);
            CQuadTreeNode rightNeighborNode = GetNode(iXRight, iZ);
            CQuadTreeNode topNeighborNode = GetNode(iX, iZTop);
            CQuadTreeNode leftNeighborNode = GetNode(iXLeft, iZ);


            if ( qtNode.mbSubdivide )
            
                if( !(curNodeLength <= 3) )
                
                    float fChildeNodeOffset = (float)((curNodeLength - 1) >> 2);
                    int tChildNodeLength = (curNodeLength + 1) >> 1;

                    //bottom-right
                    qtNode.mBottomRightNode = RefineNodeImpl(x + fChildeNodeOffset, z - fChildeNodeOffset, tChildNodeLength, enNodeType.BottomRight, topNeighborNode, rightNeighborNode, bottomNeighborNode, leftNeighborNode, viewCamera, vectorScale, desiredResolution, minResolution , useRoughnessEvalute);
                    //bottom-left
                    qtNode.mBottomLetfNode = RefineNodeImpl(x - fChildeNodeOffset, z - fChildeNodeOffset, tChildNodeLength, enNodeType.BottomLeft, topNeighborNode, rightNeighborNode, bottomNeighborNode, leftNeighborNode, viewCamera, vectorScale, desiredResolution, minResolution, useRoughnessEvalute);
                    //top-left 
                    qtNode.mTopLeftNode = RefineNodeImpl(x - fChildeNodeOffset, z + fChildeNodeOffset, tChildNodeLength, enNodeType.TopLeft, topNeighborNode, rightNeighborNode, bottomNeighborNode, leftNeighborNode, viewCamera, vectorScale, desiredResolution, minResolution, useRoughnessEvalute);
                    //top-right
                    qtNode.mTopRightNode = RefineNodeImpl(x + fChildeNodeOffset, z + fChildeNodeOffset, tChildNodeLength, enNodeType.TopRight, topNeighborNode, rightNeighborNode, bottomNeighborNode, leftNeighborNode, viewCamera, vectorScale, desiredResolution, minResolution, useRoughnessEvalute);
                        
            

            return qtNode; 
        

上述是四叉树对于地形网格结点的划分,当然我们还需要LOD算法的绘制,LOD是根据摄像机的远近进行设置的,代码如下所示:

 public void CLOD_Render( ref stTerrainMeshData meshData , Vector3 vertexScale )
        
            meshData.Reset(); 
            float fCenter = (mHeightData.mSize - 1) >> 1;

            RenderNode(fCenter, fCenter, mHeightData.mSize,ref meshData ,vertexScale);

            meshData.Present(); 
        

上述代码中比较重要的函数是RenderNode,实现代码如下所示:

private void RenderNode( float fX ,float fZ ,int curNodeLength ,ref stTerrainMeshData meshData, Vector3 vectorScale  )
        
            int tHeightMapSize = mHeightData.mSize; 
            int iX = (int)fX;
            int iZ = (int)fZ;

            CQuadTreeNode curNode = GetNode(iX, iZ); 
            if( null == curNode )
            
                Debug.LogError(string.Format("[RenderNode]No Node at :0|1", iX, iZ));
                return; 
            

            //当前节点边长的一半
            int iHalfNodeLength = (curNodeLength - 1) >> 1;
            float fHalfNodeLength = (curNodeLength - 1) / 2.0f;

            //中点左位置
            int iLeftX = iX - iHalfNodeLength;
            float fLeftX = fX - fHalfNodeLength;

            //中点右位置
            int iRightX = iX + iHalfNodeLength;
            float fRightX = fX + iHalfNodeLength;

            //顶点中点位置
            int iTopZ = iZ + iHalfNodeLength;
            float fTopZ = fZ + fHalfNodeLength;

            //底部中点位置
            int iBottomZ = iZ - iHalfNodeLength;
            float fBottomZ = fZ - fHalfNodeLength; 


            //边长减1 ?相邻节点的距离
            int iNeighborOffset = curNodeLength - 1;

            float fTexLeft = Mathf.Abs(fX - fHalfNodeLength) / tHeightMapSize;
            float fTexBottom = Mathf.Abs(fZ - fHalfNodeLength) / tHeightMapSize;
            float fTexRight = Mathf.Abs(fX+ fHalfNodeLength) / tHeightMapSize;
            float fTexTop = Mathf.Abs(fZ + fHalfNodeLength) / tHeightMapSize;

            float fTexMidX = (fTexLeft + fTexRight) / 2.0f;
            float fTexMidZ = (fTexBottom + fTexTop) / 2.0f;


            //决定是否画顶点的
            int iZBottom = iZ - iNeighborOffset;
            int iZTop = iZ + iNeighborOffset;
            int iXLeft = iX - iNeighborOffset;
            int iXRight = iX + iNeighborOffset; 

            CQuadTreeNode bottomNeighborNode = GetNode(iX,iZBottom );
            CQuadTreeNode rightNeighborNode = GetNode(iXRight, iZ);
            CQuadTreeNode topNeighborNode = GetNode(iX, iZTop);
            CQuadTreeNode leftNeighborNode = GetNode(iXLeft, iZ);

            //举一个例子,如果下边的邻结点没有,或者下面的同级的邻结点是需要划分的,即当前结点也需要划分
            bool bDrawBottomMidVertex = (iZBottom < 0) || (bottomNeighborNode != null && bottomNeighborNode.mbSubdivide);
            bool bDrawRightMidVertex = (iXRight >= tHeightMapSize) || (rightNeighborNode != null && rightNeighborNode.mbSubdivide);
            bool bDrawTopMidVertex = (iZTop >= tHeightMapSize) || (topNeighborNode != null && topNeighborNode.mbSubdivide);
            bool bDrawLeftMidVertex = (iXLeft< 0) || (leftNeighborNode != null && leftNeighborNode.mbSubdivide);


            //Center Vertex
            stVertexAtrribute tCenterVertex = GenerateVertex(iX, iZ, fX, fZ, fTexMidX, fTexMidZ, vectorScale);
            //Bottom Left Vertex 
            stVertexAtrribute tBottomLeftVertex = GenerateVertex(iLeftX, iBottomZ, fLeftX, fBottomZ, fTexLeft, fTexBottom, vectorScale);   
            //Left Mid Vertext
            stVertexAtrribute tLeftMidVertex = GenerateVertex(iLeftX, iZ, fLeftX, fZ, fTexLeft, fTexMidZ, vectorScale);
            //Top Left Vertex 
            stVertexAtrribute tTopLeftVertex = GenerateVertex(iLeftX, iTopZ, fLeftX, fTopZ, fTexLeft, fTexTop, vectorScale);
            //Top Mid Vertex 
            stVertexAtrribute tTopMidVertex = GenerateVertex(iX, iTopZ, fX, fTopZ, fTexMidX, fTexTop, vectorScale);
            //Top Right Vertex 
            stVertexAtrribute tTopRightVertex = GenerateVertex(iRightX, iTopZ, fRightX, fTopZ, fTexRight, fTexTop, vectorScale);
            //Right Mid Vertex 
            stVertexAtrribute tRightMidVertex = GenerateVertex(iRightX, iZ, fRightX, fZ, fTexRight, fTexMidZ, vectorScale);
            //Bottom Right Vertex 
            stVertexAtrribute tBottomRightVertex = GenerateVertex(iRightX, iBottomZ, fRightX, fBottomZ, fTexRight, fTexBottom, vectorScale);
            //Bottom Mide Vertex 
            stVertexAtrribute tBottomMidVertex = GenerateVertex(iX, iBottomZ, fX, fBottomZ, fTexMidX, fTexBottom, vectorScale);

            stFanGenerator tFanGenerator = new stFanGenerator(
                bDrawLeftMidVertex,
                bDrawTopMidVertex,
                bDrawRightMidVertex,
                bDrawBottomMidVertex,
                tCenterVertex,
                tBottomLeftVertex,
                tLeftMidVertex,
                tTopLeftVertex,
                tTopMidVertex,
                tTopRightVertex,
                tRightMidVertex,
                tBottomRightVertex,
                tBottomMidVertex
                ); 

            if ( curNode.mbSubdivide )
            
                #region 已经是最小的LOD

                //已经是最小的LOD的
                if( curNodeLength <= 3 )
                
                    tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                    tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);
                    tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);
                    tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);
                 // <= 3 

                #endregion

                #region 还可以继续划分

                else
                
                    int tChildHalfLength = (curNodeLength - 1) >> 2;
                    float fChildHalfLength = (float)tChildHalfLength;

                    int tChildNodeLength = (curNodeLength + 1) >> 1;

                    int tFanCode = 0;


                    int iChildRightX = iX + tChildHalfLength;
                    int iChildTopZ = iZ + tChildHalfLength;
                    int iChildLeftX = iX - tChildHalfLength;
                    int iChildBottomZ = iZ - tChildHalfLength;

                    float fChildRightX = fX + fChildHalfLength;
                    float fChildTopZ = fZ + fChildHalfLength;
                    float fChildLeftX = fX - fChildHalfLength;
                    float fChildBottomZ = fZ - fChildHalfLength;

                    CQuadTreeNode topRightChildNode = GetNode( iChildRightX,iChildTopZ);
                    CQuadTreeNode topLeftChildNode = GetNode(iChildLeftX, iChildTopZ);
                    CQuadTreeNode bottomLeftChildNode = GetNode(iChildLeftX, iChildBottomZ);
                    CQuadTreeNode bottomRightChildNode = GetNode(iChildRightX, iChildBottomZ);

                    //拆分程度不能相差2
                    //左上子结点
                    //bool bTopLeftChildDivide = CanNodeDivide(topLeftChildNode,leftNeighborNode,topNeighborNode);
                    bool bTopLeftChildDivide = topLeftChildNode != null && topLeftChildNode.mbSubdivide;
                    //右上子结点
                    //bool bTopRightChildDivide = CanNodeDivide(topRightChildNode,topNeighborNode,rightNeighborNode);
                    bool bTopRightChildDivide = topRightChildNode != null && topRightChildNode.mbSubdivide;
                    //右下
                    //bool bBottomRightChildDivide = CanNodeDivide(bottomRightChildNode,bottomNeighborNode,rightNeighborNode);
                    bool bBottomRightChildDivide = bottomRightChildNode != null && bottomRightChildNode.mbSubdivide;
                    //左下
                    //bool bBottomLeftChildDivide = CanNodeDivide(bottomLeftChildNode,bottomNeighborNode,leftNeighborNode);
                    bool bBottomLeftChildDivide = bottomLeftChildNode != null && bottomLeftChildNode.mbSubdivide; 

                    //top right sud divide
                    if ( bTopRightChildDivide )
                    
                        tFanCode |= 8;
                    

                    //top left
                    if ( bTopLeftChildDivide )
                    
                        tFanCode |= 4;
                    

                    //bottom left 
                    if (bBottomLeftChildDivide)
                    
                        tFanCode |= 2;
                    

                    //bottom right 
                    if ( bBottomRightChildDivide )
                    
                        tFanCode |= 1;
                    

                    #region 各种情况的组合 

                    enNodeTriFanType fanType = (enNodeTriFanType)tFanCode;
                    switch (fanType)
                    

                        #region 15 四个子结点都分割
                        //子结点一个都不分割
                        case enNodeTriFanType.No_Fan:
                            
                                //bottom right 
                                RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);

                                //bottom left 
                                RenderNode( fChildLeftX ,fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);

                                //top left 
                                RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                //top right 
                                RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break;
                            
                        #endregion

                        #region  5 左上右下分割,左下右上画三角形
                        //左上右下分割,左下右上画三角形
                        case enNodeTriFanType.BottomLeft_TopRight:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);

                                //bottom right 
                                RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);
                                //top left 
                                RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break;
                            
                        #endregion

                        #region 10 左下右上分割,左上右下画三角形
                        case enNodeTriFanType.BottomRight_TopLeft:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);

                                //bottom left 
                                RenderNode(fX - fChildHalfLength, fZ - fChildHalfLength, tChildNodeLength, ref meshData, vectorScale);
                                //top right 
                                RenderNode(fX + fChildHalfLength, fZ + fChildHalfLength, tChildNodeLength, ref meshData, vectorScale);

                                break;
                            

                        #endregion

                        #region 0 直接画出8个三角形
                        case enNodeTriFanType.Complete_Fan:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);

                                break;
                            

                        #endregion

                        #region 1 左下左上右上划三角形
                        case enNodeTriFanType.BottomLeft_TopLeft_TopRight:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);

                                //Bottom Right Child Node
                                RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 

                                break; 
                            

                        #endregion

                        #region 2  左上右上右下划三角形

                        case enNodeTriFanType.TopLeft_TopRight_BottomRight:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);

                                //bottom left 
                                RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            


                        #endregion

                        #region 3 左上右上划三角形

                        case enNodeTriFanType.TopLeft_TopRight:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);

                                //bottom left 
                                RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);
                                //bottom right 
                                RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            

                        #endregion

                        #region 4 右上右下左下划三角形
                        case enNodeTriFanType.TopRight_BottomRight_BottomLeft:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);

                                //top left 
                                RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            

                        #endregion

                        #region 6 右上右下划三角形
                        case enNodeTriFanType.TopRight_BottomRight:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);

                                //bottom left 
                                RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);

                                //top left 
                                RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            

                        #endregion

                        #region 7 右上划三角形
                        case enNodeTriFanType.TopRight:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight);

                                //bottom right 
                                RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);
                                //bottom left 
                                RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);
                                //top left 
                                RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            

                        #endregion

                        #region 8 右下左下左上划三角形
                        case enNodeTriFanType.BottomRight_BottomLeft_TopLeft:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);
                                //top right 
                                RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            
                        #endregion

                        #region 9 左下左上划三角形
                        case enNodeTriFanType.BottomLeft_TopLeft:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);

                                //bottom right 
                                RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);
                                //top right 
                                RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            

                        #endregion

                        #region 11 左上划三角形
                        case enNodeTriFanType.TopLeft:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Four);
                                //bottom right 
                                RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);
                                //bottom left 
                                RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);
                                //top right 
                                RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            

                        #endregion

                        #region 12 左下右下划三角形
                        case enNodeTriFanType.BottomLeft_BottomRight:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);

                                //top left 
                                RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);
                                //top right 
                                RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            

                        #endregion

                        #region 13 左下划三角形
                        case enNodeTriFanType.BottomLeft:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.Two);

                                //bottom right 
                                RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);

                                //top left 
                                RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                //top right 
                                RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break; 
                            

                        #endregion

                        #region 14 右下划三角形
                        case enNodeTriFanType.BottomRight:
                            
                                tFanGenerator.DrawFan(ref meshData, enFanPosition.One);
                                //bottom left 
                                RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale);
                                //top left 
                                RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);
                                //top right 
                                RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale);

                                break;
                            

                            #endregion
                    

                    #endregion

                

                #endregion

              // if subdivied

          //RenderNode

另外,我们还需要设置对应LOD的不同等级的纹理,在这里把在Unity的操作界面给读者展示如下:

地形贴图我们也需要Shader的渲染,地形Shader代码,比较简单,在这里并没有使用多种材质,Shader代码如下所示:

Shader "Terrain/QuadTree/TerrainRender" 

    Properties 
    
        _Color("Color Tint",Color) = (1,1,1,1)
        _MainTex ("Main Tex", 2D) = "white" 
        _DetailTex("Detail Tex",2D) = "white"
    
    SubShader 
    

        Tags  "LightMode"="ForwardBase" 

        Cull Off



        Pass
        
            CGPROGRAM
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #pragma vertex vert 
            #pragma fragment frag

            fixed4 _Color ; 
            sampler2D _MainTex ; 
            float4 _MainTex_ST ; 
            sampler2D _DetailTex ;
            float4 _DetailTex_ST ; 


            struct a2v
            
                float4  vertex : POSITION ; 
                float4  normal : NORMAL ;
                float4  texcoord : TEXCOORD0; 
             ; 

            struct v2f
            
                float4 pos : SV_POSITION ; 
                float3 worldNormal : TEXCOORD0 ;
                float3 worldPos : TEXCOORD1 ;
                float2 uv : TEXCOORD2 ;

             ;

            v2f vert(a2v v)
            
                v2f o ; 
                o.pos = UnityObjectToClipPos(v.vertex) ; 
                o.worldNormal =  mul( v.normal,(float3x3)unity_WorldToObject) ; 
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz ; 
                o.uv = v.texcoord.xy  * _MainTex_ST.xy + _MainTex_ST.zw ; 
                return o ; 
            

            fixed4 frag(v2f i ): SV_Target
            

                fixed3 color1 = tex2D(_MainTex,i.uv).rgb ;
                fixed3 color2 = tex2D(_DetailTex,i.uv).rgb ; 
                fixed3 finalColor = color1 * ( color2 * 2 ) ;

                return fixed4(finalColor,1.0) ; 

            

            ENDCG          
        
     
    FallBack "Diffuse"

最终渲染的效果图如下所示:

四叉树生成的网格节点如下所示:

具有LOD算法的网格,它是根据摄像机的远近划分的,近的比较精细,远的比较粗略,效果如下所示:

小结:
作为比较经典的算法,在大部分游戏或者引擎中还是被经常的使用,它实现起来并不复杂,在这里也是给读者简单的介绍一下,其实算法都是通用的,要想在游戏行业,或则说VR,AR,AI等IT领域有所建树,算法必须要灵活掌握,而且要知道如何使用,这样你的技能会突飞猛进,另外这个只是简单的实现使用Unity对四叉树的一种运用,算法还有很多不完善的地方,抛砖引玉吧,还可以在此基础上进行算法改进,代码读者可以自己调试一下,就知道它执行的流程了,这也是学习代码比较快的捷径。

代码下载地址:链接:https://pan.baidu.com/s/1K2goiHMl_BA9PieyGAphpA 密码:i2rt

参考:
《Focus On 3D Terrain Programming》

以上是关于在Unity中使用四叉树算法绘制地形的主要内容,如果未能解决你的问题,请参考以下文章

在一组四叉树中找到最佳深度/范围以优化边界框中点的检索

回想四叉树LOD地形(上)

[译]2D空间中使用四叉树Quadtree进行碰撞检测优化

Unity 遍历敌人——使用四叉树空间分区

Unity 3D 使用高度图创建地形|| Unity 3D 使用笔刷绘制地形

四叉树平面分割算法--快速图元搜索