main.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #!/usr/bin/env python
  2. # Author: Jason Pratt ([email protected])
  3. # Last Updated: 2015-03-13
  4. #
  5. # This project demonstrates how to use various types of
  6. # lighting
  7. #
  8. from direct.showbase.ShowBase import ShowBase
  9. from panda3d.core import PerspectiveLens
  10. from panda3d.core import NodePath
  11. from panda3d.core import AmbientLight, DirectionalLight
  12. from panda3d.core import PointLight, Spotlight
  13. from panda3d.core import TextNode
  14. from panda3d.core import Material
  15. from panda3d.core import LVector3
  16. from direct.gui.OnscreenText import OnscreenText
  17. from direct.showbase.DirectObject import DirectObject
  18. import math
  19. import sys
  20. import colorsys
  21. # Simple function to keep a value in a given range (by default 0 to 1)
  22. def clamp(i, mn=0, mx=1):
  23. return min(max(i, mn), mx)
  24. class DiscoLightsDemo(ShowBase):
  25. # Macro-like function to reduce the amount of code needed to create the
  26. # onscreen instructions
  27. def makeStatusLabel(self, i):
  28. return OnscreenText(
  29. parent=base.a2dTopLeft, align=TextNode.ALeft,
  30. style=1, fg=(1, 1, 0, 1), shadow=(0, 0, 0, .4),
  31. pos=(0.06, -0.1 -(.06 * i)), scale=.05, mayChange=True)
  32. def __init__(self):
  33. # Initialize the ShowBase class from which we inherit, which will
  34. # create a window and set up everything we need for rendering into it.
  35. ShowBase.__init__(self)
  36. # The main initialization of our class
  37. # This creates the on screen title that is in every tutorial
  38. self.title = OnscreenText(text="Panda3D: Tutorial - Lighting",
  39. style=1, fg=(1, 1, 0, 1), shadow=(0, 0, 0, 0.5),
  40. pos=(0.87, -0.95), scale = .07)
  41. # Creates labels used for onscreen instructions
  42. self.ambientText = self.makeStatusLabel(0)
  43. self.directionalText = self.makeStatusLabel(1)
  44. self.spotlightText = self.makeStatusLabel(2)
  45. self.pointLightText = self.makeStatusLabel(3)
  46. self.spinningText = self.makeStatusLabel(4)
  47. self.ambientBrightnessText = self.makeStatusLabel(5)
  48. self.directionalBrightnessText = self.makeStatusLabel(6)
  49. self.spotlightBrightnessText = self.makeStatusLabel(7)
  50. self.spotlightExponentText = self.makeStatusLabel(8)
  51. self.lightingPerPixelText = self.makeStatusLabel(9)
  52. self.lightingShadowsText = self.makeStatusLabel(10)
  53. self.disco = loader.loadModel("models/disco_hall")
  54. self.disco.reparentTo(render)
  55. self.disco.setPosHpr(0, 50, -4, 90, 0, 0)
  56. # First we create an ambient light. All objects are affected by ambient
  57. # light equally
  58. # Create and name the ambient light
  59. self.ambientLight = render.attachNewNode(AmbientLight("ambientLight"))
  60. # Set the color of the ambient light
  61. self.ambientLight.node().setColor((.1, .1, .1, 1))
  62. # add the newly created light to the lightAttrib
  63. # Now we create a directional light. Directional lights add shading from a
  64. # given angle. This is good for far away sources like the sun
  65. self.directionalLight = render.attachNewNode(
  66. DirectionalLight("directionalLight"))
  67. self.directionalLight.node().setColor((.35, .35, .35, 1))
  68. # The direction of a directional light is set as a 3D vector
  69. self.directionalLight.node().setDirection(LVector3(1, 1, -2))
  70. # These settings are necessary for shadows to work correctly
  71. self.directionalLight.setZ(6)
  72. dlens = self.directionalLight.node().getLens()
  73. dlens.setFilmSize(41, 21)
  74. dlens.setNearFar(50, 75)
  75. # self.directionalLight.node().showFrustum()
  76. # Now we create a spotlight. Spotlights light objects in a given cone
  77. # They are good for simulating things like flashlights
  78. self.spotlight = camera.attachNewNode(Spotlight("spotlight"))
  79. self.spotlight.node().setColor((.45, .45, .45, 1))
  80. self.spotlight.node().setSpecularColor((0, 0, 0, 1))
  81. # The cone of a spotlight is controlled by it's lens. This creates the
  82. # lens
  83. self.spotlight.node().setLens(PerspectiveLens())
  84. # This sets the Field of View (fov) of the lens, in degrees for width
  85. # and height. The lower the numbers, the tighter the spotlight.
  86. self.spotlight.node().getLens().setFov(16, 16)
  87. # Attenuation controls how the light fades with distance. The three
  88. # values represent the three attenuation constants (constant, linear,
  89. # and quadratic) in the internal lighting equation. The higher the
  90. # numbers the shorter the light goes.
  91. self.spotlight.node().setAttenuation(LVector3(1, 0.0, 0.0))
  92. # This exponent value sets how soft the edge of the spotlight is.
  93. # 0 means a hard edge. 128 means a very soft edge.
  94. self.spotlight.node().setExponent(60.0)
  95. # Now we create three colored Point lights. Point lights are lights that
  96. # radiate from a single point, like a light bulb. Like spotlights, they
  97. # are given position by attaching them to NodePaths in the world
  98. self.redHelper = loader.loadModel('models/sphere')
  99. self.redHelper.setColor((1, 0, 0, 1))
  100. self.redHelper.setPos(-6.5, -3.75, 0)
  101. self.redHelper.setScale(.25)
  102. self.redPointLight = self.redHelper.attachNewNode(
  103. PointLight("redPointLight"))
  104. self.redPointLight.node().setColor((.35, 0, 0, 1))
  105. self.redPointLight.node().setAttenuation(LVector3(.1, 0.04, 0.0))
  106. # The green point light and helper
  107. self.greenHelper = loader.loadModel('models/sphere')
  108. self.greenHelper.setColor((0, 1, 0, 1))
  109. self.greenHelper.setPos(0, 7.5, 0)
  110. self.greenHelper.setScale(.25)
  111. self.greenPointLight = self.greenHelper.attachNewNode(
  112. PointLight("greenPointLight"))
  113. self.greenPointLight.node().setAttenuation(LVector3(.1, .04, .0))
  114. self.greenPointLight.node().setColor((0, .35, 0, 1))
  115. # The blue point light and helper
  116. self.blueHelper = loader.loadModel('models/sphere')
  117. self.blueHelper.setColor((0, 0, 1, 1))
  118. self.blueHelper.setPos(6.5, -3.75, 0)
  119. self.blueHelper.setScale(.25)
  120. self.bluePointLight = self.blueHelper.attachNewNode(
  121. PointLight("bluePointLight"))
  122. self.bluePointLight.node().setAttenuation(LVector3(.1, 0.04, 0.0))
  123. self.bluePointLight.node().setColor((0, 0, .35, 1))
  124. self.bluePointLight.node().setSpecularColor((1, 1, 1, 1))
  125. # Create a dummy node so the lights can be spun with one command
  126. self.pointLightHelper = render.attachNewNode("pointLightHelper")
  127. self.pointLightHelper.setPos(0, 50, 11)
  128. self.redHelper.reparentTo(self.pointLightHelper)
  129. self.greenHelper.reparentTo(self.pointLightHelper)
  130. self.blueHelper.reparentTo(self.pointLightHelper)
  131. # Finally we store the lights on the root of the scene graph.
  132. # This will cause them to affect everything in the scene.
  133. render.setLight(self.ambientLight)
  134. render.setLight(self.directionalLight)
  135. render.setLight(self.spotlight)
  136. render.setLight(self.redPointLight)
  137. render.setLight(self.greenPointLight)
  138. render.setLight(self.bluePointLight)
  139. # Create and start interval to spin the lights, and a variable to
  140. # manage them.
  141. self.pointLightsSpin = self.pointLightHelper.hprInterval(
  142. 6, LVector3(360, 0, 0))
  143. self.pointLightsSpin.loop()
  144. self.arePointLightsSpinning = True
  145. # Per-pixel lighting and shadows are initially off
  146. self.perPixelEnabled = False
  147. self.shadowsEnabled = False
  148. # listen to keys for controlling the lights
  149. self.accept("escape", sys.exit)
  150. self.accept("a", self.toggleLights, [[self.ambientLight]])
  151. self.accept("d", self.toggleLights, [[self.directionalLight]])
  152. self.accept("s", self.toggleLights, [[self.spotlight]])
  153. self.accept("p", self.toggleLights, [[self.redPointLight,
  154. self.greenPointLight,
  155. self.bluePointLight]])
  156. self.accept("r", self.toggleSpinningPointLights)
  157. self.accept("l", self.togglePerPixelLighting)
  158. self.accept("e", self.toggleShadows)
  159. self.accept("z", self.addBrightness, [self.ambientLight, -.05])
  160. self.accept("x", self.addBrightness, [self.ambientLight, .05])
  161. self.accept("c", self.addBrightness, [self.directionalLight, -.05])
  162. self.accept("v", self.addBrightness, [self.directionalLight, .05])
  163. self.accept("b", self.addBrightness, [self.spotlight, -.05])
  164. self.accept("n", self.addBrightness, [self.spotlight, .05])
  165. self.accept("q", self.adjustSpotlightExponent, [self.spotlight, -1])
  166. self.accept("w", self.adjustSpotlightExponent, [self.spotlight, 1])
  167. # Finally call the function that builds the instruction texts
  168. self.updateStatusLabel()
  169. # This function takes a list of lights and toggles their state. It takes in a
  170. # list so that more than one light can be toggled in a single command
  171. def toggleLights(self, lights):
  172. for light in lights:
  173. # If the given light is in our lightAttrib, remove it.
  174. # This has the effect of turning off the light
  175. if render.hasLight(light):
  176. render.clearLight(light)
  177. # Otherwise, add it back. This has the effect of turning the light
  178. # on
  179. else:
  180. render.setLight(light)
  181. self.updateStatusLabel()
  182. # This function toggles the spinning of the point intervals by pausing and
  183. # resuming the interval
  184. def toggleSpinningPointLights(self):
  185. if self.arePointLightsSpinning:
  186. self.pointLightsSpin.pause()
  187. else:
  188. self.pointLightsSpin.resume()
  189. self.arePointLightsSpinning = not self.arePointLightsSpinning
  190. self.updateStatusLabel()
  191. # This function turns per-pixel lighting on or off.
  192. def togglePerPixelLighting(self):
  193. if self.perPixelEnabled:
  194. self.perPixelEnabled = False
  195. render.clearShader()
  196. else:
  197. self.perPixelEnabled = True
  198. render.setShaderAuto()
  199. self.updateStatusLabel()
  200. # This function turns shadows on or off.
  201. def toggleShadows(self):
  202. if self.shadowsEnabled:
  203. self.shadowsEnabled = False
  204. self.directionalLight.node().setShadowCaster(False)
  205. else:
  206. if not self.perPixelEnabled:
  207. self.togglePerPixelLighting()
  208. self.shadowsEnabled = True
  209. self.directionalLight.node().setShadowCaster(True, 512, 512)
  210. self.updateStatusLabel()
  211. # This function changes the spotlight's exponent. It is kept to the range
  212. # 0 to 128. Going outside of this range causes an error
  213. def adjustSpotlightExponent(self, spotlight, amount):
  214. e = clamp(spotlight.node().getExponent() + amount, 0, 128)
  215. spotlight.node().setExponent(e)
  216. self.updateStatusLabel()
  217. # This function reads the color of the light, uses a built-in python function
  218. #(from the library colorsys) to convert from RGB (red, green, blue) color
  219. # representation to HSB (hue, saturation, brightness), so that we can get the
  220. # brighteness of a light, change it, and then convert it back to rgb to chagne
  221. # the light's color
  222. def addBrightness(self, light, amount):
  223. color = light.node().getColor()
  224. h, s, b = colorsys.rgb_to_hsv(color[0], color[1], color[2])
  225. brightness = clamp(b + amount)
  226. r, g, b = colorsys.hsv_to_rgb(h, s, brightness)
  227. light.node().setColor((r, g, b, 1))
  228. self.updateStatusLabel()
  229. # Builds the onscreen instruction labels
  230. def updateStatusLabel(self):
  231. self.updateLabel(self.ambientText, "(a) ambient is",
  232. render.hasLight(self.ambientLight))
  233. self.updateLabel(self.directionalText, "(d) directional is",
  234. render.hasLight(self.directionalLight))
  235. self.updateLabel(self.spotlightText, "(s) spotlight is",
  236. render.hasLight(self.spotlight))
  237. self.updateLabel(self.pointLightText, "(p) point lights are",
  238. render.hasLight(self.redPointLight))
  239. self.updateLabel(self.spinningText, "(r) point light spinning is",
  240. self.arePointLightsSpinning)
  241. self.ambientBrightnessText.setText(
  242. "(z,x) Ambient Brightness: " +
  243. self.getBrightnessString(self.ambientLight))
  244. self.directionalBrightnessText.setText(
  245. "(c,v) Directional Brightness: " +
  246. self.getBrightnessString(self.directionalLight))
  247. self.spotlightBrightnessText.setText(
  248. "(b,n) Spotlight Brightness: " +
  249. self.getBrightnessString(self.spotlight))
  250. self.spotlightExponentText.setText(
  251. "(q,w) Spotlight Exponent: " +
  252. str(int(self.spotlight.node().getExponent())))
  253. self.updateLabel(self.lightingPerPixelText, "(l) Per-pixel lighting is",
  254. self.perPixelEnabled)
  255. self.updateLabel(self.lightingShadowsText, "(e) Shadows are",
  256. self.shadowsEnabled)
  257. # Appends eitehr (on) or (off) to the base string based on the bassed value
  258. def updateLabel(self, obj, base, var):
  259. if var:
  260. s = " (on)"
  261. else:
  262. s = " (off)"
  263. obj.setText(base + s)
  264. # Returns the brightness of a light as a string to put it in the instruction
  265. # labels
  266. def getBrightnessString(self, light):
  267. color = light.node().getColor()
  268. h, s, b = colorsys.rgb_to_hsv(color[0], color[1], color[2])
  269. return "%.2f" % b
  270. # Make an instance of our class and run the demo
  271. demo = DiscoLightsDemo()
  272. demo.run()