英语水平有限,欢迎大家批评指正
本文并没有将原文全部翻译,只是将其中的一些知识点翻译总结了一下,想要查看详细讲解的话,可以到原文处看一下,附上英文原文地址:http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Basic+Tutorial+3&structure=Tutorials
在以前的OGRE版本中,你要在OGRE程序中使用terrain就需要使用Terrain场景管理器,但是现在的OGRE Terrain系统就不需要了。从OGRE 1.7版本开始,有Terrain,paging和roperty.三个组件可以使用。Terrain和Paging组件有特殊的关系,Ogre Terrain可能会用Paging组件来分页(paging)。
使用OGRE Terrain组件时,在Release下添加”OgreTerrain.lib”,在debuge添加”OgreTerrain_d.lib”到你的项目的静态库中。在项目属性面板中,从Configuration下拉菜单中选择Release或Dbuge,然后点击Configuration properties -> Linker -> input,按下图进行设置:
Terrain
本教程的项目中我们使用了两个主要的组件:TerrainGroup和Terrain。还有一个TerrainGlobalOptions,但他是作为一个辅助组件。我们可以把TerrainGroup想象成像沼泽、草地或运动场(play-field)这样的中等(moderate)区域。TerrainGroup可以聚集很多Terrain片(pieces)。这样细分是用来进行LOD渲染的,他是基于摄像机距每个Terrain的距离的。Terrain由很多展开图(tile)组成,这些展开图上贴有材质(material)。
渲染Terrain之前,我们必须设置好OGRE Terrain系统所有必要的参数:
Dealing with the camera
添加如下代码:
这段代码除了设置camera的位置和方向,还调整了远、近裁剪距离。
Setting up directional and ambient light
Terrain组件使用方向光来计算terrain lightmap,所有我们放了一个方向光在我们的场景中:
Ogre::Vector3 lightdir(0.55, -0.3, 0.75); lightdir.normalise(); Ogre::Light* light = mSceneMgr->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)); mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));
Configuring our terrain
首先,我们创建一个全局的terrain操作选项集。为了完成这个任务,我们使用了TerrainGlobalOptions类。他是一个为所有我们将要创建的terrain保存默认操作选项并提供一些获取和设置函数的类。也有称为DefaultImportSettings的本地选项提供给每个TerrainGroup。
然后我们创建TerrainGroup对象,他是一个管理由terrain组成的网格的helper类,但是他并不多任何分页(paging)工作。
TerrainGroup类的构造函数将SceneManager实例,Terrain对齐选项,terrain大小和terrain世界大小作为参数。
然后我们告诉TerrainGroup在保存terrain时我们喜欢他使用什么名字,使用setFilenameConvention函数。最后我们设置terrain group的原点。
现在配置terrain:
主要我们把我们的方向光传给了该函数。
然后我们定义我们的terrain并通知TerrainGroup加载他们:
由于我们只有一个terrain,所有我们只调用defineTerrain函数一次。但如果我们有多个terrain,我们就要调用多次。
现在,如果我们只是要导入我们的terrain,我们就要计算我们的融合图(blendmaps)。
现在,剩下要做的就是初始化terrain创建后进行清除(clean up):
configureTerrainDefaults
Ogre Terrain组件可配置性很好。mTerrainGlobals是Ogre::TerrainGlobalOptions的实例。找到configureTerrainDefaults函数并添加以下代码:
// Configure global
mTerrainGlobals->setMaxPixelError(8);
// testing composite map
mTerrainGlobals->setCompositeMapDistance(3000);
首先我们来设置MaxPixelError和CompositeMapDistance两个全局变量。MaxPixelError用来确定terrain的精确度,数值越小terrain的精确度越高,相应的需要付出性能的代价(因为精确度越高,顶点数越多)。CompositeMapDistance决定Ogre terrain将在多远的距离呈现(渲染)光照贴图(lightmapped)terrain。
下面,我们使用方向光来处理光照贴图(lightmapping):
// 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());
它使用我们的光源设置方向和漫射颜色,并设置漫射颜色与我们的场景管理器的环境光相匹配。
下面我们定义一下ImportData值:
// 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; // due terrain.png is 8 bpp
defaultimp.minBatchSize = 33;
defaultimp.maxBatchSize = 65;
terrainSize和worldSize是用来与global sizes(我们所告诉过TerrainGroup的值)相匹配,inputScale决定了高度图(heightmap)的扩大程度。
一组高度图通常并不需要缩放,因为他们的值都被作为一个非缩放浮点数存储在一个数组中。
下面就是我们的纹理(textures):
// 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");
这里我们通过调用defaultimp.layerList.resize(3)设置地面纹理层数为3,然后我们通过设置worldSize和设置纹理名初始化每层。worldSize决定了每个纹理贴图的大小,值越小所渲染出的纹理层的分辨率就越高。
默认的材质生成器每层需要两个纹理:
1.diffuse_specular alpha通道中有一个反射贴图的漫射纹理
2.normal_height alpha通道中有一个高亮贴图的普通贴图
现在configureTerrainDefaults函数如下:
void BasicTutorial3::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());
// 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");
}
defineTerrain
defineTerrain函数如下:
void BasicTutorial3::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;
}
}
首先它通过TerrainGroup获取了它要生成terrain所需要的文件名。然后检查在我们的资源组中是否有一个该名字的文件。如果有,说明已经生成一个二进制terrain数据文件,而因此就不需要从一个图片中导入。如果没有,就要生成我们的terrain,所以加载图片并用来定义它。
函数使用了一个小的功能函数getTerrainImage()。
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”文件,如有必要刻意翻转。
initBlendMaps
还记得我们在configureTerrainDefaults()中定义的3中类型的terrain层吗?现在,我们就根据the height of the tile(展开图的高度)来融合(blend)这些层。在一个真实项目中,你可以使用将RGBA通道存于一个文件的基于alpha的贴图或被高度图分割的文件。
initBlendMaps函数如下:
void BasicTutorial3::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* 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);
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();
}
我们不会详细的讨论他在本教程中的功能。它使用地面高度将三层贴图到terrain上。
现在程序已经可以编译并运行,但我们可以渲染一个更加漂亮的terrain。首先我们需要添加一个新的数据成员:OgreBites::Label* mInfoLabel;然后重载三个函数:
1.frameRenderingQueued -显示terrain的生成过程并在生成后保存它。 2.createFrameListener -创建信息标签。
3.destroyScene -清除。
frameRenderingQueued
bool BasicTutorial3::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
bool ret = BaseApplication::frameRenderingQueued(evt);
if (mTerrainGroup->isDerivedDataUpdateInProgress())
{
mTrayMgr->moveWidgetToTray(mInfoLabel, OgreBites::TL_TOP, 0);
mInfoLabel->show();
if (mTerrainsImported)
{
mInfoLabel->setCaption("Building terrain, please wait...");
}
else
{
mInfoLabel->setCaption("Updating textures, patience...");
}
}
else
{
mTrayMgr->removeWidgetFromTray(mInfoLabel);
mInfoLabel->hide();
if (mTerrainsImported)
{
mTerrainGroup->saveAllTerrains(true);
mTerrainsImported = false;
}
}
return ret;
}
首先我们查询TerrainGroup是否正在生成terrain:isDerivedDataUpdateInProgress。如果是,我们显示相应的信息到信息标签上。如果没有,检查我们是否导入了一个新的terrain。如果导入了,就保存所有的terrain,这样我们下次运行程序时就可以跳过该生成过程。
createFrameListener
重载该函数时因为我们要给OgreBites::SdkTrayManager mTrayMgr添加一个新的信息标签:
void BasicTutorial3::createFrameListener(void)
{
BaseApplication::createFrameListener();
mInfoLabel = mTrayMgr->createLabel(OgreBites::TL_TOP, "TInfo", "", 350);
}
destroyScene
void BasicTutorial3::destroyScene(void)
{
OGRE_DELETE mTerrainGroup;
OGRE_DELETE mTerrainGlobals;
}
Ogre提供了三种不同类型的sky:SkyBoxes, SkyDomes, SkyPlanes.
Sky Boxes
天空盒(skybox)就是一个包围了场景中所有对象的巨大的立方体(cube)。描述他的最好办法就是展示给你,添加以下代码到createScene函数中:
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox");
编译并允许程序,就可以看到了。调用setSkyBox函数时有几个有用的参数可以设置,第一个参数是是否显示SkyBox。如果后面不想显示SkyBox,则调用‘mSceneMgr->setSkyBox(false,"");’第二个参数是SkyBox使用的材质脚本。第三个参数设置SkyBox距离Camera的距离,第四个参数设置SkyBox是否先于或后于场景中其他对象绘制。所有让我们看看当你把设置SkyBox距离Camera的距离的参数从默认的5000单位改为其他值时会发生什么:
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 10);
什么都没改变!这是因为控制SkyBox是否先绘制的第四个参数默认值为true。如果SkyBox被先绘制,那么所有之后渲染的对象将被绘制与SkyBox之上,从而使得SkyBox总是出现在背景中。(注意不要把距离设置得比Camera的近裁剪距离小,否则他将不被显示。)通常不希望首先绘制SkyBox,当你最后渲染他时,只有可见部分被绘制,这将会使渲染速度有一定的提升。所以设置我们的SkyBox最后被绘制。
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 5000, false);
现在SkyBox的不可见部分将不会被渲染,在使用这个技术时有一点要注意,如果你设置SkyBox距离太近,这将有可能删掉部分场景几何体。如下:
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 100, false);
正如你现在看到的,terrain“刺透”了SkyBox,这肯定不是我们想要的。如果你在你的程序中使用SkyBoxs,你需要决定你想要怎么使用他们。在terrain之后渲染SkyBox所得到的速度上的提升时很可观的,还有你要小心不要遮盖了你的几何体(除非这是你想要的)。总体来说,让第二个参数之后的参数保持默认值是比较安全的选择。
SkyDomes
天空顶(SkyDome)与天空盒非常相似,通过调用setSkyDome来使用天空顶。一个巨大的立方体(cube)绕Camera创建并呈现出来,但最大的不同是纹理是以球形的方式投影(projected)到SkyBox上的。你看到的仍是一个立方体,但看起来就像纹理是绕着一个球体的表面覆盖上去的。这种方法的缺点是立方体的底部是没有纹理的,所有你需要用一些terrain来隐藏。
添加如下代码:
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
当你运行时,移动Camera到terrain的正中间,再将Camera移动到距离terrain表面最近的位置。然后按R键,转换为网格视图。如你所看到的,我们仍在看一个立方体,但看起来就像那些云朵就是围绕着一个球体覆盖上去的。(注意云朵的行为是"Examples/CloudySky"材质的一个属性,而不是SkyDomes的。)
setSkyDome的前两个参数与setSkyBox一样,而且可以调用'mSceneMgr->setSkyDome(false, "");'.来关闭SkyDome。第三个参数使用的SkyDome的曲率。API参考建议使用的值为2到65;值越小,距离效果越好,值越大,失真越少平滑效果越好。API参考提到的距离效果指截图清晰。第四个参数是纹理被分片(tiled)的次数,这个你需要根据你的纹理的大小进行扭曲。确保这个参数是实数(浮点数)且不是整形。你可以tile 1.234次,只要这样正好适合你的程序。第五和六个参数是distance和drawFirst,相对的在SkyBox中已经讲过了。
SkyPlanes
天空面(SkyDome)与天空盒、天空顶有很大的不同。不是立方体,我们只是使用了一个平面来渲染天空纹理。(注意:所有的SkyPlane配置你都要面对terrain的中间并接近地面。)首先创建一个平面,并使他面朝下。setSkyPlane方法没有像SkyBox和SkyDome一样的距离参数,相对的使用Plane的d变量来设置。
Ogre::Plane plane;
plane.d = 1000;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
现在我们定义了平面,我们可以创建SkyPlane了。注意第四个参数是SkyPlane的大小(这里为1500*1500单位),第五个参数是tile纹理的次数:
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 75);
编译运行程序,这里创建的SkyPlane有两个问题。第一个,使用的纹理分辨率低,而且tile不够好。这可以通过创建一个高分辨率的、tile好的天空纹理来解决。但主要的问题是如果你水平向前看,就能看到SkyPlane的尽头。即使你有一个好的纹理,如果你可以水平观看也不够。这种SkyPlane的基本应用只适用于当你有高墙(或山)围绕视点时。
幸运的是这并不是我们能够使用SkyPlane做到的全部。第六个参数“rederFirst”前面已经讲过。第七个参数允许你设置SkyPlane的曲率,所有我们不再使用平面而是一个曲面来代替。我们现在仍需要设置x、y部分的数字来创建SkyPlane(初始时SkyPlane是一个大的矩形,但如果我们想要扭曲,我们需要使得平面有其他的小矩形组成。)第八和九个参数是x、y部分的数字。
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 50, true, 1.5f, 150, 150);
另外,你可以清除SkyPlane通过调用'mSceneMgr->setSkyPlane(false, Ogre::Plane(), "");'。
Fog Introduction
设置fog需要知道的最重要的事情就是并不是向你想象的在一个空的空间中创建一个fog实体。Fog只不过是一个适用于你现在看到的所有对象的过虑器。这有一些有趣的影响,最相关的是,当你看不到任何东西时,你也看不到fog。事实上,你只看到视口的背景颜色。所以为了使fog正确的显示,我们得设置背景为当前的fog颜色。
有两种基本的fog类型:线性(linear)和指数(exponential)型。线性fog在一个线性形状上越来越浓,而指数fog指数性的变得越来越浓(每一个距离单位上fog浓度都指数型的增长)。
Types of Fog
我们首先来看线性fog,他也是最容易理解的fog。调用setWorldGeometry之后的第一件事就是设置视口背景颜色,我们可以通过重载createViewport函数来做到,但有时我们需要设置他而不需要每次都重新创建视口:
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
你可以使用getNumViewports成员函数来获取视口的个数并迭代他们如果你有不止一个视口,但我们的案例中(我们只有一个视口),我们可以直接获取视口。设置了背景颜色后,我们现在可以创建fog了:
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 50, 500);
第一个参数是fog的类型。第二个参数我们使用的fog的颜色。第三个参数不适用与线性fog。第四和五个参数指定了fog变浓的范围。在本例中我们设置fog的起始点为50,结束点位500.这意味着camera前0到50单位距离之间没有fog。距离camera50到500单位距离,fog线性的变得越来越浓。500单位距离以外,你将看不到任何东西,除了fog。
不设置fog的起始和结束范围,我们设置fog的密度(第四、五个参数不被使用):
mSceneMgr->setFog(Ogre::FOG_EXP, fadeColour, 0.005);
mSceneMgr->setFog(Ogre::FOG_EXP2, fadeColour, 0.003);
Fog and Sky
当你在SkyBox或SkyDome中使用fog时会出现一些有趣的错误。因为SkyBox和SkyDome就是立方体,所有使用fog时会出错,因为fog是以一个球形的方式工作的。添加如下代码:
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 10, 1200);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8, 500);
编译运行程序。如果你移动camera,你就会看到SkyDome的不同部分刺透fog根据你正在看的SkyDome的那部分。这并不是我们想要的。
使用SkyPlane:
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 10, 1200);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
Ogre::Plane plane;
plane.d = 100;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
mSceneMgr->setSkyPlane(true, plane, "Examples/CloudySky", 500, 20, true, 0.5, 150, 150);
这看起来很正确。如果我们向上看就可以看到天空,而他并没有以滑稽的方式刺透fog。无论你是否使用扭曲,这解决了用户水平观察SkyPlane时显示不好的问题。
有一种可以使fog不再影响天空的方法,当他需要修改天空纹理的材质脚本。
Fog as Darkness
当你使用fog时你可能根本不想用天空,因为如果fog很浓你就看不到天空了。 我们上面介绍的fog技巧可以让我们在某些案例中做出一些漂亮而且有用的图像特效。让我们把fog的颜色设置的很暗,然后看看发生了什么(注意我们设置SkyPlane距离camera只有10个单位):
Ogre::ColourValue fadeColour(0.1, 0.1, 0.1);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 10, 150);
Ogre::Plane plane;
plane.d = 10;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 100, 45, true, 0.5, 150, 150);
编译运行程序,这就是我们所得到的。