From Wojciech Lewandowski, a range of new ShadowMap implementations include View Dependent Shadow Maps - LispSM

From Robert Osfield, refactored Wojciech's submission to integrated with osgShadow and use coding sytle more consistent with rest of OSG.
This commit is contained in:
Robert Osfield
2008-09-19 19:51:27 +00:00
parent f388d6a938
commit bf15614df6
18 changed files with 6785 additions and 0 deletions

View File

@@ -18,6 +18,16 @@ SET(LIB_PUBLIC_HEADERS
${HEADER_PATH}/SoftShadowMap
${HEADER_PATH}/ParallelSplitShadowMap
${HEADER_PATH}/Version
${HEADER_PATH}/ConvexPolyhedron
${HEADER_PATH}/DebugShadowMap
${HEADER_PATH}/LightSpacePerspectiveShadowMap
${HEADER_PATH}/MinimalCullBoundsShadowMap
${HEADER_PATH}/MinimalDrawBoundsShadowMap
${HEADER_PATH}/MinimalShadowMap
${HEADER_PATH}/ProjectionShadowMap
${HEADER_PATH}/StandardShadowMap
${HEADER_PATH}/ViewDependentShadowTechnique
)
# FIXME: For OS X, need flag for Framework or dylib
@@ -33,6 +43,14 @@ ADD_LIBRARY(${LIB_NAME}
SoftShadowMap.cpp
ParallelSplitShadowMap.cpp
Version.cpp
ConvexPolyhedron.cpp
DebugShadowMap.cpp
LightSpacePerspectiveShadowMap.cpp
MinimalCullBoundsShadowMap.cpp
MinimalDrawBoundsShadowMap.cpp
MinimalShadowMap.cpp
StandardShadowMap.cpp
ViewDependentShadowTechnique.cpp
)
LINK_INTERNAL(${LIB_NAME}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,589 @@
/* -*-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 <osgShadow/DebugShadowMap>
#include <osgShadow/ConvexPolyhedron>
#include <osgUtil/RenderLeaf>
#include <osg/Geometry>
#include <osg/PrimitiveSet>
#include <osg/MatrixTransform>
#include <osg/Depth>
#include <iostream>
#include <iomanip>
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<osg::Shader*>
( 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();
double n = _cv->getCalculatedNearPlane();
double 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;
}

View File

@@ -0,0 +1,843 @@
/* -*-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 <osgShadow/LightSpacePerspectiveShadowMap>
#include <iostream>
#include <iomanip>
#define DIRECTIONAL_ONLY 0
#define DIRECTIONAL_ADAPTED 1
#define DIRECTIONAL_AND_SPOT 2
//#define LISPSM_ALGO DIRECTIONAL_ONLY
#define LISPSM_ALGO DIRECTIONAL_ADAPTED
//#define LISPSM_ALGO DIRECTIONAL_AND_SPOT
#define PRINT_COMPUTED_N_OPT 0
using namespace osgShadow;
////////////////////////////////////////////////////////////////////////////////
// There are two slightly differing implemetations available on
// "Light Space Perspective Shadow Maps" page. One from 2004 and other from 2006.
// Our implementation is written in two versions based on these solutions.
////////////////////////////////////////////////////////////////////////////////
// Original LisPSM authors 2004 implementation excerpt. Kept here for reference.
// DIRECTIONAL AND DIRECTIONAL_ADAPTED versions are based on this code.
// DIRECTIONAL_AND_SPOT version is based on later 2006 code.
////////////////////////////////////////////////////////////////////////////////
#if 0
////////////////////////////////////////////////////////////////////////////////
// This code is copyright Vienna University of Technology, 2004.
//
// Please feel FREE to COPY and USE the code to include it in your own work,
// provided you include this copyright notice.
// This program 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.
//
// Authors Code:
// Daniel Scherzer (scherzer@cg.tuwien.ac.at)
//
// Authors Paper:
// Michael Wimmer (wimmer@cg.tuwien.ac.at)
// Daniel Scherzer (scherzer@cg.tuwien.ac.at)
// Werner Purgathofer
////////////////////////////////////////////////////////////////////////////////
void calcLispSMMtx(struct VecPoint* B) {
Vector3 min, max;
Vector3 up;
Matrix4x4 lispMtx;
struct VecPoint Bcopy = VECPOINT_NULL;
double dotProd = dot(viewDir,lightDir);
double sinGamma;
sinGamma = sqrt(1.0-dotProd*dotProd);
copyMatrix(lispMtx,IDENTITY);
copyVecPoint(&Bcopy,*B);
//CHANGED
if(useBodyVec) {
Vector3 newDir;
calcNewDir(newDir,B);
calcUpVec(up,newDir,lightDir);
}
else {
calcUpVec(up,viewDir,lightDir);
}
//temporal light View
//look from position(eyePos)
//into direction(lightDir)
//with up vector(up)
look(lightView,eyePos,lightDir,up);
//transform the light volume points from world into light space
transformVecPoint(B,lightView);
//calculate the cubic hull (an AABB)
//of the light space extents of the intersection body B
//and save the two extreme points min and max
calcCubicHull(min,max,B->points,B->size);
{
//use the formulas of the paper to get n (and f)
const double factor = 1.0/sinGamma;
const double z_n = factor*nearDist; //often 1
const double d = absDouble(max[1]-min[1]); //perspective transform depth //light space y extents
const double z_f = z_n + d*sinGamma;
const double n = (z_n+sqrt(z_f*z_n))/sinGamma;
const double f = n+d;
Vector3 pos;
//new observer point n-1 behind eye position
//pos = eyePos-up*(n-nearDist)
linCombVector3(pos,eyePos,up,-(n-nearDist));
look(lightView,pos,lightDir,up);
//one possibility for a simple perspective transformation matrix
//with the two parameters n(near) and f(far) in y direction
copyMatrix(lispMtx,IDENTITY); // a = (f+n)/(f-n); b = -2*f*n/(f-n);
lispMtx[ 5] = (f+n)/(f-n); // [ 1 0 0 0]
lispMtx[13] = -2*f*n/(f-n); // [ 0 a 0 b]
lispMtx[ 7] = 1; // [ 0 0 1 0]
lispMtx[15] = 0; // [ 0 1 0 0]
//temporal arrangement for the transformation of the points to post-perspective space
mult(lightProjection,lispMtx,lightView); // ligthProjection = lispMtx*lightView
//transform the light volume points from world into the distorted light space
transformVecPoint(&Bcopy,lightProjection);
//calculate the cubic hull (an AABB)
//of the light space extents of the intersection body B
//and save the two extreme points min and max
calcCubicHull(min,max,Bcopy.points,Bcopy.size);
}
//refit to unit cube
//this operation calculates a scale translate matrix that
//maps the two extreme points min and max into (-1,-1,-1) and (1,1,1)
scaleTranslateToFit(lightProjection,min,max);
//together
mult(lightProjection,lightProjection,lispMtx); // ligthProjection = scaleTranslate*lispMtx
}
#endif
#if ( LISPSM_ALGO == DIRECTIONAL_ONLY )
LightSpacePerspectiveShadowMapAlgorithm::LightSpacePerspectiveShadowMapAlgorithm()
{
lispsm = NULL;
}
LightSpacePerspectiveShadowMapAlgorithm::~LightSpacePerspectiveShadowMapAlgorithm()
{
}
void LightSpacePerspectiveShadowMapAlgorithm::operator()
( const ViewDependentShadow::ConvexPolyhedron* hullShadowedView,
const osg::Camera* cameraMain,
osg::Camera* cameraShadow ) const
{
osg::BoundingBox bb = hullShadowedView->computeBoundingBox( cameraMain->getViewMatrix() );
double nearDist = -bb._max[2];
const osg::Matrix & eyeViewToWorld = cameraMain->getInverseViewMatrix();
osg::Matrix lightViewToWorld = cameraShadow->getInverseViewMatrix();
osg::Vec3 eyePos = osg::Vec3( 0, 0, 0 ) * eyeViewToWorld;
osg::Vec3 viewDir( osg::Matrix::transform3x3( osg::Vec3(0,0,-1), eyeViewToWorld ) );
osg::Vec3 lightDir( osg::Matrix::transform3x3( osg::Vec3( 0,0,-1), lightViewToWorld ) );
osg::Vec3 up( osg::Matrix::transform3x3( osg::Vec3(0,1,0), lightViewToWorld ) );
osg::Matrix lightView; // compute coarse light view matrix
lightView.makeLookAt( eyePos, eyePos + lightDir, up );
bb = hullShadowedView->computeBoundingBox( lightView );
const double dotProd = viewDir * lightDir;
const double sinGamma = sqrt(1.0- dotProd*dotProd);
const double factor = 1.0/sinGamma;
const double z_n = factor*nearDist; //often 1
//use the formulas of the paper to get n (and f)
const double d = fabs( bb._max[1]-bb._min[1]); //perspective transform depth //light space y extents
const double z_f = z_n + d*sinGamma;
const double n = (z_n+sqrt(z_f*z_n))/sinGamma;
const double f = n+d;
osg::Vec3d pos = eyePos-up*(n-nearDist);
#if PRINT_COMPUTED_N_OPT
std::cout
<< " N=" << std::setw(8) << n
<< " n=" << std::setw(8) << z_n
<< " f=" << std::setw(8) << z_f
<< "\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;
#endif
lightView.makeLookAt( pos, pos + lightDir, up );
//one possibility for a simple perspective transformation matrix
//with the two parameters n(near) and f(far) in y direction
double a = (f+n)/(f-n);
double b = -2*f*n/(f-n);
osg::Matrix lispProjection( 1, 0, 0, 0,
0, a, 0, 1,
0, 0,-1, 0,
0, b, 0, 0 );
// lispProjection.makeIdentity( );
#if 0
{
osg::Matrix mvp = _camera->getViewMatrix() *
_camera->getProjectionMatrix();
extendScenePolytope( mvp, osg::Matrix::inverse( mvp ) );
}
#endif
bb = hullShadowedView->computeBoundingBox( lightView * lispProjection );
osg::Matrix fitToUnitFrustum;
fitToUnitFrustum.makeOrtho( bb._min[0], bb._max[0],
bb._min[1], bb._max[1],
-(bb._min[2]-1), -bb._max[2] );
cameraShadow->setProjectionMatrix
( lightViewToWorld * lightView * lispProjection * fitToUnitFrustum );
#if 0 // DOUBLE CHECK!
bb = computeScenePolytopeBounds
( cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix() );
if( !osg::equivalent( 0.f, (bb._min - osg::Vec3(-1,-1,-1)).length2() ) ||
!osg::equivalent( 0.f, (bb._max - osg::Vec3( 1, 1, 1)).length2() ) )
{
bb = computeScenePolytopeBounds
( cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix() );
}
#endif
}
#endif
#if ( LISPSM_ALGO == DIRECTIONAL_ADAPTED )
LightSpacePerspectiveShadowMapAlgorithm::LightSpacePerspectiveShadowMapAlgorithm()
{
lispsm = NULL;
}
LightSpacePerspectiveShadowMapAlgorithm::~LightSpacePerspectiveShadowMapAlgorithm()
{
}
void LightSpacePerspectiveShadowMapAlgorithm::operator()
( const osgShadow::ConvexPolyhedron* hullShadowedView,
const osg::Camera* cameraMain,
osg::Camera* cameraShadow ) const
{
// all computations are done in post projection light space
// which means we are in left handed coordinate system
osg::Matrix mvpLight =
cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix();
osg::Matrix m = cameraMain->getInverseViewMatrix() * mvpLight;
osg::Vec3 eye = osg::Vec3( 0, 0, 0 ) * m;
osg::Vec3 center = osg::Vec3( 0, 0, -1 ) * m;
osg::Vec3 up(0,1,0);
osg::Vec3 viewDir( center - eye );
viewDir.normalize();
m.makeLookAt( eye, center, up );
osg::BoundingBox bb = hullShadowedView->computeBoundingBox( mvpLight * m );
if( !bb.valid() )
return;
double nearDist = -bb._max[2];
#if 1
// Original LiSPSM Paper suggests that algorithm should work for all light types:
// infinte directional, omnidirectional and spot types may be treated as directional
// as all computations are performed in post projection light space.
// Frankly, I have my doubts if their error analysis and methodology still works
// in non directional lights post projective space. But since I can't prove it doesn't,
// I assume it does ;-). So I made an effort to modify their original directional algo
// to work in true light post perspective space and compute all params in this space.
// And here is a snag. Although shadowed hull fits completely into light space,
// camera position may not, and after projective transform it may land outside
// light frustum and even on/or below infinity plane. I need camera pos to compute
// minimal distance to shadowed hull. If its not right rest of the computation may
// be completely off. So in the end this approach is not singulartity free.
// I guess this problem is solvable in other way but "this other
// way" looks like a topic for other scientific paper and I am definitely not that
// ambitious ;-). So for the time being I simply try to discover when this happens and
// apply workaround, I found works. This workaround may mean that adjusted projection
// may not be optimal in original LisPSM Lmax norm sense. But as I wrote above,
// I doubt they are optimal when Light is not directional, anyway.
// Seems that most nasty case when algorithm fails is when cam pos is
// below light frustum near plane but above infinity plane - when this occurs
// shadows simply disappear. My workaround is to find this case by
// checking light postperspective transform camera z ( negative value means this )
// and make sure min distance to shadow hull is clamped to positive value.
if( eye[2] < 0 && nearDist <= 0 ) {
float clampedNearDist = 0.0001;
eye += viewDir * ( clampedNearDist - nearDist );
nearDist = clampedNearDist;
}
#endif
// Beware!!! Dirty Tricks:
// Light direction in light post proj space is actually (0,0,1)
// But since we want to pass it to std OpenGL right handed coordinate
// makeLookAt function we compensate the effects by also using right
// handed view forward vector (ie 0,0,-1) instead.
// So in the end we get left handed makeLookAt behaviour (D3D like)...
// I agree this method is bizarre. But it works so I left it as is.
// It sort of came out by itself through trial and error.
// I later understoood why it works.
osg::Vec3 lightDir(0,0,-1);
osg::Matrix lightView; // compute coarse light view matrix
lightView.makeLookAt( eye, eye + lightDir, up );
bb = hullShadowedView->computeBoundingBox( mvpLight * lightView );
if( !bb.valid() )
return;
//use the formulas from the LiSPSM paper to get n (and f)
const double dotProd = viewDir * lightDir;
const double sinGamma = sqrt(1.0- dotProd*dotProd);
const double factor = 1.0/sinGamma;
const double z_n = factor*nearDist;
//perspective transform depth light space y extents
const double d = fabs( bb._max[1]-bb._min[1]);
const double z_f = z_n + d*sinGamma;
const double n = (z_n+sqrt(z_f*z_n))/sinGamma;
const double f = n+d;
osg::Vec3d pos = eye-up*(n-nearDist);
lightView.makeLookAt( pos, pos + lightDir, up );
//one possibility for a simple perspective transformation matrix
//with the two parameters n(near) and f(far) in y direction
double a = (f+n)/(f-n);
double b = -2*f*n/(f-n);
osg::Matrix lispProjection( 1, 0, 0, 0,
0, a, 0, 1,
0, 0, 1, 0,
0, b, 0, 0 );
cameraShadow->setProjectionMatrix
( cameraShadow->getProjectionMatrix() * lightView * lispProjection );
}
#endif
#if ( LISPSM_ALGO == DIRECTIONAL_AND_SPOT )
// Adapted Modified version of LispSM authors implementation from 2006
// Nopt formula differs from the paper. I adopted original authors class to
// use with OSG
//we search the point in the LVS volume that is nearest to the camera
static const float INFINITY = FLT_MAX;
namespace osgShadow {
class LispSM {
public:
typedef std::vector<osg::Vec3d> Vertices;
void setProjectionMatrix( const osg::Matrix & projectionMatrix )
{ _projectionMatrix = projectionMatrix; }
void setViewMatrix( const osg::Matrix & viewMatrix )
{ _viewMatrix = viewMatrix; }
void setHull( const ConvexPolyhedron & hull )
{ _hull = hull; }
const ConvexPolyhedron & getHull( ) const
{ return _hull; }
const osg::Matrix & getProjectionMatrix( void ) const
{ return _projectionMatrix; }
const osg::Matrix & getViewMatrix( void ) const
{ return _viewMatrix; }
bool getUseLiSPSM() const
{ return _useLiSPSM; }
void setUseLiSPSM( bool use )
{ _useLiSPSM = use; }
bool getUseFormula() const
{ return _useFormula; }
void setUseFormula( bool use )
{ _useFormula = use; }
bool getUseOldFormula() const
{ return _useOldFormula; }
void setUseOldFormula( bool use )
{ _useOldFormula = use; }
void setN(const double& n )
{ _N = n; }
const double getN() const
{ return _N; }
//for old LispSM formula from paper
const double getNearDist() const
{ return _nearDist; }
void setNearDist( const double & nearDist )
{ _nearDist = nearDist; }
const double getFarDist() const
{ return _farDist; }
void setFarDist( const double & farDist )
{ _farDist = farDist; }
const osg::Vec3d & getEyeDir() const
{ return _eyeDir; }
const osg::Vec3d & getLightDir() const
{ return _lightDir; }
void setEyeDir( const osg::Vec3d eyeDir )
{ _eyeDir = eyeDir; }
void setLightDir( const osg::Vec3d lightDir )
{ _lightDir = lightDir; }
protected:
bool _useLiSPSM;
bool _useFormula;
bool _useOldFormula;
double _N;
double _nearDist;
double _farDist;
mutable osg::Vec3d _E;
osg::Vec3d _eyeDir;
osg::Vec3d _lightDir;
ConvexPolyhedron _hull;
osg::Matrix _viewMatrix;
osg::Matrix _projectionMatrix;
double getN(const osg::Matrix lightSpace, const osg::BoundingBox& B_ls) const;
osg::Vec3d getNearCameraPointE() const;
osg::Vec3d getZ0_ls
(const osg::Matrix& lightSpace, const osg::Vec3d& e, const double& b_lsZmax, const osg::Vec3d& eyeDir) const;
double calcNoptGeneral
(const osg::Matrix lightSpace, const osg::BoundingBox& B_ls) const;
double calcNoptOld
( const double gamma_ = 999) const;
osg::Matrix getLispSmMtx
(const osg::Matrix& lightSpace) const;
osg::Vec3d getProjViewDir_ls
(const osg::Matrix& lightSpace) const;
void updateLightMtx
(osg::Matrix& lightView, osg::Matrix& lightProj, const std::vector<osg::Vec3d>& B) const;
public:
LispSM( ) : _useLiSPSM( true ), _useFormula( true ), _useOldFormula( false ), _N( 1 ), _nearDist( 1 ), _farDist( 10 ) { }
virtual void updateLightMtx( osg::Matrix& lightView, osg::Matrix& lightProj ) const;
};
};
osg::Vec3d LispSM::getNearCameraPointE( ) const
{
const osg::Matrix& eyeView = getViewMatrix();
ConvexPolyhedron::Vertices LVS;
_hull.getPoints( LVS );
//the LVS volume is always in front of the camera
//the camera points along the neg z axis.
//-> so the nearest point is the maximum
unsigned max = 0;
for(unsigned i = 0; i < LVS.size(); i++) {
LVS[i] = LVS[i] * eyeView;
if( LVS[max].z() < LVS[i].z() ) {
max = i;
}
}
//transform back to world space
return LVS[max] * osg::Matrix::inverse( eyeView );
}
//z0 is the point that lies on the plane A parallel to the near plane through e
//and on the near plane of the C frustum (the plane z = bZmax) and on the line x = e.x
osg::Vec3d LispSM::getZ0_ls
(const osg::Matrix& lightSpace, const osg::Vec3d& e, const double& b_lsZmax, const osg::Vec3d& eyeDir) const
{
//to calculate the parallel plane to the near plane through e we
//calculate the plane A with the three points
osg::Plane A(eyeDir,e);
//to transform plane A into lightSpace
A.transform( lightSpace );
//transform to light space
const osg::Vec3d e_ls = e * lightSpace;
//z_0 has the x coordinate of e, the z coord of B_lsZmax
//and the y coord of the plane A and plane (z==B_lsZmax) intersection
#if 1
osg::Vec3d v = osg::Vec3d(e_ls.x(),0,b_lsZmax);
// x & z are given. We compute y from equations:
// A.distance( x,y,z ) == 0
// A.distance( x,y,z ) == A.distance( x,0,z ) + A.normal.y * y
// hence A.distance( x,0,z ) == -A.normal.y * y
v.y() = -A.distance( v ) / A.getNormal().y();
#else
//get the parameters of A from the plane equation n dot d = 0
const double d = A.asVec4()[3];
const osg::Vec3d n = A.getNormal();
osg::Vec3d v(e_ls.x(),(-d-n.z()*b_lsZmax-n.x()*e_ls.x())/n.y(),b_lsZmax);
#endif
return v;
}
double LispSM::calcNoptGeneral(const osg::Matrix lightSpace, const osg::BoundingBox& B_ls) const
{
const osg::Matrix& eyeView = getViewMatrix();
const osg::Matrix invLightSpace = osg::Matrix::inverse( lightSpace );
const osg::Vec3d z0_ls = getZ0_ls(lightSpace, _E,B_ls.zMax(),getEyeDir());
const osg::Vec3d z1_ls = osg::Vec3d(z0_ls.x(),z0_ls.y(),B_ls.zMin());
//to world
const osg::Vec4d z0_ws = osg::Vec4d( z0_ls, 1 ) * invLightSpace;
const osg::Vec4d z1_ws = osg::Vec4d( z1_ls, 1 ) * invLightSpace;
//to eye
const osg::Vec4d z0_cs = z0_ws * eyeView;
const osg::Vec4d z1_cs = z1_ws * eyeView;
double z0 = -z0_cs.z() / z0_cs.w();
double z1 = -z1_cs.z() / z1_cs.w();
if( z1 / z0 <= 1.0 ) {
// solve camera pos singularity in light space problem brutally:
// if extreme points of B projected to Light space extend beyond
// camera frustum simply use B extents in camera frustum
// Its not optimal selection but ceratainly better than negative N
osg::BoundingBox bb = _hull.computeBoundingBox( eyeView );
z0 = -bb.zMax();
if( z0 <= 0 )
z0 = 0.1;
z1 = -bb.zMin();
if( z1 <= z0 )
z1 = z0 + 0.1;
}
const double d = osg::absolute(B_ls.zMax()-B_ls.zMin());
double N = d/( sqrt( z1 / z0 ) - 1.0 );
#if PRINT_COMPUTED_N_OPT
std::cout
<< " N=" << std::setw(8) << N
<< " n=" << std::setw(8) << z0
<< " f=" << std::setw(8) << z1
<< "\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;
#endif
return N;
}
double LispSM::calcNoptOld( const double gamma_ ) const
{
const double& n = getNearDist();
const double& f = getFarDist();
const double d = abs(f-n);
double sinGamma(0);
if(999 == gamma_) {
double dot = getEyeDir() * getLightDir();
sinGamma = sqrt( 1.0 - dot * dot );
}
else {
sinGamma = sin(gamma_);
}
double N = (n+sqrt(n*(n+d*sinGamma)))/sinGamma;
#if PRINT_COMPUTED_N_OPT
std::cout
<< " N=" << std::setw(8) << N
<< " n=" << std::setw(8) << n
<< " f=" << std::setw(8) << f
<< "\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;
#endif
return N;
}
double LispSM::getN(const osg::Matrix lightSpace, const osg::BoundingBox& B_ls) const
{
if( getUseFormula()) {
if( getUseOldFormula() )
return calcNoptOld();
else
return calcNoptGeneral(lightSpace,B_ls);
}
else {
return getN();
}
}
//this is the algorithm discussed in the article
osg::Matrix LispSM::getLispSmMtx( const osg::Matrix& lightSpace ) const
{
const osg::BoundingBox B_ls = _hull.computeBoundingBox( lightSpace );
const double n = getN(lightSpace,B_ls);
//get the coordinates of the near camera point in light space
const osg::Vec3d e_ls = _E * lightSpace;
//c start has the x and y coordinate of e, the z coord of B.min()
const osg::Vec3d Cstart_lp(e_ls.x(),e_ls.y(),B_ls.zMax());
if( n >= INFINITY ) {
//if n is inf. than we should do uniform shadow mapping
return osg::Matrix::identity();
}
//calc C the projection center
//new projection center C, n behind the near plane of P
//we work along a negative axis so we transform +n*<the positive axis> == -n*<neg axis>
const osg::Vec3d C( Cstart_lp + osg::Vec3d(0,0,1) * n );
//construct a translation that moves to the projection center
const osg::Matrix projectionCenter = osg::Matrix::translate( -C );
//calc d the perspective transform depth or light space y extents
const double d = osg::absolute(B_ls.zMax()-B_ls.zMin());
//the lispsm perspective transformation
//here done with a standard frustum call that maps P onto the unit cube with
//corner points [-1,-1,-1] and [1,1,1].
//in directX you can use the same mapping and do a mapping to the directX post-perspective cube
//with corner points [-1,-1,0] and [1,1,1] as the final step after all the shadow mapping.
osg::Matrix P = osg::Matrix::frustum( -1.0,1.0,-1.0,1.0, n, n+d );
//invert the transform from right handed into left handed coordinate system for the ndc
//done by the openGL style frustumGL call
//so we stay in a right handed system
P = P * osg::Matrix::scale( 1.0,1.0,-1.0 );
//return the lispsm frustum with the projection center
return projectionCenter * P;
}
osg::Vec3d LispSM::getProjViewDir_ls(const osg::Matrix& lightSpace ) const {
//get the point in the LVS volume that is nearest to the camera
const osg::Vec3d e = _E;
//construct edge to transform into light-space
const osg::Vec3d b = e+getEyeDir();
//transform to light-space
osg::Vec4d e_lp = osg::Vec4d( e, 1.0 ) * lightSpace;
osg::Vec4d b_lp = osg::Vec4d( b, 1.0 ) * lightSpace;
if( e_lp[3] <= 0 )
{
e_lp[3] = e_lp[3];
}
if( b_lp[3] <= 0 )
{
osg::Vec4d v = (e_lp - b_lp)/(e_lp[3]-b_lp[3]);
v = ( e_lp + v ) * 0.5;
b_lp = v;
}
osg::Vec3d projDir( osg::Vec3( b_lp[0], b_lp[1], b_lp[2] ) / b_lp[3] -
osg::Vec3( e_lp[0], e_lp[1], e_lp[2] ) / e_lp[3] );
projDir.normalize();
//project the view direction into the shadow map plane
projDir.y() = 0.0;
return projDir;
}
void LispSM::updateLightMtx
( osg::Matrix& lightView, osg::Matrix& lightProj ) const
{
//calculate standard light space for spot or directional lights
//this routine returns two matrices:
//lightview contains the rotated translated frame
//lightproj contains in the case of a spot light the spot light perspective transformation
//in the case of a directional light a identity matrix
// calcLightSpace(lightView,lightProj);
if( _hull._faces.empty() ) {
//debug() << "empty intersection body -> completely inside shadow\n";//debug output
return;
}
_E = getNearCameraPointE();
lightProj = lightProj * osg::Matrix::scale( 1, 1, -1 );
//coordinate system change for calculations in the article
osg::Matrix switchToArticle = osg::Matrix::identity();
switchToArticle(1,1) = 0.0;
switchToArticle(1,2) =-1.0; // y -> -z
switchToArticle(2,1) = 1.0; // z -> y
switchToArticle(2,2) = 0.0;
//switch to the lightspace used in the article
lightProj = lightProj * switchToArticle;
osg::Matrix L = lightView * lightProj;
osg::Vec3d projViewDir = getProjViewDir_ls(L);
if( getUseLiSPSM() /* && projViewDir.z() < 0*/ ) {
//do Light Space Perspective shadow mapping
//rotate the lightspace so that the proj light view always points upwards
//calculate a frame matrix that uses the projViewDir[light-space] as up vector
//look(from position, into the direction of the projected direction, with unchanged up-vector)
lightProj = lightProj *
osg::Matrix::lookAt( osg::Vec3d(0,0,0), projViewDir, osg::Vec3d(0,1,0) );
osg::Matrix lispsm = getLispSmMtx( lightView * lightProj );
lightProj = lightProj * lispsm;
}
const osg::Matrix PL = lightView * lightProj;
osg::BoundingBox bb = _hull.computeBoundingBox( PL );
osg::Matrix fitToUnitFrustum;
fitToUnitFrustum.makeOrtho( bb._min[0], bb._max[0],
bb._min[1], bb._max[1],
-bb._max[2], -bb._min[2] );
//map to unit cube
lightProj = lightProj * fitToUnitFrustum;
//coordinate system change for calculations in the article
osg::Matrix switchToGL = osg::Matrix::identity();
switchToGL(1,1) = 0.0;
switchToGL(1,2) = 1.0; // y -> z
switchToGL(2,1) = -1.0; // z -> -y
switchToGL(2,2) = 0.0;
//back to open gl coordinate system y <-> z
lightProj = lightProj * switchToGL;
//transform from right handed system into left handed ndc
lightProj = lightProj * osg::Matrix::scale(1.0,1.0,-1.0);
}
void LightSpacePerspectiveShadowMapAlgorithm::operator()
( const ViewDependentShadow::ConvexPolyhedron* hullShadowedView,
const osg::Camera* cameraMain,
osg::Camera* cameraShadow ) const
{
lispsm->setHull( *hullShadowedView );
lispsm->setViewMatrix( cameraMain->getViewMatrix() );
lispsm->setProjectionMatrix( cameraMain->getViewMatrix() );
lispsm->setLightDir
( osg::Matrix::transform3x3( osg::Vec3d( 0, 0, -1 ),
osg::Matrix::inverse( cameraShadow->getViewMatrix() ) ) );
osg::Vec3d eyeDir = osg::Matrix::transform3x3( osg::Vec3d( 0, 0, -1 ),
osg::Matrix::inverse( cameraMain->getViewMatrix() ) );
osg::Matrix &proj = cameraShadow->getProjectionMatrix();
double l,r,b,t,n,f;
if( proj.getOrtho( l,r,b,t,n,f ) )
{
osg::Vec3d camPosInLightSpace =
osg::Vec3d( 0, 0, 0 ) *
osg::Matrix::inverse( cameraMain->getViewMatrix() ) *
cameraShadow->getViewMatrix() *
cameraShadow->getProjectionMatrix();
}
eyeDir.normalize();
lispsm->setEyeDir( eyeDir );
osg::BoundingBox bb =
hullShadowedView->computeBoundingBox( cameraMain->getViewMatrix() );
lispsm->setNearDist( -bb.zMax() );
lispsm->setFarDist( -bb.zMin() );
lispsm->updateLightMtx
( cameraShadow->getViewMatrix(), cameraShadow->getProjectionMatrix() );
}
LightSpacePerspectiveShadowMapAlgorithm::LightSpacePerspectiveShadowMapAlgorithm()
{
lispsm = new LispSM;
}
LightSpacePerspectiveShadowMapAlgorithm::~LightSpacePerspectiveShadowMapAlgorithm()
{
delete lispsm;
}
#endif

