back to home

Tutorial 2: Basic Lighting with OpenSceneGraph

Lighting Example

Let's create some light! We won't explain much here, instead you should have a look at the complete source code at the end of this page!

Create normals for self-made geometric objects

It's way easier to use predefined objects in the scene or models from professional 3D tools such as Maya or 3D Studio Max. But for this example we also created a cube manually to demonstrate the needed parts.

For correct light calculation each face of an object has to have a normal defined. That's why you need to define normals for the cube and then assign the normals to the vertexes. Each corner of the cube is created by 3 vertexes at the same position. Each is part of a different face. Therefore each vertex at the same position needs a normal showing in a different direction: the direction matching the face using the vertex.

Cube with Normals

Let there be light!

To create a light in OpenSceneGraph you have to define a light and a light source. In the example we are using 3 different lights which are basically the same but differ in their diffuse color. So we created a function to create a light which receives one parameter defining the color.

	Light *createLight(Vec4 color)
	{	
		Light *light = new Light();
		// each light must have a unique number
		light->setLightNum(uniqueLightNumber++);
		// we set the light's position via a PositionAttitudeTransform object
		light->setPosition(Vec4(0.0, 0.0, 0.0, 1.0));
		light->setDiffuse(color);
		light->setSpecular(Vec4(1.0, 1.0, 1.0, 1.0));
		light->setAmbient( Vec4(0.0, 0.0, 0.0, 1.0));

		return light;
	}

Then you have to attach the created light to a light source. A light source is an invisible items of the scene. To make it visible we created a sphere for each light in the color of the light. Then we created a PositionAttitudeTransform and added the light source and the sphere to it. This method makes sure that the light and the sphere are always at the same position.


Complete Source Code

	#include <osg/ShapeDrawable>
	#include <osg/Geometry>
	#include <osg/Material>
	#include <osg/StateSet>
	#include <osg/Light>
	#include <osg/LightSource>
	#include <osg/PositionAttitudeTransform>
	#include <osgViewer/Viewer>
	#include <osgGA/TrackballManipulator>

	using namespace osg;

	int uniqueLightNumber = 0;
	int const LIGHTS = 3;

	PositionAttitudeTransform *cubeTransform;
	PositionAttitudeTransform *lightTransform[LIGHTS];
	StateSet *lightStateSet;

	Geode *createCube() {
		// vertex array
		Vec3Array *vertexArray = new Vec3Array();

		// bottom front left
		vertexArray->push_back(Vec3(-1, -1,  0));
		vertexArray->push_back(Vec3(-1, -1,  0));
		vertexArray->push_back(Vec3(-1, -1,  0));
		// bottom front right
		vertexArray->push_back(Vec3(+1, -1,  0));
		vertexArray->push_back(Vec3(+1, -1,  0));
		vertexArray->push_back(Vec3(+1, -1,  0));
		// bottom back right
		vertexArray->push_back(Vec3(+1, +1,  0));
		vertexArray->push_back(Vec3(+1, +1,  0));
		vertexArray->push_back(Vec3(+1, +1,  0));
		// bottom back left
		vertexArray->push_back(Vec3(-1, +1,  0));
		vertexArray->push_back(Vec3(-1, +1,  0));
		vertexArray->push_back(Vec3(-1, +1,  0));

		// top front left
		vertexArray->push_back(Vec3(-1, -1,  2));
		vertexArray->push_back(Vec3(-1, -1,  2));
		vertexArray->push_back(Vec3(-1, -1,  2));
		// top front right
		vertexArray->push_back(Vec3(+1, -1,  2));
		vertexArray->push_back(Vec3(+1, -1,  2));
		vertexArray->push_back(Vec3(+1, -1,  2));
		// top back right
		vertexArray->push_back(Vec3(+1, +1,  2));
		vertexArray->push_back(Vec3(+1, +1,  2));
		vertexArray->push_back(Vec3(+1, +1,  2));
		// top back left
		vertexArray->push_back(Vec3(-1, +1,  2));
		vertexArray->push_back(Vec3(-1, +1,  2));
		vertexArray->push_back(Vec3(-1, +1,  2));


		// face array
		DrawElementsUInt *faceArray = new DrawElementsUInt(PrimitiveSet::TRIANGLES, 0);

		// bottom
		faceArray->push_back(0); // face 1
		faceArray->push_back(9);
		faceArray->push_back(3);
		faceArray->push_back(9); // face 2
		faceArray->push_back(6);
		faceArray->push_back(3);
		// top
		faceArray->push_back(21);  //face 3
		faceArray->push_back(12);
		faceArray->push_back(18);
		faceArray->push_back(12);  //face 4
		faceArray->push_back(15);
		faceArray->push_back(18);
		// left
		faceArray->push_back(22);  //face 5
		faceArray->push_back(10);
		faceArray->push_back(13);
		faceArray->push_back(10);  //face 6
		faceArray->push_back(1);
		faceArray->push_back(13);
		// right
		faceArray->push_back(16);  //face 7
		faceArray->push_back(4);
		faceArray->push_back(19);
		faceArray->push_back(4);  //face 8
		faceArray->push_back(7);
		faceArray->push_back(19);
		// front
		faceArray->push_back(14);  //face 9
		faceArray->push_back(2);
		faceArray->push_back(17);
		faceArray->push_back(2);   //face 10
		faceArray->push_back(5);
		faceArray->push_back(17);
		// back
		faceArray->push_back(20);  //face 11
		faceArray->push_back(8);
		faceArray->push_back(23);
		faceArray->push_back(8);   //face 12
		faceArray->push_back(11);
		faceArray->push_back(23);

		// normal array
		Vec3Array *normalArray = new Vec3Array();
		normalArray->push_back(Vec3(+1, 0, 0));
		normalArray->push_back(Vec3(-1, 0, 0));
		normalArray->push_back(Vec3(0, +1, 0));
		normalArray->push_back(Vec3(0, -1, 0));
		normalArray->push_back(Vec3(0, 0, +1));
		normalArray->push_back(Vec3(0, 0, -1));

		// normal index
		TemplateIndexArray<unsigned int, Array::UIntArrayType, 24, 4> *normalIndexArray;
		normalIndexArray = new TemplateIndexArray<unsigned int, Array::UIntArrayType, 24, 4>();

		// bottom front left					
		normalIndexArray->push_back(5);
		normalIndexArray->push_back(3);
		normalIndexArray->push_back(0);
		// bottom front right
		normalIndexArray->push_back(5);
		normalIndexArray->push_back(2);
		normalIndexArray->push_back(0);
		// bottom back right
		normalIndexArray->push_back(5);
		normalIndexArray->push_back(2);
		normalIndexArray->push_back(1);
		// bottom back left
		normalIndexArray->push_back(5);
		normalIndexArray->push_back(3);
		normalIndexArray->push_back(1);

		// top front left					
		normalIndexArray->push_back(4);
		normalIndexArray->push_back(3);
		normalIndexArray->push_back(0);
		// top front right
		normalIndexArray->push_back(4);
		normalIndexArray->push_back(2);
		normalIndexArray->push_back(0);
		// top back right
		normalIndexArray->push_back(4);
		normalIndexArray->push_back(2);
		normalIndexArray->push_back(1);
		// top back left
		normalIndexArray->push_back(4);
		normalIndexArray->push_back(3);
		normalIndexArray->push_back(1);

		Geometry *geometry = new Geometry();
		geometry->setVertexArray(vertexArray);

		geometry->setNormalArray(normalArray);
		geometry->setNormalIndices(normalIndexArray);
		geometry->setNormalBinding(Geometry::BIND_PER_VERTEX);
		geometry->addPrimitiveSet(faceArray);

		Geode *cube = new Geode();
		cube->addDrawable(geometry);
		return cube;
	}

	Light *createLight(Vec4 color)
	{	
		Light *light = new Light();
		// each light must have a unique number
		light->setLightNum(uniqueLightNumber++);
		// we set the light's position via a PositionAttitudeTransform object
		light->setPosition(Vec4(0.0, 0.0, 0.0, 1.0));
		light->setDiffuse(color);
		light->setSpecular(Vec4(1.0, 1.0, 1.0, 1.0));
		light->setAmbient( Vec4(0.0, 0.0, 0.0, 1.0));

		return light;
	}

	Material *createSimpleMaterial(Vec4 color)
	{
		Material *material = new Material();
		material->setDiffuse(Material::FRONT,  Vec4(0.0, 0.0, 0.0, 1.0));
		material->setEmission(Material::FRONT, color);

		return material;
	}


	Node *startup() {
		// we need the scene's state set to enable the light for the entire scene
		Group *scene = new Group();
		lightStateSet = scene->getOrCreateStateSet();

		Geode *sphere = new Geode();
		sphere->addDrawable(new ShapeDrawable(new Sphere(Vec3(-2, 0, 0), 1)));

		Geode *cube = createCube();
		cubeTransform = new PositionAttitudeTransform();
		cubeTransform->addChild(cube);
		cubeTransform->setPosition(Vec3(2, 0, -1));

		// create white material
		Material *material = new Material();
		material->setDiffuse(Material::FRONT,  Vec4(1.0, 1.0, 1.0, 1.0));
		material->setSpecular(Material::FRONT, Vec4(0.0, 0.0, 0.0, 1.0));
		material->setAmbient(Material::FRONT,  Vec4(0.1, 0.1, 0.1, 1.0));
		material->setEmission(Material::FRONT, Vec4(0.0, 0.0, 0.0, 1.0));
		material->setShininess(Material::FRONT, 25.0);

		// assign the material to the sphere and cube
		sphere->getOrCreateStateSet()->setAttribute(material);
		cube->getOrCreateStateSet()->setAttribute(material);

		// Create Lights - Red, Green, Blue
		Vec4 lightColors[] = {Vec4(1.0, 0.0, 0.0, 1.0), Vec4(0.0, 1.0, 0.0, 1.0), Vec4(0.0, 0.0, 1.0, 1.0)};

		Geode *lightMarker[LIGHTS];
		LightSource *lightSource[LIGHTS];

		for (int i = 0; i < LIGHTS; i++) {
			lightMarker[i] = new Geode();
			lightMarker[i]->addDrawable(new ShapeDrawable(new Sphere(Vec3(), 1)));
			lightMarker[i]->getOrCreateStateSet()->setAttribute(createSimpleMaterial(lightColors[i]));

			lightSource[i] = new LightSource();
			lightSource[i]->setLight(createLight(lightColors[i]));
			lightSource[i]->setLocalStateSetModes(StateAttribute::ON);
			lightSource[i]->setStateSetModes(*lightStateSet, StateAttribute::ON);

			lightTransform[i] = new PositionAttitudeTransform();
			lightTransform[i]->addChild(lightSource[i]);
			lightTransform[i]->addChild(lightMarker[i]);
			lightTransform[i]->setPosition(Vec3(0, 0, 5));
			lightTransform[i]->setScale(Vec3(0.1,0.1,0.1));

			scene->addChild(lightTransform[i]);
		}

		scene->addChild(sphere);
		scene->addChild(cubeTransform);
		return scene;
	}

	void update(double time) {
		lightTransform[0]->setPosition(Vec3(cos(time), sin(time),  0) * 4);
		lightTransform[1]->setPosition(Vec3(0, cos(time),  sin(time)) * 4);
		lightTransform[2]->setPosition(Vec3(sin(time), cos(time),  sin(time)) * 4);
	}

	int main() {
		Node *scene = startup();

		osgViewer::Viewer viewer;
		viewer.setSceneData(scene);
		viewer.setCameraManipulator(new osgGA::TrackballManipulator());
		viewer.realize();

		while (!viewer.done()) {
			update(viewer.elapsedTime());
			viewer.frame();
		}
	}