Ogre地形组件学习

以前学习笔记的角度是从程序流程,今次学习OGRE地形试着换个角度,我觉得这样的总结更方便日后查阅。


OGRE的地形部分分两个Component,Terrain和Paging,各由一些类构成。只要结合demo程序的流程搞清楚每个类的作用及各个类之间的关系,就能更深入探索OGRE地形处理机制(chunk lod,texture splat,skirt)了。

 

仔细研究下保存的地形数据dat文件的内容,也对学习地形很有帮助:

Ogre地形组件学习_第1张图片

Ogre地形组件学习_第2张图片

 

跟terrain有关的类:

 

Terrain:
Terrain的实例化对象就是一个Terrain Instance.


getLayerBlendMap()方法,为地形进行纹理splat时调用。如下:

 

代码
    
    
    
    
// sync load since we want everything in place when we start
mTerrainGroup -> loadAllTerrains( true );
if (mTerrainsImported)
{
Ogre::TerrainGroup::TerrainIterator ti
= mTerrainGroup -> getTerrainIterator();
while (ti.hasMoreElements())
{
Ogre::Terrain
* t = ti.getNext() -> instance;
initBlendMaps(t);
}
}


void BasicTutorial3::initBlendMaps(Ogre::Terrain * terrain)
{
Ogre::TerrainLayerBlendMap
* blendMap0 = terrain -> getLayerBlendMap( 1 );
Ogre::TerrainLayerBlendMap
* blendMap1 = terrain -> getLayerBlendMap( 2 );
Ogre::Real minHeight0
= 70 ;
Ogre::Real fadeDist0
= 40 ;
Ogre::Real minHeight1
= 70 ;
Ogre::Real fadeDist1
= 15 ;
float * pBlend1 = blendMap1 -> getBlendPointer();
for (Ogre::uint16 y = 0 ; y < terrain -> getLayerBlendMapSize(); ++ y)
{
for (Ogre::uint16 x = 0 ; x < terrain -> getLayerBlendMapSize(); ++ x)
{
Ogre::Real tx, ty;

blendMap0
-> convertImageToTerrainSpace(x, y, & tx, & ty);
Ogre::Real height
= terrain -> getHeightAtTerrainPosition(tx, ty);
Ogre::Real val
= (height - minHeight0) / fadeDist0;
val
= Ogre::Math::Clamp(val, (Ogre::Real) 0 , (Ogre::Real) 1 );

val
= (height - minHeight1) / fadeDist1;
val
= Ogre::Math::Clamp(val, (Ogre::Real) 0 , (Ogre::Real) 1 );
* pBlend1 ++ = val;
}
}
blendMap0
-> dirty();
blendMap1
-> dirty();
blendMap0
-> update();
blendMap1
-> update();
}

 

 update()方法,在Demo中是以每秒20次对每个Terrain调用该方法。作用是更新dirty sections的如下数据:

  1. The terrain geometry
  2. The terrain error metrics which determine LOD transitions
  3. The terrain normal map, if present
  4. The terrain lighting map, if present
  5. The terrain composite map, if present

       

TerrainGlobalOptions:
TerrainGlobalOptions必须在使用其他地形类之前构造。该类用来配置地形的全局选项,如:setMaxPixelError(),setLightMapDirection(),setCompositeMapDiffuse()这些。

 

TerrainGroup:

TerrainGroup管理构成整个世界的若干Terrain Instance。

含有Protected成员变量:TerrainSlotMap  mTerrainSlots;


typedef map<uint32, TerrainSlot*>::type Ogre::TerrainGroup::TerrainSlotMap
这个映射表即是根据TerrainSlot的16位x,y索引值打包成32位ID,来映射到具体的TerrainSlot。


而TerrainSlot有公共成员变量:
TerrainSlotDefinition  def;         //TerrainSlotDefinition结构体包含了该slot是由地形文件还是ImportData定义的情况
Terrain *          instance;


这就将TerrainGroup,Terrain,TerrainSlot,TerrainSlotDefinition联系起来了:TerrainGroup是总管,管理所有的TerrainSlot,每个TerrainSlot对应一个Terrain实例和TerrainSlotDefinition。

getDefaultImportSettings()方法,在这里设置所有地形实例的公共属性(ImportData结构体) ,如terrainsize,worldsize,纹理layer等。

 

