【Ogre编程入门与进阶】第八章 地形、天空与雾化效果

转载自:http://blog.csdn.net/zhanghua1816/article/details/18195357

在一款完整的3D游戏或是三维动画中,大地和天空都是构建场景时不可缺少的元素,另外,伴随着游戏中的角色形象上变得越来越饱满,越来越细节化,游戏中的地形系统也随着时间的推移变得越来越真实,越来越复杂,我们可以想象一副具有真实感的地形会给玩家带来多大的震撼效果,因此,在游戏引擎中地形系统的重要性就不言而喻了。

目前,游戏中有很多种方法来制作地形系统,当然,每一种方法都有它的优势和弱项。它可以是你的任务看起来很简单,也可以是你的工作量大的惊人。地形制作的方法多种多样,本节我们会通过相应的示例展示给大家Ogre3D中一种实现地形的方法。当然有时候我们也需要在一望无际的天空中或是在辽阔无垠的大地上增加一点雾气朦胧的感觉,这就需要用到Ogre的雾化效果,通过学习这个章节我们可以知道在Ogre中使用雾化效果是一件很简单的事情。不过,Ogre中使用地形和天空等元素,就不像以前章节中,在场景中添加一个Ninja模型那么简单了。好,下面我们就进入Ogre中地形和天空的世界,看看Ogre中是如何使用这些元素的。

8.1 地形
在开始学习“地形”的概念之前,我们先来了解一下“高度图”的概念。
高度图是一张简单的2D图片,由黑色、白色和之间的254种渐变灰度所生成,如下图所示:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第1张图片
这种方法的原理是每一种级别的灰度表示了不同的高度。色彩越亮的地方,地形就越高。

用高度图来制作地形是比较简单的,而且它可以生成一些最为真实的地形结构。在游戏中,我们通常可以在图形编辑器中,比如Photoshop中来制作高度图。用高度图的方式生产地形的优势是:制作简单,可以方便快速地做出大的变动,比较真实。但是这种方式同样也有它的劣势:很难做出小的修改,为了恢复正常可能会花费相当多的时间等。

Ogre3D过去在地形上的支持很少,过去只是让你导入一个高度图,然后在上面放一层基本纹理和一细节纹理,在1.7版本之后,Ogre在里面单独加入了terrain的模块,在地形的支持上已经非常强大。


笔者注:
在1.7之前的Ogre版本中,如果我们想要在一个3D场景中创建并使用一个地形,就必须要用到Terrain Scene Manager这个类;但是在Ogre 1.7之后的版本中,我们就可以使用Ogre Terrain System这个类来取代Terrain Scene Manager了,事实上Ogre Terrain System使用起来要方便的多。


下面我们同样使用在前面章节保存的模板代码,通过一个示例程序逐步给大家讲解Ogre中的地形系统。

第一步,修改createScene函数中的内容如下:

void createScene()
{
    mCamera->setPosition(Ogre::Vector3(1683, 50, 2116));
    mCamera->lookAt(Ogre::Vector3(1963, 50, 1660));
    mCamera->setNearClipDistance(0.1);
    mCamera->setFarClipDistance(50000);
    if(mRoot->getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_INFINITE_FAR_PLANE))
    {
        mCamera->setFarClipDistance(0);  //如果相机距离支持无限大就设置为无线距离
    }
    Ogre::MaterialManager::getSingleton().setDefaultTextureFiltering(Ogre::TFO_ANISOTROPIC);
    Ogre::MaterialManager::getSingleton().setDefaultAnisotropy(7);
}

这里我们修改的相机的位置和方向,调整了相机的远近裁剪距离,这是由于通常情况下,地形会相当的大,我们想要相机能看到很远的距离。最后几行的代码是设置材质的一些属性,这里是设置默认的各向异性值。

第二步,设置光源。

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));
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));

这里我们的地形组件使用定向光去计算地形光路图,同时我们增加了一个环境光源,用来平滑整个环境。

第三步,引入地形相关的头文件,并在VS2008中依次在菜单栏中点击:
“项目——属性——配置属性——链接器——输入”,在“附加依赖项”处添加属性页中附加OgreTerrain_d.lib。
然后在
“项目——属性——配置属性——C/C++——附加包含目录”中添加地形相关的包含目录(你的Ogre根目录)\Components\Terrain\include)。

#include 
#include 

【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第2张图片
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第3张图片

第四步,为我们的类添加如下几个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);

在构造函数中初始化成员变量,在析构函数中释放它们:

Example1()
{
     mTerrainGroup = NULL;
     mTerrainGlobals = NULL;
}
~Example1()
{
     if (mTerrainGroup)
     {
         OGRE_DELETE mTerrainGroup;
     }
     if (mTerrainGlobals)
     {
         OGRE_DELETE mTerrainGlobals;
     } 
}

第五步,我们需要设置TerrainGlobalOptions,这是一个表示地形的总体特性的类,它可以设置地形的一些特征,首先你需要new出来一个,因此继续在createScene函数中添加如下代码:

mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();

第六步,我们构造一个TerrainGroup对象,用来帮助管理地形网格,继续添加如下代码。

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

这里TerrainGroup类的构造函数需要一个SceneManager类的实例,一个地形对齐选项,一个地形大小和一个地形世界大小作为参数。然后我们使用setFilenameConvention函数告诉TerrainGroup当我们想要保存地形的时候的名字前缀和后缀名。接着,我们设置起点的位置。最后一行,我们调用configureTerrainDefaults函数,这个函数的实现代码稍后会实现。

第七步,定义地形并加载所有:

for (long x = 0;x <= 0; ++x)
     for (long y = 0;y <= 0; ++y)
         defineTerrain(x,y);
// sync load since we want everything in place when we start
mTerrainGroup->loadAllTerrains(true);

这里由于我们仅仅只有一个地形,我们将仅仅调用defineTerrain函数一次,同样,如果我们有多个地形那么就也可以在这里调用,defineTerrain函数的实现代码稍后我们也会提供。

第八步,现在,我们仅仅导入我们的地形:

if (mTerrainsImported)
{
     Ogre::TerrainGroup::TerrainIteratorti = mTerrainGroup->getTerrainIterator();
     while(ti.hasMoreElements())
     {
         Ogre::Terrain* t =ti.getNext()->instance;
         initBlendMaps(t);
     }
}

第九步,初始地形创建后,我们需要做一些清除操作:

mTerrainGroup->freeTemporaryResources();

第十步,configureTerrainDefaults函数实现:

void Example1 ::configureTerrainDefaults(Ogre::Light*light)
{
    // Configure global
    mTerrainGlobals->setMaxPixelError(8);

    // testing composite map
    mTerrainGlobals->setCompositeMapDistance(3000);

    // Important to set these so that the terrain knows 
    // what to use for derived (non-realtime) data
    mTerrainGlobals->setLightMapDirection(light->getDerivedDirection());
    mTerrainGlobals->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
    mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());
}

这里,我们知道,TerrainGlobalOptions是一个表示地形的总体特征的类,它可以设置地形的一些特征,这里第1行代码,MaxPixelError决定了我们的地形的精度,这个值越小地形越精确,当然效率就越低。

在地形的渲染中,Ogre提供了一种组合贴图的渲染,就是在较远的地方使用一种贴图混合简单光影的方式来代替实际的纹理,这有很多应用,如这样可以在较远的地方不使用精细的纹理生成,提高效率;这样可以使用这个特性实现游戏中照亮与黑暗的效果。上面的第2~5行代码就是设置这方面的参数。首先设置在多远处开始使用组合贴图(第2行),然后设置产生组合贴图的光线方向(第3行),其次就是设置组合贴图的光影颜色(第4、5行)。例如上面这些代码中的意思就是在你的摄像机周围3000范围内看到地形,而之外则是我们定义的光影颜色。

完成了上面这些代码,我们还需要往configureTerrainDefaults函数体中继续添加如下代码:

// 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");

这里,我们首先定义了一些ImportData值,我们通过getDefaultImportSettings()得到一个Terrain::ImportData类的实例,用来设置地形的一些具体属性,如,defaultimp.inputScale = 600;

我们现在使用的新版Ogre的地形的纹理贴附是这样的:它允许地形上有几个纹理层次(数目取决于你的硬件),标记为0层,1层……n层,默认情况下你只能见到第0层,我们可以设置n+1层对之前n层纹理的混合结果的混合方式,如果不混合就只能看到第0层。

首先你要定义这些层的纹理是什么及纹理的大小。

上面的第12行代码是设置有几层纹理;第13行是设置该层纹理的空间大小;第14、15行是设置该层纹理的贴图和法向贴图。(注意这里我们之所以可以引用这些纹理图片文件是因为这些文件时Ogre在Media目录下自带的。)

第十一步,defineTerrain函数实现:

void Example1::defineTerrain(longx,longy)
{
     Ogre::Stringfilename =mTerrainGroup->generateFilename(x,y);
     if(Ogre::ResourceGroupManager::getSingleton().resourceExists(mTerrainGroup->getResourceGroup(),filename))
     {
         mTerrainGroup->defineTerrain(x,y);
     }
     else
     {
         Ogre::Imageimg;
         getTerrainImage(x % 2 != 0,y % 2 != 0,img);
         mTerrainGroup->defineTerrain(x,y, &img);
         mTerrainsImported = true;
     }
}

这个函数的内容简单明了,可以看到我们首先是通过TerrainGroup的generateFilename函数生成纹理的名字,然后在我们的资源组中进行检查,看是否已经存在这个地形数据了,如果已经存在了,就不需要再次从一个图片中导入了,如果不存在我们就需要加载图片使用它去生成我们的地形。我们使用mTerrainGroup->defineTerrain(x,y, &img);这个函数产生一个地形实例,地形组是指一个二维网格的地形群,这个函数是指在地形组这个网格的(x,y)处使用这个高度图产生一个地形实例,在Ogre中每个地形实例都是方形的,所以如果你想产生矩形就需要产生多个地形实例。另外,这里我们看到使用到了一个函数getTerrainImage,下面我们就来实现这个函数。

第十二步,getTerrainImage函数实现:

void Example1::getTerrainImage(boolflipX,boolflipY,Ogre::Image&img)
{
     img.load("terrain.png",Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
     if (flipX)
         img.flipAroundY();
     if (flipY)
         img.flipAroundX();
}

这个函数就是加载我们前面在第十一步中提到过的需要加载的图片,这个图片会用来生成对应的地形数据。

第十三步,initBlendMaps函数的实现:

void Example1::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();
}

在产生了地形实例后,我们需要对地形的纹理层进行我们想要的混合方式,我们可以通过地形组的迭代器访问每一个地形实例,然后把对应的地形实例指针传递给这个函数。混合的原理机制是这样的:对于每一个纹理层,都有一个保存每个像素的alpha混合的值,可以通过Ogre::TerrainLayerBlendMap* blendMap0 =terrain->getLayerBlendMap(1);这样的方式来获取第1层的这个混合数据,初始情况下都是0,也就是说,默认情况下第1层对第0层的混合是0,那样你就只能看到0层,你可以通过修改这个数据来直接修改混合方式,这就像两张图像做混合,而每张混合图像代表了整个这个地形实例的表面图片,为了得到这个图像的边长,可以使用terrain->getLayerBlendMapSize();你还可以使用blendMap0->convertImageToTerrainSpace(x,y, &tx, &ty);来得到混合图像空间上(x,y)的那个像素对应的地形实例空间上的坐标,我们修改了混合值之后,还需要使用blendMap0->dirty();blendMap0->update();来更新。

这就是我们使用的新版的Ogre的地形,虽然看起来操作要复杂的多,但是的确可以实现非常好的地形效果。编译并运行程序,你将会看到最终效果如下所示(注意,由于我们这里定义的地形数据量太大,如果你的电脑性能不是太好,可能要花很长时间才能看到下面的地形效果,所以大家在编译运行的时候请耐心等待一下,稍后我们会介绍到怎样优化这个问题):
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第4张图片
我们可以看到虽然通过这种方式能显示出很棒的地形效果,但是我们或许会发现,当我们每次运行程序的时候,由于地形的数据量计算,导致每次运行起我们的程序会很缓慢,解决这个问题有很多种方法,这里我们可以提供一种简单的方法,我们可以把地形数据内容保存到我们本地磁盘上,这样下次程序运行的时候就不需要这么大批量的计算了。

因此,我们可以采用如下方法:
在createScene函数中的最后一行代码:mTerrainGroup->freeTemporaryResources();之前加入下面这一句代码:

mTerrainGroup->saveAllTerrains(true);

然后编译运行程序,你将会发现第一次运行的时候,显示画面的时间还是很慢,但是当我们程序运行过一次之后,以后在运行的时候速度明显比原来快多了,原因很简单,因为我们把生成的地形数据在程序第一次运行的时候已经保存的本地磁盘上去了,以后程序运行的时候可以直接从本地磁盘读取地形数据。我们可以到media目录下查看一下,有一个名叫Example1Terrain_00000000.dat的数据文件,这就是我们程序第一次运行后生成的地形数据文件。

8.2 天空
Ogre提供了三种类型的天空:天空盒,天空穹和天空面,本小节将对这三种类型的天空做详细的介绍。

8.2.1天空盒
天空盒实际上是一个立方体,它包含了场景里所有的对象。在这一小节中,我们将会在8.1小节的基础上继续添加代码,来学习Ogre中天空的用法。将下面的代码添加到createScene函数中:

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

编译并运行程序,我们可以看到如下效果:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第5张图片

代码分析:
skybox函数有4个参数,下面分别介绍这4个参数的作用。

第一个参数,代表是否启用天空盒。如果我们想要取消天空盒,那么我们只须调用一句代码mSceneMgr->setSkyBox( false, “” ); 即可。
第二个参数,用来设置天空盒使用的材质脚本。
第三个参数,用来设定天空盒与摄像机的距离。
第四个参数,决定了天空盒是在其他对象之前渲染还是其他对象之后渲染。

下面,我们将第三个参数从默认的5000改为一个很小的值然后看看有什么效果:

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

编译并运行后,发现没有任何变化,效果如下图:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第6张图片

那么为什么我们修改了代码,却没有在程序结果中看到差异呢?

这是因为,第四个参数的默认值是true,也就是说天空盒会最先渲染,场景中其它的物体会渲染到天空盒之前,从而使天空盒看起来像是在背后。


笔者注:
这里有一点希望读者注意,我们不应该让第三个参数的取值小于摄像机的近裁剪距离,否则天空盒将不能显示。


事实上,天空盒并不应该最先被渲染,因为如果最先渲染天空盒,那么其他的也将全部被渲染。但如果我们最后渲染天空盒,那么OGRE为了提高程序的运行效率,只会渲染天空盒中的可见部分。因此,我们可以来试一下最后渲染天空盒:

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

编译并运行,效果仍没有变化。

但是,实际上跟之前的结果是有那么一点不同之处的,那就是天空盒里不可见的部分没有被渲染。

下面,我们再试试如下的代码:

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

当把天空盒设置的很近时,场景管理器中的一部分物体会被裁减掉。


笔者注:
在程序中使用天空盒的时候一定要注意,setSkyBox函数的最后一个参数的默认值是true,也就是代表天空盒最先被渲染。为了不像上面的代码那样出现不正常的显示效果,就必须仔细使用这几个参数。例如:当我们选择最后渲染天空盒的时候,程序的运行速度会提高一些,但必须注意设置好参数,以免它遮住了场景中的其他物体。在实际应用中,我们大多会在程序中将第三个参数和第四个参数全部设置为默认值,以免出现错误的结果。


8.2.2 天空穹
天空穹和天空盒非常相似,在Ogre中创建一个天空穹要使用的函数是setSkyDome函数。天空穹也会创建一个包含了场景中所有对象的巨型立方体,但是它与天空盒最大的区别是:天空穹的贴图是用球体的方法投影到立方体上的。所以,天空穹其实还是一个立方体,只不过它的贴图看上去像贴到了一个球体上一样。

这种类型的天空的一个最大的缺陷就是:立方体的下面没有任何贴图(通常显示为黑色),所有必须要有一个其他的东西来将下面的黑洞遮住,以便让我们的场景看上去不是一个bug。

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

编译并运行程序,你将看到如下的效果:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第7张图片

为了让读者更好的理解天空穹的概念,我们把8.1小节中的关于地形的代码都暂时注释起来,让我们的场景中只存在一个“天空穹”。

之后,我们按下键盘上的R键,将其切换到网格状态,效果如下,可见,天空穹仍然是一个立方体,图为立方体的一角。
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第8张图片

再次按R键,切换回来,滑动鼠标将视角移动到天空穹的底部,可以看到一片立方体状的黑色区域,同样可以验证天空穹仍然是立方体,这也是上文中提到的天空穹的一个缺陷,即立方体的下面没有任何贴图,所有必须要有一个其他的东西来将下面遮住。具体的效果图如下:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第9张图片

代码分析:
mSceneMgr->setSkyDome(true,”Examples/CloudySky”, 5, 8);中的参数。
天空穹的前两个参数和天空盒一样,这里就不再赘述了。
第三个参数是天空穹的弯曲程度。OGRE的API中建议使用2到65之间的数值,我们可以在程序中修改这个参数的值来看看不同取值下的不同效果。

将第三个参数改为为2,效果如下:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第10张图片

将第三个参数改为65,效果如下:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第11张图片

第四个参数用来设置贴图重复的次数,这个参数需要根据贴图的实际大小来设置,以便让贴图可以适应我们的程序。


笔者注:
读者应该特别注意一下这个参数的类型:这个参数是Real类型而不是整形。


第五个参数和第六个参数分别用来设置天空穹的距离和渲染顺序,与天空盒的第三、四个参数类似,这里也就不再赘述了。

8.2.3 天空面
天空面与前两种类型的天空(天空盒和天空穹)有很大的区别。天空面使用一个平面来替代立方体,也就是说,我们仅仅把纹理映射到一个简单的面上,而不再映射到一个立方体上。下面我们通过实例来介绍天空面:

第一步:将8.2.2小节中createScene里所有天空穹的代码注释起来。

第二步:我们创建一个平面,设置平面是朝下的。需要注意的是,与前两个函数不同,setSkyPlane函数没有距离参数,不过,我们可以在Plane的d变量中设置它的距离。我们通过下面的代码来构建一个平面,将d变量的距离设置为1200:

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

第三步:利用setSkyPlane函数来创建一个天空面。定义好了平面之后,接下来我们就可以创建天空面了,具体代码如下:

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

setSkyPlane函数的前几个参数与之前的几种天空的类型类似,这里就不再一一详述了。第四个参数用来设定天空面的大小,在这里我们将其设置成为了1500x1500的大小。

第五个参数是他重复的次数,这里我们设置的重复次数为80.

下面编译并运行程序,效果如下:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第12张图片

代码分析:

mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 55, true, 1.5f, 160, 160);

我们就这一行代码来看一下setSkyPlane这个函数每个参数的作用。

  1. 第一个参数用来设置是否启用天空面。如果我们想要取消天空盒,那么我们只须调用一句代码mSceneMgr->setSkyPlane( false, “” ); 即可。

  2. 第三个参数用来设置天空盒使用的材质脚本。

  3. 第四个参数用来设置天空面的大小。

  4. 第五个参数用来设置天空面重复的次数。

  5. 第六个参数用来设置天空面的渲染顺序,它决定了天空面是最先被渲染还是最后被渲染。参数为true表明天空面最先被渲染,参数为false表明天空面最后被渲染。

  6. 第七个参数用来设置天空面的弯曲度,这样一来就可以实现将平面弯曲成弧形的效果了。同时我们还需要设置x和y的线段数量,这是因为天空面是一个巨大的正方形,但是假如我们想将它弯曲那么就要将它切分成许多小正方形,以便我们可以实现弯曲的效果。

  7. 第八个和第九个参数是x和y的线段数量了。因为天空面是一个大的正方形,若要将其弯曲就必须将其变成许多小的正方形,所以要设置x和y方向上的线段数量。

说明:
我们创建的天空面有两个问题,第一,使用的纹理分辨率太低,平铺后效果不好。最主要的问题在于如果你朝边上看,将看到天空面的结束,使用天空面的基本观点是当你的景点周围都是高山或丘陵时使用天空面是有用的。

setSkyPlane的第六个参数类似天空盒和天空穹中的定义’是否先绘制’的参数,第七个参数允许你定义天空面的弯曲度,所以我们可以不再使用一个平面,而是有曲率的表面。现在,我们还必须设置x和y的段数(初始时天空面是一个大的正方形,但是如果我们想平面有曲率的话需要用许多小的正方形组成平 面),第八和第九个参数用来设置x和y的段数,代码如下:

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

替换为这句代码后,我们再次编译运行,应该会发现现在的效果要比刚才的好多了:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第13张图片


笔者注:
这个天空面有一个缺陷,就是天空面的结尾处过度是十分不柔和的,如图所示:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第14张图片
天空面的这个缺陷使得天空面的使用在很多地方有了一定的局限性。不过天空面对显卡的要求比天空盒和天空穹对显卡的要求要低,可以大幅度的提升程序的运行效率。


在本节中,我们介绍了三种不同的表现天空的方式,至于到底该使用哪种,完全取决于你的应用。如你要看你周围的一切,甚至是y的负方向,那么你只能选择天空盒;如果你有地形,或者类似地板之类的,那么使用天空穹看起来更真实;如果你看不到地平线,那么使用天空面看起来效果更好并且只需要一点点GPU代价。

8.3 雾化效果
雾,在Ogre里是非常特别的。在场景中设置雾化效果,并不是我们通常想象的那样在某个地方创建“雾”的实体。实际上,雾只是一个滤镜。当场景中没有任何物体时,我们是看不见雾的。事实上,我们在窗口中看到的,只是视口(viewport)的背景色,因此,要想使雾的效果更加逼真,我们必须将视口的背景色设成“雾”的颜色。

在Ogre中,雾有两种,线性的雾和指数的雾。线性雾随着参数的变化“线性的”增加浓度,而指数雾随着参数的变化“指数的”增加浓度。下面我们通过实例向大家介绍这两种雾。

8.3.1 线性雾
在开始增加代码之前,我们先将8.2小节中用到的代码全部注释起来。另外,在调用setWorldGeometry函数前首先要设置视口的背景颜色。我们可以用createViewport方法来设置,继续在createScene函数体中增加如下代码:

Ogre::ColourValuefadeColour(0.9, 0.9, 0.9);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
mSceneMgr->setFog(Ogre::FOG_LINEAR,fadeColour, 0.0, 50, 500);

编译并运行,你将会看到如下效果:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第15张图片

代码分析:
上面的第三行代码中,第一个参数用来设置雾的类型,类型有两种:线性的或是指数的。第二个参数用来设置雾的颜色。第三个参数在线性雾里面是不用设置的。第四个和第五个参数是设置雾慢慢变浓的范围。这个慢慢变浓的范围如何理解呢?例如我们这里设置的变浓范围是60到550,这就意味着在摄像机的0到60的单位内是没有雾的,从60到550的单位内雾慢慢线性变浓,在550单位以外就全是雾了。

8.3.2 指数雾
另一种雾的类型是指数雾。在这里,我们要设置雾的密度(第四个和第五个参数不需要设置)。添加代码如下:

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

编译运行这个程序,效果如下:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第16张图片


笔者注:
在这里,如果我们使用的是DirectX渲染器,会发现场景完全在雾的外面。改善这种现象的办法是在调用setWorldGeometry函数之前,先调用setFog函数。


另外,还有一种更厉害的指数雾函数,这种指数雾,离摄像机越远它的浓度越强。我们只需要用FOG_EXP2替换上述代码中的FOG_EXP即可实现这种雾的效果,代码如下:

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

8.3.3 雾与天空面的配合使用
如果我们在程序中,想将雾的效果与天空盒或天空穹一起配合使用的话,就会遇到一些新的问题。因为天空盒或天空穹是立方体,而雾是球形的,我们接下来就通过一个例子来看一下这个问题究竟是如何产生的。

将createScene函数里的内容全部注释起来,添加如下的代码:

Ogre::ColourValue fadeColour(0.8, 0.8, 0.8);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 11, 1400);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 6, 8, 600);

编译并运行程序,可以查看效果。蓝色的天空材质与底部的白色部分有一个明显的白色边缘,这看起来非常的不自然,如果这出现在场景中必然会是一个失败之举。
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第17张图片

为了改善这种效果,我们可以选择使用天空面,而不是使用天空盒或是天空穹,因为天空面与其他两种类型的天空不同,它本质上是一个平面。

在createScene函数里用以下代码替换之前的代码:

Ogre::ColourValue fadeColour(0.8, 0.8, 0.8);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 10, 1300);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
Ogre::Plane plane;
plane.d = 110;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
mSceneMgr->setSkyPlane(true, plane, "Examples/CloudySky", 550, 25, true, 0.4, 160, 160);

编译并运行,可以查看效果。
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第18张图片

效果是不是比使用天空盒或天空穹的时候好多了,边界的过度柔和了很多,而且没有那种生硬的长方形的变色边界了,这让我们的场景看上去更加的柔美、舒服。


笔者注:
“暗雾”特效。在Ogre中有一个“暗雾”的特效。所谓“暗雾”,就是将雾的颜色设置的非常暗,而不把雾设置成明亮的颜色。我们同样通过代码来看看“暗雾”的效果。将createScene函数中的代码替换为如下:

Ogre::ColourValue fadeColour(0.1, 0.1, 0.1);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 12, 160);
Ogre::Plane plane;
plane.d = 10;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 100, 45, true, 0.6, 160, 160);

编译运行程序,可以查看效果:
【Ogre编程入门与进阶】第八章 地形、天空与雾化效果_第19张图片

“暗雾”这种效果在实际中有什么应用呢?事实上,“暗雾”本身在使用方法上跟其他类型的雾是一样的,因为“暗雾”也是雾的一种,不过这种通过改变雾的颜色来达到不同效果的方法值得读者学习,在程序设计中,往往就会用到这种类似的小技巧来使我们的游戏或动画产生各式各样的不同特效,读者朋友应该在平时的学习和实践中不断积累这种小技巧,这样在设计大型游戏或动画的时候,灵感和方法就信手拈来了。

PS:很久以前就打算把学习Ogre中遇到的知识分享给大家(虽然只是一些皮毛),但是说来惭愧,一直很懒,直到最近才抽出点时间写一些自己的理解(Ogre入门级的东西),所以难免会有很多不足之处,分享是一种快乐,同时也希望和大家多多交流!

(由于在Word中写好的东西发布到CSDN页面需要重新排版(特别是有很多图片时),所以以后更新进度可能会比较慢,同时每章节发布的时间可能不一样(比如说我首选发布的是第二章,其实第一章就是介绍下Ogre的前世今生神马的,相信读者早就了解过~~~),但是我会尽量做到不影响大家阅读,还望大家谅解。)

上述内容很多引用了网上现有的翻译或者内容,在此一并谢过(个人感觉自己有些地方写得或者翻译的不好),还望见谅,转载请注明此项!


你可能感兴趣的:(Ogre)