Ogre地形组件学习

  学无止境,把这几年收藏的文章都晒出来,大家共享吧! 声明:早期转载的文章未标明转载敬请原谅,以后将陆续改过来,向原创者致敬! C++ , Direct3D, OpenGL, GPU,OGRE,OSG,STL, Lua, Python, MFC, Win32 (有问题可留言,部分网页看不到图片可网页另存为到本地再打开即可看到) 痞子龙3D编程 QQ技术交流群:32103634
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  以前学习笔记的角度是从程序流程,今次学习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 getLayerBlendMapSize(); ++y)
  {
  for (Ogre::uint16 x =0; x 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的如下数据: TerrainGlobalOptions:
  TerrainGlobalOptions必须在使用其他地形类之前构造。该类用来配置地形的全局选项,如:setMaxPixelError(),setLightMapDirection(),setCompo siteMapDiffuse()这些。
  TerrainGroup:
  TerrainGroup管理构成整个世界的若干Terrain Instance。
  含有Protected成员变量:TerrainSlotMap mTerrainSlots;
  typedef map::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 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个树:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值