Ogre三维框架基础篇(二)

Ogre地形

在老版本中,我们使用Terrain Scene Manager来渲染场景中的地形。Ogre1.7版本后则将其转移到组件系统中。Ogre Terrain System有三个组件:Terrain,Paging和Property。其中Paging组件主要用来实现LOD技术。
Terrain中主要的两个类:TerrainTerrainGroup。每个Terrain代表一小块地域,然后通过TerrainGroup将每一片区域连接起来,并管理哥哥区域间的关系。它们用于LOD(Level of Detail)渲染。

设置摄像机和光源

createScene()方法中设置摄像机

1
2
3
4
5
6
7
8
9
10
11
12
13
   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);
//若能,则设置远距离裁剪为0(无限制)
if (infiniteClip)
mCamera->setFarClipDistance(0);
//不能则设置最远距离为50000尺寸
else
mCamera->setFarClipDistance(50000);

继续设置光源

1
2
3
4
5
6
7
8
9
10
11
12
13
//环境光
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));

设置地形

引入相关的附加文件

在设置地形前先引入相关的附加文件。“项目——属性——配置属性——链接器——输入”,在“附加依赖项”处添加属性页中附加OgreTerrain_d.lib。
添加依赖项

添加函数和成员变量

TutorialApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef __TutorialApplication_h_
#define __TutorialApplication_h_

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


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

protected:
virtual void createScene(void);
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);

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

#endif

TutorialApplication.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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)
{
}

地形的配置

createScene方法中,继续加入。
1、获取地形配置属性类

1
2
3
4
//设置地形属性
// OGRE_NEW 是一个专属于Ogre的宏定义
//TerrainGlobalOptions属于OgreTerrain中的一个类,用于配置地形的属性。
mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();

2、设置地形组

1
2
3
4
5
6
7
8
9
10
//设置地形组
//参数分别为场景管理器,terrain的朝向(铺面x,z轴),terrain的大小,整个Group的大小
mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(
mSceneMgr,
Ogre::Terrain::ALIGN_X_Z,
513, 12000.0);
//设置保存名字为“terrain.dat”
mTerrainGroup->setFilenameConvention(Ogre::String("terrain"), Ogre::String("dat"));
//设置中心点为原点
mTerrainGroup->setOrigin(Ogre::Vector3::ZERO);

3、配置地形的一些参数

1
2
//Terrain配置方法
configureTerrainDefaults(light);

4、让TerrainGroup读取所有的Terrain图

1
2
3
4
5
6
//让TerrainGroup读取所有的Terrain图
for (long x = 0; x <= 0; ++x)
for (long y = 0; y <= 0; ++y)
defineTerrain(x, y);

mTerrainGroup->loadAllTerrains(true);

4、导入配置完成的地形

1
2
3
4
5
6
7
8
9
10
11
//导入我们的地形
if (mTerrainsImported)
{
Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();

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

5、清除操作

1
2
3
4
//保存地形数据到磁盘中
mTerrainGroup->saveAllTerrains(true);
//地形建成后,清除一些内存
mTerrainGroup->freeTemporaryResources();

configureTerrainDefaults函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void TutorialApplication::configureTerrainDefaults(Ogre::Light* light)
{
//MaxPixelError决定了地形的精度,这个值越小地形越精确,当然效率就越低
mTerrainGlobals->setMaxPixelError(8);
//设置在多远处开始使用组合贴图。在摄像机周围3000范围内看到地形,而之外则是我们定义的光影颜色
mTerrainGlobals->setCompositeMapDistance(3000);

//在地形的渲染中,Ogre提供了一种组合贴图的渲染,就是在较远的地方使用一种贴图混合简单光影的方式来代替实际的纹理.
//设置产生组合贴图的光线方向
mTerrainGlobals->setLightMapDirection(light->getDerivedDirection());
//设置组合贴图的光影颜色
mTerrainGlobals->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());

//设置地形的一些属性
//terrainSize指的是地形大小(必须是2^n+1),maxBatchSize指的是最大的批次大小(必须是2^n+1,并且小于等于65),worldSize指的是地形的世界大小等等
//inputScale是对导入数据(高度图)的高度值的缩放,值越大表示越陡峭
Ogre::Terrain::ImportData& importData = mTerrainGroup->getDefaultImportSettings();
importData.terrainSize = 513;
importData.worldSize = 12000.0;
importData.inputScale = 600;
importData.minBatchSize = 33;
importData.maxBatchSize = 65;

//它允许地形上有几个纹理层次(数目取决于你的硬件),标记为0层,1层……n层,默认情况下你只能见到第0层,我们可以设置n+1层对之前n层纹理的混合结果的混合方式,如果不混合就只能看到第0层。
//首先你要定义这些层的纹理是什么及纹理的大小。
//设置有几层纹理
importData.layerList.resize(3);
//设置该层纹理的空间大小
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");
}

