您的位置:首页 > 其它

Ogre地形组件学习

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

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



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







跟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的如下数据:

The terrain geometry

The terrain error metrics which determine LOD transitions

The terrain normal map, if present

The terrain lighting map, if present

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的顶点,索引缓存具体怎么由该类来进行分配计算和管理现在没搞清楚。



找到的资料:







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个树:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: