Skip to main content

History: Basic Tutorial 3

Preview of version: 157

Tutorial Introduction
Ogre Tutorial Head

This tutorial will focus on rendering terrain in a scene. We will cover the basic set up that needs to be done, and we will introduce the use of lighting with terrains. We will also give a brief introduction to simulating a sky using Skyboxes, Skydomes, and Skyplanes. Finally, we will explain how to add a fog effect to the scene.

The full source for this tutorial is here.

Any problems you encounter during working with this tutorial should be posted in the Help Forum(external link).

Prerequisites

This tutorial assumes that you already know how to set up an Ogre project and compile it successfully. If you need help with this, then read Setting Up An Application. This tutorial is also part of the Basic Tutorials series and knowledge from the previous tutorials will be assumed.

Note: Ignore the FPS stats in the screenshots. This tutorial was written on an ancient computer.

fog_visual.png

Setting Up the Scene

The first thing we want to do is add some methods and variables to our class. Set up your TutorialApplication class like this:

TutorialApplication.h
Copy to clipboard
#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; };
TutorialApplication.cpp
Copy to clipboard
#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

Project Settings

Visual Studio

Click for Instructions

To compile this example, you need to link against the Ogre Terrain component. Add OgreTerrain.lib to your release build and OgreTerrain_d.lib to your debug build. To do this, right-click on the TutorialApplication project in the solution window and go to properties. A configuration manager will appear. Select release or debug from the top-left drop down, then Configuration Properties -> Linker -> Input. Finally, add the files we mentioned here.

Image

Code::Blocks

Click for Instructions

To be able to compile that code, you need to link to the Ogre Terrain component.
Add 'OgreTerrain.lib' for release and 'OgreTerrain_d.lib' for debug to your project library input on Windows.

To do this, click on Project in the main menu bar at the top. Go to Build options, then a configuration manager will appear.

Make sure "Debug" is selected in the project tree on the left and click the "Linker settings" tab. Under "Link Libraries" click add, type "OgreTerrain_d" without the quotes of course.
Your input should be added under the link libraries area. Now select "Release" in the project tree on the left. If it asks you to save your settings click yes.

Now with "Release" selected follow the above steps BUT leave off the "_d" at the end of your input leaving it like so: "OgreTerrain".

Now you can click ok and save your settings if prompted.

CMake

Click for Instructions

The 'FindOGRE.cmake' script already attempts to locate additional Ogre components like Terrain. All we have to do is add the Terrain variable that was defined in 'FindOGRE.cmake' to our 'CMakeLists.txt' file. Find the target_link_libraries call and add the OGRE_Terrain_LIBRARIES variable to the list.

Copy to clipboard
target_link_libraries( OgreApp ${OGRE_LIBRARIES} ${OIS_LIBRARIES} ${OGRE_Overlay_LIBRARIES} ${OGRE_Terrain_LIBRARIES})

For more information, refer to Building Your Projects With CMake.

AutoTools

Click for Instructions

For the autotools, follow the instruction in Setting Up An Application with the autotools to create your configure.ac with the OGRE Terrain component.

Now the file should be like this:

Copy to clipboard
AC_INIT(configure.ac) AM_INIT_AUTOMAKE(tuto3, 0.1) AM_CONFIG_HEADER(config.h) AC_LANG_CPLUSPLUS AC_PROG_CXX AM_PROG_LIBTOOL PKG_CHECK_MODULES(OGRE, [OGRE >= 1.2 OGRE-Terrain >= 1.7.1]) AC_SUBST(OGRE_CFLAGS) AC_SUBST(OGRE_LIBS) PKG_CHECK_MODULES(OIS, [OIS >= 1.0]) AC_SUBST(OIS_CFLAGS) AC_SUBST(OIS_LIBS) AC_CONFIG_FILES(Makefile) AC_OUTPUT


Terrain

With older versions of Ogre, we had to use the Terrain Scene Manager to render terrain in a scene. This is a separate SceneManager that runs alongside your other managers. The new Ogre Terrain System has shifted to a component system that doesn't require using a separate manager. Since Ogre 1.7 (Cthugha), there are three terrain components: Terrain, Paging, and Property. The Paging component is used together with the Terrain component to help optimize large terrains. It will be covered in later tutorials. This tutorial will focus largerly on the Terrain component.

