FilterManager.py 13 KB

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