From e2adec014b2af93e40b0c1c83ed1189d028af946 Mon Sep 17 00:00:00 2001 From: Josh Yelon Date: Tue, 12 Feb 2008 21:52:29 +0000 Subject: [PATCH] The filter directory is for image postprocessing support --- direct/src/filter/CommonFilters.py | 133 ++++++++++++++++++++ direct/src/filter/FilterManager.py | 195 +++++++++++++++++++++++++++++ direct/src/filter/__init__.py | 0 3 files changed, 328 insertions(+) create mode 100644 direct/src/filter/CommonFilters.py create mode 100644 direct/src/filter/FilterManager.py create mode 100644 direct/src/filter/__init__.py diff --git a/direct/src/filter/CommonFilters.py b/direct/src/filter/CommonFilters.py new file mode 100644 index 0000000000..9ced537dbd --- /dev/null +++ b/direct/src/filter/CommonFilters.py @@ -0,0 +1,133 @@ +""" + +Class CommonFilters implements certain common image +postprocessing filters. + +It is not ideal that these filters are all included in a single +monolithic module. Unfortunately, when you want to apply two filters +at the same time, you have to compose them into a single shader, and +the composition process isn't simply a question of concatenating them: +you have to somehow make them work together. I suspect that there +exists some fairly simple framework that would make this automatable. +However, until I write some more filters myself, I won't know what +that framework is. Until then, I'll settle for this +clunky approach. - Josh + +""" + +from FilterManager import FilterManager +from pandac.PandaModules import Point3, Vec3, Vec4 +from pandac.PandaModules import NodePath, PandaNode +from pandac.PandaModules import RenderState, Texture, Shader + +HEADER = """//Cg + +void vshader(float4 vtx_position : POSITION, + out float4 l_position : POSITION, + out float4 l_texcoord : TEXCOORD0, + uniform float4 texpad_txcolor, + uniform float4x4 mat_modelproj) +{ + l_position=mul(mat_modelproj, vtx_position); + l_texcoord=(vtx_position.xzxz * texpad_txcolor) + texpad_txcolor; +} +""" + +CARTOON_BODY=""" +float4 cartoondelta = k_cartoonseparation * texpix_txcolor.xwyw; +float4 cartoon_p0 = l_texcoord + cartoondelta.xyzw; +float4 cartoon_c0 = tex2D(k_txnormal, cartoon_p0.xy); +float4 cartoon_p1 = l_texcoord - cartoondelta.xyzw; +float4 cartoon_c1 = tex2D(k_txnormal, cartoon_p1.xy); +float4 cartoon_p2 = l_texcoord + cartoondelta.wzyx; +float4 cartoon_c2 = tex2D(k_txnormal, cartoon_p2.xy); +float4 cartoon_p3 = l_texcoord - cartoondelta.wzyx; +float4 cartoon_c3 = tex2D(k_txnormal, cartoon_p3.xy); +float4 cartoon_mx = max(cartoon_c0,max(cartoon_c1,max(cartoon_c2,cartoon_c3))); +float4 cartoon_mn = min(cartoon_c0,min(cartoon_c1,min(cartoon_c2,cartoon_c3))); +float4 cartoon_trigger = saturate(((cartoon_mx-cartoon_mn) * 3) - k_cartooncutoff.x); +float cartoon_thresh = dot(cartoon_trigger.xyz,float3(1,1,1)); +o_color = lerp(o_color, float4(0,0,0,1), cartoon_thresh); +""" + +CARTOON_PARAMS=""" +uniform float4 k_cartoonseparation, +uniform float4 k_cartooncutoff, +""" +class CommonFilters: + + """ Class CommonFilters implements certain common image postprocessing + filters. The constructor requires a filter builder as a parameter. """ + + def __init__(self, win, cam): + self.manager = FilterManager(win, cam) + self.configuration = {} + self.cleanup() + + def cleanup(self): + self.manager.cleanup() + self.textures = {} + self.finalQuad = None + + def reconfigure(self, fullrebuild, changed): + + """ Reconfigure is called whenever any configuration change is made. """ + + configuration = self.configuration + + if (fullrebuild): + + self.cleanup() + + if (len(configuration) == 0): + return + + needtexpix = False + needtex = {} + needtex["color"] = True + if (configuration.has_key("CartoonInk")): + needtex["normal"] = True + for tex in needtex: + self.textures[tex] = Texture("scene-"+tex) + needtexpix = True + + self.finalQuad = self.manager.renderSceneInto(textures = self.textures) + + text = HEADER + text += "void fshader(\n" + text += "float4 l_texcoord : TEXCOORD0,\n" + if (needtexpix): + text += "uniform float4 texpix_txcolor,\n" + for key in self.textures: + text += "uniform sampler2D k_tx" + key + ",\n" + if (configuration.has_key("CartoonInk")): + text += CARTOON_PARAMS + text += "out float4 o_color : COLOR)\n" + text += "{\n" + text += " o_color = tex2D(k_txcolor, l_texcoord.xy);\n" + if (configuration.has_key("CartoonInk")): + text += CARTOON_BODY + text += "}\n" + + print "Using shader: ", text + self.finalQuad.setShader(Shader.make(text)) + for tex in self.textures: + self.finalQuad.setShaderInput("tx"+tex, self.textures[tex]) + + if (changed == "CartoonInk") or fullrebuild: + if (configuration.has_key("CartoonInk")): + (separation, cutoff) = configuration["CartoonInk"] + self.finalQuad.setShaderInput("cartoonseparation", Vec4(separation,0,separation,0)) + self.finalQuad.setShaderInput("cartooncutoff", Vec4(cutoff,cutoff,cutoff,cutoff)) + + def setCartoonInk(self, separation=1, cutoff=0.3): + fullrebuild = (self.configuration.has_key("CartoonInk") == False) + self.configuration["CartoonInk"] = (separation, cutoff) + self.reconfigure(fullrebuild, "CartoonInk") + + def delCartoonInk(self): + if (self.configuration.has_key("CartoonInk")): + del self.configuration["CartoonInk"] + self.reconfigure(True) + + diff --git a/direct/src/filter/FilterManager.py b/direct/src/filter/FilterManager.py new file mode 100644 index 0000000000..32c52f7350 --- /dev/null +++ b/direct/src/filter/FilterManager.py @@ -0,0 +1,195 @@ +""" + +The FilterManager is a convenience class that helps with the creation +of render-to-texture buffers for image postprocessing applications. + +Still need to implement: + +* Make sure sort-order of buffers is correct. +* Matching buffer size to original region instead of original window. +* Intermediate layer creation. +* Handling of window clears. +* Resizing of windows. +* Do something about window-size roundoff problems. + +""" + +from pandac.PandaModules import Point3, Vec3, Vec4 +from pandac.PandaModules import NodePath, PandaNode +from pandac.PandaModules import RenderState, Texture, Shader +from pandac.PandaModules import CardMaker +from pandac.PandaModules import TextureStage +from pandac.PandaModules import GraphicsPipe, GraphicsOutput +from pandac.PandaModules import WindowProperties, FrameBufferProperties +from pandac.PandaModules import Camera, DisplayRegion +from pandac.PandaModules import OrthographicLens +from pandac.PandaModules import AuxBitplaneAttrib +from direct.directnotify.DirectNotifyGlobal import * + +__all__ = ["FilterManager"] + +class FilterManager: + + notify = None + + def __init__(self, win, cam): + + """ The FilterManager constructor requires you to provide + a window which is rendering a scene, and the camera which is + used by that window to render the scene. These are henceforth + called the 'original window' and the 'original camera.' """ + + # Create the notify category + + if (FilterManager.notify == None): + FilterManager.notify = directNotify.newCategory("FilterManager") + + # Find the appropriate display region. + + region = None + for i in range(win.getNumDisplayRegions()): + dr = win.getDisplayRegion(i) + drcam = dr.getCamera() + if (drcam == cam): region=dr + + if (region == None): + self.notify.error('Could not find appropriate DisplayRegion to filter') + return False + + # Instance Variables. + + self.win = win + self.engine = win.getGsg().getEngine() + self.region = region + self.camera = cam + self.caminit = cam.node().getInitialState() + self.scales = [] + self.buffers = [] + + def renderSceneInto(self, depthtex=False, colortex=False, normaltex=False, textures=None): + + """ Causes the scene to be rendered into the supplied textures + instead of into the original window. Puts a fullscreen quad + into the original window to show the render-to-texture results. + Returns the quad. Normally, the caller would then apply a + shader to the quad. + + To elaborate on how this all works: + + * An offscreen buffer is created. It is set up to mimic + the original window - it is the same size, uses the + same clear colors, and contains a DisplayRegion that + uses the original camera. + + * A fullscreen quad and an orthographic camera to render + that quad are both created. The original camera is + removed from the original window, and in its place, the + orthographic quad-camera is installed. + + * The fullscreen quad is textured with the data from the + offscreen buffer. A shader is applied that tints the + results pink. + + Hence, the original window which used to contain the actual + scene, now contains a pink-tinted quad with a texture of the + scene. It is assumed that the user will replace the shader + on the quad with a more interesting filter. """ + + if (textures): + colortex = textures.get("color", None) + depthtex = textures.get("depth", None) + normaltex = textures.get("normal", None) + + if (colortex == None): colortex = Texture("filter-base-color") + + texgroup = (depthtex, colortex, normaltex, None) + + buffer = self.createBuffer("filter-base", base.win.getXSize(), base.win.getYSize(), texgroup) + + if (buffer == None): + return None + + cm = CardMaker("filter-base-quad") + cm.setFrameFullscreenQuad() + quad = NodePath(cm.generate()) + quad.setDepthTest(0) + quad.setDepthWrite(0) + quad.setTexture(colortex) + quad.setColor(Vec4(1,0.5,0.5,1)) + + auxbits = AuxBitplaneAttrib.ABOColor; + if (normaltex != None): + auxbits += AuxBitplaneAttrib.ABOCsnormal; + + cs = NodePath("dummy") + cs.setState(self.caminit) + cs.setShaderAuto(); + cs.setAttrib(AuxBitplaneAttrib.make(auxbits)) + self.camera.node().setInitialState(cs.getState()) + + quadcamnode = Camera("filter-quad-cam") + lens = OrthographicLens() + lens.setFilmSize(2, 2) + lens.setFilmOffset(0, 0) + lens.setNearFar(-1000, 1000) + quadcamnode.setLens(lens) + quadcam = quad.attachNewNode(quadcamnode) + + self.region.setCamera(quadcam) + + buffer.getDisplayRegion(0).setCamera(self.camera) + buffer.getDisplayRegion(0).setActive(1) + + self.scales.append(1) + self.buffers.append(buffer) + + return quad + + def renderQuadInto(self, scale=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None): + + """ Creates an offscreen buffer for an intermediate + computation. Installs a quad into the buffer. Returns + the fullscreen quad. """ + + self.notify.error('renderQuadInto not implemented yet.') + return None + + def createBuffer(self, name, xsize, ysize, texgroup): + """ Low-level buffer creation. Not intended for public use. """ + winprops = WindowProperties() + props = FrameBufferProperties() + props.setRgbColor(1) + props.setDepthBits(1) + depthtex, colortex, auxtex0, auxtex1 = texgroup + if (auxtex0 != None): + props.setAuxRgba(1) + if (auxtex1 != None): + props.setAuxRgba(2) + buffer=base.graphicsEngine.makeOutput( + self.win.getPipe(), name, -1, + props, winprops, GraphicsPipe.BFRefuseWindow, + self.win.getGsg(), self.win) + if (depthtex): + buffer.addRenderTexture(depthtex, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPDepth) + if (colortex): + buffer.addRenderTexture(colortex, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor) + if (auxtex0): + buffer.addRenderTexture(auxtex0, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba0) + if (auxtex1): + buffer.addRenderTexture(auxtex1, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba1) + return buffer + + + def cleanup(self): + """ Restore everything to its original state, deleting any + new buffers in the process. """ + + for buffer in self.buffers: + buffer.clearRenderTextures() + self.engine.removeWindow(buffer) + self.scales = [] + self.buffers = [] + self.region.setCamera(self.camera) + self.camera.node().setInitialState(self.caminit) + + diff --git a/direct/src/filter/__init__.py b/direct/src/filter/__init__.py new file mode 100644 index 0000000000..e69de29bb2