FilterManager.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. """
  2. The FilterManager is a convenience class that helps with the creation
  3. of render-to-texture buffers for image postprocessing applications.
  4. Still need to implement:
  5. * Make sure sort-order of buffers is correct.
  6. * Matching buffer size to original region instead of original window.
  7. * Intermediate layer creation.
  8. * Handling of window clears.
  9. * Resizing of windows.
  10. * Do something about window-size roundoff problems.
  11. """
  12. from panda3d.core import NodePath
  13. from panda3d.core import Texture
  14. from panda3d.core import CardMaker
  15. from panda3d.core import GraphicsPipe, GraphicsOutput
  16. from panda3d.core import WindowProperties, FrameBufferProperties
  17. from panda3d.core import Camera
  18. from panda3d.core import OrthographicLens
  19. from panda3d.core import AuxBitplaneAttrib
  20. from direct.directnotify.DirectNotifyGlobal import *
  21. from direct.showbase.DirectObject import DirectObject
  22. __all__ = ["FilterManager"]
  23. class FilterManager(DirectObject):
  24. notify = None
  25. def __init__(self, win, cam, forcex=0, forcey=0):
  26. """ The FilterManager constructor requires you to provide
  27. a window which is rendering a scene, and the camera which is
  28. used by that window to render the scene. These are henceforth
  29. called the 'original window' and the 'original camera.' """
  30. # Create the notify category
  31. if FilterManager.notify is None:
  32. FilterManager.notify = directNotify.newCategory("FilterManager")
  33. # Find the appropriate display region.
  34. region = None
  35. for dr in win.getDisplayRegions():
  36. drcam = dr.getCamera()
  37. if drcam == cam:
  38. region = dr
  39. if region is None:
  40. self.notify.error('Could not find appropriate DisplayRegion to filter')
  41. return False
  42. # Instance Variables.
  43. self.win = win
  44. self.forcex = forcex
  45. self.forcey = forcey
  46. self.engine = win.getGsg().getEngine()
  47. self.region = region
  48. self.wclears = self.getClears(self.win)
  49. self.rclears = self.getClears(self.region)
  50. self.camera = cam
  51. self.caminit = cam.node().getInitialState()
  52. self.camstate = self.caminit
  53. self.buffers = []
  54. self.sizes = []
  55. self.nextsort = self.win.getSort() - 1000
  56. self.basex = 0
  57. self.basey = 0
  58. self.accept("window-event", self.windowEvent)
  59. def getClears(self,region):
  60. clears = []
  61. for i in range(GraphicsOutput.RTPCOUNT):
  62. clears.append((region.getClearActive(i), region.getClearValue(i)))
  63. return clears
  64. def setClears(self,region,clears):
  65. for i in range(GraphicsOutput.RTPCOUNT):
  66. (active, value) = clears[i]
  67. region.setClearActive(i, active)
  68. region.setClearValue(i, value)
  69. def setStackedClears(self, region, clears0, clears1):
  70. clears = []
  71. for i in range(GraphicsOutput.RTPCOUNT):
  72. (active, value) = clears0[i]
  73. if (active == 0):
  74. (active, value) = clears1[i]
  75. region.setClearActive(i, active)
  76. region.setClearValue(i, value)
  77. return clears
  78. def isFullscreen(self):
  79. return ((self.region.getLeft() == 0.0) and
  80. (self.region.getRight() == 1.0) and
  81. (self.region.getBottom() == 0.0) and
  82. (self.region.getTop() == 1.0))
  83. def getScaledSize(self, mul, div, align):
  84. """ Calculate the size of the desired window. Not public. """
  85. winx = self.forcex
  86. winy = self.forcey
  87. if winx == 0: winx = self.win.getXSize()
  88. if winy == 0: winy = self.win.getYSize()
  89. if div != 1:
  90. winx = ((winx+align-1) // align) * align
  91. winy = ((winy+align-1) // align) * align
  92. winx = winx // div
  93. winy = winy // div
  94. if mul != 1:
  95. winx = winx * mul
  96. winy = winy * mul
  97. return winx,winy
  98. def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None):
  99. """ Causes the scene to be rendered into the supplied textures
  100. instead of into the original window. Puts a fullscreen quad
  101. into the original window to show the render-to-texture results.
  102. Returns the quad. Normally, the caller would then apply a
  103. shader to the quad.
  104. To elaborate on how this all works:
  105. * An offscreen buffer is created. It is set up to mimic
  106. the original display region - it is the same size,
  107. uses the same clear colors, and contains a DisplayRegion
  108. that uses the original camera.
  109. * A fullscreen quad and an orthographic camera to render
  110. that quad are both created. The original camera is
  111. removed from the original window, and in its place, the
  112. orthographic quad-camera is installed.
  113. * The fullscreen quad is textured with the data from the
  114. offscreen buffer. A shader is applied that tints the
  115. results pink.
  116. * Automatic shader generation NOT enabled.
  117. If you have a filter that depends on a render target from
  118. the auto-shader, you either need to set an auto-shader
  119. attrib on the main camera or scene, or, you need to provide
  120. these outputs in your own shader.
  121. * All clears are disabled on the original display region.
  122. If the display region fills the whole window, then clears
  123. are disabled on the original window as well. It is
  124. assumed that rendering the full-screen quad eliminates
  125. the need to do clears.
  126. Hence, the original window which used to contain the actual
  127. scene, now contains a pink-tinted quad with a texture of the
  128. scene. It is assumed that the user will replace the shader
  129. on the quad with a more interesting filter. """
  130. if (textures):
  131. colortex = textures.get("color", None)
  132. depthtex = textures.get("depth", None)
  133. auxtex = textures.get("aux", None)
  134. auxtex0 = textures.get("aux0", auxtex)
  135. auxtex1 = textures.get("aux1", None)
  136. else:
  137. auxtex0 = auxtex
  138. auxtex1 = None
  139. if (colortex == None):
  140. colortex = Texture("filter-base-color")
  141. colortex.setWrapU(Texture.WMClamp)
  142. colortex.setWrapV(Texture.WMClamp)
  143. texgroup = (depthtex, colortex, auxtex0, auxtex1)
  144. # Choose the size of the offscreen buffer.
  145. (winx, winy) = self.getScaledSize(1,1,1)
  146. buffer = self.createBuffer("filter-base", winx, winy, texgroup)
  147. if (buffer == None):
  148. return None
  149. cm = CardMaker("filter-base-quad")
  150. cm.setFrameFullscreenQuad()
  151. quad = NodePath(cm.generate())
  152. quad.setDepthTest(0)
  153. quad.setDepthWrite(0)
  154. quad.setTexture(colortex)
  155. quad.setColor(1, 0.5, 0.5, 1)
  156. cs = NodePath("dummy")
  157. cs.setState(self.camstate)
  158. # Do we really need to turn on the Shader Generator?
  159. #cs.setShaderAuto()
  160. if (auxbits):
  161. cs.setAttrib(AuxBitplaneAttrib.make(auxbits))
  162. self.camera.node().setInitialState(cs.getState())
  163. quadcamnode = Camera("filter-quad-cam")
  164. lens = OrthographicLens()
  165. lens.setFilmSize(2, 2)
  166. lens.setFilmOffset(0, 0)
  167. lens.setNearFar(-1000, 1000)
  168. quadcamnode.setLens(lens)
  169. quadcam = quad.attachNewNode(quadcamnode)
  170. self.region.setCamera(quadcam)
  171. self.setStackedClears(buffer, self.rclears, self.wclears)
  172. if (auxtex0):
  173. buffer.setClearActive(GraphicsOutput.RTPAuxRgba0, 1)
  174. buffer.setClearValue(GraphicsOutput.RTPAuxRgba0, (0.5, 0.5, 1.0, 0.0))
  175. if (auxtex1):
  176. buffer.setClearActive(GraphicsOutput.RTPAuxRgba1, 1)
  177. self.region.disableClears()
  178. if (self.isFullscreen()):
  179. self.win.disableClears()
  180. dr = buffer.makeDisplayRegion()
  181. dr.disableClears()
  182. dr.setCamera(self.camera)
  183. dr.setActive(1)
  184. self.buffers.append(buffer)
  185. self.sizes.append((1, 1, 1))
  186. return quad
  187. def renderQuadInto(self, mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None):
  188. """ Creates an offscreen buffer for an intermediate
  189. computation. Installs a quad into the buffer. Returns
  190. the fullscreen quad. The size of the buffer is initially
  191. equal to the size of the main window. The parameters 'mul',
  192. 'div', and 'align' can be used to adjust that size. """
  193. texgroup = (depthtex, colortex, auxtex0, auxtex1)
  194. winx, winy = self.getScaledSize(mul, div, align)
  195. depthbits = bool(depthtex != None)
  196. buffer = self.createBuffer("filter-stage", winx, winy, texgroup, depthbits)
  197. if (buffer == None):
  198. return None
  199. cm = CardMaker("filter-stage-quad")
  200. cm.setFrameFullscreenQuad()
  201. quad = NodePath(cm.generate())
  202. quad.setDepthTest(0)
  203. quad.setDepthWrite(0)
  204. quad.setColor(1, 0.5, 0.5, 1)
  205. quadcamnode = Camera("filter-quad-cam")
  206. lens = OrthographicLens()
  207. lens.setFilmSize(2, 2)
  208. lens.setFilmOffset(0, 0)
  209. lens.setNearFar(-1000, 1000)
  210. quadcamnode.setLens(lens)
  211. quadcam = quad.attachNewNode(quadcamnode)
  212. dr = buffer.makeDisplayRegion((0, 1, 0, 1))
  213. dr.disableClears()
  214. dr.setCamera(quadcam)
  215. dr.setActive(True)
  216. dr.setScissorEnabled(False)
  217. # This clear stage is important if the buffer is padded, so that
  218. # any pixels accidentally sampled in the padded region won't
  219. # be reading from unititialised memory.
  220. buffer.setClearColor((0, 0, 0, 1))
  221. buffer.setClearColorActive(True)
  222. self.buffers.append(buffer)
  223. self.sizes.append((mul, div, align))
  224. return quad
  225. def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1):
  226. """ Low-level buffer creation. Not intended for public use. """
  227. winprops = WindowProperties()
  228. winprops.setSize(xsize, ysize)
  229. props = FrameBufferProperties(FrameBufferProperties.getDefault())
  230. props.setBackBuffers(0)
  231. props.setRgbColor(1)
  232. props.setDepthBits(depthbits)
  233. props.setStereo(self.win.isStereo())
  234. depthtex, colortex, auxtex0, auxtex1 = texgroup
  235. if (auxtex0 != None):
  236. props.setAuxRgba(1)
  237. if (auxtex1 != None):
  238. props.setAuxRgba(2)
  239. buffer=base.graphicsEngine.makeOutput(
  240. self.win.getPipe(), name, -1,
  241. props, winprops, GraphicsPipe.BFRefuseWindow | GraphicsPipe.BFResizeable,
  242. self.win.getGsg(), self.win)
  243. if (buffer == None):
  244. return buffer
  245. if (depthtex):
  246. buffer.addRenderTexture(depthtex, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPDepth)
  247. if (colortex):
  248. buffer.addRenderTexture(colortex, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
  249. if (auxtex0):
  250. buffer.addRenderTexture(auxtex0, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba0)
  251. if (auxtex1):
  252. buffer.addRenderTexture(auxtex1, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba1)
  253. buffer.setSort(self.nextsort)
  254. buffer.disableClears()
  255. self.nextsort += 1
  256. return buffer
  257. def windowEvent(self, win):
  258. """ When the window changes size, automatically resize all buffers """
  259. self.resizeBuffers()
  260. def resizeBuffers(self):
  261. """ Resize all buffers to match the size of the window. """
  262. for i in range(len(self.buffers)):
  263. (mul, div, align) = self.sizes[i]
  264. (xsize, ysize) = self.getScaledSize(mul, div, align)
  265. self.buffers[i].setSize(xsize, ysize)
  266. def cleanup(self):
  267. """ Restore everything to its original state, deleting any
  268. new buffers in the process. """
  269. for buffer in self.buffers:
  270. buffer.clearRenderTextures()
  271. self.engine.removeWindow(buffer)
  272. self.buffers = []
  273. self.sizes = []
  274. self.setClears(self.win, self.wclears)
  275. self.setClears(self.region, self.rclears)
  276. self.camstate = self.caminit
  277. self.camera.node().setInitialState(self.caminit)
  278. self.region.setCamera(self.camera)
  279. self.nextsort = self.win.getSort() - 1000
  280. self.basex = 0
  281. self.basey = 0
  282. #snake_case alias:
  283. is_fullscreen = isFullscreen
  284. resize_buffers = resizeBuffers
  285. set_stacked_clears = setStackedClears
  286. render_scene_into = renderSceneInto
  287. get_scaled_size = getScaledSize
  288. render_quad_into = renderQuadInto
  289. get_clears = getClears
  290. set_clears = setClears
  291. create_buffer = createBuffer
  292. window_event = windowEvent