1. 程式人生 > >Ogre基礎教程3:地形,天空,煙霧

Ogre基礎教程3:地形,天空,煙霧

本教程將專注於在一個場景中渲染地形。
我們將涉及到需要被完成的基本設定,並且將介紹地形光照的使用。
我們也會給出對使用天空盒(Skyboxes)、天空穹頂(Skydomes)以及天空面(Skyplanes)來模擬一片天空的一個簡明介紹。
最後,我們會解釋如何對場景新增一個煙霧效果。

[必備條件]
本教程假設,你已經知道如何設定一個Ogre專案,並且成功地編譯它。如果你需要這些內容的幫助,請閱讀 連結:設定一個應用程式。這個教程也是基礎教程系列的一部分,並且假設已經掌握了前置教程出現的知識。
注意:請忽略螢幕上的FPS狀態。它們是在一臺過時的計算機上被渲染的。

目錄

前提條件
設定場景
專案設定
Visual Studio
Code::Blocks
CMake
AutoTools
對地形的一個介紹
設定相機
為我們的地形設定一個光照
地形的配置
書寫:預設地形配置
書寫:地形定義
書寫:得到地形影象
書寫:初始化混合地圖
目前的場景
地形讀取標籤
清除
天空盒
天空穹頂
天空面
煙霧
為我們的場景新增煙霧
總結

場景設定
我們希望做的第一件事,是為我們的類增加一些方法和變數。將你的教程應用類進行如下的設定

TutorialApplication.h

#include <Terrain/OgreTerrain.h>
#include <Terrain/OgreTerrainGroup.h>

#include "BaseApplication.h"

class TutorialApplication : public BaseApplication
{
public:
  TutorialApplication();
  virtual ~TutorialApplication();

protected:
  virtual void createScene();
  virtual void createFrameListener();
  virtual void destroyScene();
  virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe);

private:
  void defineTerrain(long x, long y);
  void initBlendMaps(Ogre::Terrain* terrain);
  void configureTerrainDefaults(Ogre::Light* light);

  bool mTerrainsImported;
  Ogre::TerrainGroup* mTerrainGroup;
  Ogre::TerrainGlobalOptions* mTerrainGlobals;
  OgreBites::Label* mInfoLabel;

};

TutorialApplication.cpp

#include "TutorialApplication.h"

TutorialApplication::TutorialApplication()
  : mTerrainGroup(0),
    mTerrainGlobals(0),
    mInfoLabel(0)
{
}

TutorialApplication::~TutorialApplication()
{
}

void TutorialApplication::createScene()
{
}

void TutorialApplication::createFrameListener()
{
  BaseApplication::createFrameListener();
}

void TutorialApplication::destroyScene()
{
}

bool TutorialApplication::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
  bool ret = BaseApplication::frameRenderingQueued(fe);

  return ret;
}

void getTerrainImage(bool flipX, bool flipY, Ogre::Image& img)
{
}

void TutorialApplication::defineTerrain(long x, long y)
{
}

void TutorialApplication::initBlendMaps(Ogre::Terrain* terrain)
{
}

void TutorialApplication::configureTerrainDefaults(Ogre::Light* light)
{
}

// MAIN FUNCTION OMITTED FOR SPACE

專案設定:略

對地形的一個介紹
使用舊版本Ogre時,我們不得不使用“地形場景管理器(Terrain Scene Manager)”在一個場景中渲染地形。
這是一個與你其它的管理器獨立執行的一個單獨的場景管理器。
新的Ogre地形系統已經轉移到不要求使用一個單獨管理器的元件系統中。
從Ogre 1.7以來,有三個地形元件:地形(Terrain),分頁(Paging)以及效能(Property)。
分頁元件與地形元件被同時使用,來幫助優化大型地形。它將在未來的教程中被覆蓋。
本教程將關注於地形元件。
為了設定地形,我們將關注於兩個主要的類:Terrain(external link)和TerrainGroup(external link)。
Terrain類代表地形的一個塊,TerrainGroup控制一系列Terrain塊。
它被用於細節層次(Level of Detail,LOD)渲染。細節層次渲染減少了遠離攝像機處地形的解析度。
一個單獨的地形物件包括 帶有材質對映到它們上的拼貼。
我們將使用一個單獨的、沒有分頁的TerrainGroup。