To set up the terrain we will focus on two main classes: Terrain and TerrainGroup. The Terrain class represents one chunk of terrain and the TerrainGroup holds a series of Terrain pieces. It is used for LOD (Level of Detail) rendering. LOD rendering reduces the resolution for terrain that is farther away from the camera. An individual Terrain object consists of tiles with a material mapped on to them. We will use a single TerrainGroup without paging. Paging will be covered in later tutorials.

Setting Up the Camera

Let's first set up our Camera. Add the following to the beginning of createScene:

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

This should look familiar from the previous tutorial.

Copy to clipboard
bool infiniteClip = mRoot->getRenderSystem()->getCapabilities()->hasCapability( Ogre::RSC_INFINITE_FAR_PLANE); if (infiniteClip) mCamera->setFarClipDistance(0); else mCamera->setFarClipDistance(50000);

The last thing we do is check to see if our current render system has the capability to handle an infinite far clip distance. If it does, then we set the far clip distance to zero (which means no far clipping). If it does not, then we simply set the distance really high so we can see distant terrain.

Setting Up a Light for Our Terrain

The Terrain component can use a directional light to compute a lightmap. Let's add a Light for this purpose and add some ambient light to the scene while we're at it.

Copy to clipboard
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));

This was also covered in the previous tutorial if you're confused by any of it. The normalise method will make the vector's length equal to one while maintaining its direction. This is something that you will see a lot of when working with vectors. It is done to avoid extra factors showing up in calculations.

Configuring the Terrain

Now we'll get into the actual terrain setup. First, we create a new TerrainGlobalOptions using the OGRE_NEW macro.

Copy to clipboard
mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();

This is a class that holds information for all of the terrains we might create - that is why they are called global options. It also provides a few getters and setters. There are also local options for each TerrainGroup that we will see later in this tutorial.

Next we construct our TerrainGroup object. This will manage a grid of Terrains.

Copy to clipboard
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);

The TerrainGroup constructor takes the SceneManager as its first parameter. It then takes an alignment option, terrain size, and terrain world size. You can read the class reference for more information. The setFilenameConvention allows us to choose how our terrain will be saved. Finally, we set the origin to be used for our terrain.

The next thing we will do is call our terrain configuration method, which we will fill in soon. Make sure to pass the Light we created as a parameter.

Copy to clipboard
configureTerrainDefaults(light);

The next thing we do is define our terrains and ask the TerrainGroup to load them all.

Copy to clipboard
for (long x = 0; x <= 0; ++x) for (long y = 0; y <= 0; ++y) defineTerrain(x, y); mTerrainGroup->loadAllTerrains(true);

We are only using a single terrain, so the method will only be called once. The for loops are just for demonstration in our case. Again, we will fill in the defineTerrain method soon.

We will now initialize the blend maps for our terrain.

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

We get a TerrainIterator from our TerrainGroup and then loop through any Terrain elements and initialize their blend maps - initBlendMaps will also be written soon. The mTerrainsImported variable will be set during the configureTerrainDefaults function when we complete it.

The last thing we will do is make sure to cleanup any temporary resources that were created while configuring our terrain.

Copy to clipboard
mTerrainGroup->freeTemporaryResources();

That completes our createScene method. Now we just have to complete all of the methods we jumped over.

Writing configureTerrainDefaults

The Ogre Terrain component has a large number of options that can be set to change how the terrain is rendered. To start out, add the following to configureTerrainDefaults:

Copy to clipboard
mTerrainGlobals->setMaxPixelError(8); mTerrainGlobals->setCompositeMapDistance(3000);

We are setting two global options here. The first call sets the largest error in pixels allowed between our ideal terrain and the mesh that is created to render it. A smaller number will mean a more accurate terrain, because it will require more vertices to reduce the error. The second call determines the distance at which Ogre will still apply our lightmap. If you increase this, then you will see Ogre apply lighting effects out to a farther distance.

The next thing we'll do is pass our lighting information to our terrain.

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

