Basic Tutorial 3         Terrain, Sky, and Fog
Print

Tutorial Introduction
Image In this tutorial we will be exploring how to manipulate terrain, sky, and fog in your Ogre applications. After this tutorial you should understand the differences between Skyboxes, Skyplanes, and Skydomes, and be able to use them. You will also know the difference between the different types of fog, and how to use them.

 

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

 

Prerequisites

  • This tutorial assumes you have knowledge of C++ programming and are able to set up and compile an Ogre application.
  • This tutorial also assumes that you have created a project using the Ogre Wiki Tutorial Framework, either manually, using CMake or the Ogre AppWizard - see Setting Up An Application for instructions.
  • This tutorial builds on the previous basic tutorials, and it assumes you have already worked through them.

 
As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it. You can see the source code for the final state of this tutorial here. If you have trouble with the code, you should compare your project's source to this.

Getting Started

In previous versions of Ogre, you had to use the Terrain Scene Manager if you wanted to introduce terrain in your Ogre applications.
Not any more, courtesy of the new Ogre Terrain System.
Starting from Ogre 1.7 (Cthugha), there's now three components available: Terrain, Paging and Property.

The Terrain and Paging components have a special relationship in that Ogre Terrain is able to make use of the Paging component for - yes, you guessed it: paging.

This tutorial will introduce you to the Ogre Terrain component, and leave paging for a later tutorial.

Code

Modify your Basic Tutorial 3 class header to look like this:

BasicTutorial3 header
#include <Terrain/OgreTerrain.h>
#include <Terrain/OgreTerrainGroup.h>
#include "BaseApplication.h"
 
class BasicTutorial3 : public BaseApplication
{
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);
public:
    BasicTutorial3(void);
    virtual ~BasicTutorial3(void);
 
protected:
    virtual void createScene(void);
    virtual void createFrameListener(void);
    virtual void destroyScene(void);
    virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
};

Make sure that your Basic Tutorial 3 implementation file looks like this (before the main() function):

BasicTutorial3 implementation
#include "BasicTutorial3.h"
 
//-------------------------------------------------------------------------------------
BasicTutorial3::BasicTutorial3(void)
{
}
//-------------------------------------------------------------------------------------
BasicTutorial3::~BasicTutorial3(void)
{
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::destroyScene(void)
{
 
}
//-------------------------------------------------------------------------------------
void getTerrainImage(bool flipX, bool flipY, Ogre::Image& img)
{
 
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::defineTerrain(long x, long y)
{
 
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::initBlendMaps(Ogre::Terrain* terrain)
{
 
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::configureTerrainDefaults(Ogre::Light* light)
{
 
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::createScene(void)
{
 
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::createFrameListener(void)
{
 
}
//-------------------------------------------------------------------------------------
bool BasicTutorial3::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
    bool ret = BaseApplication::frameRenderingQueued(evt);
    return ret;
}

Project Settings

Visual Studio

Click for Instructions

 

Code::Blocks

Click for Instructions

 

CMake

Click for Instructions

 

AutoTools

Click for Instructions



Terrain

TODO: Describe main principles of the Ogre Terrain System and/or the tutorial project.
Image

The project we will make in this tutorial consists of two main components: TerrainGroup(external link) and Terrain(external link). There are also TerrainGlobalOptions(external link), but they are used as a supplementary component. You can imagine TerrainGroup as a moderate zone like a glade, meadow or play-field. TerrainGroup can aggregate a bunch of Terrain pieces inside. This subdivision is used for LOD rendering, which is based on the camera's distance to each of the Terrains. In turn, Terrains consist of tiles with some material strained over them. We will use a single TerrainGroup without paging; Terrain systems using paging will be described in Intermediate tutorials.

Before we render the Terrain, we must set up all the necessary parameters of the Ogre Terrain System.

Dealing with the camera

First, let's modify our Camera object for our terrain.
Put this code in the BasicTutorial3::createScene function:

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);   // enable infinite far clip distance if we can
    }

What it does, besides setting the position and orientation of the camera, is adjust the near and far clip distances.
A terrain is often fairly big, and we want our camera to be able to see far into the distance.
If the RenderSystem supports it, make it infinite.

Setting up directional and ambient light

The Terrain component uses a directional light to compute the terrain lightmap, so let's put a directional light into our scene:

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

We also set some ambient light to smooth out the lighting.

Configuring our terrain

First, we create a new set of global terrain options, using the TerrainGlobalOptions class. This is an options class that just stores default options for all terrains we will create and provides a few getters and setters. There are also local options to each TerrainGroup called DefaultImportSettings, which you will see later.

mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();

Then we construct our TerrainGroup object - a helper class to manage a grid of terrains but it does not do any paging (which is done by the paging component you can see in Intermediate tutorials soon).

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

The TerrainGroup class constructor takes our SceneManager instance, Terrain alignment option, terrain size and terrain world size as parameters.
Then we tell the TerrainGroup what name we would like it to use when saving our terrain, using the setFilenameConvention function.
And lastly we set the origin of the terrain group.

Now it's time to configure our terrain:

configureTerrainDefaults(light);

We'll deal with the configureTerrainDefaults function later.
Notice that we're passing our directional light to the function.

And then we define our terrains and instruct the TerrainGroup to load them all:

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

Since we only have one terrain, we'll only be calling the defineTerrain function once.
But if we had multiple, we would do that there.
We'll deal with the defineTerrain function later on.

Now, if we just imported our terrains, we would like our blendmaps to be calculated:

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

The code loops through the available terrains and calls initBlendMaps on each.
The initBlendMaps function will be covered later.

Now, all there is left to do is clean up after the initial terrain creation:

mTerrainGroup->freeTemporaryResources();

 

The createScene so far

This is what our BasicTutorial3 createScene function looks like so far:

void BasicTutorial3::createScene(void)
{
    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);   // enable infinite far clip distance if we can
    }
 
    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("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));
 
    mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
 
    mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(mSceneMgr, Ogre::Terrain::ALIGN_X_Z, 513, 12000.0f);
    mTerrainGroup->setFilenameConvention(Ogre::String("BasicTutorial3Terrain"), Ogre::String("dat"));
    mTerrainGroup->setOrigin(Ogre::Vector3::ZERO);
 
    configureTerrainDefaults(light);
 
    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);
 
    if (mTerrainsImported)
    {
        Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();
        while(ti.hasMoreElements())
        {
            Ogre::Terrain* t = ti.getNext()->instance;
            initBlendMaps(t);
        }
    }
 
    mTerrainGroup->freeTemporaryResources();
}

Tip_icon.png Do not try to compile now - it won't work until we implement the terrain utility functions!

configureTerrainDefaults

The Ogre Terrain component is quite configurable.
mTerrainGlobals is our Ogre::TerrainGlobalOptions instance.

Find the configureTerrainDefaults function and add the following code to it:

// Configure global
    mTerrainGlobals->setMaxPixelError(8);
    // testing composite map
    mTerrainGlobals->setCompositeMapDistance(3000);

First, we set two global options: MaxPixelError and CompositeMapDistance.
MaxPixelError decides how precise our terrain is going to be. A lower number will mean a more accurate terrain, at the cost of performance (because of more vertices).
CompositeMapDistance decides how far the Ogre terrain will render the lightmapped terrain.

Next, let's deal with the lightmapping, using our directional light:

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

It uses our light to set direction and diffuse colour and sets the diffuse colour to match our scene manager's ambient light.

Next we define some ImportData values:

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

We won't cover the what and how of those values in this tutorial, but terrainSize and worldSize are set to match our global sizes (what we told our TerrainGroup), and inputScale decides how the heightmap image is scaled up. We are using a scale here because images have limited precision (Note: You can use floating point raw heightmaps to avoid inputScaling, but those maps usually need some lossy data compression. In this case you can set inputScale = 1).
A raw heightmap, for instance, doesn't normally need scaling because the values are stored as an array of unscaled floats.

The last step is adding our 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");

Here we set the number of terrain texture layers to 3, by calling defaultimp.layerList.resize(3)
Then we initialise each layer by setting the 'worldSize' and by specifying the texture names.
'worldSize' decides how big each splat of textures is going to be. A smaller value will increase the resolution of the rendered texture layer.

The default material generator takes two textures per layer:

  1. diffuse_specular - diffuse texture with a specular map in the alpha channel
  2. normal_height - normal map with a height map in the alpha channel

 
See Ogre Terrain Textures if you want to know how these textures are made. The files used here are included in the Samples directory of the Ogre source distribution.

Our entire configureTerrainDefaults function looks like this:

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

This is our defineTerrain function:

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

This function is simple, but clever:

First, it asks our TerrainGroup what file name it would use to generate the terrain.

Then it checks if there is a file by that name in our resource group. If there is, it means that we generated a binary terrain data file already, and thus there is no need to import it from an image. If there isn't a data file present, it means we have to generate our terrain, so we load the image and use that to define it.

The function uses a small utility function called getTerrainImage().

getTerrainImage

Since this function is really small and only used once, it's living in the implementation file as a static local function:

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

It loads 'terrain.png' from our resource locations, flipping it if necessary.

Note: Flipping is done to imitate seamless terrain so you can create unlimited terrain using a single 513x513 heightmap, it's just a trick. If your terrain's heightmap is already seamless, you don't need to do any flipping; you just define the individual heightmap for each Terrain. In this tutorial, we will use only a 1x1 sized TerrainGroup (look at the createScene() function) so this code is not actually used.

(TO DO: Maybe it would be better to remove this function, for simplicity, since we are not going to describe terrain paging. But please save this text for the intermediate tutorials.)

initBlendMaps

Remember our three types of terrain layers, defined in configureTerrainDefaults()? Now, we are going to blend these layers based on the HEIGHT of the tile. In a real project, you can use alpha-based splatting stored in RGBA channels in a file or files that are separate from the heightmap.

This is the initBlendMaps function in its entirety:

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* 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();
}

We won't go into the gritty details of how it works in this tutorial. Let's just say that it uses the terrain height to splat the three layers on the terrain. Notice the use of getLayerBlendMap() and getBlendPointer().
'Nuff said.

Compile and Run - part 1

Go ahead and compile and run your Basic Tutorial 3 application now. smile

You should get a nicely rendered terrain.

However, we can improve our little app by making three additions to it:

  1. Terrain generation indicator
  2. Saving of the terrain
  3. Cleaning up after ourselves

 
First, we'll need to add a new private data member to the BasicTutorial3 class:

OgreBites::Label* mInfoLabel;

 
Then we need to override three functions in our BasicTutorial3 class:

  1. frameRenderingQueued - to show the terrain generations process and to save it when it's done
  2. createFrameListener - to construct the information label
  3. destroyScene - to clean up after ourselves

 
So, without further ado:

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

First we ask our TerrainGroup if it's working on the terrain generation: isDerivedDataUpdateInProgress.
If it is working on it, let's show the information label with the right caption.

If it's not working on it, check if we just imported a new terrain.
If we did, then save all the terrains, so that we can skip the generation process the next time we run our application.

createFrameListener

We are overriding this function because we want to add a new informational label to our OgreBites::SdkTrayManager mTrayMgr. Since the BaseApplication constructs it in the createFrameListener function, we can't add it in our 'createScene' function, because the createFrameListener function is called 'after' createScene...

Very simple:

void BasicTutorial3::createFrameListener(void)
{
    BaseApplication::createFrameListener();
 
    mInfoLabel = mTrayMgr->createLabel(OgreBites::TL_TOP, "TInfo", "", 350);
}

Call BaseApplication::createFrameListener and then sneak in our info label.

destroyScene

This is where we clean up after ourselves:

void BasicTutorial3::destroyScene(void)
{
    OGRE_DELETE mTerrainGroup;
    OGRE_DELETE mTerrainGlobals;
}

OGRE_NEW requires OGRE_DELETE.

Compile and Run - part 2

If you compile and run now, you should see a informational label telling you that the TerrainGroup is busy generating the terrain.
When the label disappears, the TerrainGroup will save the terrain.

You will probably notice that you cannot exit the program while the terrain generation is in progress.

However, if you run the program a second time now, you will not receive the message at all, and the startup will be much faster, because we're loading the generated terrain data file directly.

Go ahead and look in OGRE_HOME/media.
You should see a 'BasicTutorial3Terrain_00000000.dat' file there...

Sky

Ogre provides three different types of sky: SkyBoxes, SkyDomes, and SkyPlanes. We will take a look at each of these in detail.

SkyBoxes

Image
A SkyBox is basically a giant cube that surrounds all of the objects in the scene. The best way to describe it is to just show it to you. Add this line of code to createScene:

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

Compile and run the program. Neat huh? (Note the SkyBox is grainy because the actual texture is low resolution; a higher resolution SkyBox would look much better.) There are several useful parameters for SkyBoxes that we can set when calling setSkyBox(external link). 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:

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:

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:

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(external link). 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:

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(external link) 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:
Image
This is setting the curvature to 64:
Image
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

Image
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(external link) 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:

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:

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:

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:

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 to Use?

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

Fog Introduction

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:

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

You could use the getNumViewports(external link) 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:

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

The first parameter to the setFog(external link) 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:

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:

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.

Fog and Sky

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:

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):

Image

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

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

 
Image

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(external link) parameter is what disables fog for a material.

Fog as 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):

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:

Image

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

If you are having difficulty building this tutorial, take a look at the source code for it and compare it to your project.

Next

Proceed to Basic Tutorial 4 Frame Listeners and Unbuffered Input


Alias: Basic_Tutorial_3


Contributors to this page: holocronweaver3177 points  , tothmarcell97166 points  , SRombauts2857 points  , progman118 points  , peters181 points  , Oxymoron290464 points  , noirenex982 points  , montaropdf61 points  , matti_kariluoma7 points  , Latin1603 points  , JacobM1 points  , jacmoe180265 points  , Harkathmaker245 points  and Beauty14565 points  .
Page last modified on Sunday 10 of February, 2013 22:43:40 UTC by holocronweaver3177 points .


The content on this page is licensed under the terms of the Creative Commons Attribution-ShareAlike License.
As an exception, any source code contributed within the content is released into the Public Domain.