Basic Tutorial 3 (Terrain, Sky, and Fog) - Axiom : A 3D Rendering Engine in C#

Basic Tutorial 3 (Terrain, Sky, and Fog)

From Axiom

Jump to: navigation, search

Show code samples in



  • This tutorial assumes you have basic knowledge of C#, VB.NET, or Python, or other .NET language, though only the mentioned languages will have sample code.
  • This tutorial builds on the previous tutorials, and it assumes you have already worked through them.

Getting Started

First, we need to add Axiom.SceneManagers.Octree.dll to our References Folder. If you're using Python, just place the .dll in the same folder as your As with the previous tutorials, we will be using the TechDemo class as our starting point. We will be adding code to the CreateScene and ChooseSceneManager methods. Add the following code to your tutorial class:

protected override void ChooseSceneManager()
Protected Overrides Sub ChooseSceneManager
End Sub
def ChooseSceneManager(self):

A Note About Paging

Version 1.7 of Ogre introduced a new Terrain component with paging ability (creating big worlds from "tiles" of 3d terrain). Work on introducing this functionality into Axiom is underway as of this writing. However, until this functionality is properly integrated into Axiom and declared stable, this tutorial deals with the old terrain functionality.

The Root Object and SceneManager Creation

In this demo we will be rendering Terrain in Axiom. To do this we need to use the TerrainSceneManager instead of the default one that the the TechDemo class has been providing for us. Add the following code to your ChooseSceneManager method:

scene = Root.Instance.CreateSceneManager(SceneType.ExteriorClose);

scene = Root.Instance.CreateSceneManager(SceneType.ExteriorClose)

scene = Root.Instance.CreateSceneManager(SceneType.ExteriorClose)

In the previous tutorials, we haven't used the Root object very much before now. In your Main function, the very first thing you did was create a new Root object and Root.Instance returns that object, but what exactly is Root? The Root object is the "core" Axiom object. You can see a UML diagram of the relationships between Axiom objects[here]. You have now seen almost all of the objects mentioned in the diagram in practice, with the exception of the RenderSystem. In this line of code, we are telling the Root node that we want a SceneManager of the ExteriorClose type. The Root object then queries the SceneManagerEnumerator to find the SceneManager of the type you requested and returns it.

After your application is set up, you rarely ever have to deal with Axiom's Root object, and you don't ever interact with the SceneManagerEnumerator directly.

Note about SceneManager Creation

There is an important note about SceneManager creation and storage that will save you confusion in the future. SceneManagers are not Singletons. You can create as many of them as you want. You can have multiple SceneManagers populated with multiple separate geometries and entities at the same time. You can swap between any of these at any time by recreating the Viewport (this is covered in Intermediate Tutorial 4) or display multiple SceneManagers at the same time using multiple Viewports.


Now that we have that cleared up, it's time to actually create Terrain. The base SceneManager defines the LoadWorldGeometry method, which subclasses use for most scene creation purposes. With the TerrainSceneManager class, it expects a filename from which to load a terrain configuration. Add the following code to your CreateScene() method:

scene.LoadWorldGeometry( "Terrain.xml" );
scene.LoadWorldGeometry( "Terrain.xml" )
self.scene.LoadWorldGeometry( "Terrain.xml" )

Compile and run your program. It's that easy.

Note: If you get an exception about the Terrain.xml file not found, this is probably because you added the code above to the ChooseSceneManager instead of the CreateScene method. Resources are only loaded after the ChooseSceneManager method has executed. CreateScene is the TechDemo's method for actually populating the scene, and setting up the terrain is part of that process. You may also want to make sure that the file is located in your Media folder and your EngineConfig.xml correctly references it's location.

The Terrain.xml file

There are many options in the Terrain.xml file, and we will only cover the most basic for changing the images used to generate the terrain. A more detailed explanation of the terrain config file can be found [here].

The TerrainSceneManager uses Heightmaps to generate terrain. You can specify the heightmap you want to use by setting the "Heightmap.image" property. You can set the texture that is used for the terrain by setting the WorldTexture property. The terrain scene manager also allows you to specify a "DetailTexture" property, which is interlaced with the WorldTexture to make the terrain look more realistic. You should find each of the images currently specified by terrain.xml and take a look at them.


Sky Boxes

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 the following code to your CreateScene method:

scene.SetSkyBox(true, "Examples/SpaceSkyBox", 1000);

scene.SetSkyBox(True, "Examples/SpaceSkyBox", 1000)

self.scene.SetSkyBox(True, "Examples/SpaceSkyBox", 5000)

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

So what parameters did we just give this method? The first one is to set if the skybox is enabled, the second is the Material name of the skybox we want to use, and the last parameter is the distance from the camera.

There is another overload to the SetSkyBox method. After setting the distance, you can also set 'drawFirst' to true or false (if set to false, it means that if something is farther than the distance set <5000 in our previous example>, the SkyBox will block out that object), Next you can set the orientation, so that you can rotate the SkyBox. If you had a SkyBox with an image of a Sun directly overhead, but you wanted it to be farther to one side as if the sun was setting, you could say: Quaternion.FromAngleAxis(45, Vector3.UnitZ), for example. The last available parameter is a Resource Group Name, which is almost always going to be set to ResourceGroupManager.DefaultResourceGroupName.

So all of that together:

scene.SetSkyBox(true, "Examples/SpaceSkyBox", 10, false, Quaternion.FromAngleAxis(45, Vector3.UnitZ), ResourceGroupManager.DefaultResourceGroupName);
scene.SetSkyBox(True, "Examples/SpaceSkyBox", 10, False, Quaternion.FromAngleAxis(45, Vector3.UnitZ), ResourceGroupManager.DefaultResourceGroupName)
self.scene.SetSkyBox(True, "Examples/SpaceSkyBox", 10, False, Quaternion.FromAngleAxis(45, Vector3.UnitZ), ResourceGroupManager.DefaultResourceGroupName)

Sky Domes

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 bigest difference is 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 Axiom provides for SkyDomes will let you see this clearly. Clear out the SetSkyBox call from CreateScene method and add the following code instead:

scene.SetSkyDome( true, "Examples/CloudySky", 5, 8 );

scene.SetSkyDome( True, "Examples/CloudySky", 5, 8 )

self.scene.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's positioned fairly close to the surface of the terrain (this looks the best). After looking at this, press 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 'scene.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 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 floating point value and not an integer. You can tile it 1.234 times, if that's what looks good for your application. An optional overload has a fifth and sixth parameter, with fifth being distance, and the sixth being drawFirst, which we have already covered in the SkyBox section.

Sky Planes

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.) 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 SkyPlane. Instead that parameter is set in the d variable of Plane.

Replace the SkyDome code in the CreateScene method with the following code:

Plane plane = new Plane();
plane.D = 1000; //Set distance as 1000
plane.Normal = Vector3.NegativeUnitY; //Make the texture face down
scene.SetSkyPlane(true, plane, "Examples/SpaceSkyPlane");
Dim plane as Plane = new Plane()
plane.D = 1000 'Set distance as 1000
plane.Normal = Vector3.NegativeUnitY 'Make the texture face down
scene.SetSkyPlane(true, plane, "Examples/SpaceSkyPlane")
plane = Plane()
plane.D = 1000 #Set distance as 1000
plane.Normal = Vector3.NegativeUnitY #Make the texture face down
self.scene.SetSkyPlane(true, plane, "Examples/SpaceSkyPlane")

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.

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.


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 it 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.

Another small caveat to be aware of is that when you use the TerrainSceneManager you must be careful to call the SetFog method before the SetWorldGeometry method. (In other SceneManagers it generally doesn't matter). Depending on which is called first, a different vertex program will be chosen to create the fog and terrain.

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 LoadWorldGeometry 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. Clear the SkyPlane code from your CreateScene method and replace it with the following:

ColorEx fadeColor = ColorEx.WhiteSmoke; 
viewport.BackgroundColor = fadeColor;
scene.SetFog(FogMode.Linear, fadeColor, 0, 50, 50);
Dim fadeColor as ColorEx = ColorEx.WhiteSmoke
viewport.BackgroundColor = fadeColor
scene.SetFog(FogMode.Linear, fadeColor, 0, 50, 50)
fadeColor = ColorEx.WhiteSmoke
viewport.BackgroundColor = fadeColor
self.scene.SetFog(FogMode.Linear, fadeColor, 0, 50, 50)

The first parameter to the SetFog method is the type of fog (in this case, linear). The second parameter to SetFog is the color of the fog we are using. 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:

scene.SetFog(FogMode.Exp, fadeColor, 0.005f);
scene.SetFog(FogMode.Exp, fadeColor, 0.005F)
self.scene.SetFog(FogMode.Exp, fadeColor, 0.005)

Compile and run the application. This creates a different look to the fog that is generated.

There is also another exponential fog 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:

scene.SetFog(FogMode.Exp2, fadeColor, 0.003f);
scene.SetFog(FogMode.Exp2, fadeColor, 0.003F)
self.scene.SetFog(FogMode.Exp2, fadeColor, 0.003)

Compile and run the application again.

Fog is mostly interchangeable between the three fog types that Axiom provides. You should experiment with all three 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. What this means is that occasionally portions of the Sky will poke out through the fog. A way to avoid this is to use SkyPlanes.

Powered by MediaWiki GNU Free Documentation License 1.2