設定攝像機

讓我們首先設定攝像機。將以下程式碼新增到createScene的開始:

mCamera->setPosition(Ogre::Vector3(1683, 50, 2116));
mCamera->lookAt(Ogre::Vector3(1963, 50, 1660));
mCamera->setNearClipDistance(0.1);

這裡的內容看起來應該是與之前教程很相似的。

bool infiniteClip =
  mRoot->getRenderSystem()->getCapabilities()->hasCapability(
    Ogre::RSC_INFINITE_FAR_PLANE);

if (infiniteClip)
  mCamera->setFarClipDistance(0);
else
  mCamera->setFarClipDistance(50000);

我們做的最後一件事,是檢查我們當前的渲染系統是否有能力處理一個無限遠的裁剪距離。如果可以,那我們設定最遠裁剪距離為0(這代表著沒有遠距離裁剪)。如果不行,我們簡單地將裁剪距離設定得很高,這樣我們可以看到遠處的地形。

為我們的地形設定一個光照

地形元件可以使用一個直接的光照來計算光線對映。為此增加一個光照;並且當我們在其中時,增加一些環境光給場景。

mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));

Ogre::Vector3 lightdir(0.55, -0.3, 0.75);
lightdir.normalise();

Ogre::Light* light = mSceneMgr->createLight("TestLight");
light->setType(Ogre::Light::LT_DIRECTIONAL);
light->setDirection(lightdir);
light->setDiffuseColour(Ogre::ColourValue::White);
light->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4));

如果你被它們任何一個所困擾,這些其實在前面的教程裡也提到。標準化方法(normalise method)將使向量的長度等於1,保持方向不變;這是對向量處理時常見的行為;目的是為了避免在計算中出現額外的係數。

配置地形

現在我們將進入實際的地形配置。首先,我們使用OGRE_NEW巨集,建立一個地形全域性選項(TerrainGlobalOptions)。

mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();

這是一個儲存了我們將建立的地形所有資訊的類;因此它們被稱為全域性選項。它也提供了一些獲取器(getter)和設定器(setter)。對每個地形組也有一些區域性選項,我們將在本教程稍後部分看到。
接下來,我們構建一個我們自己的地形組物件。這將管理一些地形(a grid of Terrains)。

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

地形組建構函式將場景管理器作為它的第一個引數。之後是一個對齊選項,地形尺寸,以及地形世界尺寸。你可以閱讀類引用來獲得更多資訊。setFilenameConvention允許我們選擇我們的地形將如何被儲存。最後,我們設定我們地形中使用的原點。
我們要做的下一件事,是呼叫我們的地形配置方法;我們很快會填寫完它。要確保將我們建立的光照作為引數傳遞。

configureTerrainDefaults(light);

我們下一件要做的是定義我們的地形,並要求地形組將它們全部讀取。

for (long x = 0; x <= 0; ++x)
  for (long y = 0; y <= 0; ++y)
    defineTerrain(x, y);

mTerrainGroup->loadAllTerrains(true);

我們也只使用一個單獨的地形,這樣方法會只被呼叫一次。for迴圈在我們的例子中只是為了示範。同樣滴,地形定義方法我們很快在隨後完成它。
現在我們為我們的地形初始化混合地形。

if (mTerrainsImported)
{
  Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();

  while (ti.hasMoreElements())
  {
    Ogre::Terrain* t = ti.getNext()->instance;
    initBlendMaps(t);
  }
}

