地形
地形に関連したコンポーネントとして、Terrain、Paging、Propertyがあるらしい。Pagingは後のチュートリアルで扱い今回は扱わないとのこと。指示に従ってDemoAppクラスに、ヘッダファイルをインクルードし、Private変数とメソッドを追加。
#include <Terrain/OgreTerrain.h> #include <Terrain/OgreTerrainGroup.h>
: private: Ogre::TerrainGlobalOptions* mTerrainGlobals; Ogre::TerrainGroup* mTerrainGroup; bool mTerrainsImported; void defineTerrain(long x, long y); void initBlendMaps(Ogre::Terrain* terrain); void configureTerrainDefaults(Ogre::Light* light); :今回のチュートリアルでは2つの大きな構成要素として、TerrainGroupとTerrainを使う。もう1つ補助的な要素として、TerrainGlobalOptionsも使う。
TerrainGroupは、複数のTerrainを集約したもの。この分割によってLOD(level of detail)による描画、つまり各地形へのカメラからの距離に応じて詳細度を変えて描画ができる。
地形はマテリアルを貼られたタイルから構成される。今回はPagingも使わず、1つのTerrainGroupのみを使うとのこと。
カメラの設定
まずカメラの設定。OgreFramework::initOgre(...)のカメラの設定を次のように変更m_pCamera->setPosition(Ogre::Vector3(1683, 50, 2116)); m_pCamera->lookAt(Ogre::Vector3(1963, 50, 1660)); m_pCamera->setNearClipDistance(0.1); m_pCamera->setFarClipDistance(50000); if (m_pRoot->getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_INFINITE_FAR_PLANE)) { m_pCamera->setFarClipDistance(0); // enable infinite far clip distance if we can }カメラ位置の微妙に細かい数値の根拠は分からない。無限ファークリップがサポートされていれば、ファークリップに0を設定して無限ファークリップを使う。iPhoneで実行すると、このif文の中が実行されるのでサボートされているようである。
光源の設定
次に光源の設定。地形はライトマップの計算に平行光源を使うそうなので、DemoApp::setupDemoScene()で平行光源を配置。ついでに環境光も設定。Ogre::Vector3 lightdir(0.55, -0.3, 0.75); lightdir.normalise(); Ogre::SceneManager *sceneMgr = OgreFramework::getSingletonPtr()->m_pSceneMgr; Ogre::Light* light = sceneMgr->createLight("tstLight"); light->setType(Ogre::Light::LT_DIRECTIONAL); light->setDirection(lightdir); light->setDiffuseColour(Ogre::ColourValue::White); light->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4)); sceneMgr->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));
オプションの設定
次は地形オプションの生成。オプションにはグローバルオプションとローカルオプションがあるらしいが、最初はグローバルオプションから。グローバルオプションは全ての地形に共通的な設定。mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
地形グループ
そして、地形グループを作成する。mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(sceneMgr, Ogre::Terrain::ALIGN_X_Z, 513, 12000.0f); mTerrainGroup->setFilenameConvention(Ogre::String("BasicTutorial3Terrain"), Ogre::String("dat")); mTerrainGroup->setOrigin(Ogre::Vector3::ZERO);TerrainGroupクラスのコンストラクタには、シーンマネージャと、整列オプション、地形のサイズ、ワールドサイズを渡す。
そして、保存(?)する時に使用したい名前を設定し、原点位置を指定する。
次は平行光源を引数に、初期設定。
configureTerrainDefaults(light);全ての地形を設定し読み込む。
for (long x = 0; x <= 0; ++x) for (long y = 0; y <= 0; ++y) defineTerrain(x, y); mTerrainGroup->loadAllTerrains(true);ここでは、地形を1つしか使わないので、ループも一回しか通らない。
ブレンドマップ
次は、ブレンドマップの計算if (mTerrainsImported) { Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator(); while(ti.hasMoreElements()) { Ogre::Terrain* t = ti.getNext()->instance; initBlendMaps(t); } }そもそもブレンドマップとはなんぞや? なんだけど意味はよく分からない。その辺の説明は無いので知識有りが前提なんだろうか。とりあえずここでやってることは、全ての地形をループし、後で出てくるinitBlendMaps(...)を実行している。
後始末
最後にTerrain生成処理の後始末。mTerrainGroup->freeTemporaryResources();チュートリアルに提示されているソースでは、以下のコードがカメラの設定の後にあったので、DemoApp::initialiseRTShaderSystem(...)の中に同様に記述。
Ogre::MaterialManager::getSingleton().setDefaultTextureFiltering(Ogre::TFO_ANISOTROPIC); Ogre::MaterialManager::getSingleton().setDefaultAnisotropy(7);
地形の初期値の設定
Privateメソッド DemoApp::configureTerrainDefaults(...)を作成して、以下のコードを書く。void DemoApp::configureTerrainDefaults(Ogre::Light* light) { Ogre::SceneManager *sceneMgr = OgreFramework::getSingletonPtr()->m_pSceneMgr; // Configure global mTerrainGlobals->setMaxPixelError(8); // testing composite map mTerrainGlobals->setCompositeMapDistance(3000); }MaxPixelErrorは、Terrainの緻密さを決めるもので、値が小さいほど頂点が増えて緻密になるが、その分性能が落ちる。
CompositeMapDistanceは、Terrainがライトマップを描く時の距離の指定。この辺はよく分からない。
次は、引数で渡された平行光源を使ったライトマップの設定。
mTerrainGlobals->setLightMapDirection(light->getDerivedDirection()); mTerrainGlobals->setCompositeMapAmbient(sceneMgr->getAmbientLight()); mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());色や光源の設定を平行光源と環境光から取得して設定している。
次はインポートデータの設定
Ogre::Terrain::ImportData& defaultimp = mTerrainGroup->getDefaultImportSettings(); defaultimp.terrainSize = 513; defaultimp.worldSize = 12000.0f; defaultimp.inputScale = 600; defaultimp.minBatchSize = 33; defaultimp.maxBatchSize = 65;terrainSizeとworldSizeは、mTerrainGroupに設定した値と合わせてある。inputScaleはハイトマップ画像の拡縮の指定。この辺の意味もよく分からない。
最後にテクスチャの設定
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");defaultimp.layerList.resize(3)で、Terrainのテクスチャレイヤの数を指定し、それぞれのワールドサイズ、テクスチャ名を渡している。
ワールドサイズは、各テクスチャの大きさを指定するもので、値が小さいほどテクスチャレイヤの解像度が上がる。
規定のマテリアルジェネレータはレイヤ毎に次の2つのテクスチャをとる。1つは鏡面反射色をアルファチャネルに可能した拡散色テクスチャで、もう1つは、高さをアルファチャネルに格納したノーマルマップ
この辺もなんだかちんぷんかんぷんだけど、詳しくは、Ogre Terrain Texturesを読めとのこと。
地形の定義
次は、DemoApp::defineTerrain(...)メソッドの実装void DemoApp::defineTerrain(long x, long y) { Ogre::String filename = mTerrainGroup->generateFilename(x, y); if (Ogre::ResourceGroupManager::getSingleton().resourceExists(mTerrainGroup->getResourceGroup(), filename)) { mTerrainGroup->defineTerrain(x, y); } else { Ogre::Image img; getTerrainImage(x % 2 != 0, y % 2 != 0, img); mTerrainGroup->defineTerrain(x, y, &img); mTerrainsImported = true; } }最初に、Terrainの生成に使用するファイル名をTerrainGroupから取得している。
次に、リソースグループ中にそのファイル名があるかチェックする。もしあれば既にバイナリの地形ファイルを生成済みということで読み込む必要はない。もしなければ、地形を生成しなければならないので、イメージを読み込んで設定する。ここで使われているgetTerrainImage()は次のとおり。
この関数は一度しか使わないユーティリティ関数なのでクラスメンバでなくローカルな関数になっている。
void getTerrainImage(bool flipX, bool flipY, Ogre::Image& img) { img.load("terrain.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); if (flipX) img.flipAroundY(); if (flipY) img.flipAroundX(); }リソースの規定の場所から、terrain.pngを読み、必要に応じて反転させている。反転を使うことで1つの513x513サイズのハイトマップだけでシームレスな無限の地形を模倣することができるらしい。
地形のブレンド
次は、DemoApp::initBlendMaps(...)の実装。configureTerrainDefaults(...)の中で定義した3つのテクスチャレイヤーをタイルの高さに応じてブレンドする。void DemoApp::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* pBlend0 = blendMap0->getBlendPointer(); 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); *pBlend0++ = val; 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(); }チュートリアルでもこのコードの詳しい説明はない。なんにしろハイトマップに格納された値を使って地形の3つのレイヤをブレンドしているらしい。
実行
ということでいよいよ実行...上で使われているテクスチャは例によってSDKからコピーしておく
しかし、ビルドするとリンクでエラーになった。
全てTerrain関係のエラー。Terrainのライブラリが不足している様子。SDKのlib/releaseに、libOgreTerrainStatic.aというそれっぽいライブラリがあったので、Targetの"Build Settings"で、Other Linker Flagsに"-lOgreTerrainStatic"を追加したらビルドできた。...正しいのか?
で、今度こそ実行
ん〜、ダメ。なんとなく地形っぽい影は見えるんだけど、背景のページュ色に黒い模様らしきものが出ているだけ...フレームレートも30FPSしか出ていない。
チュートリアルでもこの時点で表示されるべき結果のスクリーンショットが無いので、何が正しいのか分からない。
ためしに、シミュレータで動かしてみる。
お! なんか出た。しかし、フレームレートが極端に低い1FPS前後しか出ていない。遅すぎ...
う〜ん、困った。そもそもポリゴン数が42000というのはiPhoneでは多すぎな気がする。まぁこの辺はパラメータで調整できるんだろうけど。しかし、iPhoneの環境では地形の利用は諦めた方がいいのかもしれない。どっちみち実機では表示できないし。
疲れたので、続きはまた今度。
しかし、チュートリアルの3回目にしてこの内容(しかもベーシックチュートリアル)って、ハードル高すぎないか?