defineTerrain方法有多种调用形式,即我们能通过不同方法来定义slot。根据API文档说明,貌似加载地形实例与定义slot是不能混为一谈的,后者只是在Terrain Grid中定义一个槽,占个位置先而已,而并没有实际加载地形,这是为了support background preparation of this terrain instance。slot(0,0)表示Terrain Grid的中心,往右x增加,往上y增加,有正负值。

defineTerrain(x,y),如果从已保存地形数据的dat文件来加载地形实例,则调用该函数。
defineTerrain(x,y,constantHeight),用来定义一个平坦地形slot.
defineTerrain(x,y,img),从高度图来定义slot.这用于第一次运行程序时(无地形dat文件),以后就能直接调用第一种形式了。

loadAllTerrains(bool  synchronous = false)方法,这才是真正开始加载地形了。参数表示是否同步加载,是则加载强制发生在主线程中。

加载完地形,如果这是第一次运行程序,如前所述,我们是通过defineTerrain(x,y,img)来定义地形高度数据的,也就是还没有给地形贴上纹理。所以要给TerrainGroup中的每个地形实例都完成这项工作。见Terrain类的分析。

 

saveAllTerrains(),第一次运行程序时调用,将地形数据写入地形dat文件中保存在硬盘上,以后运行程序时直接读入就行了。

freeTemporaryResources()方法,地形构造完成后调用该方法,释放临时资源,提高程序效率。

isDerivedDataUpdateInProgress(),询问是否Derived Data(Light Map,Normal Map,Composite Map)正在处理中或已经处理完了,可以用来做label提示用户。


TerrainLayerBlendMap :

该类管理每层纹理的Blend Map.即Terrain的该层纹理怎么与前几层的结果相Alpha混合。API文档说得很清楚,每层的Blend Map其实只是最终Blend Texture的一个RGBA channel。我们在mTerrainGroup->getDefaultImportSettings()设置地形的公共属性时,已经设置好了一共有几层Terrain layer,每层由什么纹理构成,如下:

 

代码
    
    
    
    
// Configure default import settings for if we use imported image
Ogre::Terrain::ImportData & defaultimp = mTerrainGroup -> getDefaultImportSettings();
defaultimp.terrainSize
= 513 ;
defaultimp.worldSize
= 12000.0f ;
defaultimp.inputScale
= 600 ;
defaultimp.minBatchSize
= 33 ;
defaultimp.maxBatchSize
= 65 ;
// textures
defaultimp.layerList.resize( 3 );
defaultimp.layerList[
0 ].worldSize = 100 ;
defaultimp.layerList[
0 ].textureNames.push_back( " dirt_grayrocky_diffusespecular.dds " );
defaultimp.layerList[
0 ].textureNames.push_back( " dirt_grayrocky_normalheight.dds " );
defaultimp.layerList[
1 ].worldSize = 30 ;
defaultimp.layerList[
1 ].textureNames.push_back( " grass_green-01_diffusespecular.dds " );
defaultimp.layerList[
1 ].textureNames.push_back( " grass_green-01_normalheight.dds " );
defaultimp.layerList[
2 ].worldSize = 200 ;
defaultimp.layerList[
2 ].textureNames.push_back( " growth_weirdfungus-03_diffusespecular.dds " );
defaultimp.layerList[
2 ].textureNames.push_back( " growth_weirdfungus-03_normalheight.dds " );

具体的纹理splat过程见前面对Terrian类的笔记。

 

TerrainMaterialGenerator:

地形材质生成的基类,根据渲染需要子类化它。

TerrainMaterialGeneratorA:

Demo中用到了它的SM2Profile这个内嵌类,而SM2Profile继承自TerrainMaterialGenerator::Profile。

通过TerrainGlobalOptions::getDefaultMaterialGenerator()获取默认材质生成器,然后Demo中用SM2Profile进行了一些控制材质接收阴影的相关设置。

这部分完全没看懂,关于这两个类和地形的材质部分有待深入了解。


TerrainQuadTreeNode:

每个Terrain对象都有一棵四叉树。

