main.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. #!/usr/bin/env python
  2. # Author: Josh Yelon
  3. # Date: 7/11/2005
  4. #
  5. # See the associated manual page for an explanation.
  6. #
  7. from direct.showbase.ShowBase import ShowBase
  8. from panda3d.core import FrameBufferProperties, WindowProperties
  9. from panda3d.core import GraphicsPipe, GraphicsOutput
  10. from panda3d.core import Filename, Texture, Shader
  11. from panda3d.core import RenderState, CardMaker
  12. from panda3d.core import PandaNode, TextNode, NodePath
  13. from panda3d.core import RenderAttrib, AlphaTestAttrib, ColorBlendAttrib
  14. from panda3d.core import CullFaceAttrib, DepthTestAttrib, DepthWriteAttrib
  15. from panda3d.core import LPoint3, LVector3, BitMask32
  16. from direct.gui.OnscreenText import OnscreenText
  17. from direct.showbase.DirectObject import DirectObject
  18. from direct.interval.MetaInterval import Sequence
  19. from direct.task.Task import Task
  20. from direct.actor.Actor import Actor
  21. import sys
  22. import os
  23. import random
  24. # Function to put instructions on the screen.
  25. def addInstructions(pos, msg):
  26. return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
  27. parent=base.a2dTopLeft, align=TextNode.ALeft,
  28. pos=(0.08, -pos - 0.04), scale=.05)
  29. # Function to put title on the screen.
  30. def addTitle(text):
  31. return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
  32. parent=base.a2dBottomRight, align=TextNode.ARight,
  33. fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
  34. class FireflyDemo(ShowBase):
  35. def __init__(self):
  36. # Initialize the ShowBase class from which we inherit, which will
  37. # create a window and set up everything we need for rendering into it.
  38. ShowBase.__init__(self)
  39. self.setBackgroundColor((0, 0, 0, 0))
  40. # Preliminary capabilities check.
  41. if not self.win.getGsg().getSupportsBasicShaders():
  42. self.t = addTitle("Firefly Demo: Video driver reports that Cg "
  43. "shaders are not supported.")
  44. return
  45. if not self.win.getGsg().getSupportsDepthTexture():
  46. self.t = addTitle("Firefly Demo: Video driver reports that depth "
  47. "textures are not supported.")
  48. return
  49. # This algorithm uses two offscreen buffers, one of which has
  50. # an auxiliary bitplane, and the offscreen buffers share a single
  51. # depth buffer. This is a heck of a complicated buffer setup.
  52. self.modelbuffer = self.makeFBO("model buffer", 1)
  53. self.lightbuffer = self.makeFBO("light buffer", 0)
  54. # Creation of a high-powered buffer can fail, if the graphics card
  55. # doesn't support the necessary OpenGL extensions.
  56. if self.modelbuffer is None or self.lightbuffer is None:
  57. self.t = addTitle("Firefly Demo: Video driver does not support "
  58. "multiple render targets")
  59. return
  60. # Create four render textures: depth, normal, albedo, and final.
  61. # attach them to the various bitplanes of the offscreen buffers.
  62. self.texDepth = Texture()
  63. self.texDepth.setFormat(Texture.FDepthStencil)
  64. self.texAlbedo = Texture()
  65. self.texNormal = Texture()
  66. self.texFinal = Texture()
  67. self.modelbuffer.addRenderTexture(self.texDepth,
  68. GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPDepthStencil)
  69. self.modelbuffer.addRenderTexture(self.texAlbedo,
  70. GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
  71. self.modelbuffer.addRenderTexture(self.texNormal,
  72. GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba0)
  73. self.lightbuffer.addRenderTexture(self.texFinal,
  74. GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
  75. # Set the near and far clipping planes.
  76. self.cam.node().getLens().setNear(50.0)
  77. self.cam.node().getLens().setFar(500.0)
  78. lens = self.cam.node().getLens()
  79. # This algorithm uses three cameras: one to render the models into the
  80. # model buffer, one to render the lights into the light buffer, and
  81. # one to render "plain" stuff (non-deferred shaded) stuff into the
  82. # light buffer. Each camera has a bitmask to identify it.
  83. self.modelMask = 1
  84. self.lightMask = 2
  85. self.plainMask = 4
  86. self.modelcam = self.makeCamera(self.modelbuffer,
  87. lens=lens, scene=render, mask=self.modelMask)
  88. self.lightcam = self.makeCamera(self.lightbuffer,
  89. lens=lens, scene=render, mask=self.lightMask)
  90. self.plaincam = self.makeCamera(self.lightbuffer,
  91. lens=lens, scene=render, mask=self.plainMask)
  92. # Panda's main camera is not used.
  93. self.cam.node().setActive(0)
  94. # Take explicit control over the order in which the three
  95. # buffers are rendered.
  96. self.modelbuffer.setSort(1)
  97. self.lightbuffer.setSort(2)
  98. self.win.setSort(3)
  99. # Within the light buffer, control the order of the two cams.
  100. self.lightcam.node().getDisplayRegion(0).setSort(1)
  101. self.plaincam.node().getDisplayRegion(0).setSort(2)
  102. # By default, panda usually clears the screen before every
  103. # camera and before every window. Tell it not to do that.
  104. # Then, tell it specifically when to clear and what to clear.
  105. self.modelcam.node().getDisplayRegion(0).disableClears()
  106. self.lightcam.node().getDisplayRegion(0).disableClears()
  107. self.plaincam.node().getDisplayRegion(0).disableClears()
  108. self.cam.node().getDisplayRegion(0).disableClears()
  109. self.cam2d.node().getDisplayRegion(0).disableClears()
  110. self.modelbuffer.disableClears()
  111. self.win.disableClears()
  112. self.modelbuffer.setClearColorActive(1)
  113. self.modelbuffer.setClearDepthActive(1)
  114. self.lightbuffer.setClearColorActive(1)
  115. self.lightbuffer.setClearColor((0, 0, 0, 1))
  116. # Miscellaneous stuff.
  117. self.disableMouse()
  118. self.camera.setPos(-9.112, -211.077, 46.951)
  119. self.camera.setHpr(0, -7.5, 2.4)
  120. random.seed()
  121. # Calculate the projection parameters for the final shader.
  122. # The math here is too complex to explain in an inline comment,
  123. # I've put in a full explanation into the HTML intro.
  124. proj = self.cam.node().getLens().getProjectionMat()
  125. proj_x = 0.5 * proj.getCell(3, 2) / proj.getCell(0, 0)
  126. proj_y = 0.5 * proj.getCell(3, 2)
  127. proj_z = 0.5 * proj.getCell(3, 2) / proj.getCell(2, 1)
  128. proj_w = -0.5 - 0.5 * proj.getCell(1, 2)
  129. # Configure the render state of the model camera.
  130. tempnode = NodePath(PandaNode("temp node"))
  131. tempnode.setAttrib(
  132. AlphaTestAttrib.make(RenderAttrib.MGreaterEqual, 0.5))
  133. tempnode.setShader(loader.loadShader("model.sha"))
  134. tempnode.setAttrib(DepthTestAttrib.make(RenderAttrib.MLessEqual))
  135. self.modelcam.node().setInitialState(tempnode.getState())
  136. # Configure the render state of the light camera.
  137. tempnode = NodePath(PandaNode("temp node"))
  138. tempnode.setShader(loader.loadShader("light.sha"))
  139. tempnode.setShaderInput("texnormal", self.texNormal)
  140. tempnode.setShaderInput("texalbedo", self.texAlbedo)
  141. tempnode.setShaderInput("texdepth", self.texDepth)
  142. tempnode.setShaderInput("proj", (proj_x, proj_y, proj_z, proj_w))
  143. tempnode.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd,
  144. ColorBlendAttrib.OOne, ColorBlendAttrib.OOne))
  145. tempnode.setAttrib(
  146. CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise))
  147. # The next line causes problems on Linux.
  148. # tempnode.setAttrib(DepthTestAttrib.make(RenderAttrib.MGreaterEqual))
  149. tempnode.setAttrib(DepthWriteAttrib.make(DepthWriteAttrib.MOff))
  150. self.lightcam.node().setInitialState(tempnode.getState())
  151. # Configure the render state of the plain camera.
  152. rs = RenderState.makeEmpty()
  153. self.plaincam.node().setInitialState(rs)
  154. # Clear any render attribs on the root node. This is necessary
  155. # because by default, panda assigns some attribs to the root
  156. # node. These default attribs will override the
  157. # carefully-configured render attribs that we just attached
  158. # to the cameras. The simplest solution is to just clear
  159. # them all out.
  160. render.setState(RenderState.makeEmpty())
  161. # My artist created a model in which some of the polygons
  162. # don't have textures. This confuses the shader I wrote.
  163. # This little hack guarantees that everything has a texture.
  164. white = loader.loadTexture("models/white.jpg")
  165. render.setTexture(white, 0)
  166. # Create two subroots, to help speed cull traversal.
  167. self.lightroot = NodePath(PandaNode("lightroot"))
  168. self.lightroot.reparentTo(render)
  169. self.modelroot = NodePath(PandaNode("modelroot"))
  170. self.modelroot.reparentTo(render)
  171. self.lightroot.hide(BitMask32(self.modelMask))
  172. self.modelroot.hide(BitMask32(self.lightMask))
  173. self.modelroot.hide(BitMask32(self.plainMask))
  174. # Load the model of a forest. Make it visible to the model camera.
  175. # This is a big model, so we load it asynchronously while showing a
  176. # load text. We do this by passing in a callback function.
  177. self.loading = addTitle("Loading models...")
  178. self.forest = NodePath(PandaNode("Forest Root"))
  179. self.forest.reparentTo(render)
  180. self.forest.hide(BitMask32(self.lightMask | self.plainMask))
  181. loader.loadModel([
  182. "models/background",
  183. "models/foliage01",
  184. "models/foliage02",
  185. "models/foliage03",
  186. "models/foliage04",
  187. "models/foliage05",
  188. "models/foliage06",
  189. "models/foliage07",
  190. "models/foliage08",
  191. "models/foliage09"],
  192. callback=self.finishLoading)
  193. # Cause the final results to be rendered into the main window on a
  194. # card.
  195. self.card = self.lightbuffer.getTextureCard()
  196. self.card.setTexture(self.texFinal)
  197. self.card.reparentTo(render2d)
  198. # Panda contains a built-in viewer that lets you view the results of
  199. # your render-to-texture operations. This code configures the viewer.
  200. self.bufferViewer.setPosition("llcorner")
  201. self.bufferViewer.setCardSize(0, 0.40)
  202. self.bufferViewer.setLayout("vline")
  203. self.toggleCards()
  204. self.toggleCards()
  205. # Firefly parameters
  206. self.fireflies = []
  207. self.sequences = []
  208. self.scaleseqs = []
  209. self.glowspheres = []
  210. self.fireflysize = 1.0
  211. self.spheremodel = loader.loadModel("misc/sphere")
  212. # Create the firefly model, a fuzzy dot
  213. dotSize = 1.0
  214. cm = CardMaker("firefly")
  215. cm.setFrame(-dotSize, dotSize, -dotSize, dotSize)
  216. self.firefly = NodePath(cm.generate())
  217. self.firefly.setTexture(loader.loadTexture("models/firefly.png"))
  218. self.firefly.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add,
  219. ColorBlendAttrib.O_incoming_alpha, ColorBlendAttrib.O_one))
  220. # these allow you to change parameters in realtime
  221. self.accept("escape", sys.exit, [0])
  222. self.accept("arrow_up", self.incFireflyCount, [1.1111111])
  223. self.accept("arrow_down", self.decFireflyCount, [0.9000000])
  224. self.accept("arrow_right", self.setFireflySize, [1.1111111])
  225. self.accept("arrow_left", self.setFireflySize, [0.9000000])
  226. self.accept("v", self.toggleCards)
  227. self.accept("V", self.toggleCards)
  228. def finishLoading(self, models):
  229. # This function is used as callback to loader.loadModel, and called
  230. # when all of the models have finished loading.
  231. # Attach the models to the scene graph.
  232. for model in models:
  233. model.reparentTo(self.forest)
  234. # Show the instructions.
  235. self.loading.destroy()
  236. self.title = addTitle("Panda3D: Tutorial - Fireflies using Deferred Shading")
  237. self.inst1 = addInstructions(0.06, "ESC: Quit")
  238. self.inst2 = addInstructions(0.12, "Up/Down: More / Fewer Fireflies (Count: unknown)")
  239. self.inst3 = addInstructions(0.18, "Right/Left: Bigger / Smaller Fireflies (Radius: unknown)")
  240. self.inst4 = addInstructions(0.24, "V: View the render-to-texture results")
  241. self.setFireflySize(25.0)
  242. while len(self.fireflies) < 5:
  243. self.addFirefly()
  244. self.updateReadout()
  245. self.nextadd = 0
  246. taskMgr.add(self.spawnTask, "spawner")
  247. def makeFBO(self, name, auxrgba):
  248. # This routine creates an offscreen buffer. All the complicated
  249. # parameters are basically demanding capabilities from the offscreen
  250. # buffer - we demand that it be able to render to texture on every
  251. # bitplane, that it can support aux bitplanes, that it track
  252. # the size of the host window, that it can render to texture
  253. # cumulatively, and so forth.
  254. winprops = WindowProperties()
  255. props = FrameBufferProperties()
  256. props.setRgbColor(True)
  257. props.setRgbaBits(8, 8, 8, 8)
  258. props.setDepthBits(1)
  259. props.setAuxRgba(auxrgba)
  260. return self.graphicsEngine.makeOutput(
  261. self.pipe, "model buffer", -2,
  262. props, winprops,
  263. GraphicsPipe.BFSizeTrackHost | GraphicsPipe.BFCanBindEvery |
  264. GraphicsPipe.BFRttCumulative | GraphicsPipe.BFRefuseWindow,
  265. self.win.getGsg(), self.win)
  266. def addFirefly(self):
  267. pos1 = LPoint3(random.uniform(-50, 50), random.uniform(-100, 150), random.uniform(-10, 80))
  268. dir = LVector3(random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1))
  269. dir.normalize()
  270. pos2 = pos1 + (dir * 20)
  271. fly = self.lightroot.attachNewNode(PandaNode("fly"))
  272. glow = fly.attachNewNode(PandaNode("glow"))
  273. dot = fly.attachNewNode(PandaNode("dot"))
  274. color_r = 1.0
  275. color_g = random.uniform(0.8, 1.0)
  276. color_b = min(color_g, random.uniform(0.5, 1.0))
  277. fly.setColor(color_r, color_g, color_b, 1.0)
  278. fly.setShaderInput("lightcolor", (color_r, color_g, color_b, 1.0))
  279. int1 = fly.posInterval(random.uniform(7, 12), pos1, pos2)
  280. int2 = fly.posInterval(random.uniform(7, 12), pos2, pos1)
  281. si1 = fly.scaleInterval(random.uniform(0.8, 1.5),
  282. LPoint3(0.2, 0.2, 0.2), LPoint3(0.2, 0.2, 0.2))
  283. si2 = fly.scaleInterval(random.uniform(1.5, 0.8),
  284. LPoint3(1.0, 1.0, 1.0), LPoint3(0.2, 0.2, 0.2))
  285. si3 = fly.scaleInterval(random.uniform(1.0, 2.0),
  286. LPoint3(0.2, 0.2, 0.2), LPoint3(1.0, 1.0, 1.0))
  287. siseq = Sequence(si1, si2, si3)
  288. siseq.loop()
  289. siseq.setT(random.uniform(0, 1000))
  290. seq = Sequence(int1, int2)
  291. seq.loop()
  292. self.spheremodel.instanceTo(glow)
  293. self.firefly.instanceTo(dot)
  294. glow.setScale(self.fireflysize * 1.1)
  295. glow.hide(BitMask32(self.modelMask | self.plainMask))
  296. dot.hide(BitMask32(self.modelMask | self.lightMask))
  297. dot.setColor(color_r, color_g, color_b, 1.0)
  298. self.fireflies.append(fly)
  299. self.sequences.append(seq)
  300. self.glowspheres.append(glow)
  301. self.scaleseqs.append(siseq)
  302. def updateReadout(self):
  303. self.inst2.destroy()
  304. self.inst2 = addInstructions(0.12,
  305. "Up/Down: More / Fewer Fireflies (Currently: %d)" % len(self.fireflies))
  306. self.inst3.destroy()
  307. self.inst3 = addInstructions(0.18,
  308. "Right/Left: Bigger / Smaller Fireflies (Radius: %d ft)" % self.fireflysize)
  309. def toggleCards(self):
  310. self.bufferViewer.toggleEnable()
  311. # When the cards are not visible, I also disable the color clear.
  312. # This color-clear is actually not necessary, the depth-clear is
  313. # sufficient for the purposes of the algorithm.
  314. if (self.bufferViewer.isEnabled()):
  315. self.modelbuffer.setClearColorActive(True)
  316. else:
  317. self.modelbuffer.setClearColorActive(False)
  318. def incFireflyCount(self, scale):
  319. n = int((len(self.fireflies) * scale) + 1)
  320. while (n > len(self.fireflies)):
  321. self.addFirefly()
  322. self.updateReadout()
  323. def decFireflyCount(self, scale):
  324. n = int(len(self.fireflies) * scale)
  325. if (n < 1):
  326. n = 1
  327. while (len(self.fireflies) > n):
  328. self.glowspheres.pop()
  329. self.sequences.pop().finish()
  330. self.scaleseqs.pop().finish()
  331. self.fireflies.pop().removeNode()
  332. self.updateReadout()
  333. def setFireflySize(self, n):
  334. n = n * self.fireflysize
  335. self.fireflysize = n
  336. for x in self.glowspheres:
  337. x.setScale(self.fireflysize * 1.1)
  338. self.updateReadout()
  339. def spawnTask(self, task):
  340. if task.time > self.nextadd:
  341. self.nextadd = task.time + 1.0
  342. if (len(self.fireflies) < 300):
  343. self.incFireflyCount(1.03)
  344. return Task.cont
  345. demo = FireflyDemo()
  346. demo.run()