從我們地形組中,我們得到了一個地形的迭代器;然後它們迴圈所有的地形元素並且初始化它們的混合地圖——initBlendMaps方法也在後面完成。mTerrainsImported變數將在我們完成configureTerrainDefaults函式時,於其中被設定。

最後要做的是:確保清除所有在配置我們的地形時被生成的臨時資源。

mTerrainGroup->freeTemporaryResources();

這樣就完成了我們的createScene方法。現在我們只要完成我們跳過了的所有方法。

填寫 configureTerrainDefaults

Ogre的地形元件有大量的選項可以被設定,來改變地形的渲染方式。我們從新增以下內容到configureTerrainDefaults開始:

mTerrainGlobals->setMaxPixelError(8);
mTerrainGlobals->setCompositeMapDistance(3000);

我們在這裡設定了兩個全域性選項。
第一個呼叫設定:在我們理想地形與被建立來渲染它的mesh之間,畫素之間允許的最大錯誤。一個更小的數值意味著更加精確的地形,因為它要求更多的向量來降低錯誤。
第二個呼叫決定了在什麼距離,Ogre仍將應用我們的光線對映。如果你調高這個數值,你將看到Ogre將這個光照影響應用到更遠的距離。

下一步,將我們的光照資訊傳遞給我們的地形。

mTerrainGlobals->setLightMapDirection(light->getDerivedDirection());
mTerrainGlobals->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());

在第一個呼叫中,我們確保呼叫了getDerivedDirection;對於被光照可能依附的任何場景結點,結點應用到我們的光照方向上的任何變換,將被應用。因為我們的光照被依附到了根節點,這將等同於呼叫getDirection;但這其中的不同應當被知曉。接下來兩個呼叫顧名思義:為我們的地形設定環境光和漫射顏色,來匹配我們的場景光照。
接下來,得到一個對我們的地形組重要設定的引用,並且設定一些基本的值。

Ogre::Terrain::ImportData& importData = mTerrainGroup->getDefaultImportSettings();
importData.terrainSize = 513;
importData.worldSize = 12000.0;
importData.inputScale = 600;
importData.minBatchSize = 33;
importData.maxBatchSize = 65;

我們不在本教程中講解這些選項的精確含義,但也許你已經注意到,terrainSize和worldSize被設定成匹配我們在createScene中所設定的全域性選項。inputScale決定了高度圖片將如何被為場景按比例放大。我們使用了稍微大的尺寸,因為我們的高度圖圖片有限的精密度。你可以使用浮點原始高度圖來避免應用任何輸入的放大,但這些圖片通常要求一些資料壓縮。
最後一步是新增我們地形將要使用的紋理。首先我們調整列表的大小來儲存三個紋理。

importData.layerList.resize(3);

然後我們設定每個紋理的worldSize並且將它們新增到列表中。

importData.layerList[0].worldSize = 100;
importData.layerList[0].textureNames.push_back(
  "dirt_grayrocky_diffusespecular.dds");
importData.layerList[0].textureNames.push_back(
  "dirt_grayrocky_normalheight.dds");
importData.layerList[1].worldSize = 30;
importData.layerList[1].textureNames.push_back(
  "grass_green-01_diffusespecular.dds");
importData.layerList[1].textureNames.push_back(
  "grass_green-01_normalheight.dds");
importData.layerList[2].worldSize = 200;
importData.layerList[2].textureNames.push_back(
  "growth_weirdfungus-03_diffusespecular.dds");
importData.layerList[2].textureNames.push_back(
  "growth_weirdfungus-03_normalheight.dds");

紋理的worldSize決定了當被應用到地形中時,紋理的每個貼圖將有多大。一個更小的數值將提高渲染的紋理圖層解析度,因為每一片將被較小地拉伸來填充地形。

