青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

的筆記

隨時隨地編輯

Ogre TerrainGroup地形賞析

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

 

New Terrain Early Shots

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

TerrainGlobalOptions* mTerrainGlobals;
TerrainGroup* mTerrainGroup;
mTerrainGlobals = OGRE_NEW TerrainGlobalOptions();
mTerrainGlobals->setMaxPixelError(8);
mTerrainGlobals->setCompositeMapDistance(3000);
 
mTerrainGlobals->setLightMapDirection(l->getDerivedDirection());
mTerrainGlobals->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
mTerrainGlobals->setCompositeMapDiffuse(l->getDiffuseColour());

 

2、其次要創建TerrainGroup對象

mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(mSceneMgr, Ogre::Terrain::ALIGN_X_Z, 513, 12000.0f);
mTerrainGroup->setFilenameConvention(Ogre::String("BasicTutorial3Terrain"), Ogre::String("dat"));
mTerrainGroup->setOrigin(Ogre::Vector3::ZERO);

 

3、然后設置Terrain Group

// Configure default import settings for if we use imported image
Terrain::ImportData& defaultimp = mTerrainGroup->getDefaultImportSettings();
defaultimp.terrainSize = TERRAIN_SIZE;
defaultimp.worldSize = TERRAIN_WORLD_SIZE;
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");


     4、最后執行加載

mTerrainGroup->loadAllTerrains(true)
后續需要計算blendmaps 

6、清理Terrain Group

mTerrainGroup->freeTemporaryResources();

Terrain GroupTerrain的集合,如此可以取到集合里的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

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  加載地形文件

 OgreTerrain_d.dll!Ogre::Terrain::determineLodLevels
OgreTerrain_d.dll!Ogre::Terrain::prepare
OgreTerrain_d.dll!Ogre::Terrain::prepare
OgreTerrain_d.dll!Ogre::TerrainGroup::handleRequest
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::RequestHandlerHolder::handleRequest
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::processRequest
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::processRequestResponse
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::addRequest
OgreTerrain_d.dll!Ogre::TerrainGroup::loadTerrainImpl
OgreTerrain_d.dll!Ogre::TerrainGroup::loadAllTerrains
Sample_Terrain_d.dll!Sample_Terrain::setupContent

 

首先加載全局選項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。它有2LodLevel,其size33,這個LodLevel已經是不可劃分的批次最小size了。而非葉子節點的size都比允許的批次最大size大,并且它只有一個LodLevel,其size也是批次最大size

       由此可見,允許的批次最大和最小size是劃分樹節點和LodLevel的直接依據,它們約束了節點和Lod劃分的頂點尺寸。對于樹節點,簡單來說劃分的方法是將其平均分割成4塊,如果每塊比允許的批次最大size還要大,則用同樣的方式對它再次遞歸分割。對于Lod來說,如果其所有者樹節點不是葉子,那這個Lod就讓其大小設為批次的最大size,否則,就對其進行Lod細分,讓每個Lod盡量的小,但是不能小于批次允許的最小值。

       批次最大最小size約束存在的意義是,一方面讓每個lod的頂點盡可能的少,這樣在渲染的時候可以更準確的找出最少的空間分割塊,以便選擇盡可能少的頂點;另一方面每個Lod的頂點又不能太少,否則會增加顯卡的渲染批次。

 

 +[0]New Node,size:513,lod:4,depth:0,quadrant:0,batch range[33,65]
   +[1]New Node,size:257,lod:3,depth:1,quadrant:0,batch range[33,65]
            +[2]New Node,size:129,lod:2,depth:2,quadrant:0,batch range[33,65]
                  +[ 3]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[ 4]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[ 5]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[ 6]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[7]New Node,size:129,lod:2,depth:2,quadrant:1,batch range[33,65]
                  +[ 8]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[ 9]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[10]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[11]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[12]New Node,size:129,lod:2,depth:2,quadrant:2,batch range[33,65]
                  +[13]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[14]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[15]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[16]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[17]New Node,size:129,lod:2,depth:2,quadrant:3,batch range[33,65]
                  +[18]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[19]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[20]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[21]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
       +[22]New Node,size:257,lod:3,depth:1,quadrant:1,batch range[33,65]
            +[23]New Node,size:129,lod:2,depth:2,quadrant:0,batch range[33,65]
                  +[24]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[25]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[26]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[27]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[28]New Node,size:129,lod:2,depth:2,quadrant:1,batch range[33,65]
                  +[29]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[30]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[31]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[32]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[33]New Node,size:129,lod:2,depth:2,quadrant:2,batch range[33,65]
                  +[34]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[35]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[36]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[37]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[38]New Node,size:129,lod:2,depth:2,quadrant:3,batch range[33,65]
                  +[39]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[40]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[41]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[42]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
       +[43]New Node,size:257,lod:3,depth:1,quadrant:2,batch range[33,65]
            +[44]New Node,size:129,lod:2,depth:2,quadrant:0,batch range[33,65]
                  +[45]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[46]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[47]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[48]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[49]New Node,size:129,lod:2,depth:2,quadrant:1,batch range[33,65]
                  +[50]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[51]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[52]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[53]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[54]New Node,size:129,lod:2,depth:2,quadrant:2,batch range[33,65]
                  +[55]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[56]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[57]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[58]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[59]New Node,size:129,lod:2,depth:2,quadrant:3,batch range[33,65]
                  +[60]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[61]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[62]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[63]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
       +[64]New Node,size:257,lod:3,depth:1,quadrant:3,batch range[33,65]
            +[65]New Node,size:129,lod:2,depth:2,quadrant:0,batch range[33,65]
                  +[66]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[67]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[68]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[69]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[70]New Node,size:129,lod:2,depth:2,quadrant:1,batch range[33,65]
                  +[71]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[72]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[73]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[74]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[75]New Node,size:129,lod:2,depth:2,quadrant:2,batch range[33,65]
                  +[76]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[77]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[78]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[79]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
            +[80]New Node,size:129,lod:2,depth:2,quadrant:3,batch range[33,65]
                  +[81]New Node,size: 65,lod:1,depth:3,quadrant:0,batch range[33,65]
                  +[82]New Node,size: 65,lod:1,depth:3,quadrant:1,batch range[33,65]
                  +[83]New Node,size: 65,lod:1,depth:3,quadrant:2,batch range[33,65]
                  +[84]New Node,size: 65,lod:1,depth:3,quadrant:3,batch range[33,65]
 


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點:

偏移用+x向下,+y向左的二維坐標系計算

偏移值與頂點數有-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

 

由于我們預先定義好了批次中最大最小頂點數、地形的頂點尺寸(分別是6533513),通過這個預先定義好的值,可以生成一個基于四叉樹的LOD關系數據結構。這個數據結構可以映射到任意面積尺寸的地形中,例如一個單邊為12000的正方形的地形。

  

1.7  分配地形頂點

地形定義都取到后,就開始分配地形頂點數據了。

OgreTerrain_d.dll!Ogre::Terrain::distributeVertexData
OgreTerrain_d.dll!Ogre::Terrain::prepare
OgreTerrain_d.dll!Ogre::Terrain::prepare
OgreTerrain_d.dll!Ogre::TerrainGroup::handleRequest
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::RequestHandlerHolder::handleRequest
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::processRequest
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::processRequestResponse
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::addRequest
OgreTerrain_d.dll!Ogre::TerrainGroup::loadTerrainImpl
OgreTerrain_d.dll!Ogre::TerrainGroup::loadAllTerrains
Sample_Terrain_d.d 


(分配頂點的流程圖...只能看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一樣渲染。

 

由于渲染非常小的批次不理想,以及頂點總數本質上不會很大,所以創建一個單獨的頂點集還是有可行性。在出現遙遠的地形時也將會讓頂點緩存機制更高效。

 

我們可能需要一個更大尺寸的例子,因為在這種情況下只有層級1LOD0)需要使用這種單獨的頂點數據。較高細節的地形將會需要多種層次,這里有一個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個頂點數據集。完整的細節幾何,12916個子集(X16)這樣的有完全細節的幾何體被用作LODs0-2 LOD3不能使用這個子集,因為它需要通過這些子集進行組合,而且它只有8塊地磚,所以我們需要在每一個頂點數據段最大是129個頂點的時候構造出另外一個集合來滿足這個情況。因為在這種情況下LOD3需要整個257X257)個的頂點,所以我們仍然將129分割成2X2)個集合。雖然這一套集合是好用了,也包括了LOD5,但LOD6需要一個單一且連續的頂點集,所以我們為它構造了一個33x33的頂點集。

 