In the first call, we are sure to call getDerivedDirection, because this will apply any transforms that are applied to our Light's direction by any SceneNode it may be attached to. Since our Light is attached to the root Node, this will be the same as calling getDirection, but the difference is important to know about. The next two calls should be pretty self-explanatory. We simply set the ambient light and diffuse color for our terrain to match our scene lighting.

The next thing we do is get a reference to the import settings of our TerrainGroup and set some basic values.

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

We are not going to cover the exact meaning of these options in this tutorial, but you may have noticed that {MONO}terrainSize{MONO} and worldSize are set to match the global options we set in createScene. The inputScale determines how the heightmap image will be scaled up for the scene. We are using a somewhat large scale because our heightmap image has limited precision. You can use floating point raw heightmaps to avoid applying any input scaling, but these images usually require some data compression.

The last step is adding the textures our terrain will use. First, we resize the list to hold three textures.

Copy to clipboard
importData.layerList.resize(3);

After that, we set each texture's worldSize and add them to the list.

Copy to clipboard
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");

The texture's worldSize determines how big each splat of texture is going to be when applied to the terrain. A smaller value will increase the resolution of the rendered texture layer because each piece will be stretched less to fill in the terrain.

The default material generator requires two textures per layer: a diffuse specular texture and a heightmap texture. You can read Ogre Terrain Textures if you want to learn more about these textures and how they're made. The textures used in the tutorial reside in the Samples directory of your SDK or source distribution. As of this writing, they are included in this directory: '/Samples/Media/materials/textures/nvidia/'. Remember that Ogre will not automatically search subdirectories when loading resources, so you will have to add a line to your 'resources.cfg' file telling it to include the nvidia directory, and, of course, you'll have to copy the actual textures into your project's media folder.

Writing defineTerrain

Now we will tackle our defineTerrain method. The first thing we do is ask the TerrainGroup to define a unique filename for this Terrain. Add the following to defineTerrain:

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

We want to check to see if a filename for this grid location has already been generated.

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

If it has already been generated, then we can call TerrainGroup::defineTerrain method to set up this grid location with the previously generated filename automatically. If it has not been generated, then we generate an image with getTerrainImage and then call a different overload of TerrainGroup::defineTerrain that takes a reference to our generated image. Finally, we set the mTerrainsImported flag to true.

Copy to clipboard
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; }

You might have to look at this method for a little while to fully understand it. Make sure you notice that there are three different defineTerrain methods in use. One of them from TutorialApplication and two of them from TerrainGroup.

Writing getTerrainImage

We need to write the helper function that was used by defineTerrain in the last step. This function is a static local function. If you've moved things around, then make sure this function is defined before defineTerrain. Since it is not a member function, it needs to be defined before being used. Add the following to getTerrainImage:

Copy to clipboard
img.load("terrain.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); if (flipX) img.flipAroundY(); if (flipY) img.flipAroundX();

This will load our 'terrain.png' resource. Make sure it has been added to one of your resource loading paths. It is also included in the Ogre Samples directory.

Flipping is used to create seamless terrain so that unlimited terrain can be created using a single heightmap. If your terrain's heightmap is already seamless, then you don't need to use this trick. In our case, the flipping code is also useless, because we are using a 1x1 TerrainGroup. Flipping a 1x1 tile doesn't change anything. It is just for demonstration.

Writing initBlendMaps

Finally, we will finish up our configuration methods by completing the initBlendMaps method. This method will blend together the different layers we defined in configureTerrainDefaults. For now, you should pretty much view this method as a magic. The details will not be covered in this tutorial. Basically, the method blends the textures based on the height of the terrain at that point. This is not the only way of doing blending. It's a complicated topic and sits right at the verge between Ogre and the things it tries to abstract away. Add the following to initBlendMaps:

Copy to clipboard
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();

The Scene So Far

Compile and run your application. You should get a nicely rendered terrain. Victory!

There are a number of things we will improve. We will add a label to the overlay that allows us to see when the terrain generation has finished. We will also make sure to save our terrain so that it can be reloaded instead of rebuilding it every time. Finally, we will make sure to clean up after ourselves. Just like with the normal 'new' and 'delete' in c++, every call to 'OGRE_NEW' requires a call to 'OGRE_DELETE'.

basic_terrain_visual.png

Terrain Loading Label

First, we need to add a data member to private section of our TutorialApplication header.

TutorialApplication.h
Copy to clipboard
OgreBites::Label* mInfoLabel;

And remember to initialize the pointer in the constructor.

TutorialApplication.cpp
Copy to clipboard
mInfoLabel(0)

Let's construct this label in the createFrameListener method. Add the following to the end of createFrameListener:

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

We use the TrayManager pointer that was defined in BaseApplication to request the creation of a new label. This method takes a TrayLocation, a name for the label, a caption to display, and a width.

Next we will add logic to frameRenderingQueued that tracks whether the terrain is still loading or not. We will also take care of saving our terrain after it has been loaded. Add the following to frameRenderingQueued right after the call to the parent method:

Copy to clipboard
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; } }

