Basic Tutorial 2         Cameras, Lights, and Shadows
Print

Tutorial Introduction
Image

In this tutorial you will be introduced to a few more Ogre constructs, expanding on what you have already learned.
This tutorial will deal mainly with Light objects and how they are used to create shadows in Ogre.
It will also cover the absolute basics about Cameras.

 

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

 

Prerequisites

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

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

Getting Started

As with the previous tutorial, we will be using a pre-constructed code base as our starting point. We begin by creating a BasicTutorial2 class and header file by copying the TutorialApplication files and appropriately renaming the files and classes. A similar approach will be used to create classes BasicTutorial3, 4, and so on, by copying and renaming the class of the prior tutorial. Do not forget to change your build target when doing this.

We will add two methods to our BasicTutorial2 class: createViewport and createCamera. These two functions were already defined in the base BaseApplication, but in this tutorial we will now look at them to see how Cameras and Viewports are actually created and used.

Add the declarations for createCamera and createViewports to your basic tutorial 2 class header:

BasicTutorial2 header
class BasicTutorial2 : public BaseApplication
{
public:
	BasicTutorial2(void);
	virtual ~BasicTutorial2(void);
 
protected:
	virtual void createScene(void);
	virtual void createCamera(void);
	virtual void createViewports(void);
};

 
Then you need to add the definitions to your basic tutorial 2 implementation:

BasicTutorial2 implementation
//-------------------------------------------------------------------------------------
BasicTutorial2::BasicTutorial2(void)
{
}
 
//-------------------------------------------------------------------------------------
BasicTutorial2::~BasicTutorial2(void)
{
}
 
//-------------------------------------------------------------------------------------
void BasicTutorial2::createCamera(void)
{
}
 
//-------------------------------------------------------------------------------------
void BasicTutorial2::createViewports(void)
{
}
 
//-------------------------------------------------------------------------------------
void BasicTutorial2::createScene(void)
{
}

 
This should compile, but we'll need to create and initialize several objects before we can get the application to run.

Cameras

Ogre Cameras

A Camera is what we use to view the scene that we have created. A Camera is a special object which works somewhat like a SceneNode does. The Camera object has setPosition(external link), yaw(external link),roll(external link) and pitch(external link) functions, and you can attach it to any SceneNode. Just like SceneNodes, a Camera's position is relative to its parents (it's nice to respect one's elders). For all movement and rotation, you can basically consider a Camera a SceneNode.

One thing about Ogre Cameras that is different from what you may expect is that you should only be using one Camera at a time (for now). That is, we do not create a Camera for viewing one portion of a scene, a second camera for viewing another portion of the scene and then enable or disable cameras based on what portion of the scene we want to display. Instead, the way to accomplish this is to create SceneNodes that act as "camera holders". These SceneNodes simply sit in the scene and point at what the Camera might want to look at. When it is time to display a portion of the Scene, the Camera simply attaches itself to the appropriate SceneNode. We will revisit this technique in the FrameListener tutorial.

Creating a Camera

We will be overriding the default method that BaseApplication uses to create the Camera.

Find the BasicTutorial2::createCamera member function.
The first thing we are going to do is create the Camera.
Since Cameras are tied to the SceneManager which they reside in, we use the SceneManager object to create them. Add this line of code to create the Camera:

void BasicTutorial2::createCamera(void)
{
    // create the camera
    mCamera = mSceneMgr->createCamera("PlayerCam");
}

This creates a Camera with the name "PlayerCam".
Note that you can use the getCamera(external link) function of SceneManager to get Cameras based on their name if you decide not to hold a pointer to it.

The next thing we are going to do is set the position of the Camera and the way that it's facing.
We will be placing objects around the origin, so we'll put the Camera a good distance in the +z direction and have the Camera face the origin. Add this code after the previous bit:

// set its position, direction  
    mCamera->setPosition(Ogre::Vector3(0,10,500));
    mCamera->lookAt(Ogre::Vector3(0,0,0));

The lookAt(external link) function is pretty nifty. You can have the Camera face any position you want to instead of having to yaw, rotate, and pitch your way there. SceneNodes have this function as well, which can make setting Entities facing the right direction much easier in many cases.

Finally we will set a near clipping distance of 5 units.
The clipping distance of a Camera specifies how close or far something can be before you no longer see it. Setting the near clipping distance makes it easier to see through Entities on the screen when you are very close to them. The alternative is being so close to an object that it fills the screen and you can't see anything but a tiny portion of it. You can also set the far clipping distance as well. This will stop the engine from rendering anything farther away than the given value. This is primarily used to increase the framerate if you are rendering large amounts of things on the screen for very long distances.
To set the near clipping distance, add this line:

// set the near clip distance
    mCamera->setNearClipDistance(5);

Setting the far clipping distance would simply be a similar call to setFarClipDistance(external link) (though you should not use a far clip distance with Stencil Shadows, which we will be using in this tutorial).

Now, since we are overriding the createCamera function, we need to construct an OgreBites::SdkCameraMan (camera controller) using our newly constructed camera:

mCameraMan = new OgreBites::SdkCameraMan(mCamera);   // create a default camera controller

 
The createCamera function now looks like this:

void BasicTutorial2::createCamera(void)
{
    // create the camera
    mCamera = mSceneMgr->createCamera("PlayerCam");
    // set its position, direction  
    mCamera->setPosition(Ogre::Vector3(0,10,500));
    mCamera->lookAt(Ogre::Vector3(0,0,0));
    // set the near clip distance
    mCamera->setNearClipDistance(5);
 
    mCameraMan = new OgreBites::SdkCameraMan(mCamera);   // create a default camera controller
}

Viewports

Ogre Viewports

When you start dealing with multiple Cameras, the concept of a Viewport class will become much more useful to you. We bring up the topic now because it is important for you to understand how Ogre decides which Camera to use when rendering a scene. It is possible in Ogre to have multiple SceneManagers running at the same time. It is also possible to split the screen up into multiple areas, and have separate cameras render to separate areas on the screen (think of a split view for 2 players in a console game, for example). While it is possible to do these things, we will not cover how to do them until the advanced tutorials.

To understand how Ogre renders a scene, consider three of Ogre's constructs: the Camera, the SceneManager, and the RenderWindow. The RenderWindow we have not covered, but it is basically the window in which everything is displayed. The SceneManager object creates Cameras to view the scene. You must tell the RenderWindow which Cameras to display on the screen, and what portion of the window to render it in. The area in which you tell the RenderWindow to display the Camera is your Viewport. Under most typical uses of Ogre, you will generally create only one Camera, register the Camera to use the entire RenderWindow, and thus only have one Viewport object.

In this tutorial we will go over how to register the Camera to create the Viewport. We can then use this Viewport object to set the background color of the scene we are rendering.

Creating the Viewport

We will be overriding the BaseApplication's creation of the Viewport, so find the BasicTutorial2::createViewports member function. To create the Viewport we simply call the addViewport function of RenderWindow and supply it with the Camera we are using. The BaseApplication class has already populated the mWindow class with our RenderWindow, so add this line of code:

// Create one viewport, entire window
    Ogre::Viewport* vp = mWindow->addViewport(mCamera);

Now that we have our Viewport, what can we do with it? The answer is: not much. The most important thing we can do with it is to call the setBackgroundColour function to set the background to whatever color we choose. Since we are dealing with lighting in this tutorial we will set the color to black:

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

Note that ColourValue expects a red, green, and blue color value for its parameters between the values of 0 and 1. The last, and most important thing we need to do is to set the aspect ratio of our Camera. If you are using something other than the standard full-window viewport, then failing to set this can result in a very strange looking scene. We will go ahead and set it even though we are using the default aspect ratio:

// Alter the camera aspect ratio to match the viewport
    mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));

That's all that has to be done for our simple use of the Viewport class.

Here's how the finished createViewports function looks like:

void BasicTutorial2::createViewports(void)
{
    // Create one viewport, entire window
    Ogre::Viewport* vp = mWindow->addViewport(mCamera);
    vp->setBackgroundColour(Ogre::ColourValue(0,0,0));
    // Alter the camera aspect ratio to match the viewport
    mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));    
}

 
At this point you should be able to compile and run the application, though nothing will appear but a blank scene (use the Escape key to exit). Be sure you can run the application without it crashing before continuing.

Lights and Shadows

Shadow Types that Ogre Supports

Ogre currently supports three types of Shadows:

 

  1. Modulative Texture Shadows (Ogre::SHADOWTYPE_TEXTURE_MODULATIVE) - The least computationally expensive of the three. This creates a black and white render-to-texture of shadow casters, which is then applied to the scene.
  2. Modulative Stencil Shadows (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 Additive Stencil Shadows, but it is also not as accurate.
  3. Additive Stencil Shadows (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 pass at rendering the scene.

 
Ogre does not support soft shadows as part of the engine. If you want soft shadows you will need to write your own vertex and fragment programs. Note that this is just a quick introduction here - the Ogre manual fully describes shadows(external link) in Ogre and the implications of using them.

Using Shadows in Ogre

Using shadows in Ogre is relatively simple. The SceneManager class has a setShadowTechnique(external link) member function that we can use to set the type of Shadows we want. Then whenever you create an Entity, call the setCastShadows(external link) function to set whether or not it casts shadows.

We will now set the ambient light to complete darkness, and set the shadow type. Find the BasicTutorial2::createScene member function and add this code to it:

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

Now the SceneManager uses additive stencil shadows. Let's create an object on the scene and make it cast shadows.

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

We also need something for the Ninja to stand on (so that he has something to cast shadows onto). To do this we will create a simple plane. This is not meant to be a tutorial on using MeshManager, but we will go over the very basics since we have to use it to create a plane. First we need to define the Plane(external link) object itself, which is done by supplying a normal and the distance from the origin. We could (for example) use planes to make up parts of world geometry, in which case we would need to specify something other than 0 for our origin distance. For now we just want a plane to have the positive y axis as its normal (that means we want it to face up), and no distance from the origin:

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

Now we need to register the plane so that we can use it in our application. The MeshManager class keeps track of all the meshes we have loaded into our application (for example, this keeps track of the ogrehead.mesh and the ninja.mesh that we have been using). The createPlane(external link) member function takes in a Plane definition and makes a mesh from the parameters. This registers our plane for use:

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

Again, we are not going to delve into the specifics of how to use the MeshManager just yet (consult the API reference if you want to see exactly what each parameter is doing). Basically we have registered our plane to be 1500 by 1500 in size and new mesh is called "ground". Now, we can create an Entity from this mesh and place it on the scene:

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

Neat huh? There are two more things we need to do with our ground before we are finished with it. The first is to tell the SceneManager that we don't want it to cast shadows since it is what's being used for shadows to project on. The second thing is we need to put a texture on it. Our ninja mesh already has a material script defined for him. When we manually created our ground mesh, we did not specify what texture to use on it. We will use the "Examples/Rockwall" material script that Ogre includes with its samples:

entGround->setMaterialName("Examples/Rockwall");
entGround->setCastShadows(false);

 
This is what our createScene function looks like so far:

void BasicTutorial2::createScene(void)
{
    mSceneMgr->setAmbientLight(Ogre::ColourValue(0, 0, 0));
    mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);
 
    Ogre::Entity* entNinja = mSceneMgr->createEntity("Ninja", "ninja.mesh");
    entNinja->setCastShadows(true);
    mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(entNinja);
 
    Ogre::Plane plane(Ogre::Vector3::UNIT_Y, 0);
 
    Ogre::MeshManager::getSingleton().createPlane("ground", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
        plane, 1500, 1500, 20, 20, true, 1, 5, 5, Ogre::Vector3::UNIT_Z);
 
    Ogre::Entity* entGround = mSceneMgr->createEntity("GroundEntity", "ground");
    mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(entGround);
 
    entGround->setMaterialName("Examples/Rockwall");
    entGround->setCastShadows(false);
 
}

 
Now that we have a Ninja and ground in the scene, let's compile and run the program. We see... nothing! What's going on? In the previous tutorial we added entities and they displayed fine. The reason the Ninja doesn't show up is that the scene's ambient light has been set to total darkness. So let's add a light to see what is going on.

Types of Lights

There are three types of lighting that Ogre provides.

  1. Point (Ogre::Light::LT_POINT) - Point lights emit light in every direction equally.
  2. Spotlight (Ogre::Light::LT_SPOTLIGHT) - A spotlight works exactly like a flashlight does. You have a position where the light starts, and then light heads out in a direction. You can also tell the light how large of an angle to use for the inner circle of light and the outer circle of light (you know how flashlights are brighter in the center, then lighter after a certain point?).
  3. Directional (Ogre::Light::LT_DIRECTIONAL) - A directional light simulates far-away light that hits everything in the scene from a direction. Let's say you have a night time scene and you want to simulate moonlight. You could do this by setting the ambient light for the scene, but that's not exactly realistic since the moon does not light everything equally (neither does the sun). One way to do this would be to set a directional light and point in the direction the moon would be shining.

 
Lights(external link) have a wide range of properties that describe how the light looks. Two of the most important properties of a light are its diffuse and specular color. Each material script defines how much diffuse and specular lighting the material reflects, which we will learn how to control in a later tutorial.

Creating the Lights

To create a Light in Ogre, we call the SceneManager's createLight(external link) member function and supply the light's name, very much like how we create an Entity or Camera. After we create a Light, we can either set the position of it manually or attach it to a SceneNode for movement. Unlike the Camera object, light only has setPosition(external link) and setDirection(external link) (and not the full suite of movement functions like translate, pitch, yaw, roll, etc). So if you need to create a stationary light, you should call the setPosition member function. If you need the light to move (such as creating a light that follows a character), then you should attach it to a SceneNode instead.

So, let's start with a basic point Light. The first thing we will do is create the light, set its type, and set its position:

Ogre::Light* pointLight = mSceneMgr->createLight("pointLight");
    pointLight->setType(Ogre::Light::LT_POINT);
    pointLight->setPosition(Ogre::Vector3(0, 150, 250));

Now that we have created the light, we can set its diffuse(external link) and specular(external link) color. Let's make it red:

pointLight->setDiffuseColour(1.0, 0.0, 0.0);
    pointLight->setSpecularColour(1.0, 0.0, 0.0);

Now compile and run the application. Success! We can now see the Ninja and he casts a shadow. Be sure to also look at him from the front, a complete silhouette. One thing to notice is that you do not "see" the light source. You see the light it generates but not the actual light object itself. Many of Ogre's tutorials add a simple entity to show where the light is being emitted from. If you are having trouble with lights in your application you should consider creating something similar to what they do so you can see exactly where your light is.

Next, let's try out directional light. Notice how the front of the ninja is pitch black? Let's add a small amount of yellow directional light that is shining towards the front of his body. We create the light and set the color just like we do for a point light:

Ogre::Light* directionalLight = mSceneMgr->createLight("directionalLight");
    directionalLight->setType(Ogre::Light::LT_DIRECTIONAL);
    directionalLight->setDiffuseColour(Ogre::ColourValue(.25, .25, 0));
    directionalLight->setSpecularColour(Ogre::ColourValue(.25, .25, 0));

Since directional light is supposed to come from a far-off distance, we do not have to set its position, only its direction. We'll set the direction of the light to be in the positive z and negative y direction (like it is coming from 45 degrees in front and above the ninja):

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

Compile and run the application. We now have two shadows on the screen, though since the directional light is so faint, the shadow is also faint. The last type of light we are going to play with is the spotlight. We will now create a blue spotlight:

Ogre::Light* spotLight = mSceneMgr->createLight("spotLight");
    spotLight->setType(Ogre::Light::LT_SPOTLIGHT);
    spotLight->setDiffuseColour(0, 0, 1.0);
    spotLight->setSpecularColour(0, 0, 1.0);

We also need to set both the position and the direction that the spotlight shines in. We will create a spotlight that hovers above the Ninja's right shoulder, and shines down directly on him:

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

Spotlights also allow us to specify how wide the beam of the light is. Imagine a flashlight beam for a second. There is a core beam in the center that is brighter than the surrounding light. We can set the width of both of these beams by calling the setSpotlightRange(external link) member function:

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

Compile and run the application. Purple Ninja...dangerous!

This is how our createScene function now looks:

void BasicTutorial2::createScene(void)
{
    mSceneMgr->setAmbientLight(Ogre::ColourValue(0, 0, 0));
    mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE);
 
    Ogre::Entity* entNinja = mSceneMgr->createEntity("Ninja", "ninja.mesh");
    entNinja->setCastShadows(true);
    mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(entNinja);
 
    Ogre::Plane plane(Ogre::Vector3::UNIT_Y, 0);
 
    Ogre::MeshManager::getSingleton().createPlane("ground", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
        plane, 1500, 1500, 20, 20, true, 1, 5, 5, Ogre::Vector3::UNIT_Z);
 
    Ogre::Entity* entGround = mSceneMgr->createEntity("GroundEntity", "ground");
    mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(entGround);
 
    entGround->setMaterialName("Examples/Rockwall");
    entGround->setCastShadows(false);
 
    Ogre::Light* pointLight = mSceneMgr->createLight("pointLight");
    pointLight->setType(Ogre::Light::LT_POINT);
    pointLight->setPosition(Ogre::Vector3(0, 150, 250));
 
    pointLight->setDiffuseColour(1.0, 0.0, 0.0);
    pointLight->setSpecularColour(1.0, 0.0, 0.0);
 
    Ogre::Light* directionalLight = mSceneMgr->createLight("directionalLight");
    directionalLight->setType(Ogre::Light::LT_DIRECTIONAL);
    directionalLight->setDiffuseColour(Ogre::ColourValue(.25, .25, 0));
    directionalLight->setSpecularColour(Ogre::ColourValue(.25, .25, 0));
 
    directionalLight->setDirection(Ogre::Vector3( 0, -1, 1 )); 
 
    Ogre::Light* spotLight = mSceneMgr->createLight("spotLight");
    spotLight->setType(Ogre::Light::LT_SPOTLIGHT);
    spotLight->setDiffuseColour(0, 0, 1.0);
    spotLight->setSpecularColour(0, 0, 1.0);
 
    spotLight->setDirection(-1, -1, 0);
    spotLight->setPosition(Ogre::Vector3(300, 300, 0));
 
    spotLight->setSpotlightRange(Ogre::Degree(35), Ogre::Degree(50));
}

 
Image

Things to Try

Different Shadow Types

In this demo we only set the shadow type to be SHADOWTYPE_STENCIL_ADDITIVE. Try setting it to the other two types of shadows and see what happens. There are also many other shadow-related functions in the SceneManager(external link) class. Try playing with some of them and seeing what you come up with.

Light Attenuation

Lights define a setAttenuation(external link) function which allows you to control how the light dissipates as you get farther away from it. Add a function call to the Point light that sets the attenuation to different values. How does this affect the light?

SceneManager::setAmbientLight

Experiment with the setAmbientLight(external link) function of mSceneMgr.

Viewport Background Colour

Change the default ColourValue in the createViewports function. While it is not really appropriate to change it to something other than black in this situation, it is a good thing to know how to change.

Camera::setFarClipDistance

In createCamera we set the near clip distance. Add a function call to setFarClipDistance(external link) and set it to be 500, watch what happens when you move from seeing the Ninja and not seeing the Ninja with stencil shadows turned on. Notice the slowup?

Note: You'll need to set mSceneMgr->setShadowUseInfiniteFarPlane(false), for this to work, and you might get some strange shadows. (See this thread(external link))

Planes

We did not cover much about Planes in this tutorial (it was not the focus of this article). We will go back and revisit this topic in a later tutorial, but for now you should look up the createPlane(external link) function and try playing with some of the inputs to the function.

Conclusion

Now you should have an understanding of the basis on Camera, Lights and Shadows. We will be using them more in the next tutorials.

Full Source

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

Next

Proceed to Basic Tutorial 3 Terrain, Sky, and Fog


Alias: Basic_Tutorial_2


Contributors to this page: gerymate1 points  , Tp56 points  , Sydius71 points  , SRombauts2857 points  , peters181 points  , JacobM1 points  , jacmoe180265 points  and holocronweaver3177 points  .
Page last modified on Friday 26 of September, 2014 09:45:28 UTC by gerymate1 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.