在頂點的數據存儲方面,這意味著當我們的主要數據是:

2049^ 2 =4198401個頂點

最終我們存儲的頂點數據是

16 *129^ 2+2* 129^ 2+ 33^ 2 =4327749個頂點

 

這相當于有3%的頂點冗余,但是為了從分組中減少批次它既必要又值得。此外,在LODs36(或樹深度為30)中將有機會釋放被更多細節LODs使用的數據,這在有巨大的地形的時候很重要。例如,如果我們在中等距離時為LOD0-2釋放(GPU)頂點數據,就會為地形節省平均有98%的內存開銷。

 

1.7.2  頂點數據

LODs在當前4叉樹節點構造時候被創建,其中有個字段指向頂點數據,這個頂點數據在讀取地形文件時被創建。

 

創建頂點數據

   //---------------------------------------------------------------------
   void TerrainQuadTreeNode::createGpuIndexData()
   {
      for (size_t lod = 0; lod < mLodLevels.size(); ++lod)
      {
        LodLevel* ll = mLodLevels[lod];
 
        if (!ll->gpuIndexData)
        {
           // clone, using default buffer manager ie hardware
           ll->gpuIndexData = OGRE_NEW IndexData();
           populateIndexData(ll->batchSize, ll->gpuIndexData);
        }
 
      }
   }

 

取回頂點數據

   //---------------------------------------------------------------------
   
//渲染數據
   void TerrainQuadTreeNode::getRenderOperation(RenderOperation& op)
   {
      mNodeWithVertexData->updateGpuVertexData();
 
      op.indexData = mLodLevels[mCurrentLod]->gpuIndexData;
      op.operationType = RenderOperation::OT_TRIANGLE_STRIP;
      op.useIndexes = true;
      op.vertexData = getVertexDataRecord()->gpuVertexData;
   }
 
 

 

1.7.3  同步-異步機制

異步加載機制是Steven Streeting離開前奉獻的一個重量級模塊Paging的核心功能,Paging的異步加載實現了一個通用的分頁機制,目前只知道在地形中有應用。但是這個優秀的分頁機制可以在所有時間、幀率、消息、事件、渲染命令出現瓶頸的時候使用其擴展的各種靈活策略進行異步和有區分度的分解處理從而降低單幀負載。其算法思想Steven StreetingOgre官網有詳細闡述,見章節“Page系統設計思想

       為了區別異步加載給地形帶來的復雜度,關閉了異步線程和地形Paging

 #define OGRE_THREAD_SUPPORT 0

 
//#define PAGING
 

        Root維護一個默認的工作隊列DefaultWorkQueue,執行類似壓入執行命令的邏輯用這個工作隊列完成。由于關閉了異步,壓入請求后會立即執行響應請求的子程序。

Class DefaultWorkQueue : public DefaultWorkQueueBase
OgreTerrain_d.dll!Ogre::TerrainQuadTreeNode::load
OgreTerrain_d.dll!Ogre::Terrain::load
OgreTerrain_d.dll!Ogre::TerrainGroup::handleResponse
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::processResponse
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::processRequestResponse
OgreMain_d.dll!Ogre::DefaultWorkQueueBase::addRequest
OgreTerrain_d.dll!Ogre::TerrainGroup::loadTerrainImpl
OgreTerrain_d.dll!Ogre::TerrainGroup::loadAllTerrains
Sample_Terrain_d.dll!Sample_Terrain::setupContent
Sample_Terrain_d.dll!OgreBites::SdkSample::_setup
 

 

1.8  動態LOD計算

動態LOD計算主要目的是通過相機與四叉樹節點的相對關系計算出幾個關鍵指標:

當前Lod,標記當前節點LodLevel中第N個被使用的Lod

當前節點是否渲染,標記當前節點帶領的子樹是否有節點需要被渲染

 

