| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- #!/usr/bin/env python
- # Author: Jason Pratt ([email protected])
- # Last Updated: 2015-03-13
- #
- # This project demonstrates how to use various types of
- # lighting
- #
- from direct.showbase.ShowBase import ShowBase
- from panda3d.core import PerspectiveLens
- from panda3d.core import NodePath
- from panda3d.core import AmbientLight, DirectionalLight
- from panda3d.core import PointLight, Spotlight
- from panda3d.core import TextNode
- from panda3d.core import Material
- from panda3d.core import LVector3
- from direct.gui.OnscreenText import OnscreenText
- from direct.showbase.DirectObject import DirectObject
- import math
- import sys
- import colorsys
- # Simple function to keep a value in a given range (by default 0 to 1)
- def clamp(i, mn=0, mx=1):
- return min(max(i, mn), mx)
- class DiscoLightsDemo(ShowBase):
- # Macro-like function to reduce the amount of code needed to create the
- # onscreen instructions
- def makeStatusLabel(self, i):
- return OnscreenText(
- parent=base.a2dTopLeft, align=TextNode.ALeft,
- style=1, fg=(1, 1, 0, 1), shadow=(0, 0, 0, .4),
- pos=(0.06, -0.1 -(.06 * i)), scale=.05, mayChange=True)
- def __init__(self):
- # Initialize the ShowBase class from which we inherit, which will
- # create a window and set up everything we need for rendering into it.
- ShowBase.__init__(self)
- # The main initialization of our class
- # This creates the on screen title that is in every tutorial
- self.title = OnscreenText(text="Panda3D: Tutorial - Lighting",
- style=1, fg=(1, 1, 0, 1), shadow=(0, 0, 0, 0.5),
- pos=(0.87, -0.95), scale = .07)
- # Creates labels used for onscreen instructions
- self.ambientText = self.makeStatusLabel(0)
- self.directionalText = self.makeStatusLabel(1)
- self.spotlightText = self.makeStatusLabel(2)
- self.pointLightText = self.makeStatusLabel(3)
- self.spinningText = self.makeStatusLabel(4)
- self.ambientBrightnessText = self.makeStatusLabel(5)
- self.directionalBrightnessText = self.makeStatusLabel(6)
- self.spotlightBrightnessText = self.makeStatusLabel(7)
- self.spotlightExponentText = self.makeStatusLabel(8)
- self.lightingPerPixelText = self.makeStatusLabel(9)
- self.lightingShadowsText = self.makeStatusLabel(10)
- self.disco = loader.loadModel("models/disco_hall")
- self.disco.reparentTo(render)
- self.disco.setPosHpr(0, 50, -4, 90, 0, 0)
- # First we create an ambient light. All objects are affected by ambient
- # light equally
- # Create and name the ambient light
- self.ambientLight = render.attachNewNode(AmbientLight("ambientLight"))
- # Set the color of the ambient light
- self.ambientLight.node().setColor((.1, .1, .1, 1))
- # add the newly created light to the lightAttrib
- # Now we create a directional light. Directional lights add shading from a
- # given angle. This is good for far away sources like the sun
- self.directionalLight = render.attachNewNode(
- DirectionalLight("directionalLight"))
- self.directionalLight.node().setColor((.35, .35, .35, 1))
- # The direction of a directional light is set as a 3D vector
- self.directionalLight.node().setDirection(LVector3(1, 1, -2))
- # These settings are necessary for shadows to work correctly
- self.directionalLight.setZ(6)
- dlens = self.directionalLight.node().getLens()
- dlens.setFilmSize(41, 21)
- dlens.setNearFar(50, 75)
- # self.directionalLight.node().showFrustum()
- # Now we create a spotlight. Spotlights light objects in a given cone
- # They are good for simulating things like flashlights
- self.spotlight = camera.attachNewNode(Spotlight("spotlight"))
- self.spotlight.node().setColor((.45, .45, .45, 1))
- self.spotlight.node().setSpecularColor((0, 0, 0, 1))
- # The cone of a spotlight is controlled by it's lens. This creates the
- # lens
- self.spotlight.node().setLens(PerspectiveLens())
- # This sets the Field of View (fov) of the lens, in degrees for width
- # and height. The lower the numbers, the tighter the spotlight.
- self.spotlight.node().getLens().setFov(16, 16)
- # Attenuation controls how the light fades with distance. The three
- # values represent the three attenuation constants (constant, linear,
- # and quadratic) in the internal lighting equation. The higher the
- # numbers the shorter the light goes.
- self.spotlight.node().setAttenuation(LVector3(1, 0.0, 0.0))
- # This exponent value sets how soft the edge of the spotlight is.
- # 0 means a hard edge. 128 means a very soft edge.
- self.spotlight.node().setExponent(60.0)
- # Now we create three colored Point lights. Point lights are lights that
- # radiate from a single point, like a light bulb. Like spotlights, they
- # are given position by attaching them to NodePaths in the world
- self.redHelper = loader.loadModel('models/sphere')
- self.redHelper.setColor((1, 0, 0, 1))
- self.redHelper.setPos(-6.5, -3.75, 0)
- self.redHelper.setScale(.25)
- self.redPointLight = self.redHelper.attachNewNode(
- PointLight("redPointLight"))
- self.redPointLight.node().setColor((.35, 0, 0, 1))
- self.redPointLight.node().setAttenuation(LVector3(.1, 0.04, 0.0))
- # The green point light and helper
- self.greenHelper = loader.loadModel('models/sphere')
- self.greenHelper.setColor((0, 1, 0, 1))
- self.greenHelper.setPos(0, 7.5, 0)
- self.greenHelper.setScale(.25)
- self.greenPointLight = self.greenHelper.attachNewNode(
- PointLight("greenPointLight"))
- self.greenPointLight.node().setAttenuation(LVector3(.1, .04, .0))
- self.greenPointLight.node().setColor((0, .35, 0, 1))
- # The blue point light and helper
- self.blueHelper = loader.loadModel('models/sphere')
- self.blueHelper.setColor((0, 0, 1, 1))
- self.blueHelper.setPos(6.5, -3.75, 0)
- self.blueHelper.setScale(.25)
- self.bluePointLight = self.blueHelper.attachNewNode(
- PointLight("bluePointLight"))
- self.bluePointLight.node().setAttenuation(LVector3(.1, 0.04, 0.0))
- self.bluePointLight.node().setColor((0, 0, .35, 1))
- self.bluePointLight.node().setSpecularColor((1, 1, 1, 1))
- # Create a dummy node so the lights can be spun with one command
- self.pointLightHelper = render.attachNewNode("pointLightHelper")
- self.pointLightHelper.setPos(0, 50, 11)
- self.redHelper.reparentTo(self.pointLightHelper)
- self.greenHelper.reparentTo(self.pointLightHelper)
- self.blueHelper.reparentTo(self.pointLightHelper)
- # Finally we store the lights on the root of the scene graph.
- # This will cause them to affect everything in the scene.
- render.setLight(self.ambientLight)
- render.setLight(self.directionalLight)
- render.setLight(self.spotlight)
- render.setLight(self.redPointLight)
- render.setLight(self.greenPointLight)
- render.setLight(self.bluePointLight)
- # Create and start interval to spin the lights, and a variable to
- # manage them.
- self.pointLightsSpin = self.pointLightHelper.hprInterval(
- 6, LVector3(360, 0, 0))
- self.pointLightsSpin.loop()
- self.arePointLightsSpinning = True
- # Per-pixel lighting and shadows are initially off
- self.perPixelEnabled = False
- self.shadowsEnabled = False
- # listen to keys for controlling the lights
- self.accept("escape", sys.exit)
- self.accept("a", self.toggleLights, [[self.ambientLight]])
- self.accept("d", self.toggleLights, [[self.directionalLight]])
- self.accept("s", self.toggleLights, [[self.spotlight]])
- self.accept("p", self.toggleLights, [[self.redPointLight,
- self.greenPointLight,
- self.bluePointLight]])
- self.accept("r", self.toggleSpinningPointLights)
- self.accept("l", self.togglePerPixelLighting)
- self.accept("e", self.toggleShadows)
- self.accept("z", self.addBrightness, [self.ambientLight, -.05])
- self.accept("x", self.addBrightness, [self.ambientLight, .05])
- self.accept("c", self.addBrightness, [self.directionalLight, -.05])
- self.accept("v", self.addBrightness, [self.directionalLight, .05])
- self.accept("b", self.addBrightness, [self.spotlight, -.05])
- self.accept("n", self.addBrightness, [self.spotlight, .05])
- self.accept("q", self.adjustSpotlightExponent, [self.spotlight, -1])
- self.accept("w", self.adjustSpotlightExponent, [self.spotlight, 1])
- # Finally call the function that builds the instruction texts
- self.updateStatusLabel()
- # This function takes a list of lights and toggles their state. It takes in a
- # list so that more than one light can be toggled in a single command
- def toggleLights(self, lights):
- for light in lights:
- # If the given light is in our lightAttrib, remove it.
- # This has the effect of turning off the light
- if render.hasLight(light):
- render.clearLight(light)
- # Otherwise, add it back. This has the effect of turning the light
- # on
- else:
- render.setLight(light)
- self.updateStatusLabel()
- # This function toggles the spinning of the point intervals by pausing and
- # resuming the interval
- def toggleSpinningPointLights(self):
- if self.arePointLightsSpinning:
- self.pointLightsSpin.pause()
- else:
- self.pointLightsSpin.resume()
- self.arePointLightsSpinning = not self.arePointLightsSpinning
- self.updateStatusLabel()
- # This function turns per-pixel lighting on or off.
- def togglePerPixelLighting(self):
- if self.perPixelEnabled:
- self.perPixelEnabled = False
- render.clearShader()
- else:
- self.perPixelEnabled = True
- render.setShaderAuto()
- self.updateStatusLabel()
- # This function turns shadows on or off.
- def toggleShadows(self):
- if self.shadowsEnabled:
- self.shadowsEnabled = False
- self.directionalLight.node().setShadowCaster(False)
- else:
- if not self.perPixelEnabled:
- self.togglePerPixelLighting()
- self.shadowsEnabled = True
- self.directionalLight.node().setShadowCaster(True, 512, 512)
- self.updateStatusLabel()
- # This function changes the spotlight's exponent. It is kept to the range
- # 0 to 128. Going outside of this range causes an error
- def adjustSpotlightExponent(self, spotlight, amount):
- e = clamp(spotlight.node().getExponent() + amount, 0, 128)
- spotlight.node().setExponent(e)
- self.updateStatusLabel()
- # This function reads the color of the light, uses a built-in python function
- #(from the library colorsys) to convert from RGB (red, green, blue) color
- # representation to HSB (hue, saturation, brightness), so that we can get the
- # brighteness of a light, change it, and then convert it back to rgb to chagne
- # the light's color
- def addBrightness(self, light, amount):
- color = light.node().getColor()
- h, s, b = colorsys.rgb_to_hsv(color[0], color[1], color[2])
- brightness = clamp(b + amount)
- r, g, b = colorsys.hsv_to_rgb(h, s, brightness)
- light.node().setColor((r, g, b, 1))
- self.updateStatusLabel()
- # Builds the onscreen instruction labels
- def updateStatusLabel(self):
- self.updateLabel(self.ambientText, "(a) ambient is",
- render.hasLight(self.ambientLight))
- self.updateLabel(self.directionalText, "(d) directional is",
- render.hasLight(self.directionalLight))
- self.updateLabel(self.spotlightText, "(s) spotlight is",
- render.hasLight(self.spotlight))
- self.updateLabel(self.pointLightText, "(p) point lights are",
- render.hasLight(self.redPointLight))
- self.updateLabel(self.spinningText, "(r) point light spinning is",
- self.arePointLightsSpinning)
- self.ambientBrightnessText.setText(
- "(z,x) Ambient Brightness: " +
- self.getBrightnessString(self.ambientLight))
- self.directionalBrightnessText.setText(
- "(c,v) Directional Brightness: " +
- self.getBrightnessString(self.directionalLight))
- self.spotlightBrightnessText.setText(
- "(b,n) Spotlight Brightness: " +
- self.getBrightnessString(self.spotlight))
- self.spotlightExponentText.setText(
- "(q,w) Spotlight Exponent: " +
- str(int(self.spotlight.node().getExponent())))
- self.updateLabel(self.lightingPerPixelText, "(l) Per-pixel lighting is",
- self.perPixelEnabled)
- self.updateLabel(self.lightingShadowsText, "(e) Shadows are",
- self.shadowsEnabled)
- # Appends eitehr (on) or (off) to the base string based on the bassed value
- def updateLabel(self, obj, base, var):
- if var:
- s = " (on)"
- else:
- s = " (off)"
- obj.setText(base + s)
- # Returns the brightness of a light as a string to put it in the instruction
- # labels
- def getBrightnessString(self, light):
- color = light.node().getColor()
- h, s, b = colorsys.rgb_to_hsv(color[0], color[1], color[2])
- return "%.2f" % b
- # Make an instance of our class and run the demo
- demo = DiscoLightsDemo()
- demo.run()
|