預設的材質生成器要求每個圖層兩個紋理:一個diffuse specular紋理和一個高度圖紋理。
如果你想學習更多關於這些紋理以及它們如何被製造的內容,你可以閱讀Ogre地形紋理。
在本教程中使用的紋理在你SDK的Samples目錄中或在原始碼釋出包中。
請記住:當讀取資源時,Ogre不會自動地搜尋子目錄;所以你要新增一行到你的resource.cfg檔案中,來告訴Ogre來包含nvidia目錄;並且,你要將實際的紋理拷貝到你專案的media資料夾中。

填寫 defineTerrain

現在我們將處理defineTerrain方法。首先要求TerrainGroup定義一個為這個地形唯一的檔名。新增以下內容到defineTerrain中:

Ogre::String filename = mTerrainGroup->generateFilename(x, y);

我們想檢查這個檔名是否已經被生成過。

bool exists =
  Ogre::ResourceGroupManager::getSingleton().resourceExists(
    mTerrainGroup->getResourceGroup(),
    filename);

如果它已經被生成,那我們呼叫TerrainGroup::defineTerrain方法來用之前生成的檔名自動設定這個網格位置。如果它沒有被生成,我們用getTerrainImage生成一個影象,然後呼叫TerrainGroup::defineTerrain一個不同的過載,一個我們生成影象的引用。最後,我們設定mTerrainsImport標誌為真。

if (exists)
  mTerrainGroup->defineTerrain(x, y);
else
{
  Ogre::Image img;
  getTerrainImage(x % 2 != 0, y % 2 != 0, img);
  mTerrainGroup->defineTerrain(x, y, &img);

  mTerrainsImported = true;
}

你可能需要花點時間檢視這個方法來完全搞懂它。請注意,有三個不同的defineTerrain方法。其中的一個來自於TutorialApplication,另外兩個來自於TerrainGroup。

填寫 getTerrainImage

我們需要填寫被defineTerrain在最後一步中使用的輔助函式。這個函式是一個靜態的區域性函式。如果你已經移動了其它函式定義的位置,那麼確保這個函式被定義在defineTerrain之前。因為它不是一個成員函式,它需要在被使用前就定義。將以下新增到getTerrainImage中:

img.load("terrain.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

if (flipX)
  img.flipAroundY();
if (flipY)
  img.flipAroundX();

這將載入你的‘terrain.png’資源。確保它已經被新增到你的一個資源載入路徑中。此檔案也包含在Ogre的Samples目錄中。

flip被用來建立無縫地形;這樣使用一個單一的高度圖,無限地形可以被建立。如果你地形的高度圖已經是無縫的,那麼你不需要使用這個技巧。在我們的情形中,flipping程式碼也是無用的,因為我們使用一個1*1的地形組。flipping一個1*1片不改變任何東西;這只是個示範。

填寫 initBlendMaps

最後,通過完成initBlendMaps方法,我們將完成configuration方法。這個方法將我們在configureTerrainDefaults中定義的不同圖層混合在一起。目前,你大概相當程度地視此方法為一個魔術。本篇教程不涉及細節。基本上,這個方法基於在點上的地形高度,混合了紋理。這不是進行混合的唯一途徑。這是一個複雜的話題,處於Ogre與它希望抽象化的事情之間的邊緣。將以下內容新增到initBlendMaps:

Ogre::Real minHeight0 = 70;
Ogre::Real fadeDist0 = 40;
Ogre::Real minHeight1 = 70;
Ogre::Real fadeDist1 = 15;

Ogre::TerrainLayerBlendMap* blendMap0 = terrain->getLayerBlendMap(1);
Ogre::TerrainLayerBlendMap* blendMap1 = terrain->getLayerBlendMap(2);

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();

到目前為止的場景

編譯並執行你的應用。你應該得到了一個被精細渲染的地形。

有許多我們可以改進的東西。我們將為覆蓋物增加一個允許在地形生成結束時讓我們看到的標籤。我們也將確保儲存了我們的地形,使它可以被過載而不是每次都要重建。最後,我們將確定在工作後清理。就像在c++中普通的”new”和”delete”,每個對’OGRE_NEW’的呼叫,要求一個對’OGRE_DELETE’的呼叫。

地形讀取標籤

首先,我們需要在TutorialApplication標頭檔案中的私有部分,增加一個數據成員。

TutorialApplication.h

OgreBites::Label* mInfoLabel;

並且,記住在建構函式中初始化這個指標。

TutorialApplication.cpp

mInfoLabel(0)

讓我們在createFrameListener方法中,構造這個標籤。增加以下內容到createFrameListener的末尾:

mInfoLabel = mTrayMgr->createLabel(OgreBites::TL_TOP, "TerrainInfo", "", 350);

我們使用在BaseApplication中定義的TrayManager指標來請求一個新標籤的建立。這個方法取得一個Tray位置,一個標籤名,一個用來顯示的標題,以及一個寬度。

下面我們將新增邏輯到追蹤地形是否仍然在讀取的frameRenderingQueued中。我們也將注意在地形已經被讀取之後,對其進行儲存。新增以下內容到frameRenderingQueued,在對父方法的呼叫之後:

if (mTerrainGroup->isDerivedDataUpdateInProgress())
{
  mTrayMgr->moveWidgetToTray(mInfoLabel, OgreBites::TL_TOP, 0);
  mInfoLabel->show();

  if (mTerrainsImported)
    mInfoLabel->setCaption("Building terrain...");
  else   
    mInfoLabel->setCaption("Updating terrain...");
}
else
{
  mTrayMgr->removeWidgetFromTray(mInfoLabel);
  mInfoLabel->hide();

  if (mTerrainsImported)
  {
    mTerrainGroup->saveAllTerrains(true);
    mTerrainsImported = false;
  }
}

我們做的第一件事,是決定我們的地形是否被構建。如果是,那麼我們新增我們的標籤到tray,並且要求它被顯示。然後我們檢查是否任何新的地形已經被匯入。如果有,我們顯示地形仍在被構建的文字。否則我們假設紋理正在被更新。

如果地形不再被更新,那麼我們要求SdkTrayManager移除我們的標籤視窗,並且隱藏標籤。我們也檢查,是否新的地形已經被匯入,並且將它們儲存以備未來使用。在我們的情形中,檔案會被命名為“terrain_00000000.dat”,並且它將存放在你的“bin”目錄,與你應用的可執行目錄同級。在儲存任何新地形後,我們重設定mTerrainsImported標誌。

再次編譯並執行你的應用。你現在應該看到,當地形正在被構建時,一個標籤在螢幕的頂部。當地形被讀取時,你無法按Esc來退出,並且你的移動控制會變得不連貫。這即是遊戲中的讀取畫面。但如果你退出並且第二次執行應用,它將載入第一次執行時儲存的地形檔案,這次會是一個快得多的過程。

清理

我們必須確保,每次我們呼叫OGRE_NEW時也都呼叫了OGRE_DELETE。新增以下內容到destroyScene:

OGRE_DELETE mTerrainGroup;
OGRE_DELETE mTerrainGlobals;

這些巨集將保證,任何被Ogre分配的記憶體,會以正確的方式被釋放。

天空盒 SkyBoxes

一個天空盒本質上,是一個巨大的、有紋理的、包圍在你場景所有物件周圍的立方體。這是模擬天空的方法之一。我們將需要六個紋理來覆蓋天空盒的所有內部面。來自Ogre的Samples目錄以前包括一個space-themed天空盒。這些檔案被附加在本教程裡,因為它們看起來不會再被包含了。
(以下在原網頁中均是圖片的連結)
stevecube_up.jpg
stevecube_dn.jpg
stevecube_lf.jpg
stevecube_rt.jpg
stevecube_fr.jpg
stevecube_bk.jpg

將這些檔案新增到你的源讀取路徑中。在你的場景中包含入一個天空盒非常簡單。新增以下內容到createScene的末尾:

mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox");

編譯並執行你的應用。天空盒將看起來相當地顆粒狀,因為我們使用了一個相當低解析度的紋理集合。
這個方法的第一個引數決定了是否立即開啟天空盒。如果你希望稍後禁用天空盒,你可以呼叫mSceneMgr->setSkyBox(false,”“),這禁用了天空盒。
setSkyBox的第三和第四個引數需要了解。我們已經允許在呼叫中採用了它們的預設值。第三個引數是在攝像機和天空盒之間的距離。將你的呼叫進行如下改變:

mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 300);

編譯並執行你的應用。一切未變。這是因為第四個引數設定了:是否在場景其它部分之前渲染天空盒。如果天空盒被率先渲染,那麼無論它距離多麼近,你場景的剩餘部分將在它之上被渲染。現在嘗試這樣的呼叫:

mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 300, false);

請再次編譯執行你的應用。這次你會清楚地看到某些改變。只有地形的一小塊會在攝像機下存在。環顧四周,會注意到發生了什麼。天空盒在距離攝像機僅300個畫素處被渲染,並且它不會再被先於其它任何東西渲染。

你可以通過不先渲染天空盒,來得到合適的效能提升;但如你所見,你將需要確保在進行時,不引起像這樣奇怪的問題。絕大部分情況下,讓這些額外的引數使用預設值就足夠好了。儘管你可能希望,在你的應用中刻意地使用這個奇怪的行為。嘗試別被事物的“本因如此”限制住;如果注意到了什麼,就充分研究一下。

天空穹頂 SkyDomes

模擬天空的另一個方法,是天空穹頂。天空紋理仍被應用到一個包圍在場景周圍的巨大立方體;但,紋理被投影以這樣一種方式:它似乎建立了一個場景上方的穹頂。理解這些的最好方式,是在實踐中觀看它。註釋你對setSkyBox的呼叫,並新增以下內容:

mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);

編譯執行。確保將攝像機移動到地形邊緣,你可以更好地明白髮生了什麼。這個方法的主要缺點是紋理不覆蓋立方體的地面。你需要確保使用者不會意外看到。
setSkyDome方法的前兩個引數與setSkyBox相同。你可以用同樣方式禁用SkyDome。第三個引數是穹頂投影曲率;使用2~65之間的數值。更低的值將在遠距離產生更好的效果;但更高的值將使得紋理失真更小。第四個引數是紋理將被平鋪的次數。這個引數是一個Ogre::Real值。如果你想,可以將你的紋理平鋪3.14次。最後兩個引數是距離,以及是否先繪製穹頂;這是與setSkyBox最後兩個引數相同的。

天空面 SkyPlanes

模擬天空的第三個方法與前兩個相當不同。這個方法將使用單獨的一個平面。我們需要做的第一件事是建立一個平面物件。將我們對setSkyDome的呼叫註釋、並新增以下內容:

Ogre::Plane plane;
plane.d = 1000;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;

我們通過提供一個從原點出發到我們平面的距離d,以及一個垂直於我們平面的normal,來定義一個平面。通過選擇沿著y軸的負單位向量,我們有一個平行於地面並且朝下的平面。
現在我們建立天空面。

mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 75);

第四個引數是天空面的尺寸(1500*1500個單位),第五個引數是紋理鋪開的次數。

編譯執行。我們使用的紋理仍是低解析度的。一個高清晰度紋理看起來會好得多。它也並未平鋪得很好。這兩個問題都可以通過使用更高質量的資源來解決。真正的問題是:當用戶移動到接近地形邊緣的任何位置時,使用者非常可能看到天空的盡頭。由於這個原因,一個天空面大部分是用於有高牆的嚐盡中。在這些情形中,一個天空面提供了比其它技術更好的效能提升。

