Basic Tutorial 2         Lights, Cameras, and Shadows
Print

Tutorial Introduction
Image

This tutorial will expand on the use of Lights in a scene and using them to cast shadows. It will also cover the basics of using the Camera in Ogre.

The full source for this tutorial is here.

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

Prerequsites

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.

Image

Setting Up the Scene

This time we are going to add some new methods to our TutorialApplication class. Add the following to the protected section of your header:

TutorialApplication.h
virtual void createCamera();
virtual void createViewports();

These two methods are already defined as virtual functions in the BaseApplication class. In this tutorial, we are going to provide overrides. This is how we will slowly take over some functionality that was hidden in BaseApplication.

Remember to add definitions to your cpp file as well:

TutorialApplication.cpp
void TutorialApplication::createCamera()
{
}
 
void TutorialApplication::createViewports()
{
}

 

The Ogre Camera Class

A Camera is the object we use to view our scene. A Camera(external link) is a special object that works similar to a SceneNode. It has methods like setPosition and yaw. You can also attach it to a SceneNode. For instance, you might want to temporarily attach your Camera to a SceneNode that follows a path through the sky to create an aerial cutscene. Just like a SceneNode the Camera's position will be relative to its parent SceneNode. The Camera is not a SceneNode (it actually inherits from the Frustum class), but for movement and rotation, you can treat it like a SceneNode.

Creating a Camera

We will now override the BaseApplication createCamera method. The first step will be asking the SceneManager to create a new Camera. Add the following to createCamera:

mCamera = mSceneMgr->createCamera("PlayerCam");

You can retrieve the Camera by name using the SceneManager's getCamera method.

Next, we will position the Camera and use a method called lookAt to set its direction.

mCamera->setPosition(Ogre::Vector3(0, 300, 500));
mCamera->lookAt(Ogre::Vector3(0, 0, 0));

The lookAt(external link) method is very useful. It does exactly what it says. It rotates the Camera so that its line of sight focuses on the vector you give it. It makes the Camera "look at" the point.

The last thing we'll do is set the near clipping distance to 5 units. This is the distance at which the Camera will no longer render any mesh. If you get very close to a mesh, this will sometimes cut the mesh and allow you to see inside of it. The alternative is filling the entire screen with a tiny, highly magnified piece of the mesh's texture. It's up to you what you want in your scene. For demonstration, we'll set it here.

mCamera->setNearClipDistance(5);

You can also set the far clip distance for the Camera. This will chop off meshes in the distance. Although, you should not set the far clip distance when using stencil shadows, which we will be using in this tutorial.

The last thing we'll do is create a new SdkCameraMan. This is the Camera controller provided by OgreBites.

mCameraMan = new OgreBites::SdkCameraMan(mCamera);

Since we've just requested some dynamic memory, we always have to make sure it is cleaned up appropriately. In our case, the mCameraMan variable will be taken care of by the destructor for BaseApplication, because we are simply recreating the Camera code that class was doing for us. If you look at BaseApplication::~BaseApplication, then you'll see this line:

if (mCameraMan) delete mCameraMan;

This sends the camera man home at the end of the day.

Viewports

When dealing with multiple Cameras in a scene, the concept of a Viewport becomes very useful. We will touch on it now, because it will help you understand more about how Ogre decides which Camera to use when rendering a scene. Ogre makes it possible to have multiple SceneManagers running at the same time. It also allows you to break up the screen and use separate Cameras to render different views of a scene. This would allow the creation of things like splitscreens and minimaps. These kinds of things will be covered in later tutorials.

There are three constructs that are crucial to understanding how Ogre renders a scene: the Camera, the SceneManager, and the RenderWindow. We have not yet covered the RenderWindow. It basically represents the whole window we are rendering to. The SceneManager will create Cameras to view the scene, and then we tell the RenderWindow where to display each Camera's view. The way we tell the RenderWindow which area of the screen to use is by giving it a Viewport(external link). For many circumstances, we will simply create one Camera and create a Viewport which represents the whole screen. In fact, that is exactly what was already being done for us in BaseApplication.

Creating a Viewport

Let's create a Viewport for our scene. To do this, we will use the addViewport method of the RenderWindow. Add the following to TutorialApplication::createViewports:

Ogre::Viewport* vp = mWindow->addViewport(mCamera);

mWindow is another variable defined for us in BaseApplication. Let's set the background color of the Viewport.

vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0));

We've set it to black because we are going to add colored lighting later, and we don't want the background color affecting how we see the lighting.

The last thing we are going to do is set the aspect ratio of our Camera. If you are using something other than a standard full-window viewport, then failing to set this can result in a distorted scene. We will set it here for demonstration even though we are using the default aspect ratio.

mCamera->setAspectRatio(
  Ogre::Real(vp->getActualWidth()) /
  Ogre::Real(vp->getActualHeight()));

We have retrieved the width and height from the Viewport to set the aspect ratio. As we mentioned, the default is already set to use the full screen's dimensions.

Compile and run your application. You should still only see a black screen with the overlays, just make sure it runs.

Building the Scene

Before we get to shadows and lighting, let's add some elements to our scene. Let's put a ninja right in the middle of things. Add the following to createScene right after we set the ambient light:

Ogre::Entity* ninjaEntity = mSceneMgr->createEntity("ninja.mesh");
ninjaEntity->setCastShadows(true);
 
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(ninjaEntity);

This should look familiar, except we are asking the mesh to cast shadows this time. And notice that we have created a child scene node and attached the ninjaEntity all in one call this time.

We will also create something for the ninja to be standing on. We can use the MeshManager(external link) to create meshes from scratch. We will use it to generate a textured plane to use as the ground.

The first thing we'll do is create an abstract Plane object. This is not the mesh, it is more of a blueprint.

Ogre::Plane plane(Ogre::Vector3::UNIT_Y, 0);

We create a plane by supplying a vector that is normal to our plane and its distance from the origin. So we have created a plane that is perpendicular to the y-axis and zero units from the origin. Here's a picture:
Image
There are other overloads of the Plane constructor that let us pass a second vector instead of a distance from the origin. This allows us to build any plane in 3D space we want.

Now we'll ask the MeshManager to create us a mesh using our Plane blueprint. The MeshManager is already keeping track of the resources we loaded when initializing our application. On top of this, it can create new meshes for us.

Ogre::MeshManager::getSingleton().createPlane(
  "ground",
  Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
  plane, 
  1500, 1500, 20, 20, 
  true, 
  1, 5, 5, 
  Ogre::Vector3::UNIT_Z);

This is a complicated method, and we're not entirely equipped to understand all of it yet. You can read through the MeshManager(external link) class specification if you want to learn more now. Basically, we've created a new mesh called "ground" with a size of 1500x1500.

Now we will create a new Entity using this mesh.

Ogre::Entity* groundEntity = mSceneMgr->createEntity("ground");
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(groundEntity);

Be careful that you don't confuse the parameter given to createEntity for the Entity's name. It is actually the name of the mesh we just created. We're used to seeing mesh names end with '.mesh'.

We want to tell our SceneManager not to cast shadows from our ground Entity. It would just be a waste. Don't get confused, this means the ground won't cast a shadow, it doesn't mean we can't cast shadows on to the ground.

groundEntity->setCastShadows(false);

And finally we need to give our ground a material. For now, it will be easiest to use a material from the script that Ogre includes with its samples. You should have these resources in your SDK or the source directory you downloaded to build Ogre.

groundEntity->setMaterialName("Examples/Rockwall");

Make sure you add the texture for the material and the Examples.material script to your resource loading path. In our case, the texture is called 'rockwall.tga'. You can find the name yourself by reading the entry in the material script.

Using Shadows in Ogre

Enabling shadows in Ogre is easy. The SceneManager class has a setShadowTechnique(external link) method we can use. Then whenever we create an Entity, we call setCastShadows to choose which Entities will cast shadows.

Let's turn off the ambient light so we can see the full effect of our lights. Find the setAmbientLight call in createScene, and make the following changes:

mSceneMgr->setAmbientLight(Ogre::ColourValue(0, 0, 0));
mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);

Now the SceneManager will use additive stencil shadows. Let's add some lights to see this in action.

Lights

Ogre provides three types of lighting.

  • Ogre::Light::LT_POINT - This Light speads out equally in all directions from a point.
  • Ogre::Light::LT_SPOTLIGHT - This Light works like a flashlight. It produces a solid cylinder of light that is brighter at the center and fades off.
  • Ogre::Light::LT_DIRECTIONAL - This Light simulates a huge source that is very far away - like daylight. Light hits the entire scene at the same angle everywhere.

The Light(external link) class has a wide range of properties. Two of the most important are the diffuse and specular color. Each material script defines how much specular and diffuse lighting a material reflects. These properties will be covered in some of the later tutorials.

Creating a Light

Let's add a Light to our scene. We do this by calling the SceneManager's createLight method. Add the following to createScene right after we finish creating the groundEntity:

Ogre::Light* spotLight = mSceneMgr->createLight("SpotLight");

We'll set the diffuse and specular colors to pure blue.

spotLight->setDiffuseColour(0, 0, 1.0);
spotLight->setSpecularColour(0, 0, 1.0);

Next we will set the type of the light to spotlight.

