The filter directory is for image postprocessing support

This commit is contained in:
Josh Yelon 2008-02-12 21:52:29 +00:00
parent d82fefdab0
commit e2adec014b
3 changed files with 328 additions and 0 deletions

View File

@ -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)

View File

@ -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)

View File