Jelajahi Sumber

The filter directory is for image postprocessing support

Josh Yelon 18 tahun lalu
induk
melakukan
e2adec014b

+ 133 - 0
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)
+
+

+ 195 - 0
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)
+
+

+ 0 - 0
direct/src/filter/__init__.py