通过对OGRE wiki的基础教程三学习,基本了解了地形的构建过程。但是我知道OGRE的地形是采用的chunk LOD,渲染机制包括四叉树,裂缝修补,geomorphing这些都还没在代码中看到。不过了解过chunked LOD的同学都知道,该算法里面有一个四叉树,储存了计算好的每个细节层次的各个chunk对应的误差尺度。在渲染地形时,用其来进行LOD选择。

该类应该就是完成这些工作,还有skirt index的计算,各个细节层次chunk的顶点,索引缓存具体怎么由该类来进行分配计算和管理现在没搞清楚。

 

找到的资料:

 

 

Ogre地形组件学习_第3张图片

Terrain::distributeVertexData按照OgreTerrain的LOD算法,找到拥有vertex的TerrainQuadTreeNode,然后调用assignVertexData,开始创建缓冲区VertexDataRecord。VertexDataRecord包含了vertex data和index data。

 

 

 

 

关于实时对地形的编辑,如高度值,各层纹理blend,可以参考demo的代码:

  

代码
    
    
    
    
void doTerrainModify(Terrain * terrain, const Vector3 & centrepos, Real timeElapsed)
{
Vector3 tsPos;
terrain
-> getTerrainPosition(centrepos, & tsPos);
// centrepos是中心编辑点,即鼠标停留在地形上的位置。这里把该点从世界空间转换到地形空间。
// 对于地形,有三个坐标系:世界空间,顶点空间,地形空间。
// 顶点空间即从左下的(0,0)到右上的(2^n,2^n)
// 地形空间即从左下的(0,0)到右上的(1,1)
#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE
if (mKeyboard -> isKeyDown(OIS::KC_EQUALS) || mKeyboard -> isKeyDown(OIS::KC_MINUS))
{
switch (mMode)
{
case MODE_EDIT_HEIGHT:
{
// we need point coords
Real terrainSize = (terrain -> getSize() - 1 );
// mBrushSizeTerrain决定变化区域的大小
long startx = (tsPos.x - mBrushSizeTerrainSpace) * terrainSize;
long starty = (tsPos.y - mBrushSizeTerrainSpace) * terrainSize;
long endx = (tsPos.x + mBrushSizeTerrainSpace) * terrainSize;
long endy = (tsPos.y + mBrushSizeTerrainSpace) * terrainSize;
startx
= std::max(startx, 0L );
starty
= std::max(starty, 0L );
endx
= std::min(endx, ( long )terrainSize);
endy
= std::min(endy, ( long )terrainSize);
// 有了这四个点,我们就得到了一个编辑区域矩形,接下来很容易想到可以采用权值
// 来修改每个顶点的新高度:距离中心编辑点越近,权值越大,这样最后就能形成
// 平滑的圆锥形的修改后地形。
for ( long y = starty; y <= endy; ++ y)
{
for ( long x = startx; x <= endx; ++ x)
{
Real tsXdist
= (x / terrainSize) - tsPos.x;
Real tsYdist
= (y / terrainSize) - tsPos.y;

Real weight
= std::min((Real) 1.0 ,
Math::Sqrt(tsYdist
* tsYdist + tsXdist * tsXdist) / Real( 0.5 * mBrushSizeTerrainSpace)); // 根据当前编辑顶点到中心编辑顶点的两点间距离算出权值,该步是距离越大,权值越大
weight = 1.0 - (weight * weight); // 我们需要的是,距离越大,权值越小.

float addedHeight = weight * 250.0 * timeElapsed; // 根据权值计算该点的高度变化值
float newheight;
if (mKeyboard -> isKeyDown(OIS::KC_EQUALS))
newheight
= terrain -> getHeightAtPoint(x, y) + addedHeight;
else
newheight
= terrain -> getHeightAtPoint(x, y) - addedHeight;
terrain
-> setHeightAtPoint(x, y, newheight); // 修改该点高度值

}
}
if (mHeightUpdateCountDown == 0 )
mHeightUpdateCountDown
= mHeightUpdateRate;
}
break ;

实时编辑各层layer的blend值也很相似,要根据编辑层的blend map size在image sapce进行blend data的修改,然后update().

 

 

实在是不好学啊,内容太特么的多了,脑袋完全是晕的,还有Paging组件呢。  T T 

TerrainGruop::LoadAllTerrain()读取完了地形数据后,构造整个QuadTree。一个单边513的Terrain会得到如图示的2个树:

你可能感兴趣的:(Math,vector,equals,float,Blend,textures)