LodLevel定義

      Struct LodLevel{
        /// Number of vertices rendered down one side (not including skirts)
        uint16 batchSize;
        /// Index data on the gpu
        IndexData* gpuIndexData;
        /// Maximum delta height between this and the next lower lod
        Real maxHeightDelta;
        /// Temp calc area for max height delta
        Real calcMaxHeightDelta;
        /// The most recently calculated transition distance
        Real lastTransitionDist;
        /// The cFactor value used to calculate transitionDist
        Real lastCFactor;
 
        LodLevel() : gpuIndexData(0), maxHeightDelta(0), calcMaxHeightDelta(0),
           lastTransitionDist(0), lastCFactor(0) {}
      };

 

1.8.1  LodLevel數據細節

一個節點聚合了4LOD相關屬性

 

 

基礎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一樣;非葉子節點占用1Lod,從M+1開始,越往樹根走Lod越大。這樣Lod最大的節點就是樹根了。

       動態計算Lod的關鍵因素是相機到節點中心的距離與節點中LodLevel的過渡距離,后者好比是一把尺子,用于判斷這個Lod是否可以被相機可見。計算方法見TerrainQuadTreeNode::calculateCurrentLod

 

 

每幀地形的Lod計算過程是對四叉樹遞歸遍歷的過程。首先遍歷所有子樹,然后在進行自身的計算。將計算過錯分解為第一類計算過程和第二類計算過程:

bool TerrainQuadTreeNode::calculateCurrentLod(const Camera* cam, Real cFactor)
{
  mSelfOrChildRendered = false;
  ///------------------------------------------------------------------------
  //深度優先的遍歷,首先檢查第一個葉子節點,考察其可見性,然后是兄弟節點,軟后是父節點
  int childRenderedCount = 0;
  if (!isLeaf())
  {
    for (int i = 0; i < 4; ++i)
    {
       if (mChildren[i]->calculateCurrentLod(cam, cFactor))
          ++childRenderedCount;
    }

  }
 

  //需要渲染的子節點數是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第一類計算過程

 // no children were within their LOD ranges, so we should consider our own
Vector3 localPos = cam->getDerivedPosition() - mLocalCentre - mTerrain->getPosition();
       
//相機到當前節點中心的絕對距離
Real dist = localPos.length();
dist -= (mBoundingRadius * 0.5f);
 
// For each LOD, the distance at which the LOD will transition *downwards*
// is given by
// distTransition = maxDelta * cFactor;
 
uint lodLvl = 0;
mCurrentLod = -1;
for (LodLevelList::iterator i = mLodLevels.begin(); i != mLodLevels.end(); ++i, ++lodLvl)
{
   // If we have no parent, and this is the lowest LOD, we always render
   
// this is the 'last resort' so to speak, we always enoucnter this last
   if (lodLvl+1 == mLodLevels.size() && !mParent)
   {
      mCurrentLod = lodLvl;
      mSelfOrChildRendered = true;
      mLodTransition = 0;
   }
   else
   {
      // check the distance
 
      ///------------------------------------------------------------------------
      //計算過渡距離distTransition
      
// Calculate or reuse transition distance
      Real distTransition;
      LodLevel* ll = *i;
      if (Math::RealEqual(cFactor, ll->lastCFactor))
        distTransition = ll->lastTransitionDist;
      else
      {
        distTransition = ll->maxHeightDelta * cFactor;
        ll->lastCFactor = cFactor;
        ll->lastTransitionDist = distTransition;
      }
 
      ///------------------------------------------------------------------------
      //相機是否離Lod足夠近
      
//相機到節點中心的距離小于過渡距離,則讓其顯示
      
//對于葉子節點,Lod0的過渡距離小于Lod1
      if (dist < distTransition)
      {
        // we're within range of this LOD
        mCurrentLod = lodLvl;
        mSelfOrChildRendered = true;
 
        // Lod在節點中的存儲順序是用最高細節Lod到最低細節Lod的順序
        
// 碰到第一個Lod就結束了,因為這個Lod細節已經是最高了
        
// 一般是葉子的第一個Lod,也即Lod0
        break;
      }//~相機足夠近
 
   }
}//~foreach LodLevelList  

1.8.4  LOD第二類計算過程

//跳過自身的渲染;we should not render ourself
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)
 