The first thing we do is determine if our terrain is still being built. If it is, then we add our Label to the tray and ask for it to be shown. Then we check to see if any new terrains have been imported. If they have, then we display text saying that the terrain is still being built. Otherwise we assume the textures are being updated.

If the terrain is no longer being updated, then we ask the SdkTrayManager to remove the our Label widget and hide the Label. We also check to see if new terrains have been imported and save them for future use. In our case, the file will be named 'terrain_00000000.dat' and it will reside in your 'bin' directory alongside your application's executable. After saving any new terrains, we reset the mTerrainsImported flag.

Compile and run your application again. You should now see a Label at the top of the screen while the terrain is being built. While the terrain is loading, you will not be able to press escape to exit and your movement controls will be choppy. This is what loading screens are for in games. But if you exit and run the application a second time, then it should load the terrain file that was saved the first time. This should be a much faster process.

building_terrain_label_visual.png

Cleaning Up

We must make sure to call OGRE_DELETE for every time we called OGRE_NEW. Add the following to destroyScene:

Copy to clipboard
OGRE_DELETE mTerrainGroup; OGRE_DELETE mTerrainGlobals;

These macros will ensure that any memory that was allocated by Ogre is released in the correct manner.

SkyBoxes

A SkyBox is basically a huge textured cube that surrounds all of the objects in your scene. It is one of the methods for simulating a sky. We will need six textures to cover all of the interior faces of the SkyBox. The Samples directory that comes with Ogre used to include a space-themed SkyBox. The files are attached to this tutorial, because they don't seem to be included anymore.
No such attachment on this page
No such attachment on this page
No such attachment on this page
No such attachment on this page
No such attachment on this page
No such attachment on this page
Add these files to your resource loading path. It is very easy to include a SkyBox in your scene. Add the following to the end of createScene:

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

Compile and run your application. That's all there is to it. The SkyBox will look really grainy because we are using a rather low resolution collection of textures.




There are several useful parameters for SkyBoxes that we can set when calling setSkyBox. The first option is whether or not to enable the SkyBox. If you want to later disable the SkyBox simply call 'mSceneMgr->setSkyBox(false, "");'. The second parameter is the material script to use for the SkyBox.

The third parameter and fourth parameters to setSkyBox are fairly important to understand. The third parameter sets the distance that the SkyBox is away from the Camera, and the fourth parameter sets whether or not the SkyBox is drawn before the rest of the scene or afterwards. So, lets see what happens when you change the distance parameter for the SkyBox from the default 5000 units to something very close:

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

Nothing changed! This is because the fourth parameter that controls whether to draw the SkyBox first or not is set to true by default. If the SkyBox is drawn first, then anything rendered afterwards (like our Terrain) will be drawn on top of it, thus making the SkyBox always appear in the background. (Note that you shouldn't set the distance above to be closer than the near clip distance on the Camera or it will not be shown!) It is not actually desirable to draw the SkyBox first, because the full thing is rendered. When you draw it last, only the visible portions are drawn, which will provide a modest speed improvement. So, lets try setting our SkyBox to be drawn last:

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

Again, this looks just like it did before, but now the parts of the SkyBox that are not visible won't be rendered. There is one thing you have to be careful about when using this technique though. If you set the SkyBox to be too close, you could be cutting part of the scene geometry off. For example, try this:

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

As you can see now, the terrain "pokes through" the SkyBox. Definitely not what we want. If you use SkyBoxes in your application you will have to decide how you want to use them. The speedup you get from rendering the SkyBox after the terrain is very modest, and you have to be careful not to obscure your geometry (unless that is what you are going for). Generally speaking, leaving everything past the second parameter as default is a very safe choice.

SkyDomes

SkyDomes are very similar to SkyBoxes, and you use them by calling setSkyDome. A giant cube is created around the Camera and rendered onto, but the biggest difference is that the texture is "projected" onto the SkyBox in a spherical manner. You are still looking at a cube, but it looks as if the texture is wrapped around the surface of a sphere. The primary drawback to this method is that the bottom of the cube will be untextured, so you always need to have some type of terrain that hides the base.

The example texture that Ogre provides for SkyDomes will let you see this clearly. Clear out the setSkyBox call from createScene and add this code instead:

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

When you run this, move the Camera to the dead center of the terrain and move the Camera so that it's positioned fairly close to the surface of the terrain (this looks the best). After looking at this, hit the R button to switch to the mesh view. As you can see, we are still looking at a cube (without the base), but it looks as if the clouds are wrapped around a sphere at the top. (Also note that the movement of the clouds is a property of the "Examples/CloudySky" material, not of SkyDomes in general.)

The first two paramaters of setSkyDome are the same as setSkyBox, and you can turn the SkyDome off by calling 'mSceneMgr->setSkyDome(false, "");'. The third parameter is the curvature used for the SkyDome. The API reference suggests using values between 2 and 65; lower for better distance effect, but higher values for less distortion and a smoother effect. Try setting the third paramater to 2 and 65 and look at the difference. The distance effect that the API reference was referring to can be clearly seen in these screenshots. This is setting the curvature to 2:
skydome_curvature_2.jpg
This is setting the curvature to 64:
skydome_curvature_64.jpg
The fourth parameter is the number of times the texture is tiled, which you will need to tweak depending on the size of your texture. Be sure to note that this parameter is a Real value (floating point) and not an integer. You can tile it 1.234 times, if that's what looks good for your application. The fifth and sixth parameters are distance and drawFirst, respectively, which we have already covered in the SkyBox section.

SkyPlanes

skyplane_curved.jpg
SkyPlanes are very different from SkyBoxes and SkyDomes. Instead of a cube to render the sky texture on, we use just a single plane. (Note for all of the following SkyPlane configurations you need to be somewhere towards the middle of the terrain and close to the ground.) Clear out all SkyDome code from createScene. The first thing we are going to do is create a plane, and face it downwards. The setSkyPlane method that we will be calling does not have a distance parameter like SkyBox and SkyDome. Instead that parameter is set in the d variable of Plane:

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

Now that we have the plane defined, we can create the SkyPlane. Note that the fourth parameter is the size of the SkyPlane (in this case 1500x1500 units) and the fifth parameter is how many times to tile the texture:

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

Compile and run the program. There are two problems with the SkyPlane this creates here. First of all, the texture that is used is too low resolution, and it doesn't tile well. That could be fixed by simply creating a good, high resolution sky texture that tiles well. However, the primary problem with this technique is that if you look towards the horizon, you can see where the SkyPlane ends. Even if you had a good texture, it would not look good at all if you can see to the horizon. This basic use of a SkyPlane is really only useful when you have high walls (or hills) all around the viewpoint. Using a SkyPlane in that situation would be considerably less graphics-intensive than creating a full SkyBox/SkyDome.

Fortunately, that is not all we can do with a SkyPlane. The sixth parameter to the skyplane is the familiar "renderFirst" parameter which we have already covered in the SkyBox and SkyDome sections. The seventh parameter allows you to specify the curvature of the SkyPlane, so that we are no longer using a plane, but a curved surface instead. We also have to now set the number of x and y segments used to create the SkyPlane (initially the SkyPlane was one big square, but if we want curvature we need to have the plane made up of smaller squares). The eighth and ninth parameters to the function are the number of x and y segments, respectively:

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

Compile and run the application. Now our SkyPlane looks much better, though again the tiling could use some work. You could also use this with the cloud material instead:

Copy to clipboard
mSceneMgr->setSkyPlane(true, plane, "Examples/CloudySky", 1500, 40, true, 1.5f, 150, 150);

Compile and run the application. The motion of the clouds and the way it is tiled seems to make it look slightly worse than a SkyDome, especially when you get near the edge of the Terrain and look out onto the horizon.

One other note, you can clear the SkyPlane by calling 'mSceneMgr->setSkyPlane(false, Ogre::Plane(), "");'


Which sky to use depends entirely on your application. If you have to see all around you, even in the negative y direction, then really your only real choice is to use a SkyBox. If you have terrain, or some kind of floor which blocks the view of the negative y direction, then using a SkyDome seems to give more realistic results. For areas where you cannot see to the horizon (such as a valley surrounded by mountains on all sides, or the inner courtyard of a castle), a SkyPlane will give you very good looking results for very little GPU costs. The primary reason to use a SkyPlane, as we will see in the next section, is because it plays nicely with fog effects.

These are only suggestions. For your application you should experiment and use whatever looks the best.

Fog

The most important thing to know about setting fog is that it doesn't actually create a fog entity in empty space as you might imagine you would. Instead, fog is merely a filter applied to whatever objects you are currently looking at. This has some interesting implications, the most relevant of which is that when you stare off into nothingness (i.e. when you are not looking at an object), you do not see fog. In fact, you only see whatever the viewport background color is. So, in order to have fog look correct, we have to set the background to whatever the fog color currently is.

There are two basic types of fog: linear and exponential. Linear fog gets thicker in a linear fashion, while exponential fog gets thicker exponentially (every distance unit the fog thickness increases by more than it did the previous distance unit). It's easier to see the difference than to explain it, so on to the examples.

Types of Fog

The first type of fog we will look at is linear, and it's the easiest fog to understand. The first thing we are going to do after we call setWorldGeometry is set the viewport's background color. We could do this by overriding the createViewport function (like we did in the last tutorial), but sometimes we need to set it without recreating the viewport every time. This is how we do that:

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

You could use the getNumViewports member function to get the number of viewports and iterate through them if you have more than one viewport, but since this is rarely the case (and since we know we only have one viewport), we can just get the viewport directly. Once we set the background color, we can now create the fog:

Copy to clipboard
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 50, 500);

