7 min read

While brilliant gameplay is the key to a fun and successful game, it is essential to deliver beautiful visuals to provide a pleasing experience and immerse the player in the game world. The looks of many modern productions are massively dominated by all sorts of visual magic to create the jaw-dropping visual density that is soaked up by players with joy and makes them feel connected to the action and the gameplay they are experiencing.

The appearance of your game matters a lot to its reception by players. Therefore it is important to know how to leverage your technology to get the best possible looks out of it. This is why this article will show you how Panda3D allows you to create great looking games using lights, shaders, and particles.

Adding lights and shadows in Panda3d

Lights and shadows are very important techniques for producing a great presentation. Proper scene lighting sets the mood and also adds depth to an otherwise flat-looking scene, while shadows add more realism, and more importantly, root the shadow-casting objects to the ground, destroying the impression of models floating in mid-air.

This recipe will show you how to add lights to your game scenes and make objects cast shadows to boost your visuals.

Getting ready

You need to create the setup presented in Setting up the game structure before proceeding, as this recipe continues and builds upon this base code.

How to do it…

This recipe consists of these tasks:

  1. Add the following code to Application.py:

    from direct.showbase.ShowBase import ShowBase
    from direct.actor.Actor import Actor
    from panda3d.core import *

    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)

    self.panda = Actor(“panda”, {“walk”: “panda-walk”})
    self.panda.reparentTo(render)
    self.panda.loop(“walk”)

    cm = CardMaker(“plane”)
    cm.setFrame(-10, 10, -10, 10)
    plane = render.attachNewNode(cm.generate())
    plane.setP(270)

    self.cam.setPos(0, -40, 6)

    ambLight = AmbientLight(“ambient”)
    ambLight.setColor(Vec4(0.2, 0.1, 0.1, 1.0))
    ambNode = render.attachNewNode(ambLight)
    render.setLight(ambNode)
    dirLight = DirectionalLight(“directional”)
    dirLight.setColor(Vec4(0.1, 0.4, 0.1, 1.0))
    dirNode = render.attachNewNode(dirLight)
    dirNode.setHpr(60, 0, 90)
    render.setLight(dirNode)

    pntLight = PointLight(“point”)
    pntLight.setColor(Vec4(0.8, 0.8, 0.8, 1.0))
    pntNode = render.attachNewNode(pntLight)
    pntNode.setPos(0, 0, 15)
    self.panda.setLight(pntNode)

    sptLight = Spotlight(“spot”)
    sptLens = PerspectiveLens()
    sptLight.setLens(sptLens)
    sptLight.setColor(Vec4(1.0, 0.0, 0.0, 1.0))
    sptLight.setShadowCaster(True)
    sptNode = render.attachNewNode(sptLight)
    sptNode.setPos(-10, -10, 20)
    sptNode.lookAt(self.panda)
    render.setLight(sptNode)

    render.setShaderAuto()

    
    
  2. Start the program with the F6 key. You will see the following scene:

    Panda3D Game Development: Scene Effects and Shaders

How it works…

As we can see when starting our program, the panda is lit by multiple lights, casting shadows onto itself and the ground plane. Let’s see how we achieved this effect.

After setting up the scene containing our panda and a ground plane, one of each possible light type is added to the scene. The general pattern we follow is to create new light instances before adding them to the scene using the attachNewNode() method. Finally, the light is turned on with setLight(), which causes the calling object and all of its children in the scene graph to receive light. We use this to make the point light only affect the panda but not the ground plane.

Shadows are very simple to enable and disable by using the setShadowCaster() method, as we can see in the code that initializes the spotlight.

The line render.setShaderAuto() enables the shader generator, which causes the lighting to be calculated pixel perfect. Additionally, for using shadows, the shader generator needs to be enabled. If this line is removed, lighting will look coarser and no shadows will be visible at all.

Watch the amount of lights you are adding to your scene! Every light that contributes to the scene adds additional computation cost, which will hit you if you intend to use hundreds of lights in a scene! Always try to detect the nearest lights in the level to use for lighting and disable the rest to save performance.

There’s more…

In the sample code, we add several types of lights with different properties, which may need some further explanation.

Ambient light sets the base tone of a scene. It has no position or direction—the light color is just added to all surface colors in the scene, which avoids unlit parts of the scene to appear completely black. You shouldn’t set the ambient color to very high intensities. This will decrease the effect of other lights and make the scene appear flat and washed out.

Directional lights do not have a position, as only their orientation counts. This light type is generally used to simulate sunlight—it comes from a general direction and affects all light-receiving objects equally.

A point light illuminates the scene from a point of origin from which light spreads towards all directions. You can think of it as a (very abstract) light bulb.

Spotlights, just like the headlights of a car or a flashlight, create a cone of light that originates from a given position and points towards a direction. The way the light spreads is determined by a lens, just like the viewing frustum of a camera.

Using light ramps

The lighting system of Panda3D allows you to pull off some additional tricks to create some dramatic effects with scene lights. In this recipe, you will learn how to use light ramps to modify the lights affect on the models and actors in your game scenes.

Getting ready

In this recipe we will extend the code created in Adding lights and shadows found in this article. Please review this recipe before proceeding if you haven’t done so yet.

How to do it…

Light ramps can be used like this:

  1. Open Application.py and add and modify the existing code as shown:

    from direct.showbase.ShowBase import ShowBase
    from direct.actor.Actor import Actor
    from panda3d.core import *
    from direct.interval.IntervalGlobal import *

    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)

    self.panda = Actor(“panda”, {“walk”: “panda-walk”})
    self.panda.reparentTo(render)
    self.panda.loop(“walk”)

    cm = CardMaker(“plane”)
    cm.setFrame(-10, 10, -10, 10)
    plane = render.attachNewNode(cm.generate())
    plane.setP(270)

    self.cam.setPos(0, -40, 6)

    ambLight = AmbientLight(“ambient”)
    ambLight.setColor(Vec4(0.3, 0.2, 0.2, 1.0))
    ambNode = render.attachNewNode(ambLight)
    render.setLight(ambNode)

    dirLight = DirectionalLight(“directional”)
    dirLight.setColor(Vec4(0.3, 0.9, 0.3, 1.0))
    dirNode = render.attachNewNode(dirLight)
    dirNode.setHpr(60, 0, 90)
    render.setLight(dirNode)

    pntLight = PointLight(“point”)
    pntLight.setColor(Vec4(3.9, 3.9, 3.8, 1.0))
    pntNode = render.attachNewNode(pntLight)
    pntNode.setPos(0, 0, 15)
    self.panda.setLight(pntNode)

    sptLight = Spotlight(“spot”)
    sptLens = PerspectiveLens()
    sptLight.setLens(sptLens)
    sptLight.setColor(Vec4(1.0, 0.4, 0.4, 1.0))
    sptLight.setShadowCaster(True)
    sptNode = render.attachNewNode(sptLight)
    sptNode.setPos(-10, -10, 20)
    sptNode.lookAt(self.panda)
    render.setLight(sptNode)

    render.setShaderAuto()

    self.activeRamp = 0
    toggle = Func(self.toggleRamp)
    switcher = Sequence(toggle, Wait(3))
    switcher.loop()

    def toggleRamp(self):
    if self.activeRamp == 0:
    render.setAttrib(LightRampAttrib.makeDefault())
    elif self.activeRamp == 1:
    render.setAttrib(LightRampAttrib.makeHdr0())
    elif self.activeRamp == 2:
    render.setAttrib(LightRampAttrib.makeHdr1())
    elif self.activeRamp == 3:
    render.setAttrib(LightRampAttrib.makeHdr2())
    elif self.activeRamp == 4:
    render.setAttrib(LightRampAttrib.
    makeSingleThreshold(0.1, 0.3))
    elif self.activeRamp == 5:
    render.setAttrib(LightRampAttrib.
    makeDoubleThreshold(0, 0.1, 0.3, 0.8))

    self.activeRamp += 1
    if self.activeRamp > 5:
    self.activeRamp = 0

    
    
  2. Press F6 to start the sample and see it switch through the available light ramps as shown in this screenshot:

Panda3D Game Development: Scene Effects and Shaders

How it works…

The original lighting equation that is used by Panda3D to calculate the final screen color of a lit pixel limits color intensities to values within a range from zero to one. By using light ramps we are able to go beyond these limits or even define our own ones to create dramatic effects just like the ones we can see in the sample program.

In the sample code, we increase the lighting intensity and add a method that switches between the available light ramps, beginning with LightRampAttrib.makeDefault() which sets the default clamping thresholds for the lighting calculations.

Then, the high dynamic range ramps are enabled one after another. These light ramps allow you to have a higher range of color intensities that go beyond the standard range between zero and one. These high intensities are then mapped back into the displayable range, allocating different amounts of values within it to displaying brightness.

By using makeHdr0(), we allocate a quarter of the displayable range to brightness values that are greater than one. With makeHdr1() it is a third and with makeHdr2() we are causing Panda3D to use half of the color range for overly bright values. This doesn’t come without any side effects, though. By increasing the range used for high intensities, we are decreasing the range of color intensities available for displaying colors that are within the limits of 0 and 1, thus losing contrast and making the scene look grey and washed out.

Finally, with the makeSingleThreshold() and makeDoubleThreshold() methods, we are able to create very interesting lighting effects. With a single threshold, lighting values below the given limit will be ignored, while anything that exceeds the threshold will be set to the intensity given in the second parameter of the method.

The double threshold system works analogous to the single threshold, but lighting intensity will be normalized to two possible values, depending on which of the two thresholds was exceeded.


Subscribe to the weekly Packt Hub newsletter. We'll send you this year's Skill Up Developer Skills Report.

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here