The filter directory is for image postprocessing support
This commit is contained in:
parent
d82fefdab0
commit
e2adec014b
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue