一般3D游戏的地形都是使用 LOD地形,Cocos2dx的Terrain也不例外, 原因是大的地形绘制需要在短时间内处理上亿个三角形,使用LOD技术能极大的提高地形的绘制效率。
多分辨率网格简化技术/细节层次模型技术(LOD技术),引入“分而治之”的思想,根据地形的不同复杂程度和人眼观察地形的特点,对地形的不同区域采取不同细节的描述和绘制。采用LOD技术绘制地形,在不降低表现效果的前提下,可以尽量减少三角形的数量,以提高图形绘制效率,实现地形的实时交互可视化,通俗具体讲就是将一块地形划分为很多块小地形(Chunk),对于离视点越近的区域,或者该区域地形越复杂(起伏大,如山区),绘制的三角形数目越多,地形描述精度越高;对于离视点越远的区域,或者该区域越平坦,绘制的三角形数目越少,地形描述精度越低。
在具体的实现算法中,需要用到四叉树来描述地形。
如下图所示,我们将一块地形的高程数据用一个四叉树结构组织起来,其中每个正方形代表一个节点,每个节点包含9个高程点,其中有一个中心点、4个角点和4个位于4条边上的中点。一个大正方形里面包含4个小正方形(叶子节点除外),这四个小正方形所代表的节点就是大正方形节点的子节点。
图(1)
地形的四叉树结构
首先按照地形的大小把整个地形分割成一棵满四叉树,满四叉树的概念是从满二叉树引申过来的,它必须满足以下的条件:
1) 除了叶子节点,每个节点都有4个子节点,并且其所有子节点都位于同一层上;
2) 地形越大,四叉树的深度越大
图(2)
满四叉树
这样我们就可以看出来,每个节点表示一块地形,节点的层次越低(根节点的层次最低,为0),所代表的正方形的面积越大,该节点的细节程度(Level)也越低。显然,叶子节点的层次最高,它的Level也最高。
【突起现象】
当视点移动的时候,地形网格不断更新,当节点Level发生变化的时候,地形中的某点会由一个高度突然跳变到另一个高度,这种称之为“突起”现象(Poping-up)。在地形漫游系统中,“突起”现象给人 一种不真实的感觉。为消除这种不自然的行为,引入了一种”形变“算法。形变的含义是,当节点从一个Level过渡到另一个Level的时候,其中某点平稳缓慢地从一个高度过渡到另一个高度。形变虽然没有本质上消除地形高度变化,但这种“缓变”往往肉眼难以察觉。
Cocos2dx实现地形的原理:
一个Terrain是由 n *m 个Chunk组成的,Terrain和Chunk的大小必须是2的幂次方,只有这样才可以方便的根据Chunk的LOD等级来绘制粗糙或者细致的网格。LOD的等级是根据摄像机所在位置与Chunk的距离来决定的,距离越小,LOD等级越小,绘制越精细。Terrain中定义了高度图,细节贴图,alpha贴图,Chunk的集合,原始数据等,以下是Terrain主要的成员变量:
//地形数据,包括顶点,法线,贴图坐标
struct TerrainVertexData
{
cocos2d::Vec3 _position;
cocos2d::Tex2F_texcoord;
cocos2d::Vec3 _normal;
};
//当T裂缝为INCREASE_LOWER: 缓存当前chunk的lod等级和它的四个邻居的lod等级,以及索引标志缓存, 在生成一个新的索引缓存后,并不是使用完就把它删掉,而是把它的索引标志缓存起来,下次还遇到一样的索引缓存,就直接使用即可,提高了效率。
std::vector <ChunkLODIndices>_chunkLodIndicesSet;
//当T裂缝为SKIRT:缓存当前chunk的lod等级和索引标志缓存
std::vector<ChunkLODIndicesSkirt>_chunkLodIndicesSkirtSet;
TerrainData _terrainData;
unsignedchar* _data; //高度图的数据
cocos2d::Image * _heightMapImage; //高度图
float_lodDistance[3]; //每个lod层次的距离基本值,用来和Chunk与相机的距离做比较以确定每个chunk的lod等级
Texture2D * _detailMapTextures[4]; //支持4层地表贴图
Texture2D * _alphaMap; //alpha贴图,决定4层贴图以什么方式进行显示,在shader中体现
Texture2D * _lightMap; //光照贴图
Vec3 _lightDir; //光照方向向量
QuadTree * _quadRoot; // 四叉树根节点
Chunk * _chunkesArray[MAX_CHUNKES][MAX_CHUNKES]; //chunk集合
std::vector<TerrainVertexData>_vertices; //地形数据,包括顶点,法线,贴图坐标
std::vector<unsigned int>_indices; //地形顶点索引
int _imageWidth; //高度图的宽,以字节为单位
int _imageHeight;
Size _chunkSize; //地图块的大小
bool _isEnableFrustumCull; //是否允许截视体剪裁
float _maxHeight; //地型的最高点和最低点
float _minHeight;
CrackFixedType_crackFixedType; //填补裂缝类型
enum classCrackFixedType{
SKIRT,
INCREASE_LOWER,
};
SKIRT方式: 用Chunk最外层的所有顶点都生成一个副本,并只改变副本顶点的高度值(根据什么规则来改变我没明白),把这些顶点先保存下来。之后渲染的时候先正常绘制一遍,即把整个chunk的三角形都绘制一遍,接下来就是用之前的副本顶点再一次渲染最外层的三角形。
INCREASE_LOWER方式:属于边插入方式,先绘制除了最外层的三角形,之后绘制最外层的三角形。要添加边以保证最外层的三角形和邻居Chunk的最外层三角形共用的那条边都一样长。
Chunk是一个地形块, 主要负责存储对于的顶点数据,根据当前的LOD等级生成索引缓存,以及渲染这个地形块。这里的难度在于填补T型裂缝,所要做的工作是根据crackFixedType类型来生成对于的索引缓存以及适当修改顶点数据的高度来达到无裂缝效果。以下是它的重要成员变量:
struct Chunk
{
std::vector<TerrainVertexData>_originalVertices; //原始的顶点数据
/*LODindices*/
structLOD{
std::vector<GLushort>_indices;
};
LOD _lod[4]; //四个LOD等级下的索引缓存
GLuint _vbo;
ChunkIndices_chunkIndices; //正在使用的顶点索引标志,如果相机位置改变了,每一帧都要重新计算索引缓存
struct ChunkIndices
{
GLuint _indices; //OpenGL索引标志
unsignedshort _size; //索引缓存的大小
};
//生成chunk数据
voidgenerate(int map_width, intmap_height, int m, intn, const unsignedchar* data);
//真正的绘制函数,每个chunk都会增加一个渲染批次,似乎可以合并渲染
voidbindAndDraw();
//平缓更新顶点高度值,解决突起问题
voidupdateVerticesForLOD();
//补边方式时更新索引缓存
voidupdateIndicesLOD();
//裙边方式时的更新索引缓存函数
voidupdateIndicesLODSkirt();
int_currentLod; //当前的lod等级,等级越大, chunk绘制越粗糙
int_neighborOldLOD[4];
/*theleft,right,front,back neighbors*/
Chunk * _left;
Chunk * _right;
Chunk* _front;
Chunk * _back;
QuadTree * _parent; //chunk所属的QuadTree,这个QuadTree一定是叶子节点
std::vector<TerrainVertexData>_currentVertices; //当前用于渲染的顶点缓存
};
QuadTree四叉树方便的描述了一个地形,上面图(1)和图(2)已经清楚的解释了四叉树的结构,值得注意的是QuadTree的叶子节点都包含了一个Chunk。它的作用就是把整个地形描述成一颗满四叉树,可以根据相机的截视体快速的剔除掉不可见的地形块, 提高渲染效率。另外整个地形的绘制接口起点在QuadTree的draw()函数中,根节点draw()递归地调用了每个子节点的draw(),当遇到叶子节点时便调用对于的Chunk的绘制函数bindAndDraw。以下是它的结构体:
struct QuadTree
{
//递归地创建一颗满四叉树
QuadTree(int x, inty, int width, intheight, Terrain * terrain);
//递归地调用了孩子节点的draw(),当遇到叶子节点时,则调用对应chunk的bindAndDraw();
voiddraw();
// 根据相机来剔除掉不可见的地形块
voidcullByCamera(const Camera* camera, const Mat4& worldTransform)
QuadTree * _tl; //四个孩子节点
QuadTree * _tr;
QuadTree * _bl;
QuadTree * _br;
bool _isTerminal; //是否是叶子节点
Chunk * _chunk; //叶子节点记录的chunk
int _posX; //chunk在Terrain中的位置
int _posY;
Terrain * _terrain;
bool _needDraw; //标记是否需要绘制
};
------------------------------下面介绍实现流程 -------------------
下面的函数为了突出重点都是删减版的,想看完整版请直接下载引擎源码。
首先,创建一个Terrain 需要一个TerrainData : 地形的基本信息,如高度图的路径名, Chunk大小,细节贴图路径名等。
下面为Terrain初始化函数:
bool Terrain::initWithTerrainData(TerrainData ¶meter,CrackFixedType fixedType)
{
this->_terrainData=parameter;
this->_crackFixedType= fixedType;
this->_chunkSize=parameter._chunkSize;
//根据高度图信息生成顶点,chunk集, QuadTree
this->initHeightMap(parameter._heightMapSrc.c_str());
//初始化纹理相关
this->initTextures();
//初始化属性渲染状态
this->initProperties();
}
这里主要把精力放在initHeightMap()函数上,其他可以忽略!由上面可知生成一个地形最重要的就是高度图,接着看initHeightMap函数。
//根据高度图信息生成顶点,chunk集, QuadTree
bool Terrain::initHeightMap(constchar* heightMap)
{
_heightMapImage = new Image();
_heightMapImage->initWithImageFile(heightMap);
_data =_heightMapImage->getData();
_imageWidth =_heightMapImage->getWidth();
_imageHeight =_heightMapImage->getHeight();
//只有高度图大小都是是2的幂次方才可以
if((isPOT(_imageWidth)&&isPOT(_imageHeight)) || (isPOT(_imageWidth-1)&&isPOT(_imageHeight -1)))
{
//根据高度图大小和chunk大小求出x ,y上各有多少块chunk
int chunk_amount_y = _imageHeight/_chunkSize.height;
int chunk_amount_x = _imageWidth/_chunkSize.width;
//生成顶点数据,包括顶点坐标,uv坐标,法线
loadVertices();
calculateNormal();
//生成chunk对象
for(int m =0;m<chunk_amount_y;m++)
{
for(int n =0; n<chunk_amount_x;n++)
{
_chunkesArray[m][n] = new Chunk();
_chunkesArray[m][n]->_terrain = this;
_chunkesArray[m][n]->_size =_chunkSize;
//根据高度图信息生成一个chunk
_chunkesArray[m][n]->generate(_imageWidth,_imageHeight,m,n,_data);
}
}
//邻居
for(int m =0;m<chunk_amount_y;m++)
{
for(int n =0; n<chunk_amount_x;n++)
{
if(n-1>=0)_chunkesArray[m][n]->_left = _chunkesArray[m][n-1];
if(n+1<chunk_amount_x)_chunkesArray[m][n]->_right = _chunkesArray[m][n+1];
if(m-1>=0)_chunkesArray[m][n]->_back = _chunkesArray[m-1][n];
if(m+1<chunk_amount_y)_chunkesArray[m][n]->_front = _chunkesArray[m+1][n];
}
}
//递归生成所有的QuadTree,每个QuadTree是一个chunk的parent,
//当QuadTree .size <= chunk时停止,这时的QuadTree是叶子节点
_quadRoot = new QuadTree(0,0,_imageWidth,_imageHeight,this);
//设置每个LOD的距离值,用来确定每个Chunk的LOD,比如Chunk与视点的距离小于等于_chunkSize.width, 则它的LOD等级为0
setLODDistance(_chunkSize.width,2*_chunkSize.width,3*_chunkSize.width);
}
上面的函数主要创建了高度图, 根据高度图信息生成地形顶点信息,生成Chunk集合和四叉树。这里重点是生成Chunk集合和四叉树:
_chunkesArray[m][n]->generate(_imageWidth,_imageHeight,m,n,_data);
_quadRoot = new QuadTree(0,0,_imageWidth,_imageHeight,this);
先看看generate:
void Terrain::Chunk::generate(int imgWidth,intimageHei,intm,intn,constunsignedchar* data)
{
_posY = m; //记录了在Terrain中是第几块Chunk
_posX = n;
//生成原始数据
switch(_terrain->_crackFixedType)
{
caseCrackFixedType::SKIRT:
{
for(inti=_size.height*m;i<=_size.height*(m+1);i++)
{
if(i>=imageHei) break;
for(intj=_size.width*n;j<=_size.width*(n+1);j++)
{
if(j>=imgWidth) break;
auto v =_terrain->_vertices[i*imgWidth+ j];
_originalVertices.push_back (v);
}
}
//以下为裙边方式才用的上的顶点,注意添加额外顶点的顺序, 生成索引数据时用到
float skirtHeight = _terrain->_skirtRatio*_terrain->_terrainData._mapScale*8;
//#1右边计算chunk最右边从上到下的顶点高度值
_terrain->_skirtVerticesOffset[0] = (int)_originalVertices.size();
for(int i =_size.height*m;i<=_size.height*(m+1);i++)
{
auto v = _terrain->_vertices[i*imgWidth+_size.width*(n+1)];
v._position.y -= skirtHeight;
_originalVertices.push_back (v);
}
//#2下边,计算chunk最下面从左到右的顶点高度值
_terrain->_skirtVerticesOffset[1] = (int)_originalVertices.size();
for(int j =_size.width*n;j<=_size.width*(n+1);j++)
{
auto v = _terrain->_vertices[_size.height*(m+1)*imgWidth+ j];
v._position.y -=skirtHeight;
_originalVertices.push_back (v);
}
//#3左边,计算chunk最左边从上到下的顶点高度值
_terrain->_skirtVerticesOffset[2] = (int)_originalVertices.size();
for(int i =_size.height*m; i<=_size.height*(m+1);i++)
{
auto v = _terrain->_vertices[i*imgWidth+ _size.width*n];
v._position.y -= skirtHeight;
_originalVertices.push_back (v);
}
//#4 上面,计算chunk最下面从左到右的顶点高度值
_terrain->_skirtVerticesOffset[3] = (int)_originalVertices.size();
for(int j =_size.width*n;j<=_size.width*(n+1);j++)
{
auto v = _terrain->_vertices[_size.height*m*imgWidth+j];
v._position.y -= skirtHeight;
_originalVertices.push_back (v);
}
}
break;
//补边方式,只是简单的生成了Chunk的数据
caseCrackFixedType::INCREASE_LOWER:
{
for(int i=_size.height*m;i<=_size.height*(m+1);i++)
{
if(i>=imageHei)break;
for(int j=_size.width*n;j<=_size.width*(n+1);j++)
{
if(j>=imgWidth)break;
auto v =_terrain->_vertices[i*imgWidth+ j];
_originalVertices.push_back (v);
}
}
}
break;
}
//创建了顶点VBO
finish();
}
该函数负责产生Chunk顶点数据,值得注意的是在裙边方式下加了一些改变了高度的顶点数据,这些多加的顶点位置都是属于Chunk最外层。这些顶点的索引会在渲染中计算索引缓存时用到,
float skirtHeight = _terrain->_skirtRatio*_terrain->_terrainData._mapScale*8; 不明白计算规则
v._position.y -= skirtHeight; 顶点高度都是这样改变
接下来看看如何创建四叉树
Terrain::QuadTree::QuadTree(int x, int y, int w,int h, Terrain * terrain)
{
_terrain= terrain;
_posX = x; // QuadTree在Terrain中的位置
_posY = y;
this->_height= h;
this->_width= w;
if(_width>terrain->_chunkSize.width&&_height >terrain->_chunkSize.height)//subdivision
{
_isTerminal = false;
this->_tl= new QuadTree(x,y,_width/2,_height/2,terrain);
this->_tl->_parent= this;
this->_tr= new QuadTree(x+_width/2,y,_width/2,_height/2,terrain);
this->_tr->_parent= this;
this->_bl= new QuadTree(x,y+_height/2,_width/2,_height/2,terrain);
this->_bl->_parent= this;
this->_br= new QuadTree(x+_width/2,y+_height/2,_width/2,_height/2,terrain);
this->_br->_parent= this;
}else//叶子节点
{
int m = _posY/terrain->_chunkSize.height;
int n = _posX/terrain->_chunkSize.width;
_chunk = terrain->_chunkesArray[m][n];
_isTerminal = true;
_localAABB = _chunk->_aabb;
_chunk->_parent= this; //关联Chunk和QuadTree
}
_worldSpaceAABB=_localAABB;
//世界坐标系下的AABB,用于判断和相机截视体是否相交,以确定是否需要绘制该QuadTree
_worldSpaceAABB.transform(_terrain->getNodeToWorldTransform());
}
QuadTree在构造函数中就递归地创建了所有的子节点,因此用的时候只需要创建一个根节点即可,每个节点都会记录下它的位置,大小,是否是叶子节点以及关联对于的Chunk,有了这些信息就足够了。至此,整个地形的创建流程就结束了,下面介绍渲染的流程。
Terrain渲染的流程
Terrain的实际渲染入口如下:
void Terrain::onDraw(constMat4 &transform, uint32_t flags)
{
autoglProgram = getGLProgram();
glProgram->use(); //启用TerrainShader
_stateBlock->bind(); //渲染状态相关设置
//启用顶点的三个属性
GL::enableVertexAttribs(1<<_positionLocation | 1 <<_texcordLocation | 1<<_normalLocation);
glProgram->setUniformsForBuiltins(transform);
_glProgramState->applyUniforms();
//绑定四张地形纹理贴图和alpha贴图
for(int i =0;i<_maxDetailMapValue;i++)
{
GL::bindTexture2DN(i,_detailMapTextures[i]->getName());
glUniform1i(_detailMapLocation[i],i);
glUniform1f(_detailMapSizeLocation[i],_terrainData._detailMaps[i]._detailMapSize);
}
glUniform1i(_alphaIsHasAlphaMapLocation,1);
GL::bindTexture2DN(4, _alphaMap->getName());
glUniform1i(_alphaMapLocation,4);
auto camera = Camera::getVisitingCamera();
if(memcmp(&_CameraMatrix,&camera->getViewMatrix(),sizeof(Mat4))!=0)
{
_isCameraViewChanged = true;
_CameraMatrix = camera->getViewMatrix();
}
if(_isCameraViewChanged)
{
auto m = camera->getNodeToWorldTransform();
//setlod
//根据相机的位置重新设置每个chunk的LOD等级
setChunksLOD(Vec3(m.m[12], m.m[13], m.m[14]));
}
if(_isCameraViewChanged)
{
_quadRoot->resetNeedDraw(true);//resetit
//camerafrustum culling
//相机截视体剪裁,把看不见的QuadTree节点直接设置为不绘制状态
if(_isEnableFrustumCull)
{
_quadRoot->cullByCamera(camera, _terrainModelMatrix);
}
}
//地形的绘制是从这里开始的,每个四叉树叶子节点有关联了一个chunk,
//遍历四叉树,如果看得见并且是叶子节点,则绘制地形
_quadRoot->draw();
}
上面便是Terrain的渲染过程,先是设置各种渲染状态,设置shader变量的值,顶点,光照,绑定纹理信息等。然后就是检查相机位置是否发现改变,改变的话就要重新设置每个Chunk的LOD等级,重新剪裁四叉树节点,之后便进入_quadRoot->draw()。
这里说明一下地形纹理的工作方式,每个地形不可能只有一张细节贴图,那得多单调啊。所以,一般的实现方式是提供4张细节贴图和一张alpha贴图,4张细节贴图根据不同的混合公式就可以组合出丰富的地表面,如何混合是alpha贴图和shader决定的。Shader是程序写的算法,alpha贴图是如何来的呢? 程序一般都会提供一个地形编辑器给美术,美术根据四张细节贴图来混搭出比较丰富的地表面,比如在一个区域里,有一条水泥路,路的两边有青草,泥土加石头仔。这时,美术就会使用一张水泥路的贴图,一张泥土石头仔贴图和青草的贴图,将这三张图用于该地表,分别为第一层,第二层,第三层。在水泥路的位置只显示水泥路的贴图, 路两边屏蔽掉水泥路的贴图,用泥土石头仔贴图和青草的贴图混合做路边贴图,这时便可知道alpha贴图的数据了。以下是shader算法:
void main()
{
//取得一个alpha混合数据,格式为ARGB, R代表第一层贴图的混合因子
vec4 blendFactor =texture2D(u_alphaMap,v_texCoord);\n
vec4 color = vec4(0.0,0.0,0.0,0.0);\n
//下面是四张贴图的混合算法
color = texture2D(u_texture0,v_texCoord*u_detailSize[0])*blendFactor.r +
texture2D(u_texture1,v_texCoord*u_detailSize[1])*blendFactor.g +
texture2D(u_texture2,v_texCoord*u_detailSize[2])*blendFactor.b +
texture2D(u_texture3,v_texCoord*u_detailSize[3])*(1.0 - blendFactor.a);
gl_FragColor =vec4(color.rgb*lightColor.rgb*lightFactor, 1.0);
);
_quadRoot->draw()递归地调用每个子节点的draw函数,直到叶子节点便调用Chunk的bindAndDraw来绘制。这里不足的是每个Chunk都占用一个渲染批次,优化的方式是合并所有的chunk数据,之后统一渲染。
void Terrain::Chunk::bindAndDraw()
{
glBindBuffer(GL_ARRAY_BUFFER,_vbo);
if(_terrain->_isCameraViewChanged|| _oldLod <0)
{
switch(_terrain->_crackFixedType)
{
case CrackFixedType::SKIRT:
updateIndicesLODSkirt();
break;
caseCrackFixedType::INCREASE_LOWER:
updateVerticesForLOD(); //这个是差值改变顶点高度值,使看起来高度渐变不明显
updateIndicesLOD();
break;
default:
break;
}
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_chunkIndices._indices);
unsignedlong offset = 0;
//position
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT,GL_FALSE, sizeof(TerrainVertexData), (GLvoid*)offset);
offset += sizeof(Vec3);
//texcoord
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD,2,GL_FLOAT,GL_FALSE,sizeof(TerrainVertexData),(GLvoid*)offset);
offset +=sizeof(Tex2F);
//normal
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_NORMAL,3,GL_FLOAT,GL_FALSE,sizeof(TerrainVertexData),(GLvoid*)offset);
glDrawElements(GL_TRIANGLES, (GLsizei)_chunkIndices._size, GL_UNSIGNED_SHORT,0);
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,_chunkIndices._size);
}
这里有OpenGL基础的同学都能看懂渲染部分的代码,只要的精华和难点是根据补裂缝方式生成索引缓存。
updateIndicesLODSkirt(); 是以裙边方式生成索引缓存
updateIndicesLOD();以添加边方式生成索引。
这里介绍updateIndicesLOD,要理解的深刻,最好跟着补边索引代码画出三角形。
void Terrain::Chunk::updateIndicesLOD()
{
//邻居的LOD等级
intcurrentNeighborLOD[4];
if(_left)
{
currentNeighborLOD[0] = _left->_currentLod;
}else{currentNeighborLOD[0]= -1;}
if(_right)
{
currentNeighborLOD[1] = _right->_currentLod;
}else{currentNeighborLOD[1]= -1;}
if(_back)
{
currentNeighborLOD[2]= _back->_currentLod;
}else{currentNeighborLOD[2]= -1;}
if(_front)
{
currentNeighborLOD[3] = _front->_currentLod;
}else{currentNeighborLOD[3]= -1;}
//如果新旧lod等级相同并且四个邻居的lod等级也相同,则不用更新,继续使用旧的_chunkIndices
if(_oldLod== _currentLod &&(memcmp(currentNeighborLOD,_neighborOldLOD,sizeof(currentNeighborLOD))==0))
{
return;//no need to update
}
bool isOk;
//查找ChunkIndices缓存是否已经存在,ChunkIndices有一个OpenGL生成的索引标志
// 如果有缓存直接 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_chunkIndices._indices);
_chunkIndices =_terrain->lookForIndicesLOD(currentNeighborLOD,_currentLod,&isOk);
if(isOk)
{
return;
}
memcpy(_neighborOldLOD,currentNeighborLOD,sizeof(currentNeighborLOD));
_oldLod =_currentLod;
int gridY = _size.height;
int gridX = _size.width;
int step = 1<<_currentLod;
//需要做补边
if((_left&&_left->_currentLod> _currentLod) ||(_right&&_right->_currentLod > _currentLod)
||(_back&&_back->_currentLod > _currentLod) || (_front&& _front->_currentLod > _currentLod))
{
//先计算出除了最外层的三角形索引
_lod[_currentLod]._indices.clear();
for(int i =step;i<gridY-step;i+=step)
{
for(int j = step;j<gridX-step;j+=step)
{
intnLocIndex = i * (gridX+1) + j;
_lod[_currentLod]._indices.push_back (nLocIndex);
_lod[_currentLod]._indices.push_back (nLocIndex + step * (gridX+1));
_lod[_currentLod]._indices.push_back (nLocIndex + step);
_lod[_currentLod]._indices.push_back (nLocIndex + step);
_lod[_currentLod]._indices.push_back (nLocIndex + step * (gridX+1));
_lod[_currentLod]._indices.push_back (nLocIndex + step * (gridX+1) +step);
}
}
int next_step = 1<<(_currentLod+1); //LOD等级+1的三角形边长
if(_left&&_left->_currentLod> _currentLod)//left
{
//对左边进行补边, 这里最好按照代码自己动手画一遍,文字不太好描述
for(int i =0;i<gridY;i+=next_step)
{
_lod[_currentLod]._indices.push_back(i*(gridX+1)+step);
_lod[_currentLod]._indices.push_back(i*(gridX+1));
_lod[_currentLod]._indices.push_back((i+next_step)*(gridX+1));
_lod[_currentLod]._indices.push_back(i*(gridX+1)+step);
_lod[_currentLod]._indices.push_back((i+next_step)*(gridX+1));
_lod[_currentLod]._indices.push_back((i+step)*(gridX+1)+step);
_lod[_currentLod]._indices.push_back((i+step)*(gridX+1)+step);
_lod[_currentLod]._indices.push_back((i+next_step)*(gridX+1));
_lod[_currentLod]._indices.push_back((i+next_step)*(gridX+1)+step);
}
}
else{//如果左边不需要补边,则按正常情况计算索引
int start=0;
int end =gridY;
//如果上面需要补边,则最左上角的三角形不需要计算索引,在上面补边时会计算到
if(_front&&_front->_currentLod> _currentLod) end -=step;
if(_back&&_back->_currentLod> _currentLod) start +=step;
for(int i =start;i<end;i+=step)
{
_lod[_currentLod]._indices.push_back(i*(gridX+1)+step);
_lod[_currentLod]._indices.push_back(i*(gridX+1));
_lod[_currentLod]._indices.push_back((i+step)*(gridX+1));
_lod[_currentLod]._indices.push_back(i*(gridX+1)+step);
_lod[_currentLod]._indices.push_back((i+step)*(gridX+1));
_lod[_currentLod]._indices.push_back((i+step)*(gridX+1)+step);
}
}
//这里还有上面,右边,下面的补边代码,思路都雷同
//添加chunkIndices缓存
_chunkIndices =_terrain->insertIndicesLOD(currentNeighborLOD,_currentLod,&_lod[_currentLod]._indices[0],(int)_lod[_currentLod]._indices.size());
}
else{ //如果四个邻居的LOD等级都和自己的一样,则无需补边
_lod[_currentLod]._indices.clear();
for(int i =0;i<gridY;i+=step)
{
for(int j = 0;j<gridX;j+=step)
{
int nLocIndex = i * (gridX+1) + j;
_lod[_currentLod]._indices.push_back (nLocIndex);
_lod[_currentLod]._indices.push_back (nLocIndex + step * (gridX+1));
_lod[_currentLod]._indices.push_back (nLocIndex + step);
_lod[_currentLod]._indices.push_back (nLocIndex + step);
_lod[_currentLod]._indices.push_back (nLocIndex + step * (gridX+1));
_lod[_currentLod]._indices.push_back (nLocIndex + step * (gridX+1) +step);
}
}
//添加chunkIndices缓存
_chunkIndices =_terrain->insertIndicesLOD(currentNeighborLOD,_currentLod,&_lod[_currentLod]._indices[0],(int)_lod[_currentLod]._indices.size());
}
}
这里用到了索引标志缓存,如果已经存在相同的索引标志了,便直接 返回,否则生成一个新的索引缓存。这里对不同LOD等级的索引缓存都做了缓存,十分高效。
第一次提笔写属于自己的博客,真是不容易,希望能坚持下去!
LOD地形引用和参考: http://blog.csdn.net/debugconsole/article/details/26104361
————————————————
版权声明:本文为CSDN博主「子轩Q」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tuanxuan123/article/details/49472669