RANDOM USER PROJECTS
Disney Pinball: real physics and cool settings
Kotodama: an RPG in which Japanese words have power
Blackout: a game using high-end lighting for ambiance.

Panda3D Manual: Lighting

Contents

Lighting Basics

In the real world, if you put a light in a room, objects in that room are illuminated. For example, if you put a table lamp in your living room, that lamp automatically illuminates your sofa and your chair. In a 3D engine like Panda3D, lights don't illuminate things automatically. Instead, you must tell the chair and the sofa to be illuminated by the lamp.

So to reiterate, lighting a scene in Panda3D consists of two steps:
1. Creating lights, and positioning them within the scene.
2. Telling the other objects to be illuminated by the lights.

Panda3D defines four different kinds of light objects: point, directional, ambient, and spotlight. Each of these is a node that should be attached somewhere within the scene graph. Like anything you put into the scene, lights have a position and orientation, which is determined by the basic scene graph operations like setPos(), setHpr(), etc. The lookAt() method is particularly useful for pointing spotlights and directional lights at a particular object. The following code inserts a directional light into the scene:

dlight = DirectionalLight('my dlight')
dlnp = render.attachNewNode(dlight)

Note that, unlike a real, physical light bulb, the light objects are not themselves directly visible. Although you can't see a Panda light itself, you can see the effect it has on the geometry around it. If you want to make a light visible, one simple trick is to load a simple model (like a sphere) and parent it directly to the light itself.

Creating the light and putting it in the scene graph doesn't, by itself, have any visible effect. Your next step is to tell some object to be illuminated by the light. To do this, use the nodePath.setLight() method, which turns on the light for the indicated NodePath and everything below it in the scene graph.

In the simplest case, you want all of your lights to illuminate everything they can, so you turn them on at render, the top of the scene graph:

render.setLight(plnp)

You can remove the light setting from render:

render.clearLight(plnp)

You could also apply the setLight() call to a sub-node in the scene graph, so that a given light only affects a particular object or group of objects:

sofa.setLight(plnp)


Note that there are two (or more) different NodePaths involved here: the NodePath of the light itself, which defines the position and/or orientation of the light, and the NodePath(s) on which you call setLight(), which determines what subset of the scene graph the light illuminates. There's no requirement for these two NodePaths to be related in any way.

Lots of Lights: Performance Implications

Each light slows down rendering a little. Using a half-dozen lights to illuminate an object is no problem at all. However, if you were to use a hundred lights to illuminate an object, that object would render slowly.

Because of this, when you create a big virtual world, you need to pick and choose which lights affect which objects. For example, if you had a dungeon containing a hundred torches, it would not be practical to tell every object to be illuminated by every torch. Instead, for each object in the dungeon, you would want to search for the three or four nearest torches, and tell the object to be illuminated only by those three or four torches.

When per-pixel lighting is enabled, lights are considerably more costly.

Colored Lights

All lights have a color, which is specified by light.setColor(VBase4(r, g, b, a)). The default color is full white: setColor(VBase4(1, 1, 1, 1)). The alpha component is largely irrelevant.

Note: The R, G, B values can be larger than 1, if you want brighter lights! However, you can't use lighting to make a model brighter than its texture color.

Point Lights

Point lights are the easiest kind of light to understand: a point light simulates a light originating from a single point in space and shining in all directions, like a very tiny light bulb. A point light's position is important, but its orientation doesn't matter.

plight = PointLight('plight')
plight.setColor(VBase4(0.2, 0.2, 0.2, 1))
plnp = render.attachNewNode(plight)
plnp.setPos(10, 20, 0)
render.setLight(plnp)

Attenuation

You can set the attenuation coefficients, which causes the light to drop off gradually with distance. There are three attenuation coefficients: A, B, C.

plight.setAttenuation(Point3(A, B, C))

Setting coefficients A and B to 0 and C between 0 and 1 works well.

plight.setAttenuation(Point3(0, 0, 0.5))

Directional Lights

A directional light is an infinite wave of light, always in the same direction, like sunlight. A directional light's position doesn't matter, but its orientation is important. The default directional light is shining down the forward (+Y) axis; you can use nodePath.setHpr() or nodePath.lookAt() to rotate it to face in a different direction.

dlight = DirectionalLight('dlight')
dlight.setColor(VBase4(0.8, 0.8, 0.5, 1))
dlnp = render.attachNewNode(dlight)
dlnp.setHpr(0, -60, 0)
render.setLight(dlnp)

Ambient Lights

An ambient light is used to fill in the shadows on the dark side of an object, so it doesn't look completely black. The light from an ambient light is uniformly distributed everywhere in the world, so the ambient light's position and orientation are irrelevant.

Usually you don't want to create an ambient light without also creating one of the other kinds of lights, since an object illuminated solely by ambient light will be completely flat shaded and you won't be able to see any of its details. Typically, ambient lights are given a fairly dark gray color, so they don't overpower the other lights in the scene.

alight = AmbientLight('alight')
alight.setColor(VBase4(0.2, 0.2, 0.2, 1))
alnp = render.attachNewNode(alight)
render.setLight(alnp)

Spotlights

Spotlights represent the most sophisticated kind of light. A spotlight has both a point and a direction, and a field-of-view. In fact, a spotlight contains a lens, just like a camera does; the lens should be a PerspectiveLens and is used to define the area of effect of the light (the light illuminates everything within the field of view of the lens).

Note that the English word "spotlight" is one word, as opposed to the other kinds of lights, which are two words. Thus, the class name is correctly spelled "Spotlight", not "SpotLight".

slight = Spotlight('slight')
slight.setColor(VBase4(1, 1, 1, 1))
lens = PerspectiveLens()
slight.setLens(lens)
slnp = render.attachNewNode(slight)
slnp.setPos(10, 20, 0)
slnp.lookAt(myObject)
render.setLight(slnp)

Putting it all Together

Here is an example of lighting. There are an ambient light and two directional lights lighting the scene, and a green ambient light that only affects one of the pandas.

import direct.directbase.DirectStart
from panda3d.core import *
 
# Put two pandas in the scene, panda x and panda y.
x = loader.loadModel('panda')
x.reparentTo(render)
x.setPos(10,0,-6)
 
y = loader.loadModel('panda')
y.reparentTo(render)
y.setPos(-10,0,-6)
 
# Position the camera to view the two pandas.
base.trackball.node().setPos(0, 60, 0)
 
# Now create some lights to apply to everything in the scene.
 
# Create Ambient Light
ambientLight = AmbientLight('ambientLight')
ambientLight.setColor(Vec4(0.1, 0.1, 0.1, 1))
ambientLightNP = render.attachNewNode(ambientLight)
render.setLight(ambientLightNP)
 
# Directional light 01
directionalLight = DirectionalLight('directionalLight')
directionalLight.setColor(Vec4(0.8, 0.2, 0.2, 1))
directionalLightNP = render.attachNewNode(directionalLight)
# This light is facing backwards, towards the camera.
directionalLightNP.setHpr(180, -20, 0)
render.setLight(directionalLightNP)
 
# Directional light 02
directionalLight = DirectionalLight('directionalLight')
directionalLight.setColor(Vec4(0.2, 0.2, 0.8, 1))
directionalLightNP = render.attachNewNode(directionalLight)
# This light is facing forwards, away from the camera.
directionalLightNP.setHpr(0, -20, 0)
render.setLight(directionalLightNP)
 
# Now attach a green light only to object x.
ambient = AmbientLight('ambient')
ambient.setColor(Vec4(0.5, 1, 0.5, 1))
ambientNP = x.attachNewNode(ambient)
 
# If we did not call setLightOff() first, the green light would add to
# the total set of lights on this object. Since we do call
# setLightOff(), we are turning off all the other lights on this
# object first, and then turning on only the green light.
x.setLightOff()
x.setLight(ambientNP)
 
#run the example
run()


Shadow Mapping

As for version 1.7.0, Panda3D offers fully automatic shadow mapping support for spotlights and directional lights. You can enable shadows by calling setShadowCaster(). The nodes that receive shadows will need to have the Shader Generator enabled, otherwise no shadows will appear.

# Use a 512x512 resolution shadow map
light.setShadowCaster(True, 512, 512)
# Enable the shader generator for the receiving nodes
render.setShaderAuto()

Note that, even though in general shadowing is easy to set-up, you will want to tweak the light's lens settings to get the best depth buffer precision. Use the setNearFar() method on the Lens to get a perfect fit of what is being rendered. Also, for directional lights, you will need to call setFilmSize() on the Lens and position the light properly so that the light camera will get an optimal view of the scene.

Also note that every Light is in fact also a Camera, so you can easily exclude objects from being shadowed (e.g. for performance reasons) by use of camera masks.

If you have very thin objects, you may run into self-shadowing issues if the backside of the object casts shadows on its frontside. You can easily fix this by applying a depth offset to the object in question. A depth offset of 1 means to use an offset as small as possible, but big enough to make a difference. This should generally be enough. You can call setDepthOffset() on the NodePath or use the depth-offset scalar in the .egg file.

leaves.setDepthOffset(1)