/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library 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. See the * OpenSceneGraph Public License for more details. * * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski * Thanks to to my company http://www.ai.com.pl for allowing me free this work. */ #include #include #include #include #include #include #include #include #include using namespace osgShadow; #define VECTOR_LENGTH( v ) ( sizeof(v)/sizeof(v[0]) ) #define DEFAULT_DEBUG_HUD_SIZE_X 256 #define DEFAULT_DEBUG_HUD_SIZE_Y 256 #define DEFAULT_DEBUG_HUD_ORIGIN_X 8 #define DEFAULT_DEBUG_HUD_ORIGIN_Y 8 DebugShadowMap::DebugShadowMap(): BaseClass(), _doDebugDraw( false ), _hudSize( 2, 2 ), _hudOrigin( -1, -1 ), _viewportSize( DEFAULT_DEBUG_HUD_SIZE_X, DEFAULT_DEBUG_HUD_SIZE_Y ), _viewportOrigin( DEFAULT_DEBUG_HUD_ORIGIN_X, DEFAULT_DEBUG_HUD_ORIGIN_Y ), _orthoSize( 2, 2 ), _orthoOrigin( -1, -1 ) { // Why this fancy 24 bit depth to 24 bit rainbow colors shader ? // // Depth values cannot be easily cast on color component because they are: // a) 24 or 32 bit and we loose lots of precision when cast on 8 bit // b) depth value distribution is non linear due to projection division // when cast on componenent color there is usually very minor shade variety // and its often difficult to notice that there is anything in the buffer // // Shader looks complex but it is used only for debug head-up rectangle // so performance impact is not significant. _depthColorFragmentShader = new osg::Shader( osg::Shader::FRAGMENT, #if 0 "uniform sampler2D texture; \n" " \n" "void main(void) \n" "{ \n" " float f = texture2D( texture, vec3( gl_TexCoord[0].xy, 1.0).xy ).r; \n" " gl_FragColor = vec4( 0.0, 1.0 - f, 0.5 - f, 0.5 ); \n" "} \n" #else "uniform sampler2D texture; \n" " \n" "void main(void) \n" "{ \n" " float f = texture2D( texture, vec3( gl_TexCoord[0].xy, 1.0).xy ).r; \n" " \n" " f = 256.0 * f; \n" " float fC = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fS = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fH = floor( f ) / 256.0; \n" " \n" " fS *= 0.5; \n" " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" " \n" " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" " abs( fC - 0.333333 ), \n" " abs( fC - 0.666667 ) ); \n" " \n" " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" " \n" " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" " fMax = 1.0 / fMax; \n" " \n" " vec3 color = fMax * rgb; \n" " \n" " gl_FragColor = vec4( fS + fH * color, 1 ) * gl_Color; \n" "} \n" #endif ); // end _depthColorFragmentShader } DebugShadowMap::DebugShadowMap (const DebugShadowMap& copy, const osg::CopyOp& copyop) : BaseClass(copy,copyop), _doDebugDraw( copy._doDebugDraw ), _hudSize( copy._hudSize ), _hudOrigin( copy._hudOrigin ), _viewportSize( copy._viewportSize ), _viewportOrigin( copy._viewportOrigin ), _orthoSize( copy._viewportOrigin ), _orthoOrigin( copy._viewportOrigin ) { if( copy._depthColorFragmentShader.valid() ) _depthColorFragmentShader = dynamic_cast ( copy._depthColorFragmentShader->clone(copyop) ); } DebugShadowMap::~DebugShadowMap() { } void DebugShadowMap::ViewData::cull( void ) { if( getDebugDraw() && !_cameraDebugHUD.valid() ) createDebugHUD(); BaseClass::ViewData::cull( ); cullDebugGeometry( ); } bool DebugShadowMap::ViewData::DebugBoundingBox ( const osg::BoundingBox & bb, const char * name ) { bool result = false; #if defined( _DEBUG ) || defined( DEBUG ) if( !name ) name = ""; osg::BoundingBox & bb_prev = _boundingBoxMap[ std::string( name ) ]; result = bb.center() != bb_prev.center() || bb.radius() != bb_prev.radius(); if( result ) std::cout << "Box<" << name << "> (" << ( bb._max._v[0] + bb._min._v[0] ) * 0.5 << " " << ( bb._max._v[1] + bb._min._v[1] ) * 0.5 << " " << ( bb._max._v[2] + bb._min._v[2] ) * 0.5 << ") [" << ( bb._max._v[0] - bb._min._v[0] ) << " " << ( bb._max._v[1] - bb._min._v[1] ) << " " << ( bb._max._v[2] - bb._min._v[2] ) << "] " << std::endl; bb_prev = bb; #endif return result; } bool DebugShadowMap::ViewData::DebugPolytope ( const osg::Polytope & p, const char * name ) { bool result = false; #if defined( _DEBUG ) || defined( DEBUG ) if( !name ) name = ""; osg::Polytope & p_prev = _polytopeMap[ std::string( name ) ]; result = ( p.getPlaneList() != p_prev.getPlaneList() ); if( result ) { std::cout << "Polytope<" << name << "> size(" << p.getPlaneList().size() << ")" << std::endl; if( p.getPlaneList().size() == p_prev.getPlaneList().size() ) { for( unsigned i = 0; i < p.getPlaneList().size(); ++i ) { if( p.getPlaneList()[i] != p_prev.getPlaneList()[i] ) { std::cout << "Plane<" << i << "> (" << p.getPlaneList()[i].asVec4()[0] << ", " << p.getPlaneList()[i].asVec4()[1] << ", " << p.getPlaneList()[i].asVec4()[2] << ", " << p.getPlaneList()[i].asVec4()[3] << ")" << std::endl; } } } } p_prev = p; #endif return result; } bool DebugShadowMap::ViewData::DebugMatrix ( const osg::Matrix & m, const char * name ) { bool result = false; #if defined( _DEBUG ) || defined( DEBUG ) if( !name ) name = ""; osg::Matrix & m_prev = _matrixMap[ std::string( name ) ]; result = ( m != m_prev ); if( result ) std::cout << "Matrix<" << name << "> " << std::endl <<"[ " << m(0,0) << " " << m(0,1) << " " << m(0,2) << " " << m(0,3) << " ] " << std::endl <<"[ " << m(1,0) << " " << m(1,1) << " " << m(1,2) << " " << m(1,3) << " ] " << std::endl <<"[ " << m(2,0) << " " << m(2,1) << " " << m(2,2) << " " << m(2,3) << " ] " << std::endl <<"[ " << m(3,0) << " " << m(3,1) << " " << m(3,2) << " " << m(3,3) << " ] " << std::endl; m_prev = m; #endif return result; } void DebugShadowMap::ViewData::setDebugPolytope ( const char * name, const ConvexPolyhedron & polytope, osg::Vec4 colorOutline, osg::Vec4 colorInside ) { if( !getDebugDraw() ) return; if( &polytope == NULL ) { // delete PolytopeGeometry & pg = _polytopeGeometryMap[ std::string( name ) ]; for( int i = 0; i < VECTOR_LENGTH( pg._geometry ) ; i++ ) { if( pg._geometry[i].valid() ) { if( _geode[i].valid() && _geode[i]->containsDrawable( pg._geometry[i].get() ) ) _geode[i]->removeDrawable( pg._geometry[i].get() ); pg._geometry[i] = NULL; } } _polytopeGeometryMap.erase( std::string( name ) ); } else { // update PolytopeGeometry & pg = _polytopeGeometryMap[ std::string( name ) ]; pg._polytope = polytope; if( colorOutline.a() > 0 ) pg._colorOutline = colorOutline; if( colorInside.a() > 0 ) pg._colorInside = colorInside; for( int i = 0; i < VECTOR_LENGTH( pg._geometry ) ; i++ ) { if( !pg._geometry[i].valid() ) { pg._geometry[i] = new osg::Geometry; pg._geometry[i]->setDataVariance( osg::Object::DYNAMIC ); pg._geometry[i]->setUseDisplayList( false ); pg._geometry[i]->setSupportsDisplayList( false ); } if( _geode[i].valid() && !_geode[i]->containsDrawable( pg._geometry[i].get() ) ) { osg::Geode::DrawableList & dl = const_cast< osg::Geode::DrawableList &> ( _geode[i]->getDrawableList() ); dl.insert( dl.begin(), pg._geometry[i].get() ); } } } } void DebugShadowMap::ViewData::updateDebugGeometry ( const osg::Camera * viewCam, const osg::Camera * shadowCam ) { if( !getDebugDraw() ) return; if( _polytopeGeometryMap.empty() ) return; const int num = 2; // = VECTOR_LENGTH( PolytopeGeometry::_geometry ); const osg::Camera *camera[2] = { viewCam, shadowCam }; osg::Matrix transform[ num ] = { viewCam->getViewMatrix() * // use near far clamped projection ( precomputed in cullDebugGeometry ) ( viewCam == _viewCamera ? _viewProjection : viewCam->getProjectionMatrix() ), shadowCam->getViewMatrix() * shadowCam->getProjectionMatrix() }, inverse[ num ] = { osg::Matrix::inverse( transform[0] ), osg::Matrix::inverse( transform[1] ) }; #if 0 ConvexPolyhedron frustum[ num ]; for( int i = 0; i < num; i++ ) { frustum[i].setToUnitFrustum( ); #if 1 frustum[i].transform( inverse[i], transform[i] ); #else frustum[i].transform ( osg::Matrix::inverse( camera[i]->getProjectionMatrix() ), camera[i]->getProjectionMatrix() ); frustum[i].transform ( osg::Matrix::inverse( camera[i]->getViewMatrix() ), camera[i]->getViewMatrix() ); #endif }; #else osg::Polytope frustum[ num ]; for( int i = 0; i < num; i++ ) { frustum[i].setToUnitFrustum( ); frustum[i].transformProvidingInverse( transform[i] ); } #endif transform[0] = viewCam->getViewMatrix(); inverse[0] = viewCam->getInverseViewMatrix(); for( PolytopeGeometryMap::iterator itr = _polytopeGeometryMap.begin(); itr != _polytopeGeometryMap.end(); ++itr ) { PolytopeGeometry & pg = itr->second; for( int i = 0; i < num ; i++ ) { ConvexPolyhedron cp( pg._polytope ); cp.cut( frustum[i] ); cp.transform( transform[i], inverse[i] ); pg._geometry[i] = cp.buildGeometry ( pg._colorOutline, pg._colorInside, pg._geometry[i].get() ); } } } void DebugShadowMap::ViewData::cullDebugGeometry( ) { if( !getDebugDraw() ) return; if( !_camera.valid() ) return; // View camera may use clamping projection matrix after traversal. // Since we need to know exact matrix for drawing the frusta, // we have to compute it here in exactly the same way as cull visitor // will after cull traversal completes view camera subgraph. { _viewProjection = *_cv->getProjectionMatrix(); _viewCamera = _cv->getRenderStage()->getCamera(); if( _cv->getComputeNearFarMode() ) { // Redo steps from CullVisitor::popProjectionMatrix() // which clamps projection matrix when Camera & Projection // completes traversal of their children // We have to do this now manually // because we did not complete camera traversal yet but // we need to know how this clamped projection matrix will be _cv->computeNearPlane(); osgUtil::CullVisitor::value_type n = _cv->getCalculatedNearPlane(); osgUtil::CullVisitor::value_type f = _cv->getCalculatedFarPlane(); if( n < f ) _cv->clampProjectionMatrix( _viewProjection, n, f ); } } updateDebugGeometry( _viewCamera, _camera.get() ); #if 1 // Add geometries of polytopes to main cam Render Stage _transform[0]->accept( *_cv ); #else for( PolytopeGeometryMap::iterator itr = _polytopeGeometryMap.begin(); itr != _polytopeGeometryMap.end(); ++itr ) { PolytopeGeometry & pg = itr->second; _cv->pushStateSet( _geode[0]->getStateSet() ); _cv->addDrawableAndDepth( pg._geometry[0].get(), NULL, FLT_MAX ); _cv->popStateSet( ); } #endif // Add geometries of polytopes to hud cam Render Stage _cameraDebugHUD->accept( *_cv ); } void DebugShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv ) { BaseClass::ViewData::init( st, cv ); _doDebugDrawPtr = &st->_doDebugDraw; _hudSize = st->_hudSize; _hudOrigin = st->_hudOrigin; _viewportSize = st->_viewportSize; _viewportOrigin = st->_viewportOrigin; _orthoSize = st->_orthoSize; _orthoOrigin = st->_orthoOrigin; _depthColorFragmentShader = st->_depthColorFragmentShader; // create placeholder geodeds for polytope geometries // rest of their initialization will be performed during camera hud init _geode[0] = new osg::Geode; _geode[1] = new osg::Geode; _cameraDebugHUD = NULL;//Force debug HUD rebuild ( if needed ) } // Callback used by debugging hud to display Shadow Map to color buffer // Had to do it this way because OSG does not allow to use // the same GL Texture Id with different glTexParams. // Callback simply turns compare mode off via GL while rendering hud and // restores it before rendering the scene with shadows. class DrawableDrawWithDepthShadowComparisonOffCallback: public osg::Drawable::DrawCallback { public: DrawableDrawWithDepthShadowComparisonOffCallback( osg::Texture2D *pTex ) : _pTexture( pTex ) { } virtual void drawImplementation ( osg::RenderInfo & ri,const osg::Drawable* drawable ) const { ri.getState()->applyTextureAttribute( 0, _pTexture.get() ); // Turn off depth comparison mode glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE ); drawable->drawImplementation(ri); // Turn it back on glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB ); } osg::ref_ptr< osg::Texture2D > _pTexture; }; void DebugShadowMap::ViewData::createDebugHUD( ) { _cameraDebugHUD = new osg::Camera; { // Make sure default HUD layout makes sense if( _hudSize[0] <= 0 ) _hudSize[0] = DEFAULT_DEBUG_HUD_SIZE_X; if( _hudSize[1] <= 0 ) _hudSize[1] = DEFAULT_DEBUG_HUD_SIZE_Y; if( _viewportSize[0] <= 0 ) _viewportSize[0] = _hudSize[0]; if( _viewportSize[1] <= 0 ) _viewportSize[1] = _hudSize[1]; if( _orthoSize[0] <= 0 ) _orthoSize[0] = _viewportSize[0]; if( _orthoSize[1] <= 0 ) _orthoSize[1] = _viewportSize[1]; } { // Initialize hud camera osg::Camera * camera = _cameraDebugHUD.get(); camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setViewMatrix(osg::Matrix::identity()); camera->setViewport( _viewportOrigin[0], _viewportOrigin[1], _viewportSize[0], _viewportSize[1] ); camera->setProjectionMatrixAsOrtho( _orthoOrigin[0], _orthoOrigin[0] + _orthoSize[0], _orthoOrigin[1], _orthoOrigin[1] + _orthoSize[1], -10, 10 ); camera->setClearMask(GL_DEPTH_BUFFER_BIT); camera->setRenderOrder(osg::Camera::POST_RENDER); } { // Add geode and drawable with BaseClass display // create geode to contain hud drawables osg::Geode* geode = new osg::Geode; _cameraDebugHUD->addChild(geode); // finally create and attach hud geometry osg::Geometry* geometry = osg::createTexturedQuadGeometry ( osg::Vec3(_hudOrigin[0],_hudOrigin[1],0), osg::Vec3(_hudSize[0],0,0), osg::Vec3(0,_hudSize[1],0) ); osg::StateSet* stateset = _cameraDebugHUD->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0,_texture.get(),osg::StateAttribute::ON ); stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF); // stateset->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF); stateset->setAttributeAndModes ( new osg::Depth( osg::Depth::ALWAYS, 0, 1, false ) ); stateset->setMode(GL_BLEND,osg::StateAttribute::ON); osg::Program* program = new osg::Program; program->addShader( _depthColorFragmentShader.get() ); stateset->setAttribute( program ); stateset->addUniform( new osg::Uniform( "texture" , 0 ) ); geometry->setDrawCallback ( new DrawableDrawWithDepthShadowComparisonOffCallback( _texture.get() ) ); geode->addDrawable( geometry ); } { // Create transforms and geodes to manage polytope drawing osg::StateSet * stateset = new osg::StateSet; stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::OFF); stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setAttribute( new osg::Program() ); stateset->setAttributeAndModes ( new osg::Depth( osg::Depth::LEQUAL, 0, 1, false ) ); for( int i = 0; i < 2; i++ ) { _geode[i]->setStateSet( stateset ); _transform[i] = new osg::MatrixTransform; _transform[i]->addChild( _geode[i].get() ); _transform[i]->setMatrix( osg::Matrix::identity() ); _transform[i]->setReferenceFrame( osg::MatrixTransform::ABSOLUTE_RF ); } _transform[1]->setMatrix ( osg::Matrix::translate( 1, 1, 0 ) * osg::Matrix::scale( 0.5, 0.5, 1 ) * osg::Matrix::scale( _hudSize[0], _hudSize[1], 1 ) * osg::Matrix::translate( _hudOrigin[0], _hudOrigin[1], 0 ) ); _cameraDebugHUD->addChild( _transform[1].get() ); } } osg::Vec3d DebugShadowMap::ViewData::computeShadowTexelToPixelError ( const osg::Matrix & mvpwView, const osg::Matrix & mvpwShadow, const osg::Vec3d & vWorld, const osg::Vec3d & vDelta ) { osg::Vec3d vS0 = mvpwShadow * vWorld; osg::Vec3d vS1 = mvpwShadow * ( vWorld + vDelta ); osg::Vec3d vV0 = mvpwView * vWorld; osg::Vec3d vV1 = mvpwView * ( vWorld + vDelta ); osg::Vec3d dV = vV1 - vV0; osg::Vec3d dS = vS1 - vS0; return osg::Vec3( dS[0] / dV[0], dS[1] / dV[1], dS[2] / dV[2] ); } void DebugShadowMap::ViewData::displayShadowTexelToPixelErrors ( const osg::Camera* viewCamera, const osg::Camera* shadowCamera, const ConvexPolyhedron* hull ) { osg::Matrix mvpwMain = viewCamera->getViewMatrix() * viewCamera->getProjectionMatrix() * viewCamera->getViewport()->computeWindowMatrix(); osg::Matrix mvpwShadow = shadowCamera->getViewMatrix() * shadowCamera->getProjectionMatrix() * shadowCamera->getViewport()->computeWindowMatrix(); osg::BoundingBox bb = hull->computeBoundingBox( viewCamera->getViewMatrix() ); osg::Matrix m = viewCamera->getInverseViewMatrix(); osg::Vec3d vn = osg::Vec3d( 0, 0, bb._max[2] ) * m; osg::Vec3d vf = osg::Vec3d( 0, 0, bb._min[2] ) * m; osg::Vec3d vm = osg::Vec3d( 0, 0, ( bb._max[2] + bb._min[2] ) * 0.5 ) * m; osg::Vec3d vne = computeShadowTexelToPixelError( mvpwMain, mvpwShadow, vn ); osg::Vec3d vfe = computeShadowTexelToPixelError( mvpwMain, mvpwShadow, vf ); osg::Vec3d vme = computeShadowTexelToPixelError( mvpwMain, mvpwShadow, vm ); std::cout << std::setprecision( 3 ) << " " << "ne=(" << vne[0] << "," << vne[1] << "," << vne[2] << ") " << "fe=(" << vfe[0] << "," << vfe[1] << "," << vfe[2] << ") " << "me=(" << vme[0] << "," << vme[1] << "," << vme[2] << ") " << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" << std::flush; }