View File

@@ -0,0 +1,368 @@
/* -*-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 <osgShadow/MinimalCullBoundsShadowMap>
#include <osgUtil/RenderLeaf>
#include <string.h>
#define IGNORE_OBJECTS_LARGER_THAN_HEIGHT 0
using namespace osgShadow;
MinimalCullBoundsShadowMap::MinimalCullBoundsShadowMap(): BaseClass()
{
}
MinimalCullBoundsShadowMap::MinimalCullBoundsShadowMap
(const MinimalCullBoundsShadowMap& copy, const osg::CopyOp& copyop) :
BaseClass(copy,copyop)
{
}
MinimalCullBoundsShadowMap::~MinimalCullBoundsShadowMap()
{
}
void MinimalCullBoundsShadowMap::ViewData::init
( ThisClass *st, osgUtil::CullVisitor *cv )
{
BaseClass::ViewData::init( st, cv );
}
void MinimalCullBoundsShadowMap::ViewData::aimShadowCastingCamera
( const osg::Light *light,
const osg::Vec4 &lightPos,
const osg::Vec3 &lightDir,
const osg::Vec3 &lightUp )
{
MinimalShadowMap::ViewData::aimShadowCastingCamera
( light, lightPos, lightDir, lightUp );
frameShadowCastingCamera
( _cv->getCurrentRenderBin()->getStage()->getCamera(), _camera.get() );
}
void MinimalCullBoundsShadowMap::ViewData::cullShadowReceivingScene( )
{
RenderLeafList rllOld, rllNew;
GetRenderLeaves( _cv->getRenderStage(), rllOld );
MinimalShadowMap::ViewData::cullShadowReceivingScene( );
GetRenderLeaves( _cv->getRenderStage(), rllNew );
RemoveOldRenderLeaves( rllNew, rllOld );
RemoveIgnoredRenderLeaves( rllNew );
osg::Matrix projectionToModelSpace =
osg::Matrix::inverse( *_modellingSpaceToWorldPtr *
*_cv->getModelViewMatrix() * *_cv->getProjectionMatrix() );
osg::BoundingBox bb;
if( *_cv->getProjectionMatrix() != _clampedProjection ) {
osg::Polytope polytope;
#if 1
polytope.setToUnitFrustum();
#else
polytope.add( osg::Plane( 0.0,0.0,-1.0,1.0 ) ); // only far plane
#endif
polytope.transformProvidingInverse( *_modellingSpaceToWorldPtr *
*_cv->getModelViewMatrix() * _clampedProjection );
bb = ComputeRenderLeavesBounds( rllNew, projectionToModelSpace, polytope );
} else {
bb = ComputeRenderLeavesBounds( rllNew, projectionToModelSpace );
}
cutScenePolytope( *_modellingSpaceToWorldPtr,
osg::Matrix::inverse( *_modellingSpaceToWorldPtr ), bb );
}
void MinimalCullBoundsShadowMap::ViewData::GetRenderLeaves
( osgUtil::RenderBin *rb, RenderLeafList & rll )
{
osgUtil::RenderBin::RenderBinList& bins = rb->getRenderBinList();
osgUtil::RenderBin::RenderBinList::const_iterator rbitr;
// scan pre render bins
for(rbitr = bins.begin(); rbitr!=bins.end() && rbitr->first<0; ++rbitr )
GetRenderLeaves( rbitr->second.get(), rll );
// scan fine grained ordering.
osgUtil::RenderBin::RenderLeafList& renderLeafList = rb->getRenderLeafList();
osgUtil::RenderBin::RenderLeafList::const_iterator rlitr;
for( rlitr= renderLeafList.begin(); rlitr!= renderLeafList.end(); ++rlitr )
{
rll.push_back( *rlitr );
}
// scan coarse grained ordering.
osgUtil::RenderBin::StateGraphList& stateGraphList = rb->getStateGraphList();
osgUtil::RenderBin::StateGraphList::const_iterator oitr;
for( oitr= stateGraphList.begin(); oitr!= stateGraphList.end(); ++oitr )
{
for( osgUtil::StateGraph::LeafList::const_iterator dw_itr =
(*oitr)->_leaves.begin(); dw_itr != (*oitr)->_leaves.end(); ++dw_itr)
{
rll.push_back( dw_itr->get() );
}
}
// scan post render bins
for(; rbitr!=bins.end(); ++rbitr )
GetRenderLeaves( rbitr->second.get(), rll );
}
class CompareRenderLeavesByMatrices {
public:
bool operator()( const osgUtil::RenderLeaf *a, const osgUtil::RenderLeaf *b )
{
if ( !a ) return false; // NULL render leaf goes last
return !b ||
a->_projection < b->_projection ||
a->_projection == b->_projection && a->_modelview < b->_modelview;
}
};
inline bool CheckAndMultiplyBoxIfWithinPolytope
( osg::BoundingBox & bb, osg::Matrix & m, osg::Polytope &p )
{
if( !bb.valid() ) return false;
osg::Vec3 o = bb._min * m, s[3];
for( int i = 0; i < 3; i ++ )
s[i] = osg::Vec3( m(i,0), m(i,1), m(i,2) ) * ( bb._max[i] - bb._min[i] );
for( osg::Polytope::PlaneList::iterator it = p.getPlaneList().begin();
it != p.getPlaneList().end();
++it )
{
float dist = it->distance( o ), dist_min = dist, dist_max = dist;
for( int i = 0; i < 3; i ++ ) {
dist = it->dotProductNormal( s[i] );
if( dist < 0 ) dist_min += dist; else dist_max += dist;
}
if( dist_max < 0 )
return false;
}
bb._max = bb._min = o;
#if 1
for( int i = 0; i < 3; i ++ )
for( int j = 0; j < 3; j ++ )
if( s[i][j] < 0 ) bb._min[j] += s[i][j]; else bb._max[j] += s[i][j];
#else
b.expandBy( o + s[0] );
b.expandBy( o + s[1] );
b.expandBy( o + s[2] );
b.expandBy( o + s[0] + s[1] );
b.expandBy( o + s[0] + s[2] );
b.expandBy( o + s[1] + s[2] );
b.expandBy( o + s[0] + s[1] + s[2] );
#endif
#if ( IGNORE_OBJECTS_LARGER_THAN_HEIGHT > 0 )
if( bb._max[2] - bb._min[2] > IGNORE_OBJECTS_LARGER_THAN_HEIGHT ) // ignore huge objects
return false;
#endif
return true;
}
unsigned MinimalCullBoundsShadowMap::ViewData::RemoveOldRenderLeaves
( RenderLeafList &rllNew, RenderLeafList &rllOld )
{
unsigned count = 0;
std::sort( rllOld.begin(), rllOld.end() );
RenderLeafList::iterator itNew, itOld;
for( itNew = rllNew.begin(); itNew != rllNew.end() && rllOld.size(); ++itNew )
{
itOld = std::lower_bound( rllOld.begin(), rllOld.end(), *itNew );
if( itOld == rllOld.end() || *itOld != *itNew ) continue;
// found !
rllOld.erase( itOld ); //remove it from old range to speed up search
*itNew = NULL; //its not new = invalidate it among new render leaves
count ++;
}
return count;
}
unsigned MinimalCullBoundsShadowMap::ViewData::RemoveIgnoredRenderLeaves
( RenderLeafList &rll )
{
unsigned count = 0;
for( RenderLeafList::iterator it = rll.begin(); it != rll.end(); ++it )
{
if( !*it ) continue;
const char * name = (*it)->_drawable->className();
// Its a dirty but quick test (not very future proof)
if( !name || name[0] != 'L' ) continue;
if( !strcmp( name, "LightPointDrawable" ) ||
!strcmp( name, "LightPointSpriteDrawable" ) )
{
*it = NULL; //ignored = invalidate this in new render leaves list
count++;
}
}
return count;
}
osg::BoundingBox MinimalCullBoundsShadowMap::ViewData::ComputeRenderLeavesBounds
( RenderLeafList &rll, osg::Matrix & projectionToWorld )
{
osg::BoundingBox bbResult;
if( rll.size() == 0 ) return bbResult;
std::sort( rll.begin(), rll.end(), CompareRenderLeavesByMatrices() );
osg::ref_ptr< osg::RefMatrix > modelview;
osg::ref_ptr< osg::RefMatrix > projection;
osg::Matrix viewToWorld, modelToWorld, *ptrProjection = NULL,
*ptrViewToWorld = &projectionToWorld, *ptrModelToWorld;
osg::BoundingBox bb;
// compute bounding boxes but skip old ones (placed at the end as NULLs)
for( RenderLeafList::iterator it = rll.begin(); ; ++it ) {
// we actually allow to pass one element behind end to flush bb queue
osgUtil::RenderLeaf *rl = ( it != rll.end() ? *it : NULL );
// Don't trust already computed bounds for cull generated drawables
// LightPointDrawable & LightPointSpriteDrawable are such examples
// they store wrong recorded bounds from very first pass
if( rl && rl->_modelview == NULL )
rl->_drawable->dirtyBound();
// Stay as long as possible in model space to minimize matrix ops
if( rl && rl->_modelview == modelview && rl->_projection == projection ){
bb.expandBy( rl->_drawable->getBound() );
} else {
if( bb.valid() ) {
// Conditions to avoid matrix multiplications
if( projection.valid() )
{
if( projection.get() != ptrProjection )
{
ptrProjection = projection.get();
viewToWorld = *ptrProjection * projectionToWorld;
}
ptrViewToWorld = &viewToWorld;
} else {
ptrViewToWorld = &projectionToWorld;
}
if( modelview.valid() )
{
modelToWorld = *modelview.get() * *ptrViewToWorld;
ptrModelToWorld = &modelToWorld;
} else {
ptrModelToWorld = ptrViewToWorld;
}
for( int i = 0; i < 8; i++ )
bbResult.expandBy( bb.corner( i ) * *ptrModelToWorld );
}
if( !rl ) break;
bb = rl->_drawable->getBound();
modelview = rl->_modelview;
projection = rl->_projection;
}
}
rll.clear();
return bbResult;
}
osg::BoundingBox MinimalCullBoundsShadowMap::ViewData::ComputeRenderLeavesBounds
( RenderLeafList &rll, osg::Matrix & projectionToWorld, osg::Polytope & p )
{
osg::BoundingBox bbResult, bb;
if( rll.size() == 0 ) return bbResult;
std::sort( rll.begin(), rll.end(), CompareRenderLeavesByMatrices() );
osg::ref_ptr< osg::RefMatrix > modelview;
osg::ref_ptr< osg::RefMatrix > projection;
osg::Matrix viewToWorld, modelToWorld, *ptrProjection = NULL,
*ptrViewToWorld = &projectionToWorld, *ptrModelToWorld;
// compute bounding boxes but skip old ones (placed at the end as NULLs)
for( RenderLeafList::iterator it = rll.begin(); it != rll.end(); ++it ) {
// we actually allow to pass one element behind end to flush bb queue
osgUtil::RenderLeaf *rl = *it;
if( !rl ) break;
// Don't trust already computed bounds for cull generated drawables
// LightPointDrawable & LightPointSpriteDrawable are such examples
// they store wrong recorded bounds from very first pass
if( rl && rl->_modelview == NULL )
rl->_drawable->dirtyBound();
bb = rl->_drawable->getBound();
if( !bb.valid() ) continue;
// Stay as long as possible in model space to minimize matrix ops
if( rl->_modelview != modelview || rl->_projection != projection ) {
projection = rl->_projection;
if( projection.valid() )
{
if( projection.get() != ptrProjection )
{
ptrProjection = projection.get();
viewToWorld = *ptrProjection * projectionToWorld;
}
ptrViewToWorld = &viewToWorld;
} else {
ptrViewToWorld = &projectionToWorld;
}
modelview = rl->_modelview;
if( modelview.valid() )
{
modelToWorld = *modelview.get() * *ptrViewToWorld;
ptrModelToWorld = &modelToWorld;
} else {
ptrModelToWorld = ptrViewToWorld;
}
}
if( CheckAndMultiplyBoxIfWithinPolytope( bb, *ptrModelToWorld, p ) )
bbResult.expandBy( bb );
}
rll.clear();
return bbResult;
}

View File

@@ -0,0 +1,406 @@
/* -*-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 <osgShadow/MinimalDrawBoundsShadowMap>
#include <osgShadow/ConvexPolyhedron>
#include <osg/PolygonOffset>
#include <osgUtil/RenderLeaf>
#include <osgShadow/ShadowedScene>
#include <osg/FrameBufferObject>
#include <osg/BlendEquation>
#include <osg/Depth>
#include <osg/AlphaFunc>
#include <osg/Image>
#include <iostream>
#include <string.h>
#define ANALYSIS_DEPTH 1
#define USE_FLOAT_IMAGE 1
using namespace osgShadow;
MinimalDrawBoundsShadowMap::MinimalDrawBoundsShadowMap(): BaseClass()
{
}
MinimalDrawBoundsShadowMap::MinimalDrawBoundsShadowMap
(const MinimalDrawBoundsShadowMap& copy, const osg::CopyOp& copyop) :
BaseClass(copy,copyop)
{
}
MinimalDrawBoundsShadowMap::~MinimalDrawBoundsShadowMap()
{
}
void MinimalDrawBoundsShadowMap::ViewData::cullShadowReceivingScene( )
{
BaseClass::ViewData::cullShadowReceivingScene( );
ThisClass::ViewData::cullBoundAnalysisScene( );
}
void MinimalDrawBoundsShadowMap::ViewData::cullBoundAnalysisScene( )
{
_boundAnalysisCamera->setReferenceFrame( osg::Camera::ABSOLUTE_RF );
_boundAnalysisCamera->setViewMatrix( *_cv->getModelViewMatrix() );
_boundAnalysisCamera->setProjectionMatrix( _clampedProjection );
osg::Matrix::value_type l,r,b,t,n,f;
_boundAnalysisCamera->getProjectionMatrixAsFrustum( l,r,b,t,n,f );
_mainCamera = _cv->getRenderStage()->getCamera();
extendProjection( _boundAnalysisCamera->getProjectionMatrix(),
_boundAnalysisCamera->getViewport(), osg::Vec2( 2,2 ) );
// record the traversal mask on entry so we can reapply it later.
unsigned int traversalMask = _cv->getTraversalMask();
_cv->setTraversalMask( traversalMask &
_st->getShadowedScene()->getReceivesShadowTraversalMask() );
// do RTT camera traversal
_boundAnalysisCamera->accept(*_cv);
// reapply the original traversal mask
_cv->setTraversalMask( traversalMask );
}
void MinimalDrawBoundsShadowMap::ViewData::createDebugHUD( )
{
// _hudSize[0] *= 2;
_viewportSize[0] *= 2;
_orthoSize[0] *= 2;
MinimalShadowMap::ViewData::createDebugHUD( );
osg::Camera * camera = _cameraDebugHUD.get();
osg::Geode* geode = new osg::Geode;
camera->addChild( geode );
osg::Geometry* geometry = osg::createTexturedQuadGeometry
( osg::Vec3(_hudOrigin[0]+_hudSize[0],_hudOrigin[1],0),
osg::Vec3(_hudSize[0],0,0),
osg::Vec3(0,_hudSize[1],0) );
geode->addDrawable(geometry);
osg::StateSet* stateset = geometry->getOrCreateStateSet();
stateset->setTextureAttributeAndModes
(0, _boundAnalysisTexture.get(),osg::StateAttribute::ON );
#if ANALYSIS_DEPTH
osg::Program* program = new osg::Program;
program->addShader( _depthColorFragmentShader.get() );
stateset->setAttribute( program );
stateset->addUniform( new osg::Uniform( "texture" , 0 ) );
#else
#endif
}
osg::BoundingBox MinimalDrawBoundsShadowMap::ViewData::scanImage
( const osg::Image * image, osg::Matrix m )
{
osg::BoundingBox bb, bbProj;
int components = osg::Image::computeNumComponents( image->getPixelFormat() );
if( image->getDataType() == GL_FLOAT ) {
float scale = 255.f / 254.f;
float * pf = (float *)image->data();
for( int y = 0; y < image->t(); y++ ) {
float fY = ( 0.5f + y ) / image->t();
for( int x = 0; x < image->s(); x++ ) {
float fX = ( 0.5f + x ) / image->s();
if( pf[0] < 1.0 ) {
float fMinZ = pf[0] * scale;
bbProj.expandBy( osg::Vec3( fX, fY, fMinZ ) );
bb.expandBy( osg::Vec3( fX, fY, fMinZ ) * m );
if( components > 1 ) {
float fMaxZ = scale * ( 1.f - pf[1] );
bbProj.expandBy( osg::Vec3( fX, fY, fMaxZ ) );
bb.expandBy( osg::Vec3( fX, fY, fMaxZ ) * m );
}
}
pf += components;
}
}
} else if( image->getDataType() == GL_UNSIGNED_BYTE ) {
unsigned char * pb = (unsigned char *)image->data();
float scale = 1.f / 254, bias = 0.5f;
for( int y = 0; y < image->t(); y++ ) {
float fY = ( 0.5f + y ) / image->t();
for( int x = 0; x < image->s(); x++ ) {
float fX = ( 0.5f + x ) / image->s();
if( pb[0] < 255 ) {
float fMinZ = scale * (pb[0] - 0.5f);
fMinZ = osg::clampTo( fMinZ, 0.f, 1.f );
bbProj.expandBy( osg::Vec3( fX, fY, fMinZ ) );
bb.expandBy( osg::Vec3( fX, fY, fMinZ ) * m );
if( components > 1 ) {
float fMaxZ = scale * (255 - pb[1] + 0.5f);
fMaxZ = osg::clampTo( fMaxZ, 0.f, 1.f );
bbProj.expandBy( osg::Vec3( fX, fY, fMaxZ ) );
bb.expandBy( osg::Vec3( fX, fY, fMaxZ ) * m );
}
}
pb += components;
}
}
}
return bb;
}
void MinimalDrawBoundsShadowMap::ViewData::performBoundAnalysis( const osg::Camera& camera )
{
if( !_projection.valid() )
return;
osg::Camera::BufferAttachmentMap & bam
= const_cast<osg::Camera&>( camera ).getBufferAttachmentMap();
#if ANALYSIS_DEPTH
osg::Camera::Attachment & attachment = bam[ osg::Camera::DEPTH_BUFFER ];
#else
osg::Camera::Attachment & attachment = bam[ osg::Camera::COLOR_BUFFER ];
#endif
const osg::ref_ptr< osg::Image > image = attachment._image.get();
if( !image.valid() )
return;
osg::Matrix m;
m.invert( *_modellingSpaceToWorldPtr *
camera.getViewMatrix() *
camera.getProjectionMatrix() );
m.preMult( osg::Matrix::scale( osg::Vec3( 2.f, 2.f, 2.f ) ) *
osg::Matrix::translate( osg::Vec3( -1.f, -1.f, -1.f ) ) );
osg::BoundingBox bb = scanImage( image.get(), m );
if( getDebugDraw() ) {
ConvexPolyhedron p;
p.setToBoundingBox( bb );
p.transform( *_modellingSpaceToWorldPtr,
osg::Matrix::inverse( *_modellingSpaceToWorldPtr ) );
setDebugPolytope( "scan", p,
osg::Vec4( 0,0,0,1 ), osg::Vec4( 0,0,0,0.1 ) );
}
cutScenePolytope( *_modellingSpaceToWorldPtr,
osg::Matrix::inverse( *_modellingSpaceToWorldPtr ), bb );
frameShadowCastingCamera( _mainCamera.get(), _camera.get() );
_projection->set( _camera->getProjectionMatrix( ) );
BaseClass::ViewData::_texgen->setPlanesFromMatrix(
_camera->getProjectionMatrix() *
osg::Matrix::translate(1.0,1.0,1.0) *
osg::Matrix::scale(0.5,0.5,0.5) );
updateDebugGeometry( _mainCamera.get(), _camera.get() );
}
void MinimalDrawBoundsShadowMap::ViewData::recordShadowMapParams( )
{
const osgUtil::RenderStage * rs = _cv->getCurrentRenderBin()->getStage();
setShadowCameraProjectionMatrixPtr( _cv->getProjectionMatrix() );
if( !rs->getRenderBinList().empty() || rs->getBinNum() != 0 )
{
}
#if 0
MinimalShadowMap::RenderLeafList rll;
static unsigned pass = 0, c = 0;
pass++;
std::set< osg::ref_ptr< osg::RefMatrix > > projections;
MinimalShadowMap::GetRenderLeaves( , rll );
for( unsigned i =0; i < rll.size(); i++ ) {
if( rll[i]->_projection.get() != _projection.get() ) {
osg::RefMatrix * projection = rll[i]->_projection.get();
projections.insert( rll[i]->_projection );
c++;
}
}
if( projections.size() > 0 )
_projection = (*projections.begin()).get();
c = 0;
#endif
}
void MinimalDrawBoundsShadowMap::ViewData::init
( ThisClass *st, osgUtil::CullVisitor *cv )
{
BaseClass::ViewData::init( st, cv );
_camera->setCullCallback
( new CameraCullCallback( this, _camera->getCullCallback() ) );
_boundAnalysisTexture = new osg::Texture2D;
_boundAnalysisTexture->setTextureSize
( _boundAnalysisSize[0], _boundAnalysisSize[1] );
_boundAnalysisImage = new osg::Image;
#if ANALYSIS_DEPTH
_boundAnalysisImage->allocateImage( _boundAnalysisSize[0],
_boundAnalysisSize[1], 1,
GL_DEPTH_COMPONENT, GL_FLOAT );
_boundAnalysisTexture->setInternalFormat(GL_DEPTH_COMPONENT);
// _boundAnalysisTexture->setShadowComparison(true);
_boundAnalysisTexture->setShadowTextureMode(osg::Texture2D::LUMINANCE);
_boundAnalysisImage->setInternalTextureFormat( GL_DEPTH_COMPONENT );
_boundAnalysisTexture->setInternalFormat( GL_DEPTH_COMPONENT );
#else
#if USE_FLOAT_IMAGE
_boundAnalysisImage->allocateImage( _boundAnalysisSize[0],
_boundAnalysisSize[1], 1,
GL_RGBA, GL_FLOAT );
_boundAnalysisImage->setInternalTextureFormat( GL_RGBA16F_ARB );
_boundAnalysisTexture->setInternalFormat(GL_RGBA16F_ARB);
#else
_boundAnalysisImage->allocateImage( _boundAnalysisSize[0],
_boundAnalysisSize[1], 1,
GL_RGBA, GL_UNSIGNED_BYTE );
_boundAnalysisImage->setInternalTextureFormat( GL_RGBA );
_boundAnalysisTexture->setInternalFormat( GL_RGBA );
#endif
#endif
memset( _boundAnalysisImage->data(), 0, _boundAnalysisImage->getImageSizeInBytes() );
if( getDebugDraw() )
_boundAnalysisTexture->setImage(0, _boundAnalysisImage.get() );
_boundAnalysisTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST);
_boundAnalysisTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST);
// the shadow comparison should fail if object is outside the texture
_boundAnalysisTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT);
_boundAnalysisTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT);
// set up the render to texture camera.
// create the camera
_boundAnalysisCamera = new osg::Camera;
_boundAnalysisCamera->setName( "AnalysisCamera" );
_boundAnalysisCamera->setCullCallback( new BaseClass::CameraCullCallback(st) );
// _boundAnalysisCamera->setPreDrawCallback( _camera->getPreDrawCallback() );
_boundAnalysisCamera->setPostDrawCallback( new CameraPostDrawCallback(this) );
_boundAnalysisCamera->setClearColor( osg::Vec4(1,1,1,1) );
_boundAnalysisCamera->setClearMask(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
_boundAnalysisCamera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
// set viewport
_boundAnalysisCamera->setViewport
( 0, 0, _boundAnalysisSize[0], _boundAnalysisSize[1] );
// set the camera to render before the main camera.
_boundAnalysisCamera->setRenderOrder(osg::Camera::PRE_RENDER);
// tell the camera to use OpenGL frame buffer object where supported.
_boundAnalysisCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
//_boundAnalysisCamera->setRenderTargetImplementation(osg::Camera::SEPERATE_WINDOW);
const int OVERRIDE_ON = osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON;
const int OVERRIDE_OFF = osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF;
osg::StateSet* stateset = _boundAnalysisCamera->getOrCreateStateSet();
stateset->setAttributeAndModes
( new osg::Depth( osg::Depth::LESS, 0.0, 254.f/255.f ), OVERRIDE_ON );
// stateset->setAttributeAndModes
// ( new osg::AlphaFunc( osg::AlphaFunc::EQUAL, 1.f ), OVERRIDE_ON );
stateset->setRenderBinDetails( 0, "RenderBin",
osg::StateSet::OVERRIDE_RENDERBIN_DETAILS );
osg::Program* program = new osg::Program;
program->addShader( new osg::Shader( osg::Shader::FRAGMENT,
"uniform sampler2D texture; \n"
"void main(void) \n"
"{ \n"
#if ANALYSIS_DEPTH
" gl_FragColor = texture2D( texture, gl_TexCoord[0].xy ); \n"
#else
" gl_FragColor = vec4( gl_FragCoord.z, \n"
" 1.-gl_FragCoord.z, \n"
" 1., \n"
" texture2D( texture, gl_TexCoord[0].xy ).a ); \n"
#endif
"} \n"
) ); // program->addShader Fragment
program->addShader( new osg::Shader( osg::Shader::VERTEX,
"void main(void) \n"
"{ \n"
" gl_Position = ftransform(); \n"
" gl_TexCoord[0] = gl_MultiTexCoord0; \n"
"} \n"
) ); // program->addShader Vertex
stateset->setAttribute( program, OVERRIDE_ON );
// attach the texture and use it as the color buffer.
#if ANALYSIS_DEPTH
// _boundAnalysisCamera->attach(osg::Camera::DEPTH_BUFFER, _boundAnalysisTexture.get());
_boundAnalysisCamera->attach(osg::Camera::DEPTH_BUFFER, _boundAnalysisImage.get());
stateset->setMode( GL_BLEND, OVERRIDE_OFF );
#else
// _boundAnalysisCamera->attach(osg::Camera::COLOR_BUFFER, _boundAnalysisTexture.get());
_boundAnalysisCamera->attach(osg::Camera::COLOR_BUFFER, _boundAnalysisImage.get());
stateset->setAttributeAndModes
( new osg::BlendEquation( osg::BlendEquation::RGBA_MIN ), OVERRIDE_ON );
stateset->setMode( GL_DEPTH_TEST, OVERRIDE_OFF );
#endif
}

View File

@@ -0,0 +1,500 @@
/* -*-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 <osgShadow/MinimalShadowMap>
#include <osgShadow/ConvexPolyhedron>
#include <osg/MatrixTransform>
#include <osgShadow/ShadowedScene>
#include <osg/ComputeBoundsVisitor>
using namespace osgShadow;
#define PRINT_SHADOW_TEXEL_TO_PIXEL_ERROR 0
MinimalShadowMap::MinimalShadowMap():
BaseClass(),
_minLightMargin( 0 ),
_maxFarPlane( FLT_MAX ),
_shadowReceivingCoarseBoundAccuracy( BOUNDING_BOX )
{
}
MinimalShadowMap::MinimalShadowMap
(const MinimalShadowMap& copy, const osg::CopyOp& copyop) :
BaseClass(copy,copyop),
_maxFarPlane( copy._maxFarPlane ),
_minLightMargin( copy._minLightMargin ),
_shadowReceivingCoarseBoundAccuracy( copy._shadowReceivingCoarseBoundAccuracy )
{
}
MinimalShadowMap::~MinimalShadowMap()
{
}
osg::BoundingBox MinimalShadowMap::ViewData::computeShadowReceivingCoarseBounds()
{
// Default slowest but most precise
ShadowReceivingCoarseBoundAccuracy accuracy = DEFAULT_ACCURACY;
MinimalShadowMap * msm = dynamic_cast< MinimalShadowMap* >( _st.get() );
if( msm ) accuracy = msm->getShadowReceivingCoarseBoundAccuracy();
if( accuracy == MinimalShadowMap::EMPTY_BOX )
{
// One may skip coarse scene bounds computation if light is infinite.
// Empty box will be intersected with view frustum so in the end
// view frustum will be used as bounds approximation.
// But if light is nondirectional and bounds come out too large
// they may bring the effect of almost 180 deg perspective set
// up for shadow camera. Such projection will significantly impact
// precision of further math.
return osg::BoundingBox();
}
if( accuracy == MinimalShadowMap::BOUNDING_SPHERE )
{
// faster but less precise rough scene bound computation
// however if compute near far is active it may bring quite good result
osg::Camera * camera = _cv->getRenderStage()->getCamera();
osg::Matrix m = camera->getViewMatrix() * _clampedProjection;
ConvexPolyhedron frustum;
frustum.setToUnitFrustum();
frustum.transform( osg::Matrix::inverse( m ), m );
osg::BoundingSphere bs =_st->getShadowedScene()->getBound();
osg::BoundingBox bb;
bb.expandBy( bs );
osg::Polytope box;
box.setToBoundingBox( bb );
frustum.cut( box );
// approximate sphere with octahedron. Ie first cut by box then
// additionaly cut with the same box rotated 45, 45, 45 deg.
box.transform( // rotate box around its center
osg::Matrix::translate( -bs.center() ) *
osg::Matrix::rotate( osg::PI_4, 0, 0, 1 ) *
osg::Matrix::rotate( osg::PI_4, 1, 1, 0 ) *
osg::Matrix::translate( bs.center() ) );
frustum.cut( box );
return frustum.computeBoundingBox( );
}
if( accuracy == MinimalShadowMap::BOUNDING_BOX ) // Default
{
// more precise method but slower method
// bound visitor traversal takes lot of time for complex scenes
// (note that this adds to cull time)
osg::ComputeBoundsVisitor cbbv(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN);
cbbv.setTraversalMask(_st->getShadowedScene()->getCastsShadowTraversalMask());
_st->getShadowedScene()->osg::Group::traverse(cbbv);
return cbbv.getBoundingBox();
}
return osg::BoundingBox();
}
void MinimalShadowMap::ViewData::aimShadowCastingCamera
( const osg::Light *light, const osg::Vec4 &lightPos,
const osg::Vec3 &lightDir, const osg::Vec3 &lightUp )
{
osg::BoundingBox bb = computeScenePolytopeBounds();
if( !bb.valid() ) { // empty scene or looking at the sky - substitute something
bb.expandBy( osg::BoundingSphere( _cv->getEyePoint(), 1 ) );
}
osg::Vec3 up = lightUp;
if( up.length2() <= 0 )
{
// This is extra step (not really needed but helpful in debuging)
// Compute such lightUp vector that shadow cam is intuitively aligned with eye
// We compute this vector on -ZY view plane, perpendicular to light direction
// Matrix m = ViewToWorld
#if 0
osg::Matrix m = osg::Matrix::inverse( *cv.getModelViewMatrix() );
osg::Vec3 camFw( -m( 2, 0 ), -m( 2, 1 ), -m( 2, 2 ) );
camFw.normalize();
osg::Vec3 camUp( m( 1, 0 ), m( 1, 1 ), m( 1, 2 ) );
camUp.normalize();
up = camUp * ( camFw * lightDir ) - camFw * ( camUp * lightDir );
up.normalize();
#else
osg::Matrix m = osg::Matrix::inverse( *_cv->getModelViewMatrix() );
// OpenGL std cam looks along -Z axis so Cam Fw = [ 0 0 -1 0 ] * m
up.set( -m( 2, 0 ), -m( 2, 1 ), -m( 2, 2 ) );
#endif
}
BaseClass::ViewData::aimShadowCastingCamera
( bb, light, lightPos, lightDir, up );
// Intersect scene Receiving Shadow Polytope with shadow camera frustum
// Important for cases where Scene extend beyond shadow camera frustum
// From this moment shadowed scene portion is fully contained by both
// main camera frustum and shadow camera frustum
osg::Matrix mvp = _camera->getViewMatrix() * _camera->getProjectionMatrix();
cutScenePolytope( osg::Matrix::inverse( mvp ), mvp );
MinimalShadowMap::ViewData::frameShadowCastingCamera
( _cv->getRenderStage()->getCamera(), _camera.get(), 0 );
}
void MinimalShadowMap::ViewData::frameShadowCastingCamera
( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass )
{
osg::Matrix mvp =
cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix();
ConvexPolyhedron polytope = _sceneReceivingShadowPolytope;
std::vector<osg::Vec3d> points = _sceneReceivingShadowPolytopePoints;
osg::BoundingBox bb = computeScenePolytopeBounds( mvp );
// projection was trimmed above, need to recompute mvp
if( bb.valid() && *_minLightMarginPtr > 0 ) {
// bb._max += osg::Vec3( 1, 1, 1 );
// bb._min -= osg::Vec3( 1, 1, 1 );
osg::Matrix transform = osg::Matrix::inverse( mvp );
osg::Vec3d normal = osg::Matrix::transform3x3( osg::Vec3d(0,0,-1), transform );
normal.normalize();
_sceneReceivingShadowPolytope.extrude( normal * *_minLightMarginPtr );
// Zero pass does crude shadowed scene hull approximation.
// Its important to cut it to coarse light frustum properly
// at this stage.
// If not cut and polytope extends beyond shadow projection clip
// space (-1..1), it may get "twisted" by precisely adjusted shadow cam
// projection in second pass.
if ( pass == 0 )
{ // Make sure extruded polytope does not extend beyond light frustum
osg::Polytope lightFrustum;
lightFrustum.setToUnitFrustum();
lightFrustum.transformProvidingInverse( mvp );
_sceneReceivingShadowPolytope.cut( lightFrustum );
}
_sceneReceivingShadowPolytopePoints.clear();
_sceneReceivingShadowPolytope.getPoints
( _sceneReceivingShadowPolytopePoints );
bb = computeScenePolytopeBounds( mvp );
}
setDebugPolytope( "extended",
_sceneReceivingShadowPolytope, osg::Vec4( 1, 0.5, 0, 1 ), osg::Vec4( 1, 0.5, 0, 0.1 ) );
_sceneReceivingShadowPolytope = polytope;
_sceneReceivingShadowPolytopePoints = points;
// Warning: Trim light projection at near plane may remove shadowing
// from objects outside of view space but still casting shadows into it.
// I have not noticed this issue so I left mask at default: all bits set.
if( bb.valid() )
trimProjection( cameraShadow->getProjectionMatrix(), bb, 1|2|4|8|16|32 );
///// Debuging stuff //////////////////////////////////////////////////////////
setDebugPolytope( "scene", _sceneReceivingShadowPolytope, osg::Vec4(0,1,0,1) );
#if PRINT_SHADOW_TEXEL_TO_PIXEL_ERROR
if( pass == 1 )
displayShadowTexelToPixelErrors
( cameraMain, cameraShadow, &_sceneReceivingShadowPolytope );
#endif
}
void MinimalShadowMap::ViewData::cullShadowReceivingScene( )
{
BaseClass::ViewData::cullShadowReceivingScene( );
_clampedProjection = *_cv->getProjectionMatrix();
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();
double n = _cv->getCalculatedNearPlane();
double f = _cv->getCalculatedFarPlane();
if( n < f )
_cv->clampProjectionMatrix(_clampedProjection, n, f );
}
// Aditionally clamp far plane if shadows are don't need to be cast as
// far as main projection far plane
if( 0 < *_maxFarPlanePtr )
clampProjection( _clampedProjection, 0.f, *_maxFarPlanePtr );
// Give derived classes chance to initialize _sceneReceivingShadowPolytope
osg::BoundingBox bb = computeShadowReceivingCoarseBounds( );
if( bb.valid() )
_sceneReceivingShadowPolytope.setToBoundingBox( bb );
else
_sceneReceivingShadowPolytope.clear();
// Cut initial scene using main camera frustum.
// Cutting will work correctly on empty polytope too.
// Take into consideration near far calculation and _maxFarPlane variable
osg::Matrix mvp = *_cv->getModelViewMatrix() * _clampedProjection;
cutScenePolytope( osg::Matrix::inverse( mvp ), mvp );
setDebugPolytope
( "frustum", _sceneReceivingShadowPolytope, osg::Vec4(1,0,1,1));
}
void MinimalShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv )
{
BaseClass::ViewData::init( st, cv );
_modellingSpaceToWorldPtr = &st->_modellingSpaceToWorld;
_minLightMarginPtr = &st->_minLightMargin;
_maxFarPlanePtr = &st->_maxFarPlane;
}
void MinimalShadowMap::ViewData::cutScenePolytope
( const osg::Matrix & transform,
const osg::Matrix & inverse,
const osg::BoundingBox & bb )
{
_sceneReceivingShadowPolytopePoints.clear();
if( bb.valid() ) {
osg::Polytope polytope;
polytope.setToBoundingBox( bb );
polytope.transformProvidingInverse( inverse );
_sceneReceivingShadowPolytope.cut( polytope );
_sceneReceivingShadowPolytope.getPoints
( _sceneReceivingShadowPolytopePoints );
} else
_sceneReceivingShadowPolytope.clear();
}
osg::BoundingBox
MinimalShadowMap::ViewData::computeScenePolytopeBounds( const osg::Matrix & m )
{
osg::BoundingBox bb;
if( &m )
for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i )
bb.expandBy( _sceneReceivingShadowPolytopePoints[i] * m );
else
for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i )
bb.expandBy( _sceneReceivingShadowPolytopePoints[i] );
return bb;
}
// Utility methods for adjusting projection matrices
void MinimalShadowMap::ViewData::trimProjection
( osg::Matrix & projectionMatrix, osg::BoundingBox bb, unsigned int trimMask )
{
#if 1
if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return;
double l = -1, r = 1, b = -1, t = 1, n = 1, f = -1;
#if 0
// make sure bounding box does not extend beyond unit frustum clip range
for( int i = 0; i < 3; i ++ ) {
if( bb._min[i] < -1 ) bb._min[i] = -1;
if( bb._max[i] > 1 ) bb._max[i] = 1;
}
#endif
if( trimMask & 1 ) l = bb._min[0];
if( trimMask & 2 ) r = bb._max[0];
if( trimMask & 4 ) b = bb._min[1];
if( trimMask & 8 ) t = bb._max[1];
if( trimMask & 16 ) n = -bb._min[2];
if( trimMask & 32 ) f = -bb._max[2];
projectionMatrix.postMult( osg::Matrix::ortho( l,r,b,t,n,f ) );
#else
if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return;
double l, r, t, b, n, f;
bool ortho = projectionMatrix.getOrtho( l, r, b, t, n, f );
if( !ortho && !projectionMatrix.getFrustum( l, r, b, t, n, f ) )
return; // rotated or skewed or other crooked projection - give up
// make sure bounding box does not extend beyond unit frustum clip range
for( int i = 0; i < 3; i ++ ) {
if( bb._min[i] < -1 ) bb._min[i] = -1;
if( bb._max[i] > 1 ) bb._max[i] = 1;
}
osg::Matrix projectionToView = osg::Matrix::inverse( projectionMatrix );
osg::Vec3 min =
osg::Vec3( bb._min[0], bb._min[1], bb._min[2] ) * projectionToView;
osg::Vec3 max =
osg::Vec3( bb._max[0], bb._max[1], bb._max[2] ) * projectionToView;
if( trimMask & 16 ) { // trim near
if( !ortho ) { // recalc frustum corners on new near plane
l *= -min[2] / n;
r *= -min[2] / n;
b *= -min[2] / n;
t *= -min[2] / n;
}
n = -min[2];
}
if( trimMask & 32 ) // trim far
f = -max[2];
if( !ortho ) {
min[0] *= -n / min[2];
min[1] *= -n / min[2];
max[0] *= -n / max[2];
max[1] *= -n / max[2];
}
if( l < r ) { // check for inverted X range
if( l < min[0] && ( trimMask & 1 ) ) l = min[0];
if( r > max[0] && ( trimMask & 2 ) ) r = max[0];
} else {
if( l > min[0] && ( trimMask & 1 ) ) l = min[0];
if( r < max[0] && ( trimMask & 2 ) ) r = max[0];
}
if( b < t ) { // check for inverted Y range
if( b < min[1] && ( trimMask & 4 ) ) b = min[1];
if( t > max[1] && ( trimMask & 8 ) ) t = max[1];
} else {
if( b > min[1] && ( trimMask & 4 ) ) b = min[1];
if( t < max[1] && ( trimMask & 8 ) ) t = max[1];
}
if( ortho )
projectionMatrix.makeOrtho( l, r, b, t, n, f );
else
projectionMatrix.makeFrustum( l, r, b, t, n, f );
#endif
}
void MinimalShadowMap::ViewData::clampProjection
( osg::Matrix & projection, float new_near, float new_far )
{
double r, l, t, b, n, f;
bool perspective = projection.getFrustum( l, r, b, t, n, f );
if( !perspective && !projection.getOrtho( l, r, b, t, n, f ) )
{
// What to do here ?
osg::notify( osg::WARN )
<< "MinimalShadowMap::clampProjectionFarPlane failed - non standard matrix"
<< std::endl;
} else if( n < new_near || new_far < f ) {
if( n < new_near && new_near < f ) {
if( perspective ) {
l *= new_near / n;
r *= new_near / n;
b *= new_near / n;
t *= new_near / n;
}
n = new_near;
}
if( n < new_far && new_far < f ) {
f = new_far;
}
if( perspective )
projection.makeFrustum( l, r, b, t, n, f );
else
projection.makeOrtho( l, r, b, t, n, f );
}
}
// Imagine following scenario:
// We stand in the room and look through the window.
// How should our view change if we were looking through larger window ?
// In other words how should projection be adjusted if
// window had grown by some margin ?
// Method computes such new projection which maintains perpective/world ratio
void MinimalShadowMap::ViewData::extendProjection
( osg::Matrix & projection, osg::Viewport * viewport, const osg::Vec2& margin )
{
double l,r,b,t,n,f;
//osg::Matrix projection = camera.getProjectionMatrix();
bool frustum = projection.getFrustum( l,r,b,t,n,f );
if( !frustum && !projection.getOrtho( l,r,b,t,n,f ) ) {
osg::notify( osg::WARN )
<< " Awkward projection matrix. ComputeExtendedProjection failed"
<< std::endl;
return;
}
osg::Matrix window = viewport->computeWindowMatrix();
osg::Vec3 vMin( viewport->x() - margin.x(),
viewport->y() - margin.y(),
0.0 );
osg::Vec3 vMax( viewport->width() + margin.x() * 2 + vMin.x(),
viewport->height() + margin.y() * 2 + vMin.y(),
0.0 );
osg::Matrix inversePW = osg::Matrix::inverse( projection * window );
vMin = vMin * inversePW;
vMax = vMax * inversePW;
l = vMin.x();
r = vMax.x();
b = vMin.y();
t = vMax.y();
if( frustum )
projection.makeFrustum( l,r,b,t,n,f );
else
projection.makeOrtho( l,r,b,t,n,f );
}

View File

@@ -0,0 +1,765 @@
/* -*-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 <osgShadow/StandardShadowMap>
#include <osg/PolygonOffset>
#include <osg/ComputeBoundsVisitor>
#include <osgShadow/ShadowedScene>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/CullFace>
#include <osg/Point>
#include <sstream>
using namespace osgShadow;
#define DISPLAY_SHADOW_TEXEL_TO_PIXEL_ERROR 0
StandardShadowMap::StandardShadowMap():
BaseClass(),
_polygonOffsetFactor( 1.1f ),
_polygonOffsetUnits( 4.0f ),
_textureSize( 1024, 1024 ),
_baseTextureUnit( 0 ),
_shadowTextureUnit( 1 ),
_baseTextureCoordIndex( 0 ),
_shadowTextureCoordIndex( 1 )
{
_mainFragmentShader = new osg::Shader( osg::Shader::FRAGMENT,
" // following expressions are auto modified - do not change them: \n"
" // gl_TexCoord[0] 0 - can be subsituted with other index \n"
" \n"
"float DynamicShadow( ); \n"
" \n"
"varying vec4 colorAmbientEmissive; \n"
" \n"
"uniform sampler2D baseTexture; \n"
" \n"
"void main(void) \n"
"{ \n"
" vec4 color = texture2D( baseTexture, gl_TexCoord[0].xy ); \n"
" color *= mix( colorAmbientEmissive, gl_Color, DynamicShadow() ); \n"
#if DISPLAY_SHADOW_TEXEL_TO_PIXEL_ERROR
" color.xy = abs( dFdy( gl_TexCoord[1].xy / gl_TexCoord[1].w ) )* 1024.0; \n"
" color.z = color.y; \n"
" color.x = color.z; \n"
" color.y = color.z; \n"
" color.a = 1.0; \n"
#endif
// " float fog = clamp((gl_Fog.end - gl_FogFragCoord)*gl_Fog.scale, 0.,1.);\n"
// " color.rgb = mix( gl_Fog.color.rgb, color.rgb, fog ); \n"
" gl_FragColor = color; \n"
"} \n" );
_shadowFragmentShader = new osg::Shader( osg::Shader::FRAGMENT,
" // following expressions are auto modified - do not change them: \n"
" // gl_TexCoord[1] 1 - can be subsituted with other index \n"
" \n"
"uniform sampler2DShadow shadowTexture; \n"
" \n"
"float DynamicShadow( ) \n"
"{ \n"
" return shadow2DProj( shadowTexture, gl_TexCoord[1] ).r; \n"
"} \n" );
_shadowVertexShader = new osg::Shader( osg::Shader::VERTEX,
" // following expressions are auto modified - do not change them: \n"
" // gl_TexCoord[1] 1 - can be subsituted with other index \n"
" // gl_EyePlaneS[1] 1 - can be subsituted with other index \n"
" // gl_EyePlaneT[1] 1 - can be subsituted with other index \n"
" // gl_EyePlaneR[1] 1 - can be subsituted with other index \n"
" // gl_EyePlaneQ[1] 1 - can be subsituted with other index \n"
" \n"
"void DynamicShadow( in vec4 ecPosition ) \n"
"{ \n"
" // generate coords for shadow mapping \n"
" gl_TexCoord[1].s = dot( ecPosition, gl_EyePlaneS[1] ); \n"
" gl_TexCoord[1].t = dot( ecPosition, gl_EyePlaneT[1] ); \n"
" gl_TexCoord[1].p = dot( ecPosition, gl_EyePlaneR[1] ); \n"
" gl_TexCoord[1].q = dot( ecPosition, gl_EyePlaneQ[1] ); \n"
"} \n" );
_mainVertexShader = new osg::Shader( osg::Shader::VERTEX,
" // following expressions are auto modified - do not change them: \n"
" // gl_TexCoord[0] 0 - can be subsituted with other index \n"
" // gl_TextureMatrix[0] 0 - can be subsituted with other index \n"
" // gl_MultiTexCoord0 0 - can be subsituted with other index \n"
" \n"
"const int NumEnabledLights = 1; \n"
" \n"
"void DynamicShadow( in vec4 ecPosition ); \n"
" \n"
"varying vec4 colorAmbientEmissive; \n"
" \n"
"void SpotLight(in int i, \n"
" in vec3 eye, \n"
" in vec3 ecPosition3, \n"
" in vec3 normal, \n"
" inout vec4 ambient, \n"
" inout vec4 diffuse, \n"
" inout vec4 specular) \n"
"{ \n"
" float nDotVP; // normal . light direction \n"
" float nDotHV; // normal . light half vector \n"
" float pf; // power factor \n"
" float spotDot; // cosine of angle between spotlight \n"
" float spotAttenuation; // spotlight attenuation factor \n"
" float attenuation; // computed attenuation factor \n"
" float d; // distance from surface to light source \n"
" vec3 VP; // direction from surface to light position \n"
" vec3 halfVector; // direction of maximum highlights \n"
" \n"
" // Compute vector from surface to light position \n"
" VP = vec3(gl_LightSource[i].position) - ecPosition3; \n"
" \n"
" // Compute distance between surface and light position \n"
" d = length(VP); \n"
" \n"
" // Normalize the vector from surface to light position \n"
" VP = normalize(VP); \n"
" \n"
" // Compute attenuation \n"
" attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + \n"
" gl_LightSource[i].linearAttenuation * d + \n"
" gl_LightSource[i].quadraticAttenuation *d*d); \n"
" \n"
" // See if point on surface is inside cone of illumination \n"
" spotDot = dot(-VP, normalize(gl_LightSource[i].spotDirection)); \n"
" \n"
" if (spotDot < gl_LightSource[i].spotCosCutoff) \n"
" spotAttenuation = 0.0; // light adds no contribution \n"
" else \n"
" spotAttenuation = pow(spotDot, gl_LightSource[i].spotExponent);\n"
" \n"
" // Combine the spotlight and distance attenuation. \n"
" attenuation *= spotAttenuation; \n"
" \n"
" halfVector = normalize(VP + eye); \n"
" \n"
" nDotVP = max(0.0, dot(normal, VP)); \n"
" nDotHV = max(0.0, dot(normal, halfVector)); \n"
" \n"
" if (nDotVP == 0.0) \n"
" pf = 0.0; \n"
" else \n"
" pf = pow(nDotHV, gl_FrontMaterial.shininess); \n"
" \n"
" ambient += gl_LightSource[i].ambient * attenuation; \n"
" diffuse += gl_LightSource[i].diffuse * nDotVP * attenuation; \n"
" specular += gl_LightSource[i].specular * pf * attenuation; \n"
"} \n"
" \n"
"void PointLight(in int i, \n"
" in vec3 eye, \n"
" in vec3 ecPosition3, \n"
" in vec3 normal, \n"
" inout vec4 ambient, \n"
" inout vec4 diffuse, \n"
" inout vec4 specular) \n"
"{ \n"
" float nDotVP; // normal . light direction \n"
" float nDotHV; // normal . light half vector \n"
" float pf; // power factor \n"
" float attenuation; // computed attenuation factor \n"
" float d; // distance from surface to light source \n"
" vec3 VP; // direction from surface to light position \n"
" vec3 halfVector; // direction of maximum highlights \n"
" \n"
" // Compute vector from surface to light position \n"
" VP = vec3(gl_LightSource[i].position) - ecPosition3; \n"
" \n"
" // Compute distance between surface and light position \n"
" d = length(VP); \n"
" \n"
" // Normalize the vector from surface to light position \n"
" VP = normalize(VP); \n"
" \n"
" // Compute attenuation \n"
" attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + \n"
" gl_LightSource[i].linearAttenuation * d + \n"
" gl_LightSource[i].quadraticAttenuation * d*d);\n"
" \n"
" halfVector = normalize(VP + eye); \n"
" \n"
" nDotVP = max(0.0, dot(normal, VP)); \n"
" nDotHV = max(0.0, dot(normal, halfVector)); \n"
" \n"
" if (nDotVP == 0.0) \n"
" pf = 0.0; \n"
" else \n"
" pf = pow(nDotHV, gl_FrontMaterial.shininess); \n"
" \n"
" ambient += gl_LightSource[i].ambient * attenuation; \n"
" diffuse += gl_LightSource[i].diffuse * nDotVP * attenuation; \n"
" specular += gl_LightSource[i].specular * pf * attenuation; \n"
"} \n"
" \n"
"void DirectionalLight(in int i, \n"
" in vec3 normal, \n"
" inout vec4 ambient, \n"
" inout vec4 diffuse, \n"
" inout vec4 specular) \n"
"{ \n"
" float nDotVP; // normal . light direction \n"
" float nDotHV; // normal . light half vector \n"
" float pf; // power factor \n"
" \n"
" nDotVP = max(0.0, dot(normal, \n"
" normalize(vec3(gl_LightSource[i].position)))); \n"
" nDotHV = max(0.0, dot(normal, \n"
" vec3(gl_LightSource[i].halfVector))); \n"
" \n"
" if (nDotVP == 0.0) \n"
" pf = 0.0; \n"
" else \n"
" pf = pow(nDotHV, gl_FrontMaterial.shininess); \n"
" \n"
" ambient += gl_LightSource[i].ambient; \n"
" diffuse += gl_LightSource[i].diffuse * nDotVP; \n"
" specular += gl_LightSource[i].specular * pf; \n"
"} \n"
" \n"
"void main( ) \n"
"{ \n"
" // Transform vertex to clip space \n"
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
" vec3 normal = normalize( gl_NormalMatrix * gl_Normal ); \n"
" \n"
" vec4 ecPos = gl_ModelViewMatrix * gl_Vertex; \n"
" float ecLen = length( ecPos ); \n"
" vec3 ecPosition3 = ecPos.xyz / ecPos.w; \n"
" \n"
" vec3 eye = vec3( 0.0, 0.0, 1.0 ); \n"
" //vec3 eye = -normalize(ecPosition3); \n"
" \n"
" DynamicShadow( ecPos ); \n"
" \n"
" gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; \n"
" \n"
" // Front Face lighting \n"
" \n"
" // Clear the light intensity accumulators \n"
" vec4 amb = vec4(0.0); \n"
" vec4 diff = vec4(0.0); \n"
" vec4 spec = vec4(0.0); \n"
" \n"
" // Loop through enabled lights, compute contribution from each \n"
" for (int i = 0; i < NumEnabledLights; i++) \n"
" { \n"
" if (gl_LightSource[i].position.w == 0.0) \n"
" DirectionalLight(i, normal, amb, diff, spec); \n"
" else if (gl_LightSource[i].spotCutoff == 180.0) \n"
" PointLight(i, eye, ecPosition3, normal, amb, diff, spec); \n"
" else \n"
" SpotLight(i, eye, ecPosition3, normal, amb, diff, spec); \n"
" } \n"
" \n"
" colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor + \n"
" amb * gl_FrontMaterial.ambient; \n"
" \n"
" gl_FrontColor = colorAmbientEmissive + \n"
" diff * gl_FrontMaterial.diffuse; \n"
" \n"
" gl_FrontSecondaryColor = vec4(spec*gl_FrontMaterial.specular); \n"
" \n"
" gl_BackColor = gl_FrontColor; \n"
" gl_BackSecondaryColor = gl_FrontSecondaryColor; \n"
" \n"
" gl_FogFragCoord = ecLen; \n"
"} \n" );
}
StandardShadowMap::StandardShadowMap
(const StandardShadowMap& copy, const osg::CopyOp& copyop) :
BaseClass(copy,copyop),
_polygonOffsetFactor( copy._polygonOffsetFactor ),
_polygonOffsetUnits( copy._polygonOffsetUnits ),
_textureSize( copy._textureSize ),
_baseTextureUnit( copy._baseTextureUnit ),
_shadowTextureUnit( copy._shadowTextureUnit )
{
if( copy._mainVertexShader.valid() )
_mainVertexShader = dynamic_cast<osg::Shader*>
( copy._mainVertexShader->clone(copyop) );
if( copy._mainFragmentShader.valid() )
_mainFragmentShader = dynamic_cast<osg::Shader*>
( copy._mainFragmentShader->clone(copyop) );
if( copy._shadowVertexShader.valid() )
_shadowVertexShader = dynamic_cast<osg::Shader*>
( copy._shadowVertexShader->clone(copyop) );
if( copy._shadowFragmentShader.valid() )
_shadowFragmentShader = dynamic_cast<osg::Shader*>
( copy._shadowFragmentShader->clone(copyop) );
}
StandardShadowMap::~StandardShadowMap(void)
{
}
void StandardShadowMap::updateTextureCoordIndices
( unsigned int fromTextureCoordIndex, unsigned int toTextureCoordIndex )
{
if( fromTextureCoordIndex == toTextureCoordIndex ) return;
const char *expressions[] = {
"gl_TexCoord[%d]",
"gl_TextureMatrix[%d]",
"gl_MultiTexCoord%d",
"gl_EyePlaneS[%d]",
"gl_EyePlaneT[%d]",
"gl_EyePlaneR[%d]",
"gl_EyePlaneQ[%d]",
};
for( unsigned int i = 0;
i < sizeof( expressions ) / sizeof( expressions[0] );
i++ )
{
char acFrom[ 32 ], acTo[32];
// its not elegant to mix stdio & stl strings
// but in this context I do an exception for cleaner code
std::sprintf( acFrom, expressions[i], fromTextureCoordIndex );
std::sprintf( acTo, expressions[i], toTextureCoordIndex );
std::string from( acFrom ), to( acTo );
searchAndReplaceShaderSource( getShadowVertexShader(), from, to );
searchAndReplaceShaderSource( getShadowFragmentShader(), from, to );
searchAndReplaceShaderSource( getMainVertexShader(), from, to );
searchAndReplaceShaderSource( getMainFragmentShader(), from, to );
}
dirty();
}
void StandardShadowMap::searchAndReplaceShaderSource
( osg::Shader* shader, std::string fromString, std::string toString )
{
if( !shader || fromString == toString ) return;
const std::string & srceString = shader->getShaderSource();
std::string destString;
std::string::size_type fromLength = fromString.length();
std::string::size_type srceLength = srceString.length();
for( std::string::size_type pos = 0; pos < srceLength; )
{
std::string::size_type end = srceString.find( fromString, pos );
if( end == std::string::npos )
end = srceLength;
destString.append( srceString, pos, end - pos );
if( end == srceLength )
break;
destString.append( toString );
pos = end + fromLength;
}
shader->setShaderSource( destString );
}
void StandardShadowMap::ViewData::cull()
{
// step 1:
// cull shadowed scene ie put into render bins and states into stage graphs
cullShadowReceivingScene( );
// step 2:
// find the light casting our shadows
osg::Vec4 lightPos;
osg::Vec3 lightDir;
osg::Vec3 lightUp( 0,0,0 ); // force computing most approprate dir
const osg::Light *light = selectLight( lightPos, lightDir );
if ( !light )
return;// bail out - no shadowing needed in darkest night
// step 3:
// compute shadow casting matrices and apply them to shadow map camera
aimShadowCastingCamera( light, lightPos, lightDir, lightUp );
// step 4:
// cull scene casting shadow and generate render
cullShadowCastingScene( );
// step 5:
// setup texgen generating shadow map coords for the shadow receiving scene
addShadowReceivingTexGen( );
BaseClass::ViewData::cull();
}
void StandardShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv )
{
BaseClass::ViewData::init( st, cv );
_lightPtr = &st->_light;
_shadowTextureUnitPtr = &st->_shadowTextureUnit;
_baseTextureUnitPtr = &st->_baseTextureUnit;
_texture = new osg::Texture2D;
{ // Setup shadow texture
_texture->setTextureSize( st->_textureSize.x(), st->_textureSize.y());
_texture->setInternalFormat(GL_DEPTH_COMPONENT);
_texture->setShadowComparison(true);
_texture->setShadowTextureMode(osg::Texture2D::LUMINANCE);
_texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR);
_texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR);
// the shadow comparison should fail if object is outside the texture
_texture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::CLAMP_TO_BORDER);
_texture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::CLAMP_TO_BORDER);
_texture->setBorderColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f));
}
_camera = new osg::Camera;
{ // Setup shadow map camera
_camera->setName( "ShadowCamera" );
#if 0 // Absolute reference frame INHERIT_VIEWPOINT works better than this
_camera->setCullingMode
( _camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING );
#endif
_camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
_camera->setCullCallback(new CameraCullCallback( st ));
_camera->setClearMask(GL_DEPTH_BUFFER_BIT);
#if 0 // Left in case of some debug testing
_camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
_camera->setClearColor( osg::Vec4(1.0f,1.0f,1.0f,1.0f) );
#endif
_camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
_camera->setViewport(0,0, st->_textureSize.x(), st->_textureSize.y() );
_camera->setRenderOrder(osg::Camera::PRE_RENDER);
_camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
_camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get());
}
_texgen = new osg::TexGen;
_stateset = new osg::StateSet;
{ // Create and add fake texture for use with nodes without any texture
osg::Image * image = new osg::Image;
image->allocateImage( 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE );
*(osg::Vec4ub*)image->data() = osg::Vec4ub( 0xFF, 0xFF, 0xFF, 0xFF );
osg::Texture2D* fakeTex = new osg::Texture2D( image );
fakeTex->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT);
fakeTex->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT);
fakeTex->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST);
fakeTex->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST);
_stateset->setTextureAttribute(st->_baseTextureUnit,fakeTex,osg::StateAttribute::ON);
_stateset->setTextureMode(st->_baseTextureUnit,GL_TEXTURE_1D,osg::StateAttribute::OFF);
_stateset->setTextureMode(st->_baseTextureUnit,GL_TEXTURE_2D,osg::StateAttribute::ON);
_stateset->setTextureMode(st->_baseTextureUnit,GL_TEXTURE_3D,osg::StateAttribute::OFF);
}
{ // Add shadow texture
_stateset->setTextureAttributeAndModes(st->_shadowTextureUnit,_texture.get(),osg::StateAttribute::ON);
_stateset->setTextureMode(st->_shadowTextureUnit,GL_TEXTURE_GEN_S,osg::StateAttribute::ON);
_stateset->setTextureMode(st->_shadowTextureUnit,GL_TEXTURE_GEN_T,osg::StateAttribute::ON);
_stateset->setTextureMode(st->_shadowTextureUnit,GL_TEXTURE_GEN_R,osg::StateAttribute::ON);
_stateset->setTextureMode(st->_shadowTextureUnit,GL_TEXTURE_GEN_Q,osg::StateAttribute::ON);
}
{ // Setup shaders used in shadow casting
osg::Program * program = new osg::Program();
_stateset->setAttribute( program );
if( st->_shadowFragmentShader.valid() )
program->addShader( st->_shadowFragmentShader.get() );
if( st->_mainFragmentShader.valid() )
program->addShader( st->_mainFragmentShader.get() );
if( st->_shadowVertexShader.valid() )
program->addShader( st->_shadowVertexShader.get() );
if( st->_mainVertexShader.valid() )
program->addShader( st->_mainVertexShader.get() );
_stateset->addUniform
( new osg::Uniform( "baseTexture", int( st->_baseTextureUnit ) ) );
_stateset->addUniform
( new osg::Uniform( "shadowTexture", int( st->_shadowTextureUnit ) ) );
}
{ // Setup states used for shadow map generation
osg::StateSet * stateset = _camera->getOrCreateStateSet();
stateset->setAttribute(
new osg::PolygonOffset( st->_polygonOffsetFactor, st->_polygonOffsetUnits ),
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
stateset->setMode( GL_POLYGON_OFFSET_FILL,
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
// agressive optimization
stateset->setRenderBinDetails( 0, "RenderBin",
osg::StateSet::OVERRIDE_RENDERBIN_DETAILS );
// agressive optimization
stateset->setAttributeAndModes
( new osg::ColorMask( false, false, false, false ),
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
// note soft (attribute only no mode override) setting. When this works ?
// 1. for objects prepared for backface culling
// because they usually also set CullFace and CullMode on in their state
// For them we override CullFace but CullMode remains set by them
// 2. For one faced, trees, and similar objects which cannot use
// backface nor front face so they usually use CullMode off set here.
// In this case we will draw them in their entirety.
stateset->setAttribute( new osg::CullFace( osg::CullFace::FRONT ),
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
// make sure GL_CULL_FACE is off by default
// we assume that if object has cull face attribute set to back
// it will also set cull face mode ON so no need for override
stateset->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
// optimization attributes
osg::Program* program = new osg::Program;
stateset->setAttribute( program, osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON );
stateset->setMode
( GL_LIGHTING, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF );
stateset->setMode
( GL_BLEND, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF );
#if 0
stateset->setMode
( GL_ALPHA_TEST, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF );
#endif
#if 0 // fixed pipeline seems faster (at least on my 7800)
program->addShader( new osg::Shader( osg::Shader::FRAGMENT,
"uniform sampler2D texture; \n"
"void main(void) \n"
"{ \n"
" gl_FragColor = texture2D( texture, gl_TexCoord[0].xy ); \n"
"} \n"
) ); // program->addShader Fragment
program->addShader( new osg::Shader( osg::Shader::VERTEX,
"void main(void) \n"
"{ \n"
" gl_Position = ftransform(); \n"
" gl_TexCoord[0] = gl_MultiTexCoord0; \n"
"} \n"
) ); // program->addShader Vertex
#endif
for( unsigned stage = 1; stage < 4; stage ++ )
{
stateset->setTextureMode( stage, GL_TEXTURE_1D, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF );
stateset->setTextureMode( stage, GL_TEXTURE_2D, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF );
stateset->setTextureMode( stage, GL_TEXTURE_3D, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF );
}
}
}
const osg::Light* StandardShadowMap::ViewData::selectLight
( osg::Vec4 & lightPos, osg::Vec3 & lightDir )
{
const osg::Light* light = 0;
//MR testing giving a specific light
osgUtil::RenderStage * rs = _cv->getRenderStage();
osgUtil::PositionalStateContainer::AttrMatrixList& aml =
rs->getPositionalStateContainer()->getAttrMatrixList();
osg::RefMatrix* matrix;
for(osgUtil::PositionalStateContainer::AttrMatrixList::iterator itr = aml.begin();
itr != aml.end();
++itr)
{
const osg::Light* found = dynamic_cast<const osg::Light*>(itr->first.get());
if( found )
{
if( _lightPtr->valid() && _lightPtr->get() != found )
continue; // continue search for the right one
light = found;
matrix = itr->second.get();
}
}
if( light ) { // transform light to world space
osg::Matrix localToWorld = osg::Matrix::inverse( *_cv->getModelViewMatrix() );
if( matrix ) localToWorld.preMult( *matrix );
lightPos = light->getPosition();
if( lightPos[3] == 0 )
lightDir.set( -lightPos[0], -lightPos[1], -lightPos[2] );
else
lightDir = light->getDirection();
lightPos = lightPos * localToWorld;
lightDir = osg::Matrix::transform3x3( lightDir, localToWorld );
lightDir.normalize();
}
return light;
}
void StandardShadowMap::ViewData::aimShadowCastingCamera( const osg::Light *light,
const osg::Vec4 &lightPos,
const osg::Vec3 &lightDir,
const osg::Vec3 &lightUp
/* by default = osg::Vec3( 0, 1 0 )*/ )
{
#if 0 // less precise but faster
osg::BoundingSphere bs =_st->getShadowedScene()->getBound();
#else
// get the bounds of the model.
osg::ComputeBoundsVisitor cbbv(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN);
cbbv.setTraversalMask(_st->getShadowedScene()->getCastsShadowTraversalMask());
_st->getShadowedScene()->osg::Group::traverse(cbbv);
osg::BoundingSphere bs( cbbv.getBoundingBox() );
#endif
aimShadowCastingCamera
( bs, light, lightPos, lightDir, lightUp );
}
void StandardShadowMap::ViewData::aimShadowCastingCamera(
const osg::BoundingSphere &bs,
const osg::Light *light,
const osg::Vec4 &lightPos,
const osg::Vec3 &lightDir,
const osg::Vec3 &lightUpVector
/* by default = osg::Vec3( 0, 1 0 )*/ )
{
osg::Matrix & view = _camera->getViewMatrix();
osg::Matrix & projection = _camera->getProjectionMatrix();
osg::Vec3 up = lightUpVector;
if( up.length2() <= 0 ) up.set( 0,1,0 );
if( light->getSpotCutoff() < 180.0f) // spotlight, no need for bounding box
{
osg::Vec3 position(lightPos.x(), lightPos.y(), lightPos.z());
float spotAngle = light->getSpotCutoff();
projection.makePerspective( spotAngle, 1.0, 0.1, 1000.0);
view.makeLookAt(position,position+lightDir,up);
}
else
{
if (lightPos[3]!=0.0) // point light
{
osg::Vec3 position(lightPos.x(), lightPos.y(), lightPos.z());
float centerDistance = (position-bs.center()).length();
float znear = centerDistance-bs.radius();
float zfar = centerDistance+bs.radius();
float zNearRatio = 0.001f;
if (znear<zfar*zNearRatio) znear = zfar*zNearRatio;
float top = (bs.radius()/centerDistance)*znear;
float right = top;
projection.makeFrustum(-right,right,-top,top,znear,zfar);
view.makeLookAt(position,bs.center(),up );
}
else // directional light
{
// make an orthographic projection
// set the position far away along the light direction
float radius = bs.radius();
osg::Vec3 position = bs.center() - lightDir * radius * 2;
float centerDistance = (position-bs.center()).length();
float znear = centerDistance-radius;
float zfar = centerDistance+radius;
float zNearRatio = 0.001f;
if (znear<zfar*zNearRatio)
znear = zfar*zNearRatio;
float top = radius;
float right = top;
projection.makeOrtho(-right, right, -top, top, znear, zfar);
view.makeLookAt(position,bs.center(),up);
}
}
}
void StandardShadowMap::ViewData::cullShadowReceivingScene( )
{
_cv->pushStateSet( _stateset.get() );
_st->getShadowedScene()->osg::Group::traverse( *_cv );
_cv->popStateSet();
}
void StandardShadowMap::ViewData::cullShadowCastingScene( )
{
// record the traversal mask on entry so we can reapply it later.
unsigned int traversalMask = _cv->getTraversalMask();
_cv->setTraversalMask( traversalMask &
_st->getShadowedScene()->getCastsShadowTraversalMask() );
// do RTT camera traversal
_camera->accept(*_cv);
// reapply the original traversal mask
_cv->setTraversalMask( traversalMask );
}
void StandardShadowMap::ViewData::addShadowReceivingTexGen( )
{
_texgen->setMode(osg::TexGen::EYE_LINEAR);
// compute the matrix which takes a vertex from view coords into tex coords
_texgen->setPlanesFromMatrix(
_camera->getProjectionMatrix() *
osg::Matrix::translate(1.0,1.0,1.0) *
osg::Matrix::scale(0.5f,0.5f,0.5f) );
osg::RefMatrix * refMatrix = new osg::RefMatrix
( _camera->getInverseViewMatrix() * *_cv->getModelViewMatrix() );
_cv->getRenderStage()->getPositionalStateContainer()->
addPositionedTextureAttribute
( *_shadowTextureUnitPtr, refMatrix, _texgen.get() );
}

View File

@@ -0,0 +1,127 @@
/* -*-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 <osgShadow/ViewDependentShadowTechnique>
#include <osgShadow/ShadowedScene>
using namespace osgShadow;
ViewDependentShadowTechnique::ViewDependentShadowTechnique()
{
dirty();
}
ViewDependentShadowTechnique::ViewDependentShadowTechnique
(const ViewDependentShadowTechnique& copy, const osg::CopyOp& copyop):
ShadowTechnique(copy,copyop)
{
dirty();
}
ViewDependentShadowTechnique::~ViewDependentShadowTechnique(void)
{
}
void ViewDependentShadowTechnique::traverse(osg::NodeVisitor& nv)
{
osgShadow::ShadowTechnique::traverse(nv);
}
void ViewDependentShadowTechnique::dirty()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_viewDataMapMutex);
osgShadow::ShadowTechnique::_dirty = true;
for( ViewDataMap::iterator mitr = _viewDataMap.begin();
mitr != _viewDataMap.end();
++mitr )
{
mitr->second->dirty( true );
}
}
void ViewDependentShadowTechnique::init()
{
//osgShadow::ShadowTechnique::init( );
osgShadow::ShadowTechnique::_dirty = false;
}
void ViewDependentShadowTechnique::update(osg::NodeVisitor& nv)
{
//osgShadow::ShadowTechnique::update( nv );
osgShadow::ShadowTechnique::_shadowedScene->osg::Group::traverse(nv);
}
void ViewDependentShadowTechnique::cull(osgUtil::CullVisitor& cv)
{
//osgShadow::ShadowTechnique::cull( cv );
ViewData * vd = getViewDependentData( &cv );
if ( !vd || vd->_dirty || vd->_cv != &cv || vd->_st != this ) {
vd = initViewDependentData( &cv, vd );
setViewDependentData( &cv, vd );
}
if( vd ) {
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(vd->_mutex);
vd->cull();
} else {
osgShadow::ShadowTechnique::_shadowedScene->osg::Group::traverse(cv);
}
}
void ViewDependentShadowTechnique::cleanSceneGraph()
{
//osgShadow::ShadowTechnique::cleanSceneGraph( );
}
ViewDependentShadowTechnique::ViewData *
ViewDependentShadowTechnique::getViewDependentData( osgUtil::CullVisitor * cv )
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_viewDataMapMutex);
return _viewDataMap[ cv ].get();
}
void ViewDependentShadowTechnique::setViewDependentData
( osgUtil::CullVisitor * cv, ViewData * data )
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_viewDataMapMutex);
_viewDataMap[ cv ] = data;
}
void ViewDependentShadowTechnique::ViewData::dirty( bool flag )
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
_dirty = flag;
}
void ViewDependentShadowTechnique::ViewData::init
( ViewDependentShadowTechnique *st, osgUtil::CullVisitor * cv )
{
_cv = cv;
_st = st;
dirty( false );
}
void ViewDependentShadowTechnique::ViewData::cull( void )
{
}