以下转自:http://hi.baidu.com/xocoder/item/e8d87cf53d87612b753c4cfd
OGRE地形生成
OGRE可以通过两个接口来生成地形,分别是void TerrainSceneManager::setWorldGeometry( const String& filename )以及void TerrainSceneManager::setWorldGeometry(DataStreamPtr& stream, const String& typeName ),两者的根本区别就是一个是传递地形信息文件名一个是传递地形信息数据流,设计两个接口的目的是可以让用户使用自己的地形信息配置文件,而不必局限于 OGRE定义的地形信息配置文件格式。在分析流程之前,先说明一点,为了提高渲染性能以及地形查找性能,OGRE把整个地形分成若干个地形页,每个地形页 又分成了多个地形小块,到最后会把地形页及地形小块tile挂接到场景节点上。以下是生成地形的主要流程:
一:清除地形分级索引缓冲及地形分页,根据地形信息数据流加载地形信息配置文件,
void TerrainSceneManager::loadConfig(DataStreamPtr& stream),其先将地形配置信息从数据流中逐一读出到map中,然后通过void TerrainSceneManager::selectPageSource(const String& typeName, TerrainPageSourceOptionList& optionList)设置地形数据源(目前只有高度图数据源)。在地形信息配置文件中可以配置多个地形数据源,然后根据一种数据源类型生成地形,参数 typeName就用来指定数据源类型,目前就是HeightMap,找到指定的数据源后,对指定的数据源进行初始化,
void HeightmapTerrainPageSource::initialise(TerrainSceneManager* tsm, ushort tileSize, ushort pageSize, bool asyncLoading, TerrainPageSourceOptionList& optionList)。初始化的过程主要是调用void HeightmapTerrainPageSource::loadHeightmap(void)将高度图灰度图像数据加载到内存中,如果是Raw数 据,就加载到mRawData中,否则加载到mImage中。整个加载过程其实完成了两大工作,首先加载地形配置信息,然后加载高度图数据。
二:初始化分级索引缓冲,void TerrainSceneManager::initLevelIndexes();
三:void OctreeSceneManager::resize( const AxisAlignedBox &box )
四:设置地形材质,void TerrainSceneManager::setupTerrainMaterial(void);
五:设置地形分页, void TerrainSceneManager::setupTerrainPages(void)
首先创建一个名为Terrain的场景根节点的子节点
mTerrainRoot = getRootSceneNode() -> createChildSceneNode( "Terrain" );
然后初始化TerrainPage2D mTerrainPages;
最后调用 void HeightmapTerrainPageSource::requestPage(ushort x, ushort y)requestPage只支持一个Page,首先将图像数据进行缩放
然后调用 “TerrainPageSource::firePageConstructed()”通知Listener;然后调用“TerrainPage* TerrainPageSource::buildPage(Real*heightData, const MaterialPtr& pMaterial)”创建一个新的TerrainPage对象。
buildPage()是一个比较核心的函数。它首先构造一个 TerrainPage对象,然后创建一个用于容纳该TerrainPage对象的场景节点:“page->pageSceneNode = mSceneManager->createSceneNode(name);”,然后根据对地形的分割,循环创建子SceneNode,并且创建 子场景节点对应的可渲染体TerrainRenderable,将该可渲染体attach到这个子节点上。通过 “TerrainRenderable::initialise()”来创建顶点数据、渲染方式等。
然后调用void TerrainSceneManager::attachPage(ushort pageX, ushort pageZ, TerrainPage* page)加入到mTerrainPages中;
然后在attachPage中调用“mTerrainRoot->addChild(page->pageSceneNode);”加入到SceneGraph中。
注:当前只支持一个Page,该分页被挂接到一个称谓Terrain的场景节点上,该场景节点下面又创建了很多子场景节点,每一个子场景节点对应一个tile,也就是一个独立的可渲染体TerrainRenderable
以下转自:http://blog.csdn.net/ljy1988123/article/details/7264188
Terrain是地形,就是一般你进入游戏的地图部分,是静态的那一部分(Jason Gregory说过:动态和静态没那么严格的区别)。
老规矩,从头文件
OgreTerrain.h
这个头文件定义了两个类:Terrain和TerrainGlobalOption,第一个类很明显是存放具体的地形数据的,第二个是存放地形数据的全局选项的,因为一个地形会有很多个Terrain对象组成(每个Terrain表示某种地形的一小片地域),统一的的配置对象就是TerrainGlobalOption了。
OgreTerrainGroup.h
定义了TerrainGroup这个类,按照Ogre的思路部件都是要注册到统一管理的接口的。TerrainGroup把每一片地域(Terrain)连接起来,管理各个Terrain之间的关系。你就可以直接通过配置TerrainGroup一次来配置数目可能会很多的Terrain了。(例如创建、删除、加载、卸载一个Terrain,把所有Terrain保存到硬盘上,总体配置等)
以上两个头文件就可以表示Ogre的地形了。但是LOD技术不知道听说过没有?离你(camera)远的地方细节没那么重要,你快速移动的时候,好多细节也可以忽略。按照这个思想可以对Terrain作出极大的优化。Ogre中支持这个思想的,就是实现LOD的就是Paged Component,分页的思想。这个组件是把要显示的东西分页,然后根据不同情况显示不同的页。例如远处有座山,实际上很精密的一座山,但是你离的远,本来用1w个顶点表示的,现在用100个顶点就可以满足你的视觉。这时那座山就不显示1w个顶点的page,而显示100个顶点的page。page思想还应用在texture中,mipmap应该听说过吧?不同距离选择不同大小的纹理。
Ogre中把page思想抽象成一个component,而Terrain是page思想的主要应用,在Terrain组建里有针对page接口的实例化。
首先介绍下paging component
OgrePage.h
定义了Page类,当然,这就是最基础的一个page了。整个系统都是在决定什么时候什么情况下在哪换下一个page,替上另一个细节程度不同的page.
OgrePageStrategy.h
定义了PageStrategy类。记得policy设计模式吗?一个PageStrategy对象就是一个policy,这个policy的作用是决定哪个page被换上去,什么时候。所以可以有很多policy,也就是很多PageStrategy的派生类。
OgrePagedWorldSection.h
定义了PagedWorldSection类,这个类是整个被分页的世界的一部分,具象讲,比如是Terrain(整个大地图)的东面的山区,或者西面的林区。因为不同地形应该是有不同的LOD策略,也就是可以有不同的PageStrategy。这个类就代表了地图中使用相同PageStrategy的那一部分,当然,策略有了,容器有了,这里面肯定存的就是Page类啦~~~
OgrePagedWorld.h
定义了类PagedWorld,终于到最上层了。这里面肯定就是一大堆PagedWorldSection组成的啦~~~~~所以逻辑上,整个Terrain(抽象成PagedWorld),包含好多块不同的区域,有西面的一片山地,东面的一片林地(抽象成PagedWorldSection),每一片区域根据你的距离和速度使用不同的LOD策略(抽象成PageStrategy),来选择不同细节程度的模型(抽象成Page)进行显示。
其实呢,上面只是说了个大概。还有Page的组成里,还有更细节的几个头文件在这里介绍。
OgrePageContent.h
有没有想过Page里是怎么存放东西的?你想啊,就算是一小片花丛也有各种花,各种石头,各种泥土,不同的区域块存在的。表达一个page里的最小组成但愿的就是这个头文件定义的PageContent类。
OgrePageContentCollection.h
其实啊,一个Page里有的不是一大堆乱放的PageContent,而是一些按照不同属性的PageContent分类的PageContentCollention。这个头文件定义的就是PageContentCollection。
还有两个与这个话题相关的头文件,是设计模式的abstractFactory的实现。
OgrePageContentFactory.h, OgrePageContentCollectionFactory.h
如果你知道设计模式的话,很容易就想到这个是用来创建不同PageContent和PageContentCollection的AbstractFactory接口。
以上几个类描述了分页的世界组成。接下来是管理。
OgrePageManager.h
PageManager类。这个很容易理解了,整个page世界是怎样组合的,管理不同的PageWorld(注意,可能你同时只需要显示一个PageWorld,但是整个游戏有不止一个PageWorld需要管理和注册、加载、删除),这个类就是整个PageWorld的最上层管理接口。
下面就回到Terrain啦~~最早说过,Terrain可以不同page直接实现。利用page可以增加LOD功能。以下俩头文件主要完成Terrain的page化操作
OgreTerrainPagedWorldSection.h
我们知道Page的世界要用PageManager来管理。在具化到Terrain时,也要有个PageManager来管理整个PagedWorld。而PageWorld的组成PageWorldSection则被继承特化成TerrainPagedWorldSection。这个TerrainPageWorldSection及就是在这个头文件定义的。
并且啊,这个类还给你固定选择了LOD采用的policy。现在这个PagedWorld就是由TerrainPagedWorldSection组成的,并且每个Section都采用了选定的LOD策略来挂载删除不同的Page。
OgreTerrainPaging.h
这个定义了TerrainPaging类,其实是重新实现了PageManager的创造函数。因为Terrain和TerrainGroup都没有继承PageContent或Page,所以把俩融合在一起得做好多配置,干这个活的就是TerrainPaging类。这个重新实现了createWorldSection函数,让PagedWorld里的元素都由Terrain元素填充。
以下转自:http://www.cppblog.com/flyindark/archive/2013/05/07/ogre_18_terraingroup.html
Ogre TerrainGroup地形赏析
1.1 参考
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Ogre+Terrain+System
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Ogre+Terrain+Component+FAQ
http://www.ogre3d.org/forums/viewtopic.php?f=11&t=50674
http://tulrich.com/geekstuff/sig-notes.pdf
ogre_src_v1-8-1\Components\Terrain
├─include
│ OgreTerrain.h
│ OgreTerrainGroup.h
│ OgreTerrainLayerBlendMap.h
│ OgreTerrainMaterialGenerator.h
│ OgreTerrainMaterialGeneratorA.h
│ OgreTerrainPagedWorldSection.h
│ OgreTerrainPaging.h
│ OgreTerrainPrerequisites.h
│ OgreTerrainQuadTreeNode.h
│
└─src
OgreTerrain.cpp
OgreTerrainGroup.cpp
OgreTerrainLayerBlendMap.cpp
OgreTerrainMaterialGenerator.cpp
OgreTerrainMaterialGeneratorA.cpp
OgreTerrainPagedWorldSection.cpp
OgreTerrainPaging.cpp
OgreTerrainQuadTreeNode.cpp
Sample
ogre_src_v1-8-1\Samples\Terrain
1.2 类图
<帖不了图图>
1.3 使用流程
1、首先需要创建terrain options
2、其次要创建TerrainGroup对象
3、然后设置Terrain Group
4、最后执行加载
6、清理Terrain Group
Terrain Group是Terrain的集合,如此可以取到集合里的terrain:
TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();
while(ti.hasMoreElements())
{
Terrain* t = ti.getNext()->instance;
}
至此完成了ogre最新的TerrainGroup的生命周期。
1.4 地形文件
1.4.1 Terrain文件格式
TerrainData (Identifier 'TERR') [Version 1]
Name |
Type |
Description |
Terrain orientation |
uint8 |
The orientation of the terrain; XZ = 0, XY = 1, YZ = 2 |
Terrain size |
uint16 |
The number of vertices along one side of the terrain |
Terrain world size |
Real |
The world size of one side of the terrain |
Max batch size |
uint16 |
The maximum batch size in vertices along one side |
Min batch size |
uint16 |
The minimum batch size in vertices along one side |
Position |
Vector3 |
The location of the centre of the terrain |
Height data |
float[size*size] |
List of floating point heights |
LayerDeclaration |
LayerDeclaration* |
The layer declaration for this terrain (see below) |
Layer count |
uint8 |
The number of layers in this terrain |
LayerInstance list |
LayerInstance* |
A number of LayerInstance definitions based on layer count (see below) |
Layer blend map size |
uint16 |
The size of the layer blend maps as stored in this file |
Packed blend texture data |
uint8* |
layerCount-1 sets of blend texture data interleaved as either RGB or RGBA depending on layer count |
Optional derived map data |
TerrainDerivedMap list |
0 or more sets of map data derived from the original terrain |
Delta data |
float[size*size] |
At each vertex, delta information for the LOD at which this vertex disappears |
Quadtree delta data |
float[quadtrees*lods] |
At each quadtree node, for each lod a record of the max delta value in the region |
TerrainLayerDeclaration (Identifier 'TDCL') [Version 1]
Name |
Type |
Description |
TerrainLayerSampler Count |
uint8 |
Number of samplers in this declaration |
TerrainLayerSampler List |
TerrainLayerSampler* |
List of TerrainLayerSampler structures |
Sampler Element Count |
uint8 |
Number of sampler elements in this declaration |
TerrainLayerSamplerElement* |
List of TerrainLayerSamplerElement structures |
TerrainLayerSampler (Identifier 'TSAM') [Version 1]
Name |
Type |
Description |
Alias |
String |
Alias name of this sampler |
Format |
uint8 |
Desired pixel format |
TerrainLayerSamplerElement (Identifier 'TSEL') [Version 1]
Name |
Type |
Description |
Source |
uint8 |
Sampler source index |
Semantic |
uint8 |
Semantic interpretation of this element |
Element start |
uint8 |
Start of this element in the sampler |
Element count |
uint8 |
Number of elements in the sampler used by this entry |
LayerInstance (Identifier 'TLIN') [Version 1]
Name |
Type |
Description |
World size |
Real |
The world size of this layer (determines UV scaling) |
Texture list |
String* |
List of texture names corresponding to the number of samplers in the layer declaration |
TerrainDerivedData (Identifier 'TDDA') [Version 1]
Name |
Type |
Description |
Derived data type name |
String |
Name of the derived data type ('normalmap', 'lightmap', 'colourmap', 'compositemap') |
Size |
uint16 |
Size of the data along one edge |
Data |
varies based on type |
The data |
1.4.2 加载地形文件
首先加载全局选项TerrainGlobalOptions
然后从本地terrain文件中读取(@Terrain::prepare)
18:12:36: DefaultWorkQueueBase('Root') - QUEUED(thread:main): ID=1 channel=1 requestType=1
18:12:36: DefaultWorkQueueBase('Root') - PROCESS_REQUEST_START(main): ID=1 channel=1 requestType=1
18:12:36: Terrain created; size=513 minBatch=33 maxBatch=65 treeDepth=4 lodLevels=5 leafLods=2
18:12:36: Terrain::distributeVertexData processing source terrain size of 513
18:12:36: Assigning vertex data, resolution=513 startDepth=2 endDepth=4 splits=4
18:12:36: Assigning vertex data, resolution=129 startDepth=0 endDepth=2 splits=1
18:12:36: Terrain::distributeVertexData finished
18:12:36: DefaultWorkQueueBase('Root') - PROCESS_REQUEST_END(main): ID=1 channel=1 requestType=1 processed=1
18:12:36: DefaultWorkQueueBase('Root') - PROCESS_RESPONSE_START(thread:main): ID=1 success=1 messages=[] channel=1 requestType=1
18:12:36: Font Default/Vera using texture size 512x256
18:12:36: Info: Freetype returned null for character 160 in font Default/Vera
18:12:36: Texture: Default/VeraTexture: Loading 1 faces(PF_BYTE_LA,512x256x1) with 0 generated mipmaps from Image. Internal format is PF_BYTE_LA,512x256x1.
18:12:36: Mesh: Loading axes.mesh.
18:12:36: WARNING: axes.mesh is an older format ([MeshSerializer_v1.30]); you should upgrade it as soon as possible using the OgreMeshUpgrade tool.
18:12:36: Texture: axes.png: Loading 1 faces(PF_R8G8B8,256x256x1) Internal format is PF_X8R8G8B8,256x256x1.
18:12:36: DefaultWorkQueueBase('Root') - PROCESS_RESPONSE_END(thread:main): ID=1 success=1 messages=[] channel=1 requestType=1
1.4.3 地形表面网格
已经不存在一个具体的地形表面网格的概念了,地形是“分层分批处理”的东西,地形对象不再拥有一个具体的地形顶点数据,这些数据是在LODs中的。
http://www.ogre3d.org/forums/viewtopic.php?f=11&t=50674&start=275#p365005
Actually, the Terrain object doesn't hold this information. The terrain is what I call "hierarchically batched" which means there is no set of vertex data at the highest LOD which covers the entire terrain - instead there are a series of hierarchical nodes which each store a specific range of LODs, each of which has a different coverage of the terrain. The only batch which has the whole terrain stored in one are the lowest LOD levels - used when the terrain is very far away. This allows us to efficiently render the entire terrain in one batch when far away, but closer up smaller (physically) batches are used for higher LODs but overall the vertex data for each batch is of the same size (or within a small range). This also allows us to deal with terrains that would be impossible to address with 16-bit indexes - any patch with more than 256 vertices on each side is actually impossible to address as one batch anyway without 32-bit indexes, which I avoid for compatibility. My hierarchical batch system allows very large terrain patches while still respecting 16-bit indexes and generally giving better performance. Unfortunately, it can never be as simple as a single top-level set of vertex data.
So, if I gave you access to what we use internally, I think you'd just be very confused You really do just need to extract the raw heights or just walk across the terrain using getPoint() if you want something 'raw'. I suppose I could provide an API which dumps unindexed full-LOD triangles into a buffer (or maybe with 32-bit indexing), but this will be really inefficient if you then have to re-process the buffer yourself anyway. It's much better just to hook out the points and plug those into your system directly.
1.5 四叉树结构
每个叶子节点的size都是允许划分的批次最大size,也即65。它有2个LodLevel,其size是33,这个LodLevel已经是不可划分的批次最小size了。而非叶子节点的size都比允许的批次最大size大,并且它只有一个LodLevel,其size也是批次最大size。
由此可见,允许的批次最大和最小size是划分树节点和LodLevel的直接依据,它们约束了节点和Lod划分的顶点尺寸。对于树节点,简单来说划分的方法是将其平均分割成4块,如果每块比允许的批次最大size还要大,则用同样的方式对它再次递归分割。对于Lod来说,如果其所有者树节点不是叶子,那这个Lod就让其大小设为批次的最大size,否则,就对其进行Lod细分,让每个Lod尽量的小,但是不能小于批次允许的最小值。
批次最大最小size约束存在的意义是,一方面让每个lod的顶点尽可能的少,这样在渲染的时候可以更准确的找出最少的空间分割块,以便选择尽可能少的顶点;另一方面每个Lod的顶点又不能太少,否则会增加显卡的渲染批次。
Depth |
Size |
LOD |
节点数 |
0 |
513 |
4 |
1 |
1 |
257 |
3 |
4 |
2 |
129 |
2 |
16 |
3 |
65 |
1 |
64 |
1.5.1 叶子节点
叶子节点的顺序如下
1 |
2 |
|
|
|
|
|
|
3 |
4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
每个节点记录了一个当前节点的偏移值,这个偏移值是相对于父节点的继承偏移方式计算出的数值,有点类似场景管理器中的Node._getDerivedPosition,也即是相对于当前地形的偏移值,准确来说是相对于当前地形左上角的偏移,而不是相对其父节点。这个偏移值需要注意2点:
u 偏移用+x向下,+y向左的二维坐标系计算
u 偏移值与顶点数有-1差,也即最小的格子(LOD=1)的宽度的整数倍,当前是64。
1.6 预计算基础数据
首先,预先计算好一些全局基础数据,例如树深度,最大Lod,叶子节点的Lod。见Terrain::determineLodLevels。例如,计算结果如下:
18:12:36: DefaultWorkQueueBase('Root') - QUEUED(thread:main): ID=1 channel=1 requestType=1
18:12:36: DefaultWorkQueueBase('Root') - PROCESS_REQUEST_START(main): ID=1 channel=1 requestType=1
18:12:36: Terrain created; size=513 minBatch=33 maxBatch=65 treeDepth=4 lodLevels=5 leafLods=2
18:12:36: Terrain::distributeVertexData processing source terrain size of 513
18:12:36: Assigning vertex data, resolution=513 startDepth=2 endDepth=4 splits=4
18:12:36: Assigning vertex data, resolution=129 startDepth=0 endDepth=2 splits=1
18:12:36: Terrain::distributeVertexData finished
18:12:36: DefaultWorkQueueBase('Root') - PROCESS_RESPONSE_END(thread:main): ID=1 success=1 messages=[] channel=1 requestType=1
由于我们预先定义好了批次中最大最小顶点数、地形的顶点尺寸(分别是65,33,513),通过这个预先定义好的值,可以生成一个基于四叉树的LOD关系数据结构。这个数据结构可以“映射”到任意面积尺寸的地形中,例如一个单边为12000的正方形的地形。
1.7 分配地形顶点
地形定义都取到后,就开始分配地形顶点数据了。
(分配顶点的流程图...只能看pdf了)
1.7.1 算法思想
现在需要找出如何分配顶点数据。我们要兼容16位的索引,这意味着我们最多可以拼凑129x129个地形,即使是松散的拼凑低细节的LODs也如此,因为下一个可拼凑数是257x257,这个数字太大了,所以不用它。
因此,我们需要将顶点数据分割成129块。主要地砖上创建的的数目也即表明了它上面的点,如果不使用其他的顶点数据我们在树节点中就不能在更低的LODs中合并地砖了。例如,使用如上所述的257x257的输入,顶点数据为了适合129x129的范围将不得不被分为2个(在每个维度上)。这些数据可以被树深度从1开始的所有的树共享,不过Lods 3-1将会从129x129的稀疏数据中采样,而LOD0将会从所有顶点数据中采样。
然而,最低的LOD4将不能使用同样的顶点数据进行处理,因为它需要覆盖整个地形。这里有2个选择:在17x17上创建另一组仅用于LOD4的顶点数据的,或者在出现树深度为1的时候用LOD4(例如仍旧分拆),并且沿着每一边像2x9一样渲染。
由于渲染非常小的批次不理想,以及顶点总数本质上不会很大,所以创建一个单独的顶点集还是有可行性。在出现遥远的地形时也将会让顶点缓存机制更高效。
我们可能需要一个更大尺寸的例子,因为在这种情况下只有层级1(LOD0)需要使用这种单独的顶点数据。较高细节的地形将会需要多种层次,这里有一个65/33批次设置的2049x2049的例子:
LODlevels = log2(2049 - 1) - log2(33 - 1) + 1 = 11 - 5 + 1 = 7
TreeDepth = log2((2049 - 1) / (65 - 1)) + 1 = 6
在最多细节层次上拆分的顶点数
(size - 1) / (TERRAIN_MAX_BATCH_SIZE - 1) = 2048 / 128 = 16
|
|
|
|
|
|
|
|
|
|
|
LOD |
0: |
2049 |
vertices |
32 x 65 |
vertex |
tiles |
(tree depth 5) |
vdata |
0-15 |
[129x16] |
LOD |
1: |
1025 |
vertices |
32 x 33 |
vertex |
tiles |
(tree depth 5) |
vdata |
0-15 |
[129x16] |
LOD |
2: |
513 |
vertices |
16 x 33 |
vertex |
tiles |
(tree depth 4) |
vdata |
0-15 |
[129x16] |
LOD |
3: |
257 |
vertices |
8 x 33 |
vertex |
tiles |
(tree depth 3) |
vdata |
16-17 |
[129x2] |
LOD |
4: |
129 |
vertices |
4 x 33 |
vertex |
tiles |
(tree depth 2) |
vdata |
16-17 |
[129x2] |
LOD |
5: |
65 |
vertices |
2 x 33 |
vertex |
tiles |
(tree depth 1) |
vdata |
16-17 |
[129x2] |
LOD |
6: |
33 |
vertices |
1 x 33 |
vertex |
tiles |
(tree depth 0) |
vdata |
18 |
[33] |
所有的顶点总数都是一个平方数,它们正好是沿着一条边的情形。所以,你可以看到我们需要有3个级别的顶点数据来满足(诚然,相当极端)这个情况,并且一共有19个顶点数据集。完整的细节几何,129的16个子集(X16)这样的有完全细节的几何体被用作LODs0-2。 LOD3不能使用这个子集,因为它需要通过这些子集进行组合,而且它只有8块地砖,所以我们需要在每一个顶点数据段最大是129个顶点的时候构造出另外一个集合来满足这个情况。因为在这种情况下LOD3需要整个257(X257)个的顶点,所以我们仍然将129分割成2(X2)个集合。虽然这一套集合是好用了,也包括了LOD5,但LOD6需要一个单一且连续的顶点集,所以我们为它构造了一个33x33的顶点集。
在顶点的数据存储方面,这意味着当我们的主要数据是:
2049^ 2 =4198401个顶点
最终我们存储的顶点数据是
(16 *129)^ 2+(2* 129)^ 2+ 33^ 2 =4327749个顶点
这相当于有3%的顶点冗余,但是为了从分组中减少批次它既必要又值得。此外,在LODs3和6(或树深度为3和0)中将有机会释放被更多细节LODs使用的数据,这在有巨大的地形的时候很重要。例如,如果我们在中等距离时为LOD0-2释放(GPU)顶点数据,就会为地形节省平均有98%的内存开销。
1.7.2 顶点数据
LODs在当前4叉树节点构造时候被创建,其中有个字段指向顶点数据,这个顶点数据在读取地形文件时被创建。
J 创建顶点数据
J 取回顶点数据
1.7.3 同步-异步机制
异步加载机制是Steven Streeting离开前奉献的一个重量级模块Paging的核心功能,Paging的异步加载实现了一个通用的分页机制,目前只知道在地形中有应用。但是这个优秀的分页机制可以在所有时间、帧率、消息、事件、渲染命令出现瓶颈的时候使用其扩展的各种灵活策略进行异步和有区分度的分解处理从而降低单帧负载。其算法思想Steven Streeting在Ogre官网有详细阐述,见章节“Page系统设计思想”。
为了区别异步加载给地形带来的复杂度,关闭了异步线程和地形Paging 。
#define OGRE_THREAD_SUPPORT 0
Root维护一个默认的工作队列DefaultWorkQueue,执行类似压入执行命令的逻辑用这个工作队列完成。由于关闭了异步,压入请求后会立即执行响应请求的子程序。
1.8 动态LOD计算
动态LOD计算主要目的是通过相机与四叉树节点的相对关系计算出几个关键指标:
u 当前Lod,标记当前节点LodLevel中第N个被使用的Lod
u 当前节点是否渲染,标记当前节点带领的子树是否有节点需要被渲染
LodLevel定义
1.8.1 LodLevel数据细节
一个节点聚合了4个LOD相关属性
|
|
基础Lod(Base Lod) |
预先计算好,最深的叶子节点是0,然后由内向外递增,且兄弟节点一样 |
当前Lod(Current Lod) |
动态计算,@TerrainQuadTreeNode::calculateCurrentLod |
Lod层级(Lod Level) |
保存Lod对应的具体顶点信息,每个节点“挂”一个或多个LodLevel |
Lod层级列表(Lod Level List) |
|
所有节点的Lod相关属性大部分都是初始化时就计算好,只有当前LOD是动态计算的。非叶子节点只有一个LodLevel,其顶点数量(LodLevel.batchSize)是当前terrain的批次顶点最小值;非叶子节点的基础Lod是父节点的基础Lod-1。叶子节点有多个LodLevel,其数量是由当前terrain预先计算好(NumLodLevelsPerLeaf),每个LodLevel中的顶点数量(LodLevel.batchSize)是当前terrain的批次顶点最大值(MaxBatchSize)------与非叶子节点的情况正好相反;叶子节点的基础LOD总是0。
可以看到,节点的Lod值由内从0开始向外逐渐递增,且同一深度(depth)的节点Lod也一样。而每个节点都会“挂”上一个或多个LodLevel,非叶子节点只“挂”一个,叶子节点“挂”多个,这个数量是有terrain根据世界尺寸、最大、最小单批次顶点数等值预先计算好的。
非叶子节点“挂”的LodLevel所包含的顶点数是terrain的允许的单个批次顶点最小数量。叶子节点的情况则不同,“挂”的第一个LodLevel所包含的顶点数是单个批次顶点的最大数,然后第二个减少一半---实际情况稍微复杂,数量由公式(((sz - 1) * 0.5) + 1)给出,也即几何学上的四边形顶点减半。有点类似D3DX中的层级纹理。
一切都很天衣无缝,只等当前Lod动态计算时,按照特定的规则决定哪些顶点需要被渲染,也即哪些LodLevel参与渲染。
1.8.2 动态计算Lod
一般的,Lod表示的值从0开始,依次递增,越到后面对象的细节越少。典型的如层级纹理。在地形中也如此。首先,在整个四叉树节点中,每个节点有自己的Lod,或者叫基础Lod,这个值是固定的:叶子节点的Lod占用0~M;兄弟关系的Lod一样;非叶子节点占用1个Lod,从M+1开始,越往树根走Lod越大。这样Lod最大的节点就是树根了。
动态计算Lod的关键因素是相机到节点中心的距离与节点中LodLevel的过渡距离,后者好比是一把尺子,用于判断这个Lod是否可以被相机可见。计算方法见TerrainQuadTreeNode::calculateCurrentLod。
每帧地形的Lod计算过程是对四叉树递归遍历的过程。首先遍历所有子树,然后在进行自身的计算。将计算过错分解为第一类计算过程和第二类计算过程:
}
//需要渲染的子节点数是0,或者是叶子节点,或者是所有子节点都不参与渲染的子树 //所有叶子节点都不需要渲染,那就只有考察节点自身是否需要渲染了 if (childRenderedCount == 0) {
/// 第一类计算过程 /// } //当前节点有子节点参与渲染,那自身就不需要参与渲染了, //如果需要渲染的子节点数量大于或等于4,只需做个标记,不用再计算当前节点了 else { /// 第二类计算过程 /// //跳过自身的渲染 mCurrentLod = -1; //当前子树需要被渲染 mSelfOrChildRendered = true; //只考虑需要渲染的子节点小于4的情形 if (childRenderedCount < 4) { // only *some* children decided to render on their own, but either // none or all need to render, so set the others manually to their lowest for (int i = 0; i < 4; ++i) { TerrainQuadTreeNode* child = mChildren[i]; if (!child->isSelfOrChildRenderedAtCurrentLod()) { child->setCurrentLod(child->getLodCount()-1); child->setLodTransition(1.0); } } } // (childRenderedCount < 4) } // (childRenderedCount == 0)}
1.8.3 LOD第一类计算过程
1.8.4 LOD第二类计算过程
第二类计算步骤比较简单,当前节点有子节点参与渲染,那自身就不需要参与渲染了。首先标记下这个子树需要被渲染,然后考察其递归子节点需要被渲染的数量,如果数量在[1,3]这个区间,则需要处理这个节点的4个直接子节点。
需要处理当前节点的4个直接子节点的了,如果这个子节点包含自身的树都不需要渲染,则将这个节点的Lod数量-1。之前在预计算全局基础数据时已经知道,就当前这个实例而言,叶子节点的Lod数量是2,非叶子节点的Lod数量是0。这样,对于当前节点的4个子树,如果这个子树不参与渲染,则其当前Lod=0。
1.9 渲染
地形四叉树的渲染使用了2个小技巧。一是每个树节点Hook了一个内嵌类TerrainQuadTreeNode.Rend和Movable参与到场景的管理,而它们并不是一个真实的场景对象和渲染对象,可以将它们理解为很多人喜欢使用的虚拟对象,在真正需要渲染的时候,将逻辑还是传递给TerrainQuadTreeNode。二是地形监听了场景管理器预渲染方法,这个方法正好在场景管理器八叉树遍历场景对象前执行。好处显而易见:让复杂的程序结构清晰易读。
首先,地形四叉树只应用于到地形,而不干涉场景。地形在渲染方面主要做了三件事,一是构建了一个四叉树和对应的Lod;二是构建了与每个Lod关联的顶点数据;三是每个四叉树节点都构造一个影子render对象和movable对象。
影子moveable对象在地形初次load的时候构建完成,每个四叉树节点都会新建一个场景节点,并关节上这个引子movable对象,这样让每个地形四叉树节点参与到场景八叉树节点的可见性计算中(当前考虑的场景管理器是默认的八叉树场景管理器)。
而在最开始,地形对象监听了场景管理器的SceneManager.firePreFindVisibleObjects方法,而这个方法正好仅仅在计算场景可见渲染对象的前一步:
Class Terrain : public SceneManager::Listener
![](http://www.cppblog.com/Images/dot.gif)
![](http://www.cppblog.com/Images/dot.gif)
![](http://www.cppblog.com/Images/dot.gif)
![](http://www.cppblog.com/Images/dot.gif)
![](http://www.cppblog.com/Images/dot.gif)
于是在渲染场景的时候,每个地形的四叉树节点由2个紧邻的分计算完成。首先预计算地形Lod,然后的计算其实是一个通用的场景管理器计算过程,地形并未与场景中其他节点有所不同。
(地形预计算Lod )
(场景管理器通用渲染过程)
实际计算场景中的可见对象时,如果当前地形四叉树的影子Movable对象在相机中可见,那么只要这个四叉树节点中的当前Lod不是-1,就将其加入到渲染队列。还记得当前Lod表示的时候这个四叉树节点LodLevel层级中需要被渲染的那个Lod,这样在这里,这个渲染对象还是间接的渲染的一个LodLevel,这个真实的渲染对象在于场景管理器打交道的时候,表现为它的影子渲染对象TerrainQuadTreeNode.Rend。
如此这般,场景中需要渲染的对象都组装好了,好好的躺在渲染队列中。万事俱备只欠东风,是时候渲染了,这个渲染方法SceneManager.renderVisibleObjects在场景对象的预计算、实际计算两个步骤之后。
之前说过,加入到渲染队列的是影子渲染对象,真实渲染对象是一个LodLevel,这是一个很好的桥接模式,避免了地形四叉树场景八叉树之间的耦合。其实实现原理也非常简单,不能直接交互的2个对象之间需要交互,就让可以变通的对象投其所好,构建一个让另外那个对象熟悉的影子对象给它使用,只是在这个引子对象被访问的时候,将访问权限还是还给它的“真身”。
这样在渲染具体的LodLevel的时候,立即通过当前Lod标记找到这个具体的LodLevel,装配好需要的数据交给GPU完成图形设备的渲染流程。
以下转自:http://blog.sina.com.cn/s/blog_53a68f770100npk4.html
Ogre的场景管理体系:
Octree Scene Manager
> Terrain Scene Manager (Small Terrain)
>Paging Scene Manager (Big Terrain)
Portal Connected Zone Scene Manager (提取source并且编译: https://ogreaddons.svn.sourceforge.net/svnroot/ogreaddons/trunk/paginglandscape/ )
PagingLandScapeSceneManager
PagingLandScapeSceneManager实现了场景管理。组织PagingLandScapeRenderables成一个完整的地形。它调用setWorldGeometry从一个.cfg中获取地形信息,并创建地形。
PagingLandScapeTileManager
PagingLandScapeTileManager实现了对所有PagingLandScapeTile的管理。最多可以管理256个PagingLandScapeTile。PagingLandScapeTileManager会把所有生成的PagingLandScapeTile的指针保存到一个PagingLandScapeQueue< PagingLandScapeTile > mQueue中。
PagingLandScapeTile
PagingLandScapeTile表示一个地形Tile,是地形的最小单元。
PagingLandScapeListenerManager
PagingLandScapeListenerManager根据事件的类型,通过维护多种事件侦听器的列表,实现了对场景管理的多种类型的事件侦听,实现了Observer模式。这些类型包括:
- 地形就绪:mTerrainReadyListeners - 触发函数为fireTerrainReady
- 地形页方面:
- 地形页显示:mShowPageListeners - 触发函数为firePageShow
- 地形页隐藏:mHidePageListeners- 触发函数为firePageHide
- 地形加载前:mPreloadPageListeners- 触发函数为firePagePreloaded
- 地形页加载:mLoadPageListeners- 触发函数为firePageLoaded
- 地形页卸载:mUnloadPageListeners- 触发函数为firePageUnloaded
- 地形页卸载后:mPostunloadPageListeners- 触发函数为firePagePostunloaded
- 地形页修改:mModifyPageListeners- 触发函数为firePagePreloaded
- 地形Tile方面:
- 地形Tile显示:mShowTileListeners- 触发函数为fireTileShow
- 地形Tile隐藏:mHideTileListeners- 触发函数为fireTileHide
- 地形Tile加载:mLoadTileListeners- 触发函数为fireTileLoaded
- 地形Tile卸载:mUnloadTileListeners- 触发函数为fireTileUnloaded
- 地形Tile修改:mModifyTileListeners- 触发函数为fireTileDeformed
PagingLandScapeData2DManager
PLSM通过提供Data Source Loading Modes 支持多种数据源,这些都派生自Ogre::PagingLandScapeData2D,这些数据源都通过Ogre::PagingLandScapeData2DManager来管理。Ogre::PagingLandScapeData2DManager采用类似原型化得Abstract Factory模式来管理这些数据源。这些数据源包括:
- Ogre::PagingLandScapeData2D_HeightField : Loads data from a 8bits grey image.
- Ogre::PagingLandScapeData2D_HeightFieldN : Loads data from a 8bits grey image, and Normals from 32bits image
- Ogre::PagingLandScapeData2D_HeightFieldRaw : Loads data from 16bits rawimage
- Ogre::PagingLandScapeData2D_HeightFieldRawTC : Loads data from 16bits rawimage and expands heights using TC algo.
- Ogre::PagingLandScapeData2D_HeightFieldTC : Loads data from 8bits grey image and expands heights using TC algo.
- Ogre::PagingLandScapeData2D_HeightFieldNTC : Loads data from 8bits grey image and expands heights using TC algo, and Normals from 32bits image
- Ogre::PagingLandScapeData2D_Spline : create on the fly spline terrain
- Ogre::PagingLandScapeData2D_HeightFieldBlendNeighbor: Loads data from a 8bits grey image, and blend it with already loaded neighbors. (dynamically created pages) intensively tested and supported.
PagingLandScapeTextureManager
PagingLandScapeTextureManager支持多种Texture Format,Ogre::PagingLandScapeTextureManager采用类似原型化的Abstract Factory模式来管理这些Texture Format或者说Texture Type。
PagingLandScapeIndexBufferManager
PagingLandScapeIndexBufferManager是Index Buffer管理器。
PagingLandScapeTextureCoordinatesManager
PagingLandScapeTextureCoordinatesManager是texture coordinate管理器。
PagingLandScapeRenderable
PagingLandScapeRenderable是地形渲染单元。
PagingLandScapeRenderableManager
PagingLandScapeRenderableManager是渲染管理器。它负责管理所有的PagingLandScapeRenderables,采用PagingLandScapeRenderableSet组织PagingLandScapeRenderables。
PagingLandScapeRenderables
PagingLandScapeRenderables (Pool and Loading Queue)
PagingLandScapeRenderableSet
PagingLandScapeRenderableSet实现了所有预分配的PagingLandScapeRenderable的管理。
Ogre::PagingLandScapePageManager是page管理器
Ogre::PagingLandScapeTile是地形单元,Ogre::PagingLandScapeRenderable是渲染单元。这些渲染单元都通过Ogre::PagingLandScapeRenderableManager进行管理。PagingLandScapePageManager实现了类似操作系统中的调页机制,有空闲页和活动页概念。PagingLandScapePageManager还对frame event进行了侦听。
PagingLandScapeOptions
PagingLandScapeOptions对PagingLandscape2.cfg进行了解析,得到Map List。并调用loadMap加载map。对map的加载是解析map对应的.cfg配置文件,如g_canyon_height_4k2k.cfg。
渲染:
PagingLandScapeSceneManager::_updateSceneGraph
> mPageManager->updatePaging
>> PagingLandScapePageManager::getPage
> OctreeSceneManager::_updateSceneGraph(cam);
一个地形Page对应一个Scene Node,命名规范为PagingLandScapePage.PageX.PageZ.Node
一个地形Tile对应一个Scene Node,命名规范为PagingLandScapeTile.PageX.PageZ.TileX.TileZ.Node
PagingLandScapeRenderable::load
PagingLandScapeTile::load
PagingLandScapeRenderable | PagingLandScapeRenderableManager |
PagingLandScapeTile | PagingLandScapeTileManager |
PagingLandScapePage | PagingLandScapePageManager |
PagingLandScapeTexture | PagingLandScapeTextureManager |
PagingLandScapeTextureCoordinatesManager | |
参考: