wiki:Tutorial/OpenSG1/Light
close Warning:

Previous Chapter: Geometry

Tutorial Overview

Next Chapter: Windows and Viewports


Light

This chapter is dedicated to another very important aspect of computer graphics: light sources. Well, most scenes have at least one, but often more, light sources to simulate a realistic environment. OpenSG features the common OpenGL light sources: spot, directional and point lights. The actual usage is easy and should be similar to OpenGL. At the end of this chapter there will be a tutorial in which we will add some light sources to the water mesh from the last chapter.

Point Light

The most common type of light source is the point light. It is emitting light in all directions equally. You have to define a position and some attenuation parameters which will affect the intensity of light depending on the distance to the lit object. Additionally you have to provide information about the light's color, of course. This code fragment shows how a point light could be created

#!cpp

PointLightPtr pLight = PointLight::create();



beginEditCP(pLight, PointLight::PositionFieldMask             |

                    PointLight::ConstantAttenuationFieldMask  |

                    PointLight::LinearAttenuationFieldMask    |

                    PointLight::QuadraticAttenuationFieldMask |

                    PointLight::DiffuseFieldMask              |

                    PointLight::AmbientFieldMask              |

                    PointLight::SpecularFieldMask             |

                    PointLight::BeaconFieldMask);

                        

    //Attenuation parameters

    pLight->setConstantAttenuation(1);

    pLight->setLinearAttenuation(0);

    pLight->setQuadraticAttenuation(2);

    

    //color information

    pLight->setDiffuse(Color4f(1,1,1,1));

    pLight->setAmbient(Color4f(0.2,0.2,0.2,1));

    pLight->setSpecular(Color4f(1,1,1,1));

    

    //set the beacon

    pLight->setBeacon(someNodePtr);

    

endEditCP  (pLight, PointLight::PositionFieldMask             |

                    PointLight::ConstantAttenuationFieldMask  |

                    PointLight::LinearAttenuationFieldMask    |

                    PointLight::QuadraticAttenuationFieldMask |

                    PointLight::DiffuseFieldMask              |

                    PointLight::AmbientFieldMask              |

                    PointLight::SpecularFieldMask             |

                    PointLight::BeaconFieldMask);

Directional Light

Directional light sources are faster to compute than point light sources, but often they look less realistic. Light is not emitted from a specific point in the world but from infinity so light is coming in parallel. In real world light from sun is nearly parallel too, but do not think that parallel light is looking any better. A directional light source only needs a direction, where the light is coming from, beside of the color information thus the code is even shorter.

#!cpp

DirectionalLightPtr dLight = DirectionalLight::create();



beginEditCP(dLight);

    

    //color information

    dLight->setDiffuse(Color4f(1,1,1,1));

    dLight->setAmbient(Color4f(0.2,0.2,0.2,1));

    dLight->setSpecular(Color4f(1,1,1,1));

    

    //set the beacon

    dLight->setBeacon(someNodePtr);

    

endEditCP  (dLight);

Spot Light

The spot light is the most expensive light source to compute. It is similar to a point light in that it has colors, attenuation and a position, but spot lights have an additional direction and angle which will define the area that is lit. Another value is needed to specify how fast intensity drops off as light reaches the border of the lit area. I suggest that you play around with the parameters to see what happens.

#!cpp

SpotLightPtr sLight = SpotLight::create();



beginEditCP(sLight);

    

    //set how fast light intesity decreases at the border

    sLight->setSpotExponent(2);

    //set the opening angle

    sLight->setSpotCutOff(deg2rad(30));

    

    //color information

    sLight->setDiffuse(Color4f(1,1,1,1));

    sLight->setAmbient(Color4f(0.2,0.2,0.2,1));

    sLight->setSpecular(Color4f(1,1,1,1));

    

    //set the beacon

    sLight->setBeacon(someNodePtr);

    

endEditCP  (sLight);

As mentioned above, spot lights are really expensive and should be used with caution!

Light Position, Direction and Influence Area

If you read the above sections carefully you have noticed that I talked about positions and directions, but never set them on the lights. Instead I set something I haven't talked about: the beacon. The problem with light source nodes in scenegraphs is that their position in the graph can be used to define two things: their position and orientation (via the transformations defined by their ancestors), and their area of influence (via their descendants). It generally makes sense to control both independently, for example to have a light source attached to a moving object like a car, but influence the local surroundings like a street, and not the whole scene, like the mountains in the distance. But a single node can only be in one place in a graph at any time. Therefore in OpenSG a second node is employed, the beacon. The location of the light source in the scene graph defines its area of influence: only the descendants of the light source are lit. This generally means that light sources are located at the top of the graph, to influence a large part of it. The position and orientation of the light source is defined by the Node referenced as the beacon. Light sources are located at the origin of the beacon's coordinate system, the direction is along the positive z-axis. The following picture shows a simple graph with a light source.

Light setup

This is a typical setup if you want to move the light source independent of the rest of the scene. But the beacon is not limited in any way, it can be an arbitrary Node in the graph, including nodes below the light source or even the light source itself.

The main motivation for specifying the position/orientation of the lights via a beacon Node and not via for example a Matrix is consistency and automatisms. This approach hides the difference between light sources and other Nodes for purposes of manipulation and animation. Light sources are manipulated exactly like any other node, by changing transformations on a Transform or ComponentTransform node. This also allows passive changes in the sense that a light source can be literally attached to another object and move whenever it moves. All just by setting the beacon.

For the attachment to another object situation the current setup is still somewhat limited. Given that the position and orientation of the light source is defined by constant values in the coordinate system of the beacon, you'd have to add temporary transformations to move the light source inside this coordinate system, for example to move the two headlights of a car to the correct position within the car. To simplify these operations, the light sources actually do have position and direction Fields, as applicable to the give type of light source. But you still need to set a beacon to define the reference coordinate system. If in doubt, just use the light source's Node as a beacon.

Just to mention it: The camera uses the same beacon mechanism to define the position and orientation of the viewer. This unifies moving and animation of all relevant objects in the scenegraph to changing Transformation nodes.

Another comment: the SimpleSceneManager by default provides a directional light set up as a headlight. That is the reason why we didn't have a black sceen in the previous tutorials, although we had not defined a single light source.

Note:

Right now the limitation of the area of influence is not implemented yet, a light source will also influence its siblings, not only its children. It is still strongly recommended to follow the above mentioned screen setup, otherwise things will break once the area feature is implemented, and even today unexpected effects can appear due to culling of light sources. So do it right from the beginning and you never have to worry.

Where's the switch?

So far there is no way to turn lights on and off, expect for removing them from the graph. That would be too inconvenient, therefore lights also have an on/off switch. it is just a boolean SField (remember what these were? See Single and Multifields if you don't) called "on".

#!cpp

    LightPtr l = roomlight;

    

    beginEditCP(l, Light::OnFieldMask);

    

    l->setOn(false);    // nighty night!

    

    endEditCP(l, Light::OnFieldMask);

Attenuation Parameters

To avoid confusion, I want to say some words about attenuation parameters. All light sources, except for the directional light, have attenuation parameters which specify how fast light intensity will decrease as the distance to an object increases. There are three values constant- , linear- and quadric attenuation. The default value for constant attenuation is one and zero for the others. The following image shows results for some different attenuation values.

Attenuation parameters

The image to the very left has only a very small factor for constant attenuation (0.1). Attenuation of the intensity is not really visible as the plane seems equally lit. The image in the middle has some reasonable values where as the right image has a quadric attenuation value of one resulting in a much too dark image.

You see that some caution is advised, else you might end up with a blank screen. After hours of search for the error you might begin to dislike these parameters... so keep it in mind ;-)

Tutorial - Light it up

As promised we are going to extend the last tutorial with some light sources, which will hopefully enhance the visual appearance of our water simulation ;-). Please take progs/09geometry_water3.cpp as a starting point. The first thing we need to do is to shut off the default head light, which the simple scene manger creates automatically. This is very easy indeed, just add the following line in the main method somewhere after we created the simple scene manager and before we enter the glutMainLoop!

#!cpp

    mgr->setHeadlight(false);

If you execute the application you will notice that nothing has really changed. That is because of the material we defined previously. If you look what we have done before: We simply created a SimpleMaterial and assigned it to the geometry without providing any additional information. The material we created in that way is completely unaffected by light sources. It is equally lit from every direction. So first thing we do is to assign a correct material:

in the createScenegraph() function locate the line that says

#!cpp

    SimpleMaterialPtr mat = SimpleMaterial::create();

and add the following code right below that line

#!cpp

beginEditCP(mat);

    mat->setDiffuse(Color3f(1,0,0));

endEditCP(mat);

When executing we see, as expected, nothing, because our only geometry is rendered black, as there is no light source left to do the job! If you turn the headlight back on, you will see that the water mesh is blue and not red as the material's diffuse color we just set. Why that? If you look a few lines upwards you might remember that we provided every single vertex with a specific color - blue. Remove the block where the GeoColor3f property is created as well as the line that says

#!cpp

    geo->setColors(colors);

during the creation of the geometry core. That will remove the "hand-made" color values and will no longer overwrite the attributes of our red material. If you compile and execute the application now you will see red water. Notice, that the color will change if you move the camera. Remember that we already provided normals which where all parallel to the y axis. If you want to, you can change the material's color from red back to blue as this was only to demonstrate the conflict between assigned vertex colors and the color of a material. I do not want to persuade anybody to use red water... ;-)

If you saw nothing but a black screen, you forgot to turn the headlight back on. Anyway, turn the headlight off, as we want to add our own light sources now. We start with the most common a point light.

At first add two new headers

#!cpp

    #include <OpenSG/OSGPointLight.h>

    #include <OpenSG/OSGSpotLight.h>

Locate the following block of code right at the very end of the createScenegraph() function

#!cpp

    NodePtr root = Node::create();

    beginEditCP(root);

        root->setCore(geo);

    endEditCP(root);

and replace the entire block with the following code

#!cpp

    PointLightPtr pLight = PointLight::create();

    NodePtr root = Node::create();

    NodePtr water = Node::create();

    NodePtr pLightTransformNode = Node::create();

    TransformPtr pLightTransform = Transform::create();

    NodePtr pLightNode = Node::create();

    

    beginEditCP(pLightNode);

        pLightNode->setCore(Group::create());

    endEditCP(pLightNode);

    

    Matrix m;

    m.setIdentity();

    m.setTranslate(50,25,50);

    

    beginEditCP(pLightTransform);

        pLightTransform->setMatrix(m);

    endEditCP(pLightTransform);

    

    //we add a little sphere that will represent the light source

    GeometryPtr sphere = makeSphereGeo(2,2);



    SimpleMaterialPtr sm = SimpleMaterial::create();



    beginEditCP(sm, SimpleMaterial::DiffuseFieldMask |

                    SimpleMaterial::LitFieldMask);

    {

        sm->setLit(false);

        sm->setDiffuse(Color3f(1,1,1));

    }

    endEditCP  (sm, SimpleMaterial::DiffuseFieldMask |

                    SimpleMaterial::LitFieldMask);



    beginEditCP(sphere, Geometry::MaterialFieldMask);

    {

        sphere->setMaterial(sm);

    }

    endEditCP  (sphere, Geometry::MaterialFieldMask);

    

    NodePtr sphereNode = Node::create();

    beginEditCP(sphereNode);

        sphereNode->setCore(sphere);

    endEditCP(sphereNode);



    beginEditCP(pLightTransformNode);

        pLightTransformNode->setCore(pLightTransform);

        pLightTransformNode->addChild(pLightNode);

        pLightTransformNode->addChild(sphereNode);

    endEditCP(pLightTransformNode);



    beginEditCP(pLight);

        pLight->setPosition(Pnt3f(0,0,0));

    

        //Attenuation parameters

        pLight->setConstantAttenuation(1);

        pLight->setLinearAttenuation(0);

        pLight->setQuadraticAttenuation(0);

        

        //color information

        pLight->setDiffuse(Color4f(1,1,1,1));

        pLight->setAmbient(Color4f(0.2,0.2,0.2,1));

        pLight->setSpecular(Color4f(1,1,1,1));

        

        //set the beacon

        pLight->setBeacon(pLightNode);

    endEditCP  (pLight);



    

    beginEditCP(water);

        water->setCore(geo);

    endEditCP(water);

    

    beginEditCP(root);

        root->setCore(pLight);

        root->addChild(water);

        root->addChild(pLightTransformNode);

    endEditCP(root);

If you run the application it will crash and the terminal tells you something about a "segmentation fault". I personally do not like this error, because the only thing you are told by this error is, that something really does not work! So what can it be? When you have a look at the display function you see right at the beginning that we are accessing the geometry core of the scene node. That exactly is our problem! We need the geometry pointer to modify the geometry and we assumed that the core of the root node holds the geometry core. Well that was correct, until we changed the design of the scenegraph. Now it is the one and only child of the root node which holds the geometry. Change the line that says

#!cpp

    GeometryPtr geo = GeometryPtr::dcast(scene->getCore());

to

#!cpp

    GeometryPtr geo = GeometryPtr::dcast(scene->getChild(0)->getCore());

Now you should see blue and lit water. The blue is not constant any more, you can see that the borders are in a darker blue that the center. This is because the light source is floating right above the center and there the distance is the shortest of course. Now we would have to calculate correct normals in order to render a more realistic result.

Water correct lit with incorrect normals

The normals need to be computed every frame as they change steadily. This is a problem because computation of normals is quite expensive. However this is not our concern for now. If you like to, you can try to compute the normals. You could also use the calcVertexNormals() utility function I introduced in the last chapter, but I warned you, that this will slow down the simulation significantly. calcVertexNormals() is very generic and can handle some pretty nasty situations, but that takes time. My Linux box with 1400 Mhz can't do the job in real time!

The code so far can be found in the file progs/10water_lit.cpp. We will now replace the point light with a directional light. If you have not completed the last part of the tutorial you can take the file mentioned above as a starting point!

Replace the line

#!cpp

    PointLightPtr pLight = PointLight::create();

with

#!cpp

    DirectionalLightPtr pLight = DirectionalLight::create();

Just to mention it: We need not to include another header file for the directional light. We have used the simple scene manager which comes along with a default light source and thus includes automatically the header for directional light sources.

Next locate and remove the following lines

#!cpp

    pLight->setPosition(Pnt3f(0,0,0));

     

    //Attenuation parameters

    pLight->setConstantAttenuation(1);

    pLight->setLinearAttenuation(0);

    pLight->setQuadraticAttenuation(0);

and add that line, but make sure it is within the begin-/ endEditCP(pLight) block!

#!cpp

    pLight->setDirection (Vec3f(0,1,0));

I hope you wonder why the direction of the light is straight "up" into the sky and not the other way round, because I still wonder, too. Maybe I have overseen or misunderstood something, but to me it seems that the direction of a directional light is always inverse. So I am not quite sure, if it is a bug or a feature ;-) Anyway if you have a hint for me, fell free to send me a <a href="mailto:mail@…">mail</a>''

Water lit by directional light with wrong normals

If you now compare this result with the last one, where we used a point light you will notice the difference in shading. This one here is again uniformly shaded, whereas the point light result looked better. Well, you can now think about what happened... this question can be found again right down here in the exercises part ;-)

Exercises

1) Different Light Sources - Different results

A few lines up ahead we realized that the results from directional and point light sources differ in shading. Explain the cause for that phenomena.

2) Spot Light

Now replace the light source with a spot light. You may take 10water_lit.cpp or 10water_lit2.cpp as a starting point.


Previous Chapter: Geometry

Tutorial Overview

Next Chapter: Windows and Viewports

Last modified 7 years ago Last modified on 01/17/10 01:11:44

Attachments (4)

Download all attachments as: .zip