CodeSnippits DOF

From PyWiki

Jump to: navigation, search

Depth of Field

PO DOF.jpg

Following on from the snippet below - easy material management, here is an example of a depth-of-field effect. Its different from that provided in the Ogre SDK, and uses a DOF shader from Blender's example files, re-coded into Cg. IMHO the blur is of a higher quality than in the standard Ogre demo. DOF is quite a tricky effect to set up, but this demo demonstrates that it is entirely possible, and can look quite good too. Most of the work is already done in the previous snippit, and all we're doing here is adding a compositor to the mix, and attaching a rendertarget listener to an MRT texture contained in the compositor. The tricky bit is obtaining a reference to that texture, which you do with the following lines ( this also attaches the necessary renderTargetListener to that texture, so we can focibly swap in a depth material as before ):

instance = ogre.CompositorManager.getSingleton().addCompositor(self.vp, 'Blender_DOF', 0)
ogre.CompositorManager.getSingleton().setCompositorEnabled(self.vp, 'Blender_DOF', True)
tex = ogre.TextureManager.getSingleton().getByName( instance.getTextureInstanceName("mrt0",0) )
rt = tex.getBuffer().getRenderTarget()
rt.addListener( self.depthTargetListener )

So without further ado - here's the mountain of code you'll need:

DOF_Demo.py

import sys
sys.path.insert(0,'..')
import PythonOgreConfig
import psyco
psyco.full()
 
import ogre.renderer.OGRE as ogre
import ogre.io.OIS as OIS
import SampleFramework as sf
 
# Simple frameListener that just enables a compositor for testing
# We need at least one frame rendered so the compositor can work
class tListener(sf.FrameListener):
    def __init__(self, rw, cam, dcam):
        sf.FrameListener.__init__(self, rw, cam)
        self.it = 0
        self.vp = rw.getViewport(0)
        self.depthCam = dcam
 
    def frameStarted(self, evt):
        self.it += 1
        self.depthCam.setOrientation ( self.camera.getOrientation() )
        self.depthCam.setPosition (self.camera.getPosition())
        if self.it == 10:
            self.matListener = MaterialSwitcher()
            self.depthTargetListener = depthRenderListener( self.matListener )
            instance = ogre.CompositorManager.getSingleton().addCompositor(self.vp, 'Blender_DOF', 0)
            ogre.CompositorManager.getSingleton().setCompositorEnabled(self.vp, 'Blender_DOF', True)
            tex = ogre.TextureManager.getSingleton().getByName( instance.getTextureInstanceName("mrt0",0) )
            rt = tex.getBuffer().getRenderTarget()
            rt.addListener( self.depthTargetListener )
        return sf.FrameListener.frameStarted(self, evt)
 
 
# class to handle material switching without having to modify scene materials individually
class MaterialSwitcher( ogre.MaterialManager.Listener ):
    def __init__(self):
        ogre.MaterialManager.Listener.__init__(self)
        depthMat = ogre.MaterialManager.getSingleton().getByName("BasicDepthWrite")
        depthMat.load()
        self.depthTechnique = depthMat.getBestTechnique()
        normMat = ogre.MaterialManager.getSingleton().getByName("BasicWorldNormalWrite")
        normMat.load()
        self.normalsTechnique = normMat.getBestTechnique()
 
    def handleSchemeNotFound(self, index, name, material, lod, rend):
        if name == "DepthPass":
            return self.depthTechnique
        elif name == "NormalsPass":
            return self.normalsTechnique
 
# We need this attached to the depth target, otherwise we get problems with the compositor
# MaterialManager.Listener should NOT be running all the time - rather only when we're
# specifically rendering the target that needs it
class depthRenderListener(ogre.RenderTargetListener):
    def __init__(self, materialListener):
        ogre.RenderTargetListener.__init__(self)
        self.materialListener = materialListener
 
    def preRenderTargetUpdate(self, evt):
        ogre.MaterialManager.getSingleton().addListener( self.materialListener )
 
    def postRenderTargetUpdate(self, evt):
        ogre.MaterialManager.getSingleton().removeListener( self.materialListener )
 
 
# A simple application to test it all out
class RenderTargetExample( sf.Application ):
    def __init__(self):
        sf.Application.__init__(self)
    def _createScene(self):
        sm = self.sceneManager
        # This is the material listener - Note: it is controlled by a seperate
        # RenderTargetListener, not applied globally to all targets
        self.materialSwitchListener = MaterialSwitcher()
        # Create a simple test scene
        for i in range(10):
            e = self.sceneManager.createEntity("head" + str(i), "ogrehead.mesh")
            n = sm.getRootSceneNode().createChildSceneNode()
            n.attachObject( e )
            n.setPosition( ogre.Vector3( 0, 0, 100 * i) )
 
        for i in range(10):
            e = self.sceneManager.createEntity("robot" + str(i), "robot.mesh")
            n = sm.getRootSceneNode().createChildSceneNode()
            n.attachObject( e )
            n.setPosition( ogre.Vector3( 100, 0, 100 * i) )
        self.sceneManager.setAmbientLight(ogre.ColourValue(0.3, 0.3, 0.2)) 
        l = self.sceneManager.createLight("Light2") 
        dir_ = ogre.Vector3(-1,-1,0) 
        dir_.normalise() 
        l.setType(ogre.Light.LT_DIRECTIONAL) 
        l.setDirection(dir_) 
        l.setDiffuseColour(1, 1, 0.8) 
        l.setSpecularColour(1, 1, 1) 
        self.depthCam = self.sceneManager.createCamera("DepthCam")
        self.depthCam.setNearClipDistance(self.camera.getNearClipDistance())
        self.depthCam.setFarClipDistance(self.camera.getFarClipDistance())
        w = self.renderWindow.getViewport(0).getActualWidth() 
        h = self.renderWindow.getViewport(0).getActualHeight ()
        self.depthCam.setAspectRatio ( float(w)/float(h) )
        self.mPlane = ogre.MovablePlane("ReflectPlane")
        self.mPlane.d = 0
        self.mPlane.normal =ogre.Vector3().UNIT_Y
        ogre.MeshManager.getSingleton().createPlane("ReflectionPlane", 
            ogre.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME, 
            self.mPlane, 2000.0, 2000.0, 
            1, 1, True, 1, 50.0, 50.0, ogre.Vector3().UNIT_Z,
            ogre.HardwareBuffer.HBU_STATIC_WRITE_ONLY, ogre.HardwareBuffer.HBU_STATIC_WRITE_ONLY, 
            True,True
            )
        self.mPlaneEnt = self.sceneManager.createEntity( "Plane", "ReflectionPlane" )
        self.mPlaneEnt.setMaterialName("Ogre/Skin")
        rootNode = self.sceneManager.getRootSceneNode()
        self.mPlaneNode = rootNode.createChildSceneNode()
        self.mPlaneNode.attachObject(self.mPlaneEnt)
        self.mPlaneNode.attachObject(self.mPlane) 
        self.mPlaneNode.translate( ogre.Vector3(0, -10, 0))
 
        ## un-comment to see the render targets
        #self.createRenderTargets()
        #self.createRTTOverlays()
 
    def createRenderTargets(self):
        # depth pass    
        self.depthTargetListener = depthRenderListener( self.materialSwitchListener )
        texture = ogre.TextureManager.getSingleton().createManual("DepthPassTex", 
                                                                    ogre.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME, 
                                                                    ogre.TEX_TYPE_2D, 
                                                                    self.viewport.getActualWidth(), 
                                                                    self.viewport.getActualHeight(), 
                                                                    0, ogre.PixelFormat.PF_R8G8B8, ogre.TU_RENDERTARGET)
 
        self.renderTexture = texture.getBuffer().getRenderTarget()
        self.renderTexture.setPriority(0)                                                         
        self.renderTexture.addViewport( self.depthCam )
        self.renderTexture.getViewport(0).setOverlaysEnabled(False)
        self.renderTexture.getViewport(0).setClearEveryFrame( True )
        self.renderTexture.addListener( self.depthTargetListener )
        self.renderTexture.getViewport(0).setMaterialScheme("DepthPass")
 
        # normals pass
        texture = ogre.TextureManager.getSingleton().createManual("NormalPassTex", 
                                                                    ogre.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME, 
                                                                    ogre.TEX_TYPE_2D, 
                                                                    self.viewport.getActualWidth(), 
                                                                    self.viewport.getActualHeight(), 
                                                                    0, ogre.PixelFormat.PF_R8G8B8, ogre.TU_RENDERTARGET)
        self.normalTexture = texture.getBuffer().getRenderTarget()                                                           
        self.normalTexture.addViewport( self.depthCam )
        self.normalTexture.getViewport(0).setOverlaysEnabled(False)
        self.normalTexture.getViewport(0).setClearEveryFrame( True )
        self.normalTexture.addListener( self.depthTargetListener )
        self.normalTexture.getViewport(0).setMaterialScheme("NormalsPass")
 
    def createRTTOverlays(self):
        baseWhite = ogre.MaterialManager.getSingletonPtr().getByName("BaseWhite")
        DepthShadowTexture = baseWhite.clone("DepthDebugMaterial")
        textureUnit = DepthShadowTexture.getTechnique(0).getPass(0).createTextureUnitState()
        textureUnit.setTextureName("DepthPassTex")
        baseWhite = ogre.MaterialManager.getSingletonPtr().getByName("BaseWhite")
        DepthShadowTexture = baseWhite.clone("NormalDebugMaterial")
        textureUnit = DepthShadowTexture.getTechnique(0).getPass(0).createTextureUnitState()
        textureUnit.setTextureName("NormalPassTex")
        overlayManager = ogre.OverlayManager.getSingleton()
        # Create an overlay
        self.mDebugOverlay = overlayManager.create("OverlayName")
        # Create a panel
        panel = overlayManager.createOverlayElement("Panel", "PanelName0")
        panel.setMetricsMode(ogre.GMM_PIXELS)
        panel.setPosition(10, 10)
        panel.setDimensions(320, 240)
        panel.setMaterialName("DepthDebugMaterial") 
        self.mDebugOverlay.add2D(panel)
        panel = overlayManager.createOverlayElement("Panel", "PanelName1")
        panel.setMetricsMode(ogre.GMM_PIXELS)
        panel.setPosition(340, 10)
        panel.setDimensions(320, 240)
        panel.setMaterialName("NormalDebugMaterial") 
        self.mDebugOverlay.add2D(panel)
        self.mDebugOverlay.show()
 
    def _createFrameListener(self):
        self.fL = tListener(self.renderWindow, self.camera, self.depthCam)
        self.root.addFrameListener(self.fL)
 
