#!/bin/env python # Copyright (C) 2002-2003 Gideon May (gideon@computer.org) # # Permission to copy, use, sell and distribute this software is granted # provided this copyright notice appears in all copies. # Permission to modify the code and to distribute modified code is granted # provided this copyright notice appears in all copies, and a notice # that the code was modified is included with the copyright notice. # # This software is provided "as is" without express or implied warranty, # and with no claim as to its suitability for any purpose. # -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield # # This application is open source and may be redistributed and/or modified # freely and without restriction, both in commericial and non commericial applications, # as long as this copyright notice is maintained. # # This application is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # """ A simple demo demonstrating planar reflections using multiple renderings of a subgraph, overriding of state attribures and use of the stencil buffer. The multipass system implemented here is a variation if Mark Kilgard's paper "Improving Shadows and Reflections via the Stencil Buffer" which can be found on the developer parts of the NVidia web site. The variations comes from the fact that the mirrors stencil values are done on the first pass, rather than the second as in Mark's paper. The second pass is now Mark's first pass - drawing the unreflected scene, but also unsets the stencil buffer. This variation stops the unreflected world poking through the mirror to be seen in the final rendering and also obscures the world correctly when on the reverse side of the mirror. Although there is still some unresolved issue with the clip plane needing to be flipped when looking at the reverse side of the mirror. Niether of these issues are mentioned in the Mark's paper, but trip us up when we apply them. """ import sys import math from PyOSG import * from OpenGL.GL import * def createMirrorTexturedState(filename): dstate = osg.StateSet() dstate.setMode(GL_CULL_FACE,osg.StateAttribute.OFF|osg.StateAttribute.PROTECTED) # set up the texture. image = osgDB.readImageFile(filename) if image: texture = osg.Texture2D() texture.setImage(image) dstate.setTextureAttributeAndModes(0,texture,osg.StateAttribute.ON|osg.StateAttribute.PROTECTED) return dstate def createMirrorSurface( xMin, xMax, yMin, yMax, z): # set up the drawstate. # set up the Geometry. geom = osg.Geometry() coords = osg.Vec3Array(4) coords[0].set(xMin,yMax,z) coords[1].set(xMin,yMin,z) coords[2].set(xMax,yMin,z) coords[3].set(xMax,yMax,z) geom.setVertexArray(coords) norms = osg.Vec3Array(1) norms[0].set(0.0,0.0,1.0) geom.setNormalArray(norms) geom.setNormalBinding(osg.Geometry.BIND_OVERALL) tcoords = osg.Vec2Array(4) tcoords[0].set(0.0,1.0) tcoords[1].set(0.0,0.0) tcoords[2].set(1.0,0.0) tcoords[3].set(1.0,1.0) geom.setTexCoordArray(0,tcoords) colours = osg.Vec4Array(1) colours[0].set(1.0,1.0,1.0,1.0) geom.setColorArray(colours) geom.setColorBinding(osg.Geometry.BIND_OVERALL) geom.addPrimitiveSet(osg.DrawArrays(osg.PrimitiveSet.QUADS,0,4)) return geom def createMirroredScene(model): # calculate where to place the mirror according to the # loaded models bounding sphere. bs = model.getBound() width_factor = 1.5 height_factor = 0.3 xMin = bs.center()._x-bs.radius()*width_factor xMax = bs.center()._x+bs.radius()*width_factor yMin = bs.center()._y-bs.radius()*width_factor yMax = bs.center()._y+bs.radius()*width_factor z = bs.center()._z-bs.radius()*height_factor # create a textured, transparent node at the appropriate place. mirror = createMirrorSurface(xMin,xMax,yMin,yMax,z) rootNode = osg.MatrixTransform() rootNode.setMatrix(osg.Matrix.rotate(osg.inDegrees(45.0),1.0,0.0,0.0)) # make sure that the global color mask exists. rootColorMask = osg.ColorMask() rootColorMask.setMask(True,True,True,True); # set up depth to be inherited by the rest of the scene unless # overrideen. this is overridden in bin 3. rootDepth = osg.Depth() rootDepth.setFunction(osg.Depth.LESS) rootDepth.setRange(0.0,1.0) rootStateSet = osg.StateSet() rootStateSet.setAttribute(rootColorMask) rootStateSet.setAttribute(rootDepth) rootNode.setStateSet(rootStateSet) # bin1 - set up the stencil values and depth for mirror. if 1: # set up the stencil ops so that the stencil buffer get set at # the mirror plane stencil = osg.Stencil() stencil.setFunction(osg.Stencil.ALWAYS,1,~0) stencil.setOperation(osg.Stencil.KEEP, osg.Stencil.KEEP, osg.Stencil.REPLACE) # switch off the writing to the color bit planes. colorMask = osg.ColorMask() colorMask.setMask(False,False,False,False) statesetBin1 = osg.StateSet() statesetBin1.setRenderBinDetails(1,"RenderBin") statesetBin1.setMode(GL_CULL_FACE,osg.StateAttribute.OFF) statesetBin1.setAttributeAndModes(stencil,osg.StateAttribute.ON) statesetBin1.setAttribute(colorMask) # set up the mirror geode. geode = osg.Geode() geode.addDrawable(mirror) geode.setStateSet(statesetBin1) rootNode.addChild(geode) # bin one - draw scene without mirror or reflection, unset # stencil values where scene is infront of mirror and hence # occludes the mirror. if 1: stencil = osg.Stencil() stencil.setFunction(osg.Stencil.ALWAYS,0,~0) stencil.setOperation(osg.Stencil.KEEP, osg.Stencil.KEEP, osg.Stencil.REPLACE) statesetBin2 = osg.StateSet(); statesetBin2.setRenderBinDetails(2,"RenderBin") statesetBin2.setAttributeAndModes(stencil,osg.StateAttribute.ON) groupBin2 = osg.Group() groupBin2.setStateSet(statesetBin2) groupBin2.addChild(model) rootNode.addChild(groupBin2) # bin3 - set up the depth to the furthest depth value if 1: # set up the stencil ops so that only operator on this mirrors stencil value. stencil = osg.Stencil() stencil.setFunction(osg.Stencil.EQUAL,1,~0) stencil.setOperation(osg.Stencil.KEEP, osg.Stencil.KEEP, osg.Stencil.KEEP) # switch off the writing to the color bit planes. colorMask = osg.ColorMask() colorMask.setMask(False,False,False,False) # set up depth so all writing to depth goes to maximum depth. depth = osg.Depth() depth.setFunction(osg.Depth.ALWAYS) depth.setRange(1.0,1.0) statesetBin3 = osg.StateSet() statesetBin3.setRenderBinDetails(3,"RenderBin") statesetBin3.setMode(GL_CULL_FACE,osg.StateAttribute.OFF) statesetBin3.setAttributeAndModes(stencil,osg.StateAttribute.ON) statesetBin3.setAttribute(colorMask) statesetBin3.setAttribute(depth) # set up the mirror geode. geode = osg.Geode() geode.addDrawable(mirror) geode.setStateSet(statesetBin3) rootNode.addChild(geode) # bin4 - draw the reflection. if 1: # now create the 'reflection' of the loaded model by applying # create a Transform which flips the loaded model about the z axis # relative to the mirror node, the loadedModel is added to the # Transform so now appears twice in the scene, but is shared so there # is negligable memory overhead. Also use an osg.StateSet # attached to the Transform to override the face culling on the subgraph # to prevert an 'inside' out view of the reflected model. # set up the stencil ops so that only operator on this mirrors stencil value. # this clip plane removes any of the scene which when mirror would # poke through the mirror. However, this clip plane should really # flip sides once the eye point goes to the back of the mirror... clipplane = osg.ClipPlane() clipplane.setClipPlane(0.0,0.0,-1.0,z) clipplane.setClipPlaneNum(0) clipNode = osg.ClipNode() clipNode.addClipPlane(clipplane) dstate = clipNode.getOrCreateStateSet() dstate.setRenderBinDetails(4,"RenderBin") dstate.setMode(GL_CULL_FACE,osg.StateAttribute.OVERRIDE|osg.StateAttribute.OFF) stencil = osg.Stencil() stencil.setFunction(osg.Stencil.EQUAL,1,~0) stencil.setOperation(osg.Stencil.KEEP, osg.Stencil.KEEP, osg.Stencil.KEEP) dstate.setAttributeAndModes(stencil,osg.StateAttribute.ON) reverseMatrix = osg.MatrixTransform() reverseMatrix.setStateSet(dstate) reverseMatrix.preMult(osg.Matrix.translate(0.0,0.0,-z)* osg.Matrix.scale(1.0,1.0,-1.0)* osg.Matrix.translate(0.0,0.0,z)) reverseMatrix.addChild(model) clipNode.addChild(reverseMatrix) rootNode.addChild(clipNode) # bin5 - draw the textured mirror and blend it with the reflection. if 1: # set up depth so all writing to depth goes to maximum depth. depth = osg.Depth() depth.setFunction(osg.Depth.ALWAYS) stencil = osg.Stencil() stencil.setFunction(osg.Stencil.EQUAL,1,~0) stencil.setOperation(osg.Stencil.KEEP, osg.Stencil.KEEP, osg.Stencil.ZERO) # set up additive blending. trans = osg.BlendFunc() trans.setFunction(osg.BlendFunc.ONE,osg.BlendFunc.ONE) statesetBin5 = createMirrorTexturedState("Images/tank.rgb") statesetBin5.setRenderBinDetails(5,"RenderBin") statesetBin5.setMode(GL_CULL_FACE,osg.StateAttribute.OFF) statesetBin5.setAttributeAndModes(stencil,osg.StateAttribute.ON) statesetBin5.setAttributeAndModes(trans,osg.StateAttribute.ON) statesetBin5.setAttribute(depth) # set up the mirror geode. geode = osg.Geode() geode.addDrawable(mirror) geode.setStateSet(statesetBin5) rootNode.addChild(geode) return rootNode ####################################################/ # # create the viewer # load a model # decoate the model so it renders using a multipass stencil buffer technique for planar reflections. # release the viewer # run main loop. # def main(argv ): # use an ArgumentParser object to manage the program arguments. arguments = osg.ArgumentParser(argv) # set up the usage document, in case we need to print out how to use this program. arguments.getApplicationUsage().setDescription(arguments.getApplicationName()+" is the example which demonstrates the use multi-pass rendering, stencil buffer to create a planer reflection effect.") arguments.getApplicationUsage().setCommandLineUsage(arguments.getApplicationName()+" [options] filename ...") arguments.getApplicationUsage().addCommandLineOption("-h or --help","Display this information") # construct the viewer. viewer = osgProducer.Viewer(arguments) # set up the value with sensible default event handlers. viewer.setUpViewer(osgProducer.Viewer.STANDARD_SETTINGS) # get details on keyboard and mouse bindings used by the viewer. viewer.getUsage(arguments.getApplicationUsage()) # if user request help write it out to cout. if arguments.read("-h") or arguments.read("--help"): arguments.getApplicationUsage().write() return 1 # any option left unread are converted into errors to write out later. arguments.reportRemainingOptionsAsUnrecognized() # report any errors if they have occured when parsing the program aguments. if arguments.errors(): arguments.writeErrorMessages() return 1 if arguments.argc()<=1: arguments.getApplicationUsage().write(osg.ApplicationUsage.COMMAND_LINE_OPTION) return 1 # read the scene from the list of file specified commandline args. loadedModel = osgDB.readNodeFiles(arguments) # if no model has been successfully loaded report failure. if not loadedModel: print arguments.getApplicationName() + ": No data loaded" return 1 # optimize the scene graph, remove rendundent nodes and state etc. optimizer = osgUtil.Optimizer() optimizer.optimize(loadedModel) # add a transform with a callback to animate the loaded model. loadedModelTransform = osg.MatrixTransform() loadedModelTransform.addChild(loadedModel) nc = osgUtil.TransformCallback(loadedModelTransform.getBound().center(),osg.Vec3(0.0,0.0,1.0),osg.inDegrees(45.0)) loadedModelTransform.setUpdateCallback(nc) # finally decorate the loaded model so that it has the required multipass/bin scene graph to do the reflection effect. rootNode = createMirroredScene(loadedModelTransform) # set the scene to render viewer.setSceneData(rootNode) # create the windows and run the threads. viewer.realize() while not viewer.done(): # wait for all cull and draw threads to complete. viewer.sync() # update the scene by traversing it with the the update visitor which will # call all node update callbacks and animations. viewer.update() # fire off the cull and draw traversals of the scene. viewer.frame() # wait for all cull and draw threads to complete before exit. viewer.sync() return 0 if __name__ == "__main__": main(sys.argv)