/* -*-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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // // 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. osg::StateSet* createMirrorTexturedState(const std::string& filename) { osg::StateSet* dstate = new osg::StateSet; dstate->setMode(GL_CULL_FACE,osg::StateAttribute::OFF|osg::StateAttribute::PROTECTED); // set up the texture. osg::Image* image = osgDB::readImageFile(filename.c_str()); if (image) { osg::Texture2D* texture = new osg::Texture2D; texture->setImage(image); dstate->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON|osg::StateAttribute::PROTECTED); } return dstate; } osg::Drawable* createMirrorSurface(float xMin,float xMax,float yMin,float yMax,float z) { // set up the drawstate. // set up the Geometry. osg::Geometry* geom = new osg::Geometry; osg::Vec3Array* coords = new 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); osg::Vec3Array* norms = new osg::Vec3Array(1); (*norms)[0].set(0.0f,0.0f,1.0f); geom->setNormalArray(norms); geom->setNormalBinding(osg::Geometry::BIND_OVERALL); osg::Vec2Array* tcoords = new osg::Vec2Array(4); (*tcoords)[0].set(0.0f,1.0f); (*tcoords)[1].set(0.0f,0.0f); (*tcoords)[2].set(1.0f,0.0f); (*tcoords)[3].set(1.0f,1.0f); geom->setTexCoordArray(0,tcoords); osg::Vec4Array* colours = new osg::Vec4Array(1); (*colours)[0].set(1.0f,1.0f,1.0,1.0f); geom->setColorArray(colours); geom->setColorBinding(osg::Geometry::BIND_OVERALL); geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); return geom; } osg::Node* createMirroredScene(osg::Node* model) { // calculate where to place the mirror according to the // loaded models bounding sphere. const osg::BoundingSphere& bs = model->getBound(); float width_factor = 1.5; float height_factor = 0.3; float xMin = bs.center().x()-bs.radius()*width_factor; float xMax = bs.center().x()+bs.radius()*width_factor; float yMin = bs.center().y()-bs.radius()*width_factor; float yMax = bs.center().y()+bs.radius()*width_factor; float z = bs.center().z()-bs.radius()*height_factor; // create a textured, transparent node at the appropriate place. osg::Drawable* mirror = createMirrorSurface(xMin,xMax,yMin,yMax,z); osg::MatrixTransform* rootNode = new osg::MatrixTransform; rootNode->setMatrix(osg::Matrix::rotate(osg::inDegrees(45.0f),1.0f,0.0f,0.0f)); // make sure that the global color mask exists. osg::ColorMask* rootColorMask = new 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. osg::Depth* rootDepth = new osg::Depth; rootDepth->setFunction(osg::Depth::LESS); rootDepth->setRange(0.0,1.0); osg::StateSet* rootStateSet = new osg::StateSet(); rootStateSet->setAttribute(rootColorMask); rootStateSet->setAttribute(rootDepth); rootNode->setStateSet(rootStateSet); // bin1 - set up the stencil values and depth for mirror. { // set up the stencil ops so that the stencil buffer get set at // the mirror plane osg::Stencil* stencil = new 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. osg::ColorMask* colorMask = new osg::ColorMask; colorMask->setMask(false,false,false,false); osg::StateSet* statesetBin1 = new 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. osg::Geode* geode = new 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. { osg::Stencil* stencil = new osg::Stencil; stencil->setFunction(osg::Stencil::ALWAYS,0,~0); stencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::REPLACE); osg::StateSet* statesetBin2 = new osg::StateSet(); statesetBin2->setRenderBinDetails(2,"RenderBin"); statesetBin2->setAttributeAndModes(stencil,osg::StateAttribute::ON); osg::Group* groupBin2 = new osg::Group(); groupBin2->setStateSet(statesetBin2); groupBin2->addChild(model); rootNode->addChild(groupBin2); } // bin3 - set up the depth to the furthest depth value { // set up the stencil ops so that only operator on this mirrors stencil value. osg::Stencil* stencil = new 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. osg::ColorMask* colorMask = new osg::ColorMask; colorMask->setMask(false,false,false,false); // set up depth so all writing to depth goes to maximum depth. osg::Depth* depth = new osg::Depth; depth->setFunction(osg::Depth::ALWAYS); depth->setRange(1.0,1.0); osg::StateSet* statesetBin3 = new 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. osg::Geode* geode = new osg::Geode; geode->addDrawable(mirror); geode->setStateSet(statesetBin3); rootNode->addChild(geode); } // bin4 - draw the reflection. { // 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... osg::ClipPlane* clipplane = new osg::ClipPlane; clipplane->setClipPlane(osg::Vec4(0.0f,0.0f,-1.0f,z)); clipplane->setClipPlaneNum(0); osg::ClipNode* clipNode = new osg::ClipNode; clipNode->addClipPlane(clipplane); osg::StateSet* dstate = clipNode->getOrCreateStateSet(); dstate->setRenderBinDetails(4,"RenderBin"); dstate->setMode(GL_CULL_FACE,osg::StateAttribute::OVERRIDE|osg::StateAttribute::OFF); osg::Stencil* stencil = new 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); osg::MatrixTransform* reverseMatrix = new osg::MatrixTransform; reverseMatrix->setStateSet(dstate); reverseMatrix->preMult(osg::Matrix::translate(0.0f,0.0f,-z)* osg::Matrix::scale(1.0f,1.0f,-1.0f)* osg::Matrix::translate(0.0f,0.0f,z)); reverseMatrix->addChild(model); clipNode->addChild(reverseMatrix); rootNode->addChild(clipNode); } // bin5 - draw the textured mirror and blend it with the reflection. { // set up depth so all writing to depth goes to maximum depth. osg::Depth* depth = new osg::Depth; depth->setFunction(osg::Depth::ALWAYS); osg::Stencil* stencil = new osg::Stencil; stencil->setFunction(osg::Stencil::EQUAL,1,~0); stencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::ZERO); // set up additive blending. osg::BlendFunc* trans = new osg::BlendFunc; trans->setFunction(osg::BlendFunc::ONE,osg::BlendFunc::ONE); osg::StateSet* 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. osg::Geode* geode = new 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. // int main( int argc, char **argv ) { // use an ArgumentParser object to manage the program arguments. osg::ArgumentParser arguments(&argc,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. osgProducer::Viewer 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") || arguments.read("--help")) { arguments.getApplicationUsage()->write(std::cout); 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(std::cout); return 1; } if (arguments.argc()<=1) { arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION); return 1; } // read the scene from the list of file specified commandline args. osg::ref_ptr loadedModel = osgDB::readNodeFiles(arguments); // if no model has been successfully loaded report failure. if (!loadedModel) { std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl; return 1; } // optimize the scene graph, remove rendundent nodes and state etc. osgUtil::Optimizer optimizer; optimizer.optimize(loadedModel.get()); // add a transform with a callback to animate the loaded model. osg::ref_ptr loadedModelTransform = new osg::MatrixTransform; loadedModelTransform->addChild(loadedModel.get()); osg::ref_ptr nc = new osgUtil::TransformCallback(loadedModelTransform->getBound().center(),osg::Vec3(0.0f,0.0f,1.0f),osg::inDegrees(45.0f)); loadedModelTransform->setUpdateCallback(nc.get()); // finally decorate the loaded model so that it has the required multipass/bin scene graph to do the reflection effect. osg::ref_ptr rootNode = createMirroredScene(loadedModelTransform.get()); // set the scene to render viewer.setSceneData(rootNode.get()); // create the windows and run the threads. viewer.realize(); while( !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; }