The first parameter to the setFog function is the type of fog (in this case, linear). The second parameter to setFog is the color of the fog we are using (in this case a very very light grey or "WhiteSmoke" for C#). The third parameter is not used in linear fog. The fourth and fifth parameters specify the range where the fog gets thicker. In this case we have set the fog starting point to be 50 and the stopping point to be 500. This means that from 0 to 50 units in front of the camera, there is no fog. From 50 to 500 units away from the Camera, the fog gets thicker in a linear fashion. At 500 units away from the Camera, you can no longer see anything other than fog. Compile and run the application.

Another type of fog that we can use is exponential fog. Instead of setting starting and stopping bounds for fog, we instead set a density for the fog (the fourth and fifth parameters are unused). Replace the previous call to setFog with this:

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

Compile and run the application.
This creates a different look to the fog that is generated. There is also another exponential fog function which is more severe than the first one (i.e. fog gets much thicker each unit you move away from the Camera compared to the first fog function). Note that there is more fog-per-density when using FOG_EXP2. Replace the previous call to setFog with this:

Copy to clipboard
mSceneMgr->setFog(Ogre::FOG_EXP2, fadeColour, 0.003);

Compile and run the application again. Fog is mostly interchangeable between the three functions that Ogre provides. You should experiment with all three fog functions and see which looks best in your application.

Conflicts Between Sky and Fog

You can run into some interesting problems when trying to use fog with a SkyBox and SkyDome. Since SkyDomes and SkyBoxes are just cubes, using them with fog is problematic since fog works in a spherical manner. If we cleverly choose our SkyDome and fog parameters, we can see the problem directly:

Copy to clipboard
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9); mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 300, 600); mWindow->getViewport(0)->setBackgroundColour(fadeColour); mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8, 500);

