Skip to main content

History: Basic Tutorial 3

Source of version: 159

Copy to clipboard
            {TRANSCLUDE(page="tutbox")}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 ((BasicTutorial3SourceCurrent|here)).
{TRANSCLUDE}
%tutorialhelp%
!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.

{img fileId="2285" rel="box[g]"}

{maketoc}
!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:
{CODE(caption="TutorialApplication.h" wrap="1" colors="c++")}
#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;

};
{CODE}
{CODE(caption="TutorialApplication.cpp" wrap="1" colors="c++")}
#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
{CODE}

!Project Settings
!!Visual Studio
{FADE(label="Click for Instructions")}
To compile this example, you need to link against the Ogre Terrain component. Add {MONO()}OgreTerrain.lib{MONO} to your release build and {MONO()}OgreTerrain_d.lib{MONO} 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.

{IMG(src="display1904")}{IMG}
{FADE}

!!Code::Blocks
{FADE(label="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.
{FADE}

!!CMake
{FADE(label="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 {MONO()}target_link_libraries{MONO} call and add the {MONO()}OGRE_Terrain_LIBRARIES{MONO} variable to the list.
{CODE(wrap="1" colors="cmake")}
target_link_libraries(
  OgreApp
  ${OGRE_LIBRARIES}
  ${OIS_LIBRARIES}
  ${OGRE_Overlay_LIBRARIES}
  ${OGRE_Terrain_LIBRARIES})
{CODE}
For more information, refer to ((Building Your Projects With CMake)).
{FADE}

!!AutoTools
{FADE(label="Click for Instructions")}
For the autotools, follow the instruction in ((Setting Up An Application - Autotools - Linux|#http://www.ogre3d.org/tikiwiki/Setting+Up+An+Application+-+Autotools+-+Linux|Setting Up An Application with the autotools)) to create your configure.ac with the OGRE Terrain component.

Now the file should be like this:
{CODE(wrap="1")}
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
{CODE}
{FADE}
%clear%

!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: [http://www.ogre3d.org/docs/api/html/classOgre_1_1Terrain.html|Terrain] and [http://www.ogre3d.org/docs/api/html/classOgre_1_1TerrainGroup.html|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 {MONO()}createScene{MONO}:
{CODE(wrap="1", colors="c++")}    
mCamera->setPosition(Ogre::Vector3(1683, 50, 2116));
mCamera->lookAt(Ogre::Vector3(1963, 50, 1660));
mCamera->setNearClipDistance(0.1);
{CODE}
This should look familiar from the previous tutorial.
{CODE(wrap="1", colors="c++")}    
bool infiniteClip =
  mRoot->getRenderSystem()->getCapabilities()->hasCapability(
    Ogre::RSC_INFINITE_FAR_PLANE);

if (infiniteClip)
  mCamera->setFarClipDistance(0);
else
  mCamera->setFarClipDistance(50000);
{CODE}
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.
{CODE(wrap="1", colors="c++")}    
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));
{CODE}
This was also covered in the previous tutorial if you're confused by any of it. The {MONO()}normalise{MONO} 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 {MONO()}OGRE_NEW{MONO} macro. 
{CODE(wrap="1", colors="c++")}
mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
{CODE}
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.
{CODE(wrap="1", colors="c++")}
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);
{CODE}
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 [http://www.ogre3d.org/docs/api/1.9/class_ogre_1_1_terrain_group.html|class reference] for more information. The {MONO()}setFilenameConvention{MONO} 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.
{CODE(wrap="1", colors="c++")}
configureTerrainDefaults(light);
{CODE}
The next thing we do is define our terrains and ask the TerrainGroup to load them all.
{CODE(wrap="1", colors="c++")}
for (long x = 0; x <= 0; ++x)
  for (long y = 0; y <= 0; ++y)
    defineTerrain(x, y);

mTerrainGroup->loadAllTerrains(true);
{CODE}
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 {MONO()}defineTerrain{MONO} method soon.

We will now initialize the blend maps for our terrain.
{CODE(wrap="1", colors="c++")}    
if (mTerrainsImported)
{
  Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();
  
  while (ti.hasMoreElements())
  {
    Ogre::Terrain* t = ti.getNext()->instance;
    initBlendMaps(t);
  }
}
{CODE}
We get a TerrainIterator from our TerrainGroup and then loop through any Terrain elements and initialize their blend maps - {MONO()}initBlendMaps{MONO} will also be written soon. The {MONO()}mTerrainsImported{MONO} variable will be set during the {MONO()}configureTerrainDefaults{MONO} 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.
{CODE(wrap="1", colors="c++")}
mTerrainGroup->freeTemporaryResources();
{CODE}
That completes our {MONO()}createScene{MONO} method. Now we just have to complete all of the methods we jumped over.
!Writing {MONO()}configureTerrainDefaults{MONO}
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 {MONO()}configureTerrainDefaults{MONO}:
{CODE(wrap="1", colors="c++")}
mTerrainGlobals->setMaxPixelError(8);
mTerrainGlobals->setCompositeMapDistance(3000);
{CODE}
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.
{CODE(wrap="1", colors="c++")}    
mTerrainGlobals->setLightMapDirection(light->getDerivedDirection());
mTerrainGlobals->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());
{CODE}
In the first call, we are sure to call {MONO()}getDerivedDirection{MONO}, 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 {MONO()}getDirection{MONO}, 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.
{CODE(wrap="1", colors="c++")}    
Ogre::Terrain::ImportData& importData = mTerrainGroup->getDefaultImportSettings();
importData.terrainSize = 513;
importData.worldSize = 12000.0;
importData.inputScale = 600;
importData.minBatchSize = 33;
importData.maxBatchSize = 65;
{CODE}
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 {MONO()}worldSize{MONO} are set to match the global options we set in {MONO()}createScene{MONO}. The {MONO()}inputScale{MONO} 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.
{CODE(wrap="1", colors="c++")}
importData.layerList.resize(3);
{CODE}
After that, we set each texture's {MONO()}worldSize{MONO} and add them to the list.
{CODE(wrap="1", colors="c++")}
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");
{CODE}
The texture's {MONO()}worldSize{MONO} 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 {MONO()}defineTerrain{MONO}
Now we will tackle our {MONO()}defineTerrain{MONO} method. The first thing we do is ask the TerrainGroup to define a unique filename for this Terrain. Add the following to {MONO()}defineTerrain{MONO}:
{CODE(wrap="1", colors="c++")}
Ogre::String filename = mTerrainGroup->generateFilename(x, y);
{CODE}
We want to check to see if a filename for this grid location has already been generated.
{CODE(wrap="1", colors="c++")}
bool exists =
  Ogre::ResourceGroupManager::getSingleton().resourceExists(
    mTerrainGroup->getResourceGroup(),
    filename);
{CODE}
If it has already been generated, then we can call {MONO()}TerrainGroup::defineTerrain{MONO} 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 {MONO()}getTerrainImage{MONO} and then call a different overload of {MONO()}TerrainGroup::defineTerrain{MONO} that takes a reference to our generated image. Finally, we set the {MONO()}mTerrainsImported{MONO} flag to true.
{CODE(wrap="1", colors="c++")}
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;
}
{CODE}
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 {MONO()}defineTerrain{MONO} methods in use. One of them from TutorialApplication and two of them from TerrainGroup.
!Writing {MONO()}getTerrainImage{MONO}
We need to write the helper function that was used by {MONO()}defineTerrain{MONO} 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'' {MONO()}defineTerrain{MONO}. Since it is not a member function, it needs to be defined before being used. Add the following to {MONO()}getTerrainImage{MONO}:
{CODE(wrap="1", colors="c++")}
img.load("terrain.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

if (flipX)
  img.flipAroundY();
if (flipY)
  img.flipAroundX();
{CODE}
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 {MONO()}initBlendMaps{MONO}
Finally, we will finish up our configuration methods by completing the {MONO()}initBlendMaps{MONO} method. This method will blend together the different layers we defined in {MONO()}configureTerrainDefaults{MONO}. 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 {MONO()}initBlendMaps{MONO}:
{CODE(wrap="1", colors="c++")}
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();
{CODE}

!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'.

{img fileId="2286" rel="box[g]"}

!Terrain Loading Label
First, we need to add a data member to private section of our TutorialApplication header.
{CODE(caption="TutorialApplication.h" wrap="1" colors="c++")}
OgreBites::Label* mInfoLabel;
{CODE}
And remember to initialize the pointer in the constructor.
{CODE(caption="TutorialApplication.cpp" wrap="1" colors="c++")}
mInfoLabel(0)
{CODE}
Let's construct this label in the {MONO()}createFrameListener{MONO} method. Add the following to the end of {MONO()}createFrameListener{MONO}:
{CODE(wrap="1", colors="c++")}
mInfoLabel = mTrayMgr->createLabel(OgreBites::TL_TOP, "TerrainInfo", "", 350);
{CODE}
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 {MONO()}frameRenderingQueued{MONO} 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 {MONO()}frameRenderingQueued{MONO} right after the call to the parent method:

{CODE(wrap="1", colors="c++")}
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;
  }
}
{CODE}
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 {MONO()}mTerrainsImported{MONO} 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.

{img fileId="2288" rel="box[g]"}
!Cleaning Up
We must make sure to call OGRE_DELETE for every time we called OGRE_NEW. Add the following to {MONO()}destroyScene{MONO}:
{CODE(wrap="1", colors="c++")}
OGRE_DELETE mTerrainGroup;
OGRE_DELETE mTerrainGlobals;
{CODE}
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.

{ATTACH(id="213")}{ATTACH}
{ATTACH(id="214")}{ATTACH}
{ATTACH(id="215")}{ATTACH}
{ATTACH(id="216")}{ATTACH}
{ATTACH(id="217")}{ATTACH}
{ATTACH(id="218")}{ATTACH}

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 {MONO()}createScene{MONO}:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox");
{CODE}
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.

The first parameter of this method determines whether or not to immediately enable the SkyBox. If you want to later disable the SkyBox you can call {MONO()}mSceneMgr->setSkyBox(false, ""){MONO}. This disables the SkyBox. 

The third and fourth parameters to {MONO()}setSkyBox{MONO} are important to understand. We have allowed them to take their default values in our call. The third parameter is the distance between the Camera and the SkyBox. Make this change to your call:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 10);
{CODE}
Compile and run your application. Nothing has changed. This is because the fourth parameter sets whether or not to render the SkyBox before the rest of the scene. If the SkyBox is rendered first, then no matter how close it is the rest of your scene objects will be rendered on top of it. Now try this call:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 10, false);
{CODE}



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:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 100, false);
{CODE}
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 [http://www.ogre3d.org/docs/api/html/classOgre_1_1SceneManager.html#Ogre_1_1SceneManagera86|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:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
{CODE}
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 [http://www.ogre3d.org/docs/api/html/classOgre_1_1SceneManager.html#Ogre_1_1SceneManagera86|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:
{img fileId="2159" thumb="y" rel="box[g]"}
This is setting the curvature to 64:
{img fileId="2160" thumb="y" rel="box[g]"}
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
{img fileId="2162" thumb="y" rel="box[g]"}
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 [http://www.ogre3d.org/docs/api/html/classOgre_1_1SceneManager.html#Ogre_1_1SceneManagera78|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:
{CODE(wrap="1", colors="c++")}
Ogre::Plane plane;
plane.d = 1000;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
{CODE}
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:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 75);
{CODE}
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:
{CODE(wrap="1", colors="c++")}
    mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 50, true, 1.5f, 150, 150);
{CODE}
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:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setSkyPlane(true, plane, "Examples/CloudySky", 1500, 40, true, 1.5f, 150, 150);
{CODE}
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:
{CODE(wrap="1", colors="c++")}
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
{CODE}
You could use the [http://www.ogre3d.org/docs/api/html/classOgre_1_1RenderTarget.html#Ogre_1_1RenderWindowa22|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:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 50, 500);
{CODE}
The first parameter to the [http://www.ogre3d.org/docs/api/html/classOgre_1_1SceneManager.html#Ogre_1_1SceneManagera90|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:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setFog(Ogre::FOG_EXP, fadeColour, 0.005);
{CODE}
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:
{CODE(wrap="1", colors="c++")}
mSceneMgr->setFog(Ogre::FOG_EXP2, fadeColour, 0.003);
{CODE}
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:
{CODE(wrap="1", colors="c++")}
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);
{CODE}
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):

{img fileId="2164" thumb="y" rel="box[g]"}

This is certainly not what we want. Another option is to use a SkyPlane instead. Make the following modifications:
{CODE(wrap="1", colors="c++")}
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);
{CODE}

{img fileId="2165" thumb="y" rel="box[g]"}

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 [http://www.ogre3d.org/docs/manual/manual_16.html#SEC64|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):
{CODE(wrap="1", colors="c++")}
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);
{CODE}
Compile and run the application. This is what we get:

{img fileId="2166" thumb="y" rel="box[g]"}

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 ((BasicTutorial3SourceCurrent|here)).
!Next
((Basic Tutorial 4))
---
Alias: (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