if __name__ == '__main__':
    try:
        application = RenderTargetExample()
        application.go()
    except ogre.OgreException, e:
        print e

blenderDOF.compositor

compositor Blender_DOF
{
	technique
	{
		// temporary texture (MRT!)
		// 4 sub-surfaces, all 32-bit
		texture mrt0 target_width_scaled 0.5 target_height_scaled 0.5 PF_A8R8G8B8
		texture scene target_width target_height PF_A8R8G8B8
		texture dof_pass1 target_width target_height PF_A8R8G8B8
 
		target scene
		{
		    input previous
		}
 
        target mrt0
        {
            // Render scene using MRT-compatible material scheme
            input none
            material_scheme DepthPass
			pass clear
			{
 
			}
			pass render_scene
			{
			}
        }
 
		target dof_pass1
		{
			input none
            pass render_quad
            {
                // Renders a fullscreen quad
				material dof_blender_1st_pass
				// bind 4 MRT surfaces as texture inputs
                input 0 scene
                input 1 mrt0 0
 
          }
 
		}
 
		target_output
		{
		    input none
 
		    pass render_quad
		    {
		        material dof_blender_2nd_pass
		        input 0 dof_pass1
		        input 1 mrt0 0
		    }
		}
	}
}

BlenderDoF.material

fragment_program dof_blender_pass1 cg
{
    source BlenderDoF.cg
    entry_point blurPass1
    profiles ps_2_x arbfp1
    default_params
    {
        param_named focalDepth float 0.95
    }
}
 
fragment_program dof_blender_pass2 cg
{
    source BlenderDoF.cg
    entry_point blurPass2
    profiles ps_2_x arbfp1
    default_params
    {
        param_named focalDepth float 0.95
    }
}
 
material dof_blender_1st_pass
{
    technique
    {
        pass
        {
 
			vertex_program_ref Ogre/Compositor/StdQuad_Tex2a_vp
			{
			}
 
			fragment_program_ref dof_blender_pass1
			{
			}
 
			texture_unit scene
			{
			    tex_coord_set 0
			    tex_address_mode clamp
			    filtering trilinear
			}
 
			texture_unit RT0
			{
			    tex_coord_set 0
			    tex_address_mode clamp
			    filtering trilinear
			}
        }
    }
}
 
material dof_blender_2nd_pass
{
    technique
    {
        pass
        {
            vertex_program_ref Ogre/Compositor/StdQuad_Tex2a_vp
            {
            }
 
            fragment_program_ref dof_blender_pass2
            {
            }
 
            texture_unit scene
            {
                tex_coord_set 0
                tex_address_mode clamp
                filtering trilinear
            }
 
            texture_unit RT0
            {
                tex_coord_set 0
                tex_address_mode clamp
                filtering trilinear
            }
        }
    }
}

BlenderDoF.cg

//gets the color backbuffer
//uniform sampler2D bgl_RenderedTexture;
 
//gets the depth backbuffer
//(as a normalized tex2d, not a true 24bit depth buffer)
//uniform sampler2D bgl_DepthTexture;
 
 
float4 blurPass1(
    uniform sampler2D scene: register(s0),
    uniform sampler2D pdepth: register(s1),
    uniform float focalDepth,
    in float2 texcoord : TEXCOORD0
    ) : COLOR
{
	const float tap = 4;	    //1-8
	const float pmax = 0.003;
 
	//float focus = 0.1;			//Focal Distance, normalized 0.8-0.999
	float radius = 2.00;	  	//0-20.0
 
    float4 sum = float4(0,0,0,0);
	int j;
    int i;
	//float focus = tex2D(pdepth, float2(0.5,0.5)).r;
    float focus = focalDepth;
	float depth = tex2D(pdepth, texcoord);
	float delta = (abs(depth - focus)*abs(depth - focus))/(tap);
	delta = clamp(radius*delta, -pmax, pmax);
 
    for( i= -tap ;i < tap; i++)
    {
			sum += tex2D(scene, texcoord + float2(-i, i)*delta);
	}
	//Dof Pass
	float4 pass1 = sum/(2.0*tap);
	//float4 pass1 = float4(depth, depth, depth, 1.0);
 
    return pass1*1.2;
}
 
float4 blurPass2(
    uniform sampler2D scene: register(s0),
    uniform sampler2D pdepth: register(s1),
    uniform float focalDepth,
    in float2 texcoord : TEXCOORD0
    ) : COLOR
{
	const float tap = 4;	    //1-8
	const float pmax = 0.003;
 
	//float focus = 0.1;			//Focal Distance, normalized 0.8-0.999
	float radius = 2.00;	  	//0-20.0
 
    float4 sum = float4(0,0,0,0);
	int j;
    int i;
	//float focus = tex2D(pdepth, float2(0.5,0.5)).r;
    float focus = focalDepth;
	float depth = tex2D(pdepth, texcoord);
	float delta = (abs(depth - focus)*abs(depth - focus))/(tap);
	delta = clamp(radius*delta, -pmax, pmax);
 
    for( i= -tap ;i < tap; i++)
    {
			sum += tex2D(scene, texcoord + float2(i, i)*delta);
	}
	//Dof Pass
	float4 pass1 = sum/(2.0*tap);
 
	//float4 pass1 = float4(depth, depth, depth, 1.0);
 
    return pass1*1.2;
}

basic_passes.cg - Note this is slightly different from the previous example in that the depth is written inversely

// Write Depth to texture
 
void basicDepthWrite_vp (
    float4 pos: POSITION,
 
    out float4 oPos: POSITION,
    out float depth: TEXCOORD0,
 
    uniform float4x4 wvp,
    uniform float maxDepth,
    uniform float minDepth
    )
    {
        oPos = mul(wvp, pos);
        depth = (maxDepth - oPos.z) / maxDepth;
    }
 
float4 basicDepthWrite_fp (
    float depth: TEXCOORD0
    ) : COLOR
    {
        return float4( depth, depth, depth, 1.0 );
    }
 
 
// Write World Normal to texture
 
void worldNormalWrite_vp (
    float4 pos:POSITION,
    float3 n: NORMAL,
 
    out float4 oPos: POSITION,
    out float3 oNorm: TEXCOORD0,
 
    uniform float4x4 wvp,
    uniform float4x4 w)
    {
        oPos = mul(wvp, pos);
        oNorm = n;
    }
 
float4 worldNormalWrite_fp (
    float3 norm: TEXCOORD0) : COLOR
    {
        return float4 (norm.xyz, 1);
    }

And finally basic_passes.material ( same as before )

// Depth
 
vertex_program basicDepthVP cg
{
    source basic_passes.cg
    entry_point basicDepthWrite_vp
    profiles vs_2_0 arbvp1
 
 
    default_params
    {
 
		param_named_auto wvp worldviewproj_matrix
		param_named minDepth float 10.0
		param_named maxDepth float 1000.0
 
    }
}
 
fragment_program basicDepthFP cg
{
    source basic_passes.cg
    entry_point basicDepthWrite_fp
    profiles ps_2_0 arbfp1
 
    default_params
    {
    }
}
 
material BasicDepthWrite
{
    technique
    {
        pass
        {
            vertex_program_ref basicDepthVP
            {
            }
 
            fragment_program_ref basicDepthFP
            {
            }
        }
    }
}
 
// Normals
 
vertex_program worldNormalsVP cg
{
    source basic_passes.cg
    entry_point worldNormalWrite_vp
    profiles vs_2_0 arbvp1
    default_params
    {
        param_named_auto wvp worldviewproj_matrix
        param_named_auto w world_matrix
    }
}
 
fragment_program worldNormalsFP cg
{
    source basic_passes.cg
    entry_point worldNormalWrite_fp
    profiles ps_2_0 arbfp1
    default_params
    {
    }
}
 
material BasicWorldNormalWrite
{
    technique
    {
        pass
        {
            vertex_program_ref worldNormalsVP
            {
            }
 
            fragment_program_ref worldNormalsFP
            {
            }
        }
    }
}

Phew - lot of code, but worth it I think...

Enjoy ;)

Personal tools