本教程将专注于在一个场景中渲染地形。
我们将涉及到需要被完成的基本设置,并且将介绍地形光照的使用。
我们也会给出对使用天空盒(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的烟雾效果。通过对我们的场景应用一个允许视口背景色基于到摄像机的距离而渗滤的过滤器,来渲染烟雾。
这是一篇你应当花费足够时间进行实验的教程。所有这些特性可以被大量地配置。你可以只使用我们目前涉及到的内容,创建一些非常逼真的场景。