CodeSnippits DOF
From PyWiki
Depth of Field
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 ;)