spotLight->setType(Ogre::Light::LT_SPOTLIGHT);

The spotlight requires both a position and a direction - remember it acts like a flashlight. We'll place the spotlight above the right shoulder of the ninja shining down on him at a 45 degree angle.

spotLight->setDirection(-1, -1, 0);
spotLight->setPosition(Ogre::Vector3(200, 200, 0));

Image
Finally, we set what is called the spotlight range. These are the angles that determine where the light fades from bright in the middle to dimmer on the outside edges.

spotLight->setSpotlightRange(Ogre::Degree(35), Ogre::Degree(50));

Compile and run the application. You should see the shadowy blue figure of a ninja.

Image

Creating More Lights

Next we'll add a directional light to our scene. This type of light essentially simulates daylight or moonlight. The light is cast at the same angle across the entire scene equally. As before, we'll start by creating the Light and setting its type.

Ogre::Light* directionalLight = mSceneMgr->createLight("DirectionalLight");
directionalLight->setType(Ogre::Light::LT_DIRECTIONAL);

Now we'll set the diffuse and specular colors to a dark red.

directionalLight->setDiffuseColour(Ogre::ColourValue(.4, 0, 0));
directionalLight->setSpecularColour(Ogre::ColourValue(.4, 0, 0));

Finally, we need to set the Light's direction. A directional light does not have a position because it is modeled as a point light that is infinitely far away.

directionalLight->setDirection(Ogre::Vector3(0, -1, 1));

Image
The Light class also defines a setAttenuation(external link) function which allows you to control how the light dissipates as you get farther away from it. After you finish this tutorial, try using this method in your scene to see how it affects your lights.

Compile and run the application. Your ninja should now have a shadow cast behind him, and the scene should be filled with red light.

Image

To complete the set, we will now add a point light to our scene.

Ogre::Light* pointLight = mSceneMgr->createLight("PointLight");
pointLight->setType(Ogre::Light::LT_POINT);

We'll set the the specular and diffuse colors to a dark gray.

pointLight->setDiffuseColour(.3, .3, .3);
pointLight->setSpecularColour(.3, .3, .3);

A point light has no direction. It only has a position. We will place our last light above and behind the ninja.

pointLight->setPosition(Ogre::Vector3(0, 150, 250));

Compile and run the application. You should see a long shadow cast in front of the ninja now. And you should see the effects of the point light brightening up the area behind the ninja. Try to think about why the colors turn out the way they do. For instance, why does the shadow behind the ninja appear to have no red at all?

Image

Shadow Types

Ogre currently supports three types of Shadows:

  1. Ogre::SHADOWTYPE_TEXTURE_MODULATIVE - This is the least computationally expensive type. A black and white render-to-texture is created using all of the shadow casters. This is then applied to the scene.
  2. Ogre::SHADOWTYPE_STENCIL_MODULATIVE - This technique renders all shadow volumes as a modulation after all non-transparent objects have been rendered to the scene. This is not as intensive as Ogre::SHADOWTYPE_STENCIL_ADDITIVE, but it is also less accurate.
  3. Ogre::SHADOWTYPE_STENCIL_ADDITIVE - This technique renders each light as a separate additive pass on the scene. This is very hard on the graphics card because each additional light requires an additional rendering pass.

Try experimenting with the different shadow types. There are also other shadow-related methods in the SceneManager(external link) class that you can play with.

Ogre does not provide soft shadows as part of the engine. You can write your own vertex and fragment programs to implement soft shadows and many other things. The Ogre Manual(external link) has a full description of shadows.

Conclusion

This tutorial introduced the use of lights and shadows into the scene. To begin, we covered how to use the MeshManager(external link) to generate meshes from scratch. We then chose which shadow type Ogre should use. Finally, we begin adding an example of each type of Light(external link) to our scene. We created a spotlight, a directional light, and a point light. You can even extend Ogre's lighting and shadow systems by writing your own vertex and fragment programs. Refer to the Ogre Manual for more details.

There are a lot of different settings we've covered that allow you to customize how Ogre renders light and shadow. After you've finished each tutorial, it is a good idea to play around with the new tools you have. This will greatly increase your comfort level working with the library, and it is an excellent way to learn how to navigate API documentation.

Full Source

The full source for this tutorial is here.

Next

Basic Tutorial 3


Alias: Basic_Tutorial_2


Contributors to this page: Duke86 points  , Tp56 points  , Sydius71 points  , SRombauts2857 points  , peters181 points  , kabbotta41 points  , JacobM1 points  , jacmoe180265 points  , holocronweaver3177 points  , gerymate1 points  , dsobotta1 points  and a09036381 points  .
Page last modified on Friday 10 of July, 2015 22:13:22 UTC by Duke86 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.