Basic Tutorial 3

From Axiom

Jump to: navigation, search

 Show code samples in


Beginner Tutorial 3: Terrain, Sky, and Fog

Original version (for Ogre) by Clay Culver.

Converted to Axiom by David Clifton.

Feedback is appreciated.

Contents

Prerequisites

This tutorial assumes you have knowledge of C# programming and are able to setup and compile an Axiom application. This tutorial builds on the previous beginner tutorials, and it assumes you have already worked through them.

Introduction

In this tutorial we will be exploring how to manipulate terrain, sky, and fog in your Axiom 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 it.

As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it. There is no substitute for writing actual code and seeing the results, so please try to refrain from just reading through it.

Getting Started

As with the previous tutorials, we will be using a pre-constructed code base as our starting point. Take your previous tutorial application and add this new method:

/// <summary>
/// Override for choosing the scene manager
/// </summary>
protected override void ChooseSceneManager() {
}
''' <summary>
''' Overrider for choosing the scene manager
''' </summary>
Protected Overrides Sub ChooseSceneManager()

End Sub

Also clear all code out of the CreateScene method, so that it is an empty method for our purposes. Here we are overriding another method used by the ExampleApplication class, ChooseSceneManager, which is used to select the scene manager to be used.

The Root Object and SceneManager Creation

Root

In this demo we will be rendering Terrain in Axiom. To do this we need to set the SceneManager to the TerrainSceneManager instead of the default one that the ExampleApplication sets up for us. Find the ChooseSceneManager member, and add the following code:

SceneManager = Root.SceneManagers.GetSceneManager( SceneType.ExteriorClose );
SceneManager = Root.SceneManagers.GetSceneManager(SceneType.ExteriorClose)

The Root object is the "core" Axiom object. In this chunk of code, we are accessing the SceneManagers Enumerator which is initialized on the Root object. We ask the Enumerator for the SceneManager which is currently fulfilling our needs for Close, Exterior environments (as opposed to far, like a paging landscape under an airplane simulation).

After your application is set up, you rarely ever have to deal with Axiom's Root object.

SceneManager Creation

I would like to go ahead and talk to you about SceneManager creation and storage to save you the confusion in the future. SceneManagers are not Singletons. You can create as many of them as you want, and unlike SceneNodes/Lights/etc you can create them directly with a "new SceneManager()" statement (you do not have to use Root's SenceManagers GetSceneManager method, but you really should). 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 (not covered in this tutorial) or display multiple SceneManagers at the same time using multiple Viewports.

Why do we use the GetSceneManager function instead of creating our SceneManager object manually? Well, the plugin system in Axiom gives us a great deal of flexibility when working with single SceneManagers. There are only a few scene types defined in the SceneType enum.

Until now, the ExampleApplication has been choosing Generic as our SceneManager. You might think that this is the base SceneManager class, but as long as you did not fiddle with the scene managers which are in your output directory, then that's not what you have been using! The OctreeSceneManager registers itself as Generic and overrides the base SceneManager class if you are using the OctreeSceneManager plugin (the system detects these plugins on startup). The OctreeSceneManager uses a system of culling objects that are not visible and thus it's generally faster than the regular SceneManager. If you removed the OctreeSceneManager plugin from your plugins.cfg you would probably be using the basic SceneManager when you request Generic, or you could possibly be using something better depending on your plugins. That's the beauty of the system.

Another way to get your scene manager would be to directly utilize the SceneManagerEnumerator to return a singleton instance of itself, instead of utilize root, like this:

SceneManager = SceneManagerEnumerator.Instance.GetSceneManager( SceneType.ExteriorClose );
SceneManager = SceneManagerEnumerator.Instance.GetSceneManager(SceneType.ExteriorClose)

Different SceneManagers provide different benefits depending upon the scene you wish to load. Currently Axiom only comes with the BSP and Octree scene managers. The terrain scene manager is a subset of the Octree scene manager. It is recommended that when choosing an appropriate SceneManager for your scene, you consult the web for detailed information on the benefits of BSP vs. Octree scene managers.

Terrain

The Terrain.xml File

In order to render terrain it is necessary to build a file which details some important aspects as to how the terrain should look first. Create a new file called Terrain.xml in your output directory (Debug most likely) and place the following text in it, then save:

<TerrainConfig>
	<Terrain>terrain.png</Terrain>
	<WorldTexture>terrain_texture.jpg</WorldTexture>
	<DetailTexture>terrain_detail.jpg</DetailTexture>
	<DetailTile>3</DetailTile>
	<TileSize>17</TileSize>
	<MaxPixelError>8</MaxPixelError>
	<ScaleX>1</ScaleX>
	<ScaleY>0.2</ScaleY>
	<ScaleZ>1</ScaleZ>
	<MaxMipMapLevel>5</MaxMipMapLevel>
	<VertexNormals>no</VertexNormals>
</TerrainConfig>

There are many options in the Terrain.xml file, and I am only going to cover the most basic for changing the images used to generate the terrain. One major thing to note about the TerrainSceneManager is that it has been designed with paging functionality in mind, but it has not been implemented yet. Paging terrain is a system where the terrain is broken into chunks, and only displayed when the user would be able to see it. This allows you define a huge world and be able to use it without dropping the framerate by a significant amount. There is an Ogre plugin that does this, the Paging Scene Manager, but it is not currently implemented for Axiom.

The TerrainSceneManager uses Height maps to generate terrain. You can specify the height map you want to use by setting the Terrain tag. You can set the texture that is used for the terrain by setting the WorldTexture tag. The terrain scene manager also allows you to specify a DetailTexture tag, 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 above and take a look at them (they should be in the Media/Textures folder).

Details on how to create height maps have been discussed ad nauseum around the web. Google for heightmap and you are sure to find some good information. For a brief overview of the concept, Wikipedia is always a good way to go: Wikipedia Heightmap

Adding Terrain to the Scene

Now that we have that terrain file setup, 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. Find the CreateScene member function and add this line of code:

SceneManager.LoadWorldGeometry( "Terrain.xml" );
SceneManager.LoadWorldGeometry("Terrain.xml")

Compile and run your program. It's that easy. You might want to set the Camera to start in a place that's over the terrain if it bothers you that its initial position is on an even level with the terrain and far away.

Lighting Terrain

We just spent the entire previous tutorial going over lights and shadows, but the bad news is it's not easy to get this to work in the TerrainSceneManager. For now, just know that it's much easier to take the detail texture and add lighting effects to it than it is to get standard lighting working.

Sky

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

SkyBoxes

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:

// Set the skybox
SceneManager.SetSkyBox( true, "Skybox/Space", 5000 );
' Set the skybox
SceneManager.SetSkyBox(True, "Skybox/Space", 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.) There are several useful parameters for SkyBoxes that we can set when calling SetSkyBox. The first option is whether or not to enable the SkyBox. If you want to later disable the SkyBox simply call SetSkyBox with the first parameter false, and the second parameter and empty string. The second parameter is the material script to use for the SkyBox.

The third parameter to SetSkyBox is fairly important to understand. The third parameter sets the distance that the SkyBox is away from the Camera. So, lets see what happens when you change the distance parameter for the SkyBox to something very close:

SceneManager.SetSkyBox( true, "Skybox/Space", 10 );
SceneManager.SetSkyBox(True, "Skybox/Space", 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:

SceneManager.SetSkyBox( true, "Skybox/Space", 5000, false, Quaternion.Identity );
SceneManager.SetSkyBox(True, "Skybox/Space", 5000, False, Quaternion.Identity)

Note that we have to pass a Quaternion for orientation…for now just use the Identity and ignore this parameter. You can explore its uses by playing around with building various quaternions and viewing the results.

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:

SceneManager.SetSkyBox( true, "Skybox/Space", 300, false, Quaternion.Identity );
SceneManager.SetSkyBox(True, "Skybox/Space", 300, False, Quaternion.Identity)

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, using the parameters as we first had them is a safe choice.

SkyDomes

SkyDomes are very similar to SkyBoxes, and you use them by calling SetSkyDome. A giant cube is created around the Camera and rendered onto, but the 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 and add this code instead:

SceneManager.SetSkyDome( true, "Examples/CloudySky", 5, 8 );
SceneManager.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). 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. 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 Real value (floating point) and not an integer. You can tile it 1.234 times, if that's what looks good for your application.

SkyPlanes

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

Plane plane = new Plane();
plane.D = 1000;
plane.Normal = Vector3.NegativeUnitY;
Dim plane As New Plane
plane.D = 1000
plane.Normal = Vector3.NegativeUnitY

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. The sixth is whether the SkyPlane should be drawn first (as discussed previously) and the seventh is a bow factor:

SceneManager.SetSkyPlane( true, plane, "Skyplane/Space", 1500, 75, true, 0 );
SceneManager.SetSkyPlane(True, plane, "Skyplane/Space", 1500, 75, True, 0)

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.

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

Fog

Fog Introduction

Fog in Axiom is very easy to use. There is one caveat that you need to know about before you try to use fog in your program. When you use the TerrainSceneManager, you must be careful to call the SetFog function before the LoadWorldGeometry function. (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.

Before we get started, clear out all contents of the CreateScene function except for the call to LoadWorldGeometry.

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 (IE 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 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. This is how we do that:

RenderWindow.GetViewport( 0 ).BackgroundColor = new ColorEx( 0.9f, 0.9f, 0.9f );
RenderWindow.GetViewport(0).BackgroundColor = New ColorEx(0.9F, 0.9F, 0.9F)

Once we set the background color, we can now create the fog. Remember, this code must appear before the LoadWorldGeometry call:

SceneManager.SetFog( FogMode.Linear, new ColorEx( 0.9f, 0.9f, 0.9f ), 0, 50, 500 );
SceneManager.SetFog(FogMode.Linear, New ColorEx(0.9F, 0.9F, 0.9F), 0, 50, 500)

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

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

SceneManager.SetFog( FogMode.Exp, new ColorEx( 0.9f, 0.9f, 0.9f ), 0.005f );
SceneManager.SetFog(FogMode.Exp, New ColorEx(0.9F, 0.9F, 0.9F), 0.005F)

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 (IE 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 Exp2. Replace the previous call to SetFog with this:

SceneManager.SetFog( FogMode.Exp2, new ColorEx( 0.9f, 0.9f, 0.9f ), 0.003f );
SceneManager.SetFog(FogMode.Exp2, New ColorEx(0.9F, 0.9F, 0.9F), 0.003F)

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

Personal tools