Compile and run the application. If you move the camera around, you will see different portions of the SkyDome poke through the fog depending on what part of the SkyDome you are looking at (notice the blue coming through on the sides, but not in the middle):

fog_and_sky_break.jpg

This is certainly not what we want. Another option is to use a SkyPlane instead. Make the following modifications:

Copy to clipboard
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9); mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 300, 600); 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_and_sky_fixed.jpg

This looks correct. If we look upwards we can see sky (which is the case in real life if the fog is just right), but it's not poking through in funny ways. No matter if you use curvature or not, this solves our problem of the user being able to see the horizon where the SkyPlane does not look right.

There is a way to make fog not affect the sky entirely, but it requires modifying the material script for the sky texture. That is beyond the scope of this tutorial, but for future reference this parameter is what disables fog for a material.

Using Fog To Simulate Darkness

You may not want to use sky at all when you set fog, because if the fog is thick enough you cannot see the sky anyway. The trick with fog that we described above allows us to perform a nifty graphic hack that can be useful in some cases. Instead of setting the fog to a bright color, lets set it to be very dark and see what happens (note we have set the SkyPlane to be only 10 units away from the camera, which is before the fog sets in):

Copy to clipboard
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);

Compile and run the application. This is what we get:

fog_as_darkness.jpg

Not too terrible. Of course, once you are able to, you should use proper lighting instead of this hack, but it does show the flexibility of fog, and some of the interesting things you can do with the engine. Using black fog might also be an interesting way to do a "blindness" or "darkness" spell effect if you are writing a game that uses first-person view.

Conclusion

Now you should have a base understanding of Terrain, Sky and Fog.

Full Source

The full source for this tutorial is here.

Next

Basic Tutorial 4


Alias: Basic_Tutorial_3

History

Information Version
Wed 01 of May, 2019 20:29 GMT-0000 paroj 187
Sat 16 of Feb, 2019 17:47 GMT-0000 paroj 186
Tue 16 of Jun, 2015 12:10 GMT-0000 Nightmask3 A missing declaration in the tutorial. Causes a bit of confusion when setting up the project for Tutorial 3. 185
Tue 16 of Jun, 2015 12:10 GMT-0000 Nightmask3 A missing declaration in the tutorial. Causes a bit of confusion when setting up the project for Tutorial 3. 184
Wed 27 of May, 2015 03:39 GMT-0000 kabbotta 183
Mon 18 of May, 2015 19:49 GMT-0000 jstormgraphics 182
Sun 05 of Apr, 2015 07:59 GMT-0000 kabbotta 181
Sun 05 of Apr, 2015 07:48 GMT-0000 kabbotta 180
Sun 05 of Apr, 2015 07:48 GMT-0000 kabbotta 179
Sun 05 of Apr, 2015 07:44 GMT-0000 kabbotta 178
Sun 05 of Apr, 2015 07:38 GMT-0000 kabbotta 177
Sun 05 of Apr, 2015 07:38 GMT-0000 kabbotta 176
Sun 05 of Apr, 2015 07:37 GMT-0000 kabbotta 175
Sun 05 of Apr, 2015 07:35 GMT-0000 kabbotta 174
Sun 05 of Apr, 2015 07:29 GMT-0000 kabbotta 173
Sun 05 of Apr, 2015 07:28 GMT-0000 kabbotta 172
Sun 05 of Apr, 2015 07:26 GMT-0000 kabbotta 171
Sun 05 of Apr, 2015 07:19 GMT-0000 kabbotta 170
Sun 05 of Apr, 2015 07:19 GMT-0000 kabbotta 169
Sun 05 of Apr, 2015 07:17 GMT-0000 kabbotta 168
Sun 05 of Apr, 2015 07:14 GMT-0000 kabbotta 167
Sun 05 of Apr, 2015 07:13 GMT-0000 kabbotta 166
Sun 05 of Apr, 2015 07:05 GMT-0000 kabbotta 165
Sun 05 of Apr, 2015 06:54 GMT-0000 kabbotta 164
Sun 05 of Apr, 2015 06:43 GMT-0000 kabbotta 163