第二類計算步驟比較簡單,當前節點有子節點參與渲染,那自身就不需要參與渲染了。首先標記下這個子樹需要被渲染,然后考察其遞歸子節點需要被渲染的數量,如果數量在[1,3]這個區間,則需要處理這個節點的4個直接子節點。

需要處理當前節點的4個直接子節點的了,如果這個子節點包含自身的樹都不需要渲染,則將這個節點的Lod數量-1。之前在預計算全局基礎數據時已經知道,就當前這個實例而言,葉子節點的Lod數量是2,非葉子節點的Lod數量是0。這樣,對于當前節點的4個子樹,如果這個子樹不參與渲染,則其當前Lod=0

  

1.9  渲染

地形四叉樹的渲染使用了2個小技巧。一是每個樹節點Hook了一個內嵌類TerrainQuadTreeNode.RendMovable參與到場景的管理,而它們并不是一個真實的場景對象和渲染對象,可以將它們理解為很多人喜歡使用的虛擬對象,在真正需要渲染的時候,將邏輯還是傳遞給TerrainQuadTreeNode。二是地形監聽了場景管理器預渲染方法,這個方法正好在場景管理器八叉樹遍歷場景對象前執行。好處顯而易見:讓復雜的程序結構清晰易讀。

首先,地形四叉樹只應用于到地形,而不干涉場景。地形在渲染方面主要做了三件事,一是構建了一個四叉樹和對應的Lod;二是構建了與每個Lod關聯的頂點數據;三是每個四叉樹節點都構造一個影子render對象和movable對象。

影子moveable對象在地形初次load的時候構建完成,每個四叉樹節點都會新建一個場景節點,并關節上這個引子movable對象,這樣讓每個地形四叉樹節點參與到場景八叉樹節點的可見性計算中(當前考慮的場景管理器是默認的八叉樹場景管理器)

 

而在最開始,地形對象監聽了場景管理器的SceneManager.firePreFindVisibleObjects方法,而這個方法正好僅僅在計算場景可見渲染對象的前一步:

 Class Terrain : public SceneManager::Listener

 
void SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)
{
   
   firePreFindVisibleObjects(vp);
 
   findVisibleObjects(camera);
   firePostFindVisibleObjects(vp);
   
 
    // Begin the frame
    mDestRenderSystem->_beginFrame();
 
    // Set rasterisation mode
    mDestRenderSystem->_setPolygonMode(camera->getPolygonMode());
 
   // Set initial camera state
   mDestRenderSystem->_setProjectionMatrix(mCameraInProgress->getProjectionMatrixRS());
  
  
    // Render scene content
   renderVisibleObjects();
  
    // End frame
mDestRenderSystem->_endFrame();
 
 ......

 

于是在渲染場景的時候,每個地形的四叉樹節點由2個緊鄰的分計算完成。首先預計算地形Lod,然后的計算其實是一個通用的場景管理器計算過程,地形并未與場景中其他節點有所不同。

OgreTerrain_d.dll!Ogre::TerrainQuadTreeNode::calculateCurrentLod
OgreTerrain_d.dll!Ogre::Terrain::calculateCurrentLod
OgreTerrain_d.dll!Ogre::Terrain::preFindVisibleObjects
OgreMain_d.dll!Ogre::SceneManager::firePreFindVisibleObjects
OgreMain_d.dll!Ogre::SceneManager::_renderScene
OgreMain_d.dll!Ogre::Camera::_renderScene
OgreMain_d.dll!Ogre::Viewport::update
OgreMain_d.dll!Ogre::RenderTarget::_updateViewport
RenderSystem_Direct3D9_d.dll!Ogre::D3D9RenderWindow::_updateViewport
OgreMain_d.dll!Ogre::RenderTarget::_updateAutoUpdatedViewports
OgreMain_d.dll!Ogre::RenderTarget::updateImpl
OgreMain_d.dll!Ogre::RenderTarget::update
OgreMain_d.dll!Ogre::RenderSystem::_updateAllRenderTargets
OgreMain_d.dll!Ogre::Root::_updateAllRenderTargets
OgreMain_d.dll!Ogre::Root::renderOneFrame

(地形預計算Lod )

OgreMain_d.dll!Ogre::RenderQueue::addRenderable
OgreTerrain_d.dll!Ogre::TerrainQuadTreeNode::updateRenderQueue
OgreTerrain_d.dll!Ogre::TerrainQuadTreeNode::Movable::_updateRenderQueue
OgreMain_d.dll!Ogre::RenderQueue::processVisibleObject
Plugin_OctreeSceneManager_d.dll!Ogre::OctreeNode::_addToRenderQueue
Plugin_OctreeSceneManager_d.dll!Ogre::OctreeSceneManager::walkOctree
Plugin_OctreeSceneManager_d.dll!Ogre::OctreeSceneManager::walkOctree
Plugin_OctreeSceneManager_d.dll!Ogre::OctreeSceneManager::walkOctree
Plugin_OctreeSceneManager_d.dll!Ogre::OctreeSceneManager::walkOctree
Plugin_OctreeSceneManager_d.dll!Ogre::OctreeSceneManager::_findVisibleObjects
OgreMain_d.dll!Ogre::SceneManager::_renderScene
OgreMain_d.dll!Ogre::Camera::_renderScene
OgreMain_d.dll!Ogre::Viewport::update
OgreMain_d.dll!Ogre::RenderTarget::_updateViewport
RenderSystem_Direct3D9_d.dll!Ogre::D3D9RenderWindow::_updateViewport
OgreMain_d.dll!Ogre::RenderTarget::_updateAutoUpdatedViewports
OgreMain_d.dll!Ogre::RenderTarget::updateImpl
OgreMain_d.dll!Ogre::RenderTarget::update
OgreMain_d.dll!Ogre::RenderSystem::_updateAllRenderTargets
OgreMain_d.dll!Ogre::Root::_updateAllRenderTargets
OgreMain_d.dll!Ogre::Root::renderOneFrame

 

(場景管理器通用渲染過程)

       實際計算場景中的可見對象時,如果當前地形四叉樹的影子Movable對象在相機中可見,那么只要這個四叉樹節點中的當前Lod不是-1,就將其加入到渲染隊列。還記得當前Lod表示的時候這個四叉樹節點LodLevel層級中需要被渲染的那個Lod,這樣在這里,這個渲染對象還是間接的渲染的一個LodLevel,這個真實的渲染對象在于場景管理器打交道的時候,表現為它的影子渲染對象TerrainQuadTreeNode.Rend

 

       如此這般,場景中需要渲染的對象都組裝好了,好好的躺在渲染隊列中。萬事俱備只欠東風,是時候渲染了,這個渲染方法SceneManager.renderVisibleObjects在場景對象的預計算、實際計算兩個步驟之后。

OgreTerrain_d.dll!Ogre::TerrainQuadTreeNode::getRenderOperation
OgreTerrain_d.dll!Ogre::TerrainQuadTreeNode::Rend::getRenderOperation
OgreMain_d.dll!Ogre::SceneManager::renderSingleObject
OgreMain_d.dll!Ogre::SceneManager::SceneMgrQueuedRenderableVisitor::visit
OgreMain_d.dll!Ogre::QueuedRenderableCollection::acceptVisitorGrouped
OgreMain_d.dll!Ogre::QueuedRenderableCollection::acceptVisitor
OgreMain_d.dll!Ogre::SceneManager::renderObjects
OgreMain_d.dll!Ogre::SceneManager::renderBasicQueueGroupObjects
OgreMain_d.dll!Ogre::SceneManager::_renderQueueGroupObjects
OgreMain_d.dll!Ogre::SceneManager::renderVisibleObjectsDefaultSequence
OgreMain_d.dll!Ogre::SceneManager::_renderVisibleObjects
OgreMain_d.dll!Ogre::SceneManager::_renderScene
OgreMain_d.dll!Ogre::Camera::_renderScene
OgreMain_d.dll!Ogre::Viewport::update
OgreMain_d.dll!Ogre::RenderTarget::_updateViewport
RenderSystem_Direct3D9_d.dll!Ogre::D3D9RenderWindow::_updateViewport
OgreMain_d.dll!Ogre::RenderTarget::_updateAutoUpdatedViewports
OgreMain_d.dll!Ogre::RenderTarget::updateImpl
OgreMain_d.dll!Ogre::RenderTarget::update
OgreMain_d.dll!Ogre::RenderSystem::_updateAllRenderTargets
OgreMain_d.dll!Ogre::Root::_updateAllRenderTargets
OgreMain_d.dll!Ogre::Root::renderOneFrame

 

//渲染數據
void TerrainQuadTreeNode::getRenderOperation(RenderOperation& op)
{
   mNodeWithVertexData->updateGpuVertexData();
 
   op.indexData = mLodLevels[mCurrentLod]->gpuIndexData;
   op.operationType = RenderOperation::OT_TRIANGLE_STRIP;
   op.useIndexes = true;
   op.vertexData = getVertexDataRecord()->gpuVertexData;
}

 


       之前說過,加入到渲染隊列的是影子渲染對象,真實渲染對象是一個LodLevel,這是一個很好的橋接模式,避免了地形四叉樹場景八叉樹之間的耦合。其實實現原理也非常簡單,不能直接交互的2個對象之間需要交互,就讓可以變通的對象投其所好,構建一個讓另外那個對象熟悉的影子對象給它使用,只是在這個引子對象被訪問的時候,將訪問權限還是還給它的“真身”。

       這樣在渲染具體的LodLevel的時候,立即通過當前Lod標記找到這個具體的LodLevel,裝配好需要的數據交給GPU完成圖形設備的渲染流程。


posted on 2013-05-07 23:32 的筆記 閱讀(5532) 評論(3)  編輯 收藏 引用

評論

# re: Ogre TerrainGroup地形賞析 2013-05-09 22:53 eryar

使用LiveWriter可以貼圖……  回復  更多評論   

# re: Ogre TerrainGroup地形賞析 2013-05-10 21:32 夸父的筆記

多謝@eryar
  回復  更多評論   

# re: Ogre TerrainGroup地形賞析 2013-05-22 01:49 Render Donkey

樓主很認真啊。 我反正是寫不出這么細致的BLOG。  回復  更多評論   


只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲激情午夜| 亚洲综合成人在线| 久久精品国产精品亚洲综合| 麻豆亚洲精品| 久久亚洲私人国产精品va| 亚洲精品黄网在线观看| 国产精品久久久久久久9999| 亚洲自拍偷拍麻豆| 羞羞色国产精品| 欧美视频在线观看免费| 欧美在线不卡| 久久天堂国产精品| 午夜视黄欧洲亚洲| 久久影视精品| 麻豆av福利av久久av| 一本久道久久综合婷婷鲸鱼| 国产色视频一区| 欧美黄色大片网站| 国模精品一区二区三区| 欧美自拍偷拍| 日韩五码在线| 日韩一二三区视频| 性8sex亚洲区入口| 久久本道综合色狠狠五月| 久久er精品视频| 噜噜噜躁狠狠躁狠狠精品视频| 宅男66日本亚洲欧美视频| 亚洲国产合集| 欧美性一二三区| 欧美精品综合| 国产精品欧美日韩一区| 欧美日韩美女在线| 欧美区一区二区三区| 久久久久网址| 亚洲大胆人体视频| 亚洲国产婷婷香蕉久久久久久99| 亚洲国产精品第一区二区| 亚洲一区日韩在线| 最新日韩av| 欧美gay视频激情| 亚洲视频在线观看网站| 1000精品久久久久久久久| 亚洲激情视频在线观看| 一区二区三区视频在线| 欧美一区亚洲一区| 日韩视频亚洲视频| 久久久久久穴| 影音先锋亚洲精品| 久久国内精品视频| 亚洲永久在线观看| 中文精品一区二区三区| 亚洲夜间福利| 久久资源av| 久久国产精品亚洲77777| 欧美日韩黄视频| 亚洲美女在线一区| 欧美成人精品激情在线观看| 久久成人综合视频| 亚洲大片av| 久久夜色精品国产欧美乱| 欧美中文字幕不卡| 极品少妇一区二区三区精品视频| 亚洲欧美在线网| 亚洲综合首页| 在线播放豆国产99亚洲| 美脚丝袜一区二区三区在线观看 | 午夜综合激情| 韩日成人av| 亚洲国产婷婷综合在线精品| 欧美激情bt| 国产真实精品久久二三区| 亚洲国产视频a| 欧美久久在线| 亚洲精品资源| 国产私拍一区| 欧美一区二区| 久久青青草综合| 亚洲国产导航| 欧美激情亚洲一区| 欧美在线综合视频| 一区二区91| 香蕉久久精品日日躁夜夜躁| 狠狠色丁香婷婷综合| 亚洲人成高清| 国产伊人精品| 国产综合色产在线精品| 亚洲国产精品一区二区www在线| 日韩视频一区二区三区| 伊人成人在线视频| 久久精品视频导航| 久久人人看视频| 激情自拍一区| 玖玖综合伊人| 亚洲国内自拍| 亚洲小说欧美另类社区| 欧美天堂亚洲电影院在线播放| 亚洲人成在线播放网站岛国| 亚洲免费观看在线视频| 亚洲国产午夜| 欧美日韩免费观看一区二区三区| 久久国产精品久久国产精品| 老鸭窝毛片一区二区三区| 亚洲精品视频在线观看免费| 日韩亚洲欧美一区| 欧美成人午夜免费视在线看片 | 韩国三级电影久久久久久| 国产日韩欧美一区二区| 欧美三级电影一区| 免费黄网站欧美| 国产精品亚洲美女av网站| 激情欧美一区二区| 亚洲欧美日韩电影| 欧美激情一区二区三区不卡| 亚洲精品午夜精品| 国产精品九九久久久久久久| 亚洲欧美日韩中文在线制服| 欧美岛国在线观看| 午夜久久久久久久久久一区二区| 欧美成人一品| 久久午夜色播影院免费高清| 在线观看三级视频欧美| 欧美精品观看| 欧美三级在线视频| 欧美黄色一区二区| 久久夜色精品国产| 欧美一区综合| 午夜精品www| 久久久www成人免费无遮挡大片| 日韩午夜在线播放| 亚洲最新色图| 亚洲午夜小视频| 亚洲视频视频在线| 正在播放亚洲| 羞羞色国产精品| 欧美一区二区在线免费播放| 狠狠狠色丁香婷婷综合激情| 国产日韩欧美一区在线 | 欧美一区二区大片| 欧美一区午夜视频在线观看| 日韩一级在线| 亚洲无线视频| 久久久久久91香蕉国产| 久久视频一区| 欧美日韩在线观看视频| 国产精品区免费视频| 亚洲成色www8888| 亚洲精品你懂的| 久久国产日本精品| 欧美黄在线观看| 久久五月天婷婷| 亚洲久久成人| 久久综合九色九九| 欧美视频不卡| 在线成人av.com| 久久国产精品网站| 亚洲欧洲精品一区二区精品久久久| 亚洲国产精品va在线看黑人动漫| 日韩一区二区精品在线观看| 亚洲在线视频| 欧美黑人多人双交| 欧美中文字幕久久| 国产精品免费一区豆花| 欧美日韩免费| 亚洲精品国产精品国自产观看浪潮| 亚洲天堂av在线免费观看| 久久精品国产96久久久香蕉| 欧美一区二区三区视频| 欧美视频在线观看| 在线免费观看日本一区| 欧美一级视频精品观看| 亚洲欧美日韩国产精品| 国产亚洲高清视频| 一区二区三区视频在线| 午夜精品在线| 亚洲视频高清| 亚洲电影在线免费观看| 国产精品日韩久久久| 亚洲国产精品成人精品| 国产精品亚洲一区二区三区在线| 久久综合给合久久狠狠狠97色69| 久久精品国产亚洲一区二区| 在线一区二区三区四区五区| 午夜在线成人av| 亚洲精品人人| 欧美jizz19hd性欧美| 久久久久久有精品国产| 欧美日韩国产色站一区二区三区| 亚洲综合日韩在线| 久久夜色精品国产亚洲aⅴ| 亚洲欧美中文字幕| 欧美日韩久久| 一本久久a久久精品亚洲| 亚洲另类黄色| 免费欧美高清视频| 男女激情久久| 欧美不卡视频一区发布| 亚洲第一精品夜夜躁人人爽| 亚洲第一在线综合在线| 在线播放亚洲一区| 永久久久久久|