defineTerrain函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
用来导入高度图
通过TerrainGroup的generateFilename函数生成纹理的名字,然后在我们的资源组中进行检查,看是否已经存在这个地形数据了,
如果已经存在了,就不需要再次从一个图片中导入了,如果不存在我们就需要加载图片使用它去生成我们的地形
*/
void TutorialApplication::defineTerrain(long x, long y)
{
Ogre::String filename = mTerrainGroup->generateFilename(x, y);

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

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;
}
}

getTerrainImage函数实现

1
2
3
4
5
6
7
8
9
10
11
12
/*
这个函数就是加载我们前面中提到过的需要加载的图片,这个图片会用来生成对应的地形数据。
*/
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();
}

initBlendMaps函数的实现

在产生了地形实例后,我们需要对地形的纹理层进行我们想要的混合方式,我们可以通过地形组的迭代器访问每一个地形实例,然后把对应的地形实例指针传递给这个函数。混合的原理机制是这样的:对于每一个纹理层,都有一个保存每个像素的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();来更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
对地形的纹理层进行混合
*/
void TutorialApplication::initBlendMaps(Ogre::Terrain* terrain)
{
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();
}

运行结果

Ogre地形

天空

Ogre提供了三种类型的天空:天空盒,天空穹和天空面。

SkyBoxs——天空盒子

天空盒实际上是一个立方体,它包含了场景里所有的对象。将下面代码加入createScene方法中,运行可以看到

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

Skybox
skybox函数有4个参数,下面分别介绍这4个参数的作用。

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

SkyDomes——天空穹

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

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

前两个参数跟天空盒一样,第三个参数是天空穹的弯曲程度,OGRE的API中建议使用2到65之间的数值。第四个参数用来设置贴图重复的次数,这个参数需要根据贴图的实际大小来设置,以便让贴图可以适应我们的程序。
SkyDomes

SkyPlanes——天空面

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

1
2
3
4
5
6
//一个平行于xz轴,高度为1000的平面
Ogre::Plane plane;
plane.d = 1000;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
//创建天空面
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 75);

第四个参数用来设定天空面的大小,在这里将其设置成为了1500x1500的大小。第五个参数是重复的次数,这里设置的重复次数为75。
天空面
将创建天空面的代码换成

1
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方向上的线段数量。

在Ogre中,雾有两种,线性的雾和指数的雾。线性雾随着参数的变化“线性的”增加浓度,而指数雾随着参数的变化“指数的”增加浓度。在场景中设置雾化效果,实际只是添加一个雾的滤镜而已。
在设置地形前先设置视口的背景颜色

1
2
3
//设置视口的背景颜色
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);

雾的类型

Tips:代码放在设置地形前
线性雾

1
2
//设置雾的效果,线性雾
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0, 60, 900);

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

指数雾之一
通过第三个参数“雾的密度”来设置雾的浓度,第四和第五不需要设置

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

指数雾之二
这种指数雾,离摄像机越远它的浓度越强

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

雾

结语

这章学的不轻松,很多三维建模中的知识都不清楚,以后要找机会补习一下三维建模的知识。


参考文章:
1、【Ogre编程入门与进阶】第八章 地形、天空与雾化效果
2、官方基础教程3

文章作者: cpacm
文章链接: http://www.cpacm.net/2015/02/09/Ogre三维框架基础篇(二)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 cpacm
打赏
  • 微信
  • 支付宝

评论