天空面有一些其它的屬性,可以被用來產生更好的效果。setSkyPlane的第六個引數是“renderFirst”引數,我們在前面兩個方法中已經介紹了。第七個引數允許我們為天空面定製一個曲率。這會將天空面的角向下拉,使得天空面形成一個彎曲的表面而不是一個平面。如果我們設定了曲率給其它一些平的東西,我們也需要設定Ogre應該用來渲染平面的分割的數量。當天空面是一個平面時,每一樣事物都是一個大的正方形;但如果我們添加了曲率,那麼它將要求更復雜的幾何學。第八個和第九個引數是平面每個維度中分割的數量。

讓我們測試這些吧。將這些改動應用到我們的呼叫中:

mSceneMgr->setSkyPlane(
  true, plane, "Examples/SpaceSkyPlane", 1500, 50, true, 1.5, 150, 150);

編譯執行。這將幫助尋找我們的天空面。移動到地形邊緣,來更好地觀察新增曲率後發生的改變。

煙霧 Fog

就像在圖形程式中幾乎每個事物,煙霧效果在Ogre中是個幻覺。Ogre並不渲染一個煙霧物件到場景中,而是在場景中應用一個過濾器。基於物體到攝像機的距離,這個過濾器允許視口的背景顏色以不同等級穿過我們的佈景。這意味著,你的煙霧將與視口的背景顏色相同。

在Ogre中有兩種基本的煙霧型別:線性的和指數的。不同之處是,當你將攝像機移走時,煙霧的密集變化率。

在場景中新增煙霧

我們首先在場景中新增線性的煙霧。我們需要確保,將我們想要的煙霧顏色,設定為視口的背景色。在createScene中設定地形之前,新增以下內容:

Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);

保證你在地形程式碼之前添加了這些,否則將無效。如果你使用多於一個的視口,那麼你可能需要通過使用getNumViewports,迭代遍歷它們。
現在我們建立煙霧。

mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0, 600, 900);

第一個引數是煙霧型別。第二個引數是我們設定了視口的背景色。第三個引數並不用於線性煙霧。第四個和第五個引數定製了煙霧起始及終結的範圍。在我們的例子中,煙霧將從距離攝像機600個單位的地方開始,在距離900個單位的地方停止。將此稱為線性煙霧的原因是,在兩個值之間的厚度上升是線性的。請編譯執行你的應用。

下一個煙霧型別是指數型。如圖所示,指數型煙霧起初增長緩慢,之後迅速濃稠。我們不對這個煙霧設定範圍,而是給出一個希望的稠密度。

mSceneMgr->setFog(Ogre::FOG_EXP, fadeColour, 0.002);

編譯執行。你可以看到這建立了一個不同種類的煙霧效果。這更像填滿了攝像機周圍區域的一個霧霾。這是指數型煙霧以更快速率增長的變型。

mSceneMgr->setFog(Ogre::FOG_EXP2, fadeColour, 0.002);

編譯執行來看看這產生的不同。

總結

本教程涉及了使用Ogre地形系統的基本內容。我們給出了使得匯入一個地形高度圖到場景中,需要進行設定的概覽。我們提到了一個地形組的概念,儘管在這篇教程中,我們只是在我們的“組”中使用了一個地形物件。我們也確保了初始化我們的地形,使用一個有向光照,這樣我們可以得到地形上的鏡面反射以及投影。

我們也涉及了Ogre所提供的,在你場景中模擬天空的不同方法,這些包括了:天空盒、天空穹頂以及天空面。最後,我們介紹了Ogre的煙霧效果。通過對我們的場景應用一個允許視口背景色基於到攝像機的距離而滲濾的過濾器,來渲染煙霧。

這是一篇你應當花費足夠時間進行實驗的教程。所有這些特性可以被大量地配置。你可以只使用我們目前涉及到的內容,建立一些非常逼真的場景。