在Unity中使用四叉树算法绘制地形
四叉树算法在游戏中获得了广泛的应用,前几年3D引擎实现的地形绘制大部分都是用四叉树生成的,因为移动端在硬件方面的限制,我们的地形使用的是美术自己制作的地形,对于程序来说省去了不少工作量,但是作为程序开发者尤其是想从事引擎开发的程序员,我们还是要自己实现一遍四叉树算法的,其实网上很多资料都有C++ OpenGL或者C++ DX实现的四叉树地形。很多引擎的地形它也是在四叉树的基础上改进而成,如果我们想优化四叉树,不知道它的算法实现,即看不懂代码何谈优化?所以我们可以尝试自己动手在Unity的基础上实现四叉树算法。
四叉树算法是一个老生常谈的话题了,其实虽然过去了这么多年,作为经典的算法,它的应用还是很广的,比如我们的服务器同步,在某个区域内的玩家是可以同步的,这个区域划分以及区域角色的查找就可以使用四叉树算法,另外我们的动态寻路也可以使用四叉树算法,把地形不断的划分成四叉树的小格子,然后根据格子中填充的数据判断哪些格子是有建筑物?哪些格子没有建筑物,以及格子与格子之间的最短距离计算;再有就是地形的绘制了,四叉树会结合着LOD算法一起使用,四叉树的表述看下图所示:
四叉树算法的实现方式有两种:
- 采用盲目搜索,与二叉树的递归遍历类似,可采用后序遍历或前序遍历或中序遍历对其进行搜索某一对象,时间复杂度为O(n)
- 根据对象在区域里的位置来搜索,采用分而治之思想,时间复杂度只与四叉树的深度有关。比起盲目搜索,这种搜索在区域里的对象越多时效果越明显
再介绍一下四叉树实现地形时为了避免出现裂缝,采用四叉树结构,不同级别的分块之间可能会出现裂缝(Cracks),我们采取的解决方式如下所示: - 一般的做法是:主要分为跳点法和加点法。跳点法就是在较高分辨率的分块的边界上跳过一些点不绘制,这样可以保持相邻分块的连续性。加点法就是在较低分辨率的分块边界上新增一些顶点以达到两个分块顶点保持连续的目的;
- 另一种方法时采用一种效率更高的消除裂缝的办法,对每个分块的四条边,在现有的顶点的基础上再延伸出一圈,他们和单个分块的边界共享顶点,只是高度值不同,这种延伸出来的一圈叫做“裙子”(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;
}
//这里作用是如果预处理之后,还是发生了*****的情况,就强行拆面
//一个节点是否能够划分
//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》
推荐阅读