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

@ -0,0 +1,123 @@
/* -*-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.
*/
#ifndef OSGSHADOW_CONVEXPOLYHEDRON
#define OSGSHADOW_CONVEXPOLYHEDRON 1
#include <osg/Geometry>
#include <osg/Polytope>
#include <osgShadow/Export>
////////////////////////////////////////////////////////////////////////////////
// Class based on CustomPolytope defined and used in osgSim::OverlayNode.cpp.
// Honors should go to Robert Osfield for writing such useful piece of code.
// First incarnations of my ConvexPolyhedron were derived from CustomPolytope.
// Later I made a number of modifications aimed at improving convex hull
// precision of intersection & extrusion operations and ended up with code
// so mixed that I decided to rewrite it as separate class.
////////////////////////////////////////////////////////////////////////////////
namespace osgShadow {
class OSGSHADOW_EXPORT ConvexPolyhedron
{
public:
typedef std::vector<osg::Vec3d> Vertices;
static const osg::Matrix & defaultMatrix;
struct Face
{
std::string name;
osg::Plane plane;
Vertices vertices;
};
typedef std::list<Face> Faces;
Faces _faces;
ConvexPolyhedron( void ) { }
ConvexPolyhedron( const osg::Matrix& matrix, const osg::Matrix& inverse,
const osg::BoundingBox& bb = osg::BoundingBox(-1,-1,-1,1,1,1));
Face& createFace() { _faces.push_back(Face()); return _faces.back(); }
void clear() { _faces.clear(); }
void setToUnitFrustum(bool withNear=true, bool withFar=true);
void setToBoundingBox(const osg::BoundingBox& bb);
void transform(const osg::Matrix& matrix, const osg::Matrix& inverse);
void transformClip(const osg::Matrix& matrix, const osg::Matrix& inverse);
bool mergeFaces
( const Face & face0, const Face & face1, Face & face );
void mergeCoplanarFaces( const double & plane_normal_dot_tolerance = 0.0,
const double & plane_distance_tolerance = 0.0 );
void removeDuplicateVertices( void );
static int pointsColinear
( const osg::Vec3d & va, const osg::Vec3d & vb, const osg::Vec3d & vc,
const double & edge_normal_dot_tolerance = 0.0,
const double & null_edge_length_tolerance = 0.0 );
static int isFacePolygonConvex( Face & face, bool ignoreCollinearVertices = true );
bool checkCoherency
( bool checkForNonConvexPolys = false, const char * errorPrefix = NULL );
void cut(const osg::Polytope& polytope);
void cut(const ConvexPolyhedron& polytope);
void cut(const osg::Plane& plane, const std::string& name=std::string());
void extrude( const osg::Vec3d & offset );
void translate( const osg::Vec3d & offset );
void getPolytope(osg::Polytope& polytope) const;
void getPoints(Vertices& vertices) const;
osg::BoundingBox computeBoundingBox( const osg::Matrix & m = osgShadow::ConvexPolyhedron::defaultMatrix ) const;
osg::Geometry* buildGeometry( const osg::Vec4d& colorOutline,
const osg::Vec4d& colorInside,
osg::Geometry* useGeometry = NULL ) const;
bool dumpGeometry( const Face * face = NULL,
const osg::Plane * plane = NULL,
ConvexPolyhedron * basehull = NULL,
const char * filename = "convexpolyhedron.osg",
const osg::Vec4d& colorOutline = osg::Vec4( 0,1,0,0.5 ),
const osg::Vec4d& colorInside = osg::Vec4( 0,1,0,0.25 ),
const osg::Vec4d& faceColorOutline = osg::Vec4( 0,0,1,0.5 ),
const osg::Vec4d& faceColorInside = osg::Vec4( 0,0,1,0.25 ),
const osg::Vec4d& planeColorOutline = osg::Vec4( 1,0,0,0.5 ),
const osg::Vec4d& planeColorInside = osg::Vec4( 1,0,0,0.25 ),
const osg::Vec4d& baseColorOutline = osg::Vec4( 0,0,0,0.5 ),
const osg::Vec4d& baseColorInside = osg::Vec4( 0,0,0,0.25 ) ) const;
};
} // namespace osgShadow
#endif

View File

@ -0,0 +1,178 @@
/* -*-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.
*/
#ifndef OSGSHADOW_DEBUGSHADOWMAP
#define OSGSHADOW_DEBUGSHADOWMAP 1
#include <osgShadow/ViewDependentShadowTechnique>
#include <osgShadow/ConvexPolyhedron>
#include <osg/MatrixTransform>
#include <osg/Geode>
#include <osg/Geometry>
#include <string>
#include <map>
namespace osgShadow {
/**
Class used as a layer for debuging resources used by derived xxxShadowMap classes.
As designed by its base ViewDepndentShadowTechnique, DebugShadowMap serves mainly as container of
DebugShadowMap::ViewData objects. Most of the debuging support work is done by these objects.
DebugShadowMap technique only initializes them in initViewDependentData method.
Debuging outputs present:
Shadow maps (pseudo colored to improve readability)
Shadow and related volumes (represented as convex polyhedra)
*/
class OSGSHADOW_EXPORT DebugShadowMap : public ViewDependentShadowTechnique
{
public :
/*
All classes stemming from ViewDependentShadowTechnique follow the same pattern.
They are always based on some underlying level base Technique and they always
derive their ViewData from ViewData structure defined in underlying base Technique.
I use some typedefs to make these inheritance patterns easier to declare/define.
*/
/** Convenient typedef used in definition of ViewData struct and methods */
typedef DebugShadowMap ThisClass;
/** Convenient typedef used in definition of ViewData struct and methods */
typedef ViewDependentShadowTechnique BaseClass;
/** Classic OSG constructor */
DebugShadowMap();
/** Classic OSG cloning constructor */
DebugShadowMap(const DebugShadowMap& dsm, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, DebugShadowMap );
/** Turn on/off debuging hud & rendering of debug volumes in main view */
void setDebugDraw( bool draw ) { _doDebugDraw = draw; }
/** Tell if debuging hud & rendering of debug volumes is active */
bool getDebugDraw( void ) { return _doDebugDraw; }
protected:
/** Classic protected OSG destructor */
virtual ~DebugShadowMap();
osg::Vec2s _hudSize;
osg::Vec2s _hudOrigin;
osg::Vec2s _viewportSize;
osg::Vec2s _viewportOrigin;
osg::Vec2s _orthoSize;
osg::Vec2s _orthoOrigin;
bool _doDebugDraw;
osg::ref_ptr< osg::Shader > _depthColorFragmentShader;
struct ViewData: public BaseClass::ViewData
{
/**
Texture used as ShadowMap - initialized by derived classes.
But it has to be defined now since DebugShadowMap::ViewData methods use it
*/
osg::ref_ptr< osg::Texture2D > _texture;
/**
Camera used to render ShadowMap - initialized by derived classes.
But it has to be defined now since DebugShadowMap::ViewData methods use it
*/
osg::ref_ptr< osg::Camera > _camera;
osg::Matrix _viewProjection;
osg::Camera * _viewCamera;
// Debug hud variables
/** Coloring Shader used to present shadow depth map contents */
osg::ref_ptr< osg::Shader > _depthColorFragmentShader;
struct PolytopeGeometry {
ConvexPolyhedron _polytope;
osg::ref_ptr< osg::Geometry > _geometry[2];
osg::Vec4 _colorOutline;
osg::Vec4 _colorInside;
};
typedef std::map< std::string, PolytopeGeometry > PolytopeGeometryMap;
osg::Vec2s _hudSize;
osg::Vec2s _hudOrigin;
osg::Vec2s _viewportSize;
osg::Vec2s _viewportOrigin;
osg::Vec2s _orthoSize;
osg::Vec2s _orthoOrigin;
bool *_doDebugDrawPtr;
PolytopeGeometryMap _polytopeGeometryMap;
osg::ref_ptr< osg::Geode > _geode[2];
osg::ref_ptr< osg::MatrixTransform > _transform[2];
std::map< std::string, osg::Matrix > _matrixMap;
std::map< std::string, osg::Polytope > _polytopeMap;
std::map< std::string, osg::BoundingBox > _boundingBoxMap;
osg::ref_ptr<osg::Camera> _cameraDebugHUD;
bool getDebugDraw() { return *_doDebugDrawPtr; }
virtual void init( ThisClass * st, osgUtil::CullVisitor * cv );
virtual void cull( );
virtual void createDebugHUD( void );
virtual void cullDebugGeometry( );
virtual void updateDebugGeometry( const osg::Camera * screenCam,
const osg::Camera * shadowCam );
void setDebugPolytope( const char * name,
const ConvexPolyhedron & polytope = *(ConvexPolyhedron*)( NULL ),
osg::Vec4 colorOutline = osg::Vec4(0,0,0,0),
osg::Vec4 colorInside = osg::Vec4(0,0,0,0) );
bool DebugBoundingBox( const osg::BoundingBox & bb, const char * name = "" );
bool DebugPolytope( const osg::Polytope & p, const char * name = "" );
bool DebugMatrix( const osg::Matrix & m, const char * name = "" );
static osg::Vec3d computeShadowTexelToPixelError
( const osg::Matrix & mvpwView,
const osg::Matrix & mvpwShadow,
const osg::Vec3d & vWorld,
const osg::Vec3d & vDelta = osg::Vec3d( 0.01,0.01,0.01 ) );
static void displayShadowTexelToPixelErrors
( const osg::Camera * viewCam,
const osg::Camera * shadowCam,
const ConvexPolyhedron * hull );
};
META_ViewDependentShadowTechniqueData( ThisClass, ViewData )
};
} // namespace osgShadow
#endif

View File

@ -0,0 +1,122 @@
/* -*-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.
*/
#ifndef OSGSHADOW_LIGHTSPACEPERSPECTIVESHADOWMAP
#define OSGSHADOW_LIGHTSPACEPERSPECTIVESHADOWMAP 1
#include <osgShadow/MinimalCullBoundsShadowMap>
#include <osgShadow/MinimalDrawBoundsShadowMap>
#include <osgShadow/ProjectionShadowMap>
namespace osgShadow {
// Class implements
// "Light Space Perspective Shadow Maps" algorithm by
// Michael Wimmer, Daniel Scherzer, Werner Purgathofer
// http://www.cg.tuwien.ac.at/research/vr/lispsm/
class LispSM;
class OSGSHADOW_EXPORT LightSpacePerspectiveShadowMapAlgorithm
{
public:
LightSpacePerspectiveShadowMapAlgorithm();
~LightSpacePerspectiveShadowMapAlgorithm();
void operator() (
const osgShadow::ConvexPolyhedron* hullShadowedView,
const osg::Camera* cameraMain,
osg::Camera* cameraShadow ) const;
protected:
LispSM * lispsm;
};
// Optimized for draw traversal shadow bounds
class OSGSHADOW_EXPORT LightSpacePerspectiveShadowMapDB: public ProjectionShadowMap< MinimalDrawBoundsShadowMap, LightSpacePerspectiveShadowMapAlgorithm >
{
public:
/** Convenient typedef used in definition of ViewData struct and methods */
typedef ProjectionShadowMap< MinimalDrawBoundsShadowMap, LightSpacePerspectiveShadowMapAlgorithm > BaseClass;
/** Classic OSG constructor */
LightSpacePerspectiveShadowMapDB()
{
}
/** Classic OSG cloning constructor */
LightSpacePerspectiveShadowMapDB(
const LightSpacePerspectiveShadowMapDB& copy,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop)
{
}
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, LightSpacePerspectiveShadowMapDB );
};
// Optimized for cull traversal shadow bounds
class OSGSHADOW_EXPORT LightSpacePerspectiveShadowMapCB: public ProjectionShadowMap< MinimalCullBoundsShadowMap, LightSpacePerspectiveShadowMapAlgorithm >
{
public:
/** Convenient typedef used in definition of ViewData struct and methods */
typedef ProjectionShadowMap< MinimalCullBoundsShadowMap, LightSpacePerspectiveShadowMapAlgorithm > BaseClass;
/** Classic OSG constructor */
LightSpacePerspectiveShadowMapCB()
{
}
/** Classic OSG cloning constructor */
LightSpacePerspectiveShadowMapCB(
const LightSpacePerspectiveShadowMapCB& copy,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop)
{
}
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, LightSpacePerspectiveShadowMapCB );
};
// Optimized for view frustum bounds
class OSGSHADOW_EXPORT LightSpacePerspectiveShadowMapVB: public ProjectionShadowMap< MinimalShadowMap, LightSpacePerspectiveShadowMapAlgorithm >
{
public:
/** Convenient typedef used in definition of ViewData struct and methods */
typedef ProjectionShadowMap< MinimalShadowMap, LightSpacePerspectiveShadowMapAlgorithm > BaseClass;
/** Classic OSG constructor */
LightSpacePerspectiveShadowMapVB()
{
}
/** Classic OSG cloning constructor */
LightSpacePerspectiveShadowMapVB(
const LightSpacePerspectiveShadowMapVB& copy,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop)
{
}
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, LightSpacePerspectiveShadowMapVB );
};
typedef LightSpacePerspectiveShadowMapDB LightSpacePerspectiveShadowMap;
} // namespace osgShadow
#endif

View File

@ -0,0 +1,82 @@
/* -*-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.
*/
#ifndef OSGSHADOW_MINIMALCULLBOUNDSSHADOWMAP
#define OSGSHADOW_MINIMALCULLBOUNDSSHADOWMAP 1
#include <osgShadow/MinimalShadowMap>
namespace osgShadow {
class OSGSHADOW_EXPORT MinimalCullBoundsShadowMap
: public MinimalShadowMap
{
public :
/** Convenient typedef used in definition of ViewData struct and methods */
typedef MinimalCullBoundsShadowMap ThisClass;
/** Convenient typedef used in definition of ViewData struct and methods */
typedef MinimalShadowMap BaseClass;
/** Classic OSG constructor */
MinimalCullBoundsShadowMap();
/** Classic OSG cloning constructor */
MinimalCullBoundsShadowMap(
const MinimalCullBoundsShadowMap& mcbsm,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, MinimalCullBoundsShadowMap );
protected:
/** Classic protected OSG destructor */
virtual ~MinimalCullBoundsShadowMap(void);
struct ViewData: public MinimalShadowMap::ViewData
{
virtual void init( ThisClass * st, osgUtil::CullVisitor * cv );
virtual void cullShadowReceivingScene( );
virtual void aimShadowCastingCamera( const osg::Light *light,
const osg::Vec4 &worldLightPos,
const osg::Vec3 &worldLightDir,
const osg::Vec3 &worldLightUp
= osg::Vec3(0,1,0) );
typedef std::vector< osgUtil::RenderLeaf* > RenderLeafList;
static unsigned RemoveOldRenderLeaves
( RenderLeafList &rllNew, RenderLeafList &rllOld );
static unsigned RemoveIgnoredRenderLeaves( RenderLeafList &rll );
static osg::BoundingBox ComputeRenderLeavesBounds
( RenderLeafList &rll, osg::Matrix & projectionToWorld );
static osg::BoundingBox ComputeRenderLeavesBounds
( RenderLeafList &rll, osg::Matrix & projectionToWorld, osg::Polytope & polytope );
static void GetRenderLeaves
( osgUtil::RenderBin *rb, RenderLeafList &rll );
};
META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData )
};
} // namespace osgShadow
#endif

View File

@ -0,0 +1,126 @@
/* -*-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.
*/
#ifndef OSGSHADOW_MINIMALDRAWBOUNDSSHADOWMAP
#define OSGSHADOW_MINIMALDRAWBOUNDSSHADOWMAP 1
#include <osgShadow/MinimalShadowMap>
namespace osgShadow {
class OSGSHADOW_EXPORT MinimalDrawBoundsShadowMap
: public MinimalShadowMap
{
public :
/** Convenient typedef used in definition of ViewData struct and methods */
typedef MinimalDrawBoundsShadowMap ThisClass;
/** Convenient typedef used in definition of ViewData struct and methods */
typedef MinimalShadowMap BaseClass;
/** Classic OSG constructor */
MinimalDrawBoundsShadowMap();
/** Classic OSG cloning constructor */
MinimalDrawBoundsShadowMap(
const MinimalDrawBoundsShadowMap& mdbsm,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, MinimalDrawBoundsShadowMap );
protected:
/** Classic protected OSG destructor */
virtual ~MinimalDrawBoundsShadowMap(void);
struct ViewData: public BaseClass::ViewData
{
osg::ref_ptr< osg::RefMatrix > _projection;
osg::Vec2s _boundAnalysisSize;
osg::ref_ptr< osg::Image > _boundAnalysisImage;
osg::ref_ptr< osg::Texture2D > _boundAnalysisTexture;
osg::ref_ptr< osg::Camera > _boundAnalysisCamera;
osg::ref_ptr< osg::Camera > _mainCamera;
void setShadowCameraProjectionMatrixPtr( osg::RefMatrix * projection )
{ _projection = projection; }
osg::RefMatrix * getShadowCameraProjectionMatrixPtr( void )
{ return _projection.get(); }
virtual void init( ThisClass * st, osgUtil::CullVisitor * cv );
virtual void cullShadowReceivingScene( );
virtual void createDebugHUD( );
virtual void recordShadowMapParams( );
virtual void cullBoundAnalysisScene( );
static osg::BoundingBox scanImage( const osg::Image * image, osg::Matrix m );
virtual void performBoundAnalysis( const osg::Camera& camera );
ViewData( void ): _boundAnalysisSize( 64, 64 ) {}
};
META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData )
struct CameraPostDrawCallback : public osg::Camera::DrawCallback {
CameraPostDrawCallback( ViewData * vd ): _vd( vd )
{
}
virtual void operator ()( const osg::Camera& camera ) const
{
if( _vd.valid() )
_vd->performBoundAnalysis( camera );
}
osg::observer_ptr< ViewData > _vd;
};
struct CameraCullCallback: public osg::NodeCallback {
CameraCullCallback(ViewData * vd, osg::NodeCallback * nc): _vd(vd), _nc(nc)
{
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgUtil::CullVisitor *cv = dynamic_cast< osgUtil::CullVisitor *>( nv );
if( _nc.valid() )
_nc->operator()(node,nv);
else
traverse(node,nv);
if( cv )
_vd->recordShadowMapParams( );
}
protected:
osg::ref_ptr< osg::NodeCallback > _nc;
osg::observer_ptr< ViewData > _vd;
};
};
} // namespace osgShadow
#endif

View File

@ -0,0 +1,156 @@
/* -*-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.
*/
#ifndef OSGSHADOW_MINIMALSHADOWMAP
#define OSGSHADOW_MINIMALSHADOWMAP 1
#include <osgShadow/StandardShadowMap>
namespace osgShadow {
class OSGSHADOW_EXPORT MinimalShadowMap : public StandardShadowMap
{
public :
/** Convenient typedef used in definition of ViewData struct and methods */
typedef MinimalShadowMap ThisClass;
/** Convenient typedef used in definition of ViewData struct and methods */
typedef StandardShadowMap BaseClass;
/** Classic OSG constructor */
MinimalShadowMap();
/** Classic OSG cloning constructor */
MinimalShadowMap(
const MinimalShadowMap& msm,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, MinimalShadowMap );
void setModellingSpaceToWorldTransform( const osg::Matrix & modellingSpaceToWorld )
{ _modellingSpaceToWorld = modellingSpaceToWorld; }
const osg::Matrix & getModellingSpaceToWorldTransform( void )
{ return _modellingSpaceToWorld; }
float getMaxFarPlane( )
{ return _maxFarPlane; }
void setMaxFarPlane( float maxFarPlane )
{ _maxFarPlane = maxFarPlane; }
float getMinLightMargin( )
{ return _minLightMargin; }
void setMinLightMargin( float minLightMargin )
{ _minLightMargin = minLightMargin; }
enum ShadowReceivingCoarseBoundAccuracy {
EMPTY_BOX,
BOUNDING_SPHERE,
BOUNDING_BOX,
DEFAULT_ACCURACY = BOUNDING_BOX,
};
void setShadowReceivingCoarseBoundAccuracy
( ShadowReceivingCoarseBoundAccuracy accuracy )
{ _shadowReceivingCoarseBoundAccuracy = accuracy; }
ShadowReceivingCoarseBoundAccuracy
getShadowReceivingCoarseBoundAccuracy()
{ return _shadowReceivingCoarseBoundAccuracy; }
protected:
/** Classic protected OSG destructor */
virtual ~MinimalShadowMap(void);
protected:
// Matrix modellingSpaceToWorld and its inverse
// are used to define Modelling Space where shadowed scene drawables
// have minimal (smallest possible extent) bounding boxes.
// Computing visible shadow range in this space
// allows for optimal use of ShadowMap resolution.
// By default it is set to identity ie computations are in world space.
// But it should be set to ElipsoidModel::localToWorld
// when scene objects are put on earth ellipsoid surface.
// Other scenarios are also possible for example when models are
// built in XZY space which would require identity matrix with swapped colums
osg::Matrix _modellingSpaceToWorld;
float _maxFarPlane;
float _minLightMargin;
ShadowReceivingCoarseBoundAccuracy _shadowReceivingCoarseBoundAccuracy;
struct ViewData: public BaseClass::ViewData
{
osg::Matrix *_modellingSpaceToWorldPtr;
float *_maxFarPlanePtr;
float *_minLightMarginPtr;
ConvexPolyhedron _sceneReceivingShadowPolytope;
std::vector< osg::Vec3d > _sceneReceivingShadowPolytopePoints;
osg::Matrix _clampedProjection;
virtual void init( ThisClass * st, osgUtil::CullVisitor * cv );
virtual osg::BoundingBox computeShadowReceivingCoarseBounds( );
virtual void cullShadowReceivingScene( );
virtual void aimShadowCastingCamera( const osg::Light *light,
const osg::Vec4 &worldLightPos,
const osg::Vec3 &worldLightDir,
const osg::Vec3 &worldLightUp
= osg::Vec3(0,1,0) );
virtual void frameShadowCastingCamera
( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass = 1 );
void cutScenePolytope( const osg::Matrix & matrix,
const osg::Matrix & inverse,
const osg::BoundingBox &bb =
osg::BoundingBox(-1,-1,-1,1,1,1) );
osg::BoundingBox computeScenePolytopeBounds
( const osg::Matrix & m = *(osg::Matrix*)(NULL) );
// Utility methods for adjusting projection matrices
// Modify projection matrix so that some output subrange
// is remapped to whole clip space (-1..1,-1..1,-1..1).
// Bit mask can be used to limit remaping to selected bounds only.
static void trimProjection
( osg::Matrix & projection, osg::BoundingBox subrange,
unsigned int trimMask = (1|2|4|8|16|32)
/*1=left|2=right|4=bottom|8=top|16=near|32=far*/);
static void clampProjection
( osg::Matrix & projection, float n = 0, float f = FLT_MAX );
static void extendProjection
( osg::Matrix & projection, osg::Viewport * viewport, const osg::Vec2& margin );
};
META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData )
};
} // namespace osgShadow
#endif

View File

@ -0,0 +1,82 @@
/* -*-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.
*/
#ifndef OSGSHADOW_PROJECTIONSHADOWMAP
#define OSGSHADOW_PROJECTIONSHADOWMAP 1
#include <osgShadow/MinimalShadowMap>
namespace osgShadow {
template< typename MinimalBoundsBaseClass, typename ShadowProjectionAlgorithmClass >
class OSGSHADOW_EXPORT ProjectionShadowMap : public MinimalBoundsBaseClass
{
public :
/** Convenient typedef used in definition of ViewData struct and methods */
typedef MinimalBoundsBaseClass BaseClass;
/** Convenient typedef used in definition of ViewData struct and methods */
typedef ProjectionShadowMap<MinimalBoundsBaseClass,ShadowProjectionAlgorithmClass> ThisClass;
/** Classic OSG constructor */
ProjectionShadowMap() : BaseClass()
{
}
/** Classic OSG cloning constructor */
ProjectionShadowMap(
const ProjectionShadowMap& copy,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop)
{
}
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, ProjectionShadowMap );
protected:
/** Classic protected OSG destructor */
virtual ~ProjectionShadowMap(void)
{
}
struct ViewData: public BaseClass::ViewData,
public ShadowProjectionAlgorithmClass
{
#if 0
virtual void init( ThisClass * st, osgUtil::CullVisitor * cv );
{
BaseClass::ViewData::init( st, cv );
}
#endif
virtual void frameShadowCastingCamera
( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass = 1 )
{
// Force dependent name lookup
ShadowProjectionAlgorithmClass::operator()
( &this->_sceneReceivingShadowPolytope, cameraMain, cameraShadow );
// DebugBoundingBox( computeScenePolytopeBounds(), "ProjectionShadowMap" );
BaseClass::ViewData::frameShadowCastingCamera( cameraMain, cameraShadow, pass );
}
};
META_ViewDependentShadowTechniqueData( ThisClass, typename ThisClass::ViewData )
};
} // namespace osgShadow
#endif

View File

@ -0,0 +1,195 @@
/* -*-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.
*/
#ifndef OSGSHADOW_STANDARDSHADOWMAP
#define OSGSHADOW_STANDARDSHADOWMAP 1
#include <osgShadow/DebugShadowMap>
namespace osgShadow {
class OSGSHADOW_EXPORT StandardShadowMap : public DebugShadowMap
{
public :
/** Convenient typedef used in definition of ViewData struct and methods */
typedef StandardShadowMap ThisClass;
/** Convenient typedef used in definition of ViewData struct and methods */
typedef DebugShadowMap BaseClass;
/** Classic OSG constructor */
StandardShadowMap();
/** Classic OSG cloning constructor */
StandardShadowMap(const StandardShadowMap& ssm,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, StandardShadowMap );
void setBaseTextureUnit( unsigned int unit )
{ _baseTextureUnit = unit; dirty(); }
unsigned int getBaseTextureUnit( void )
{ return _baseTextureUnit; }
void setShadowTextureUnit( unsigned int unit )
{ _shadowTextureUnit = unit; dirty(); }
unsigned int getShadowTextureUnit( void )
{ return _shadowTextureUnit; }
// Texture Indices are changed by search and replace on shader source
// Carefully order these calls when changing both base and shadow indices
// In worst case when intend to swap indices
// one will have to call these methods more than once
// with one extra pass to change index to unused value to avoid
// unwanted superfluous replace:
//
// Example: Imagine we want to swap base(0) and shadow(1) indices:
// We have to do an extra step to make sure both do not end up as 1
//
// // initialy change base to something else than 1
// setBaseTextureCoordIndex( 100 );
// // now search and replace all gl_TexCord[1] to gl_TexCord[0]
// setShadowTextureCoordIndex( 0 );
// // finally change base from 100 to 0
// setBaseTextureCoordIndex( 1 );
void setBaseTextureCoordIndex( unsigned int index )
{ updateTextureCoordIndices( _baseTextureCoordIndex, index );
_baseTextureCoordIndex = index; }
unsigned int getBaseTextureCoordIndex( void )
{ return _baseTextureCoordIndex; }
// Texture Indices are changed by search and replace on shader source
// Look at the comment above setBaseTextureCoordIndex
void setShadowTextureCoordIndex( unsigned int index )
{ updateTextureCoordIndices( _shadowTextureCoordIndex, index );
_shadowTextureCoordIndex = index; }
unsigned int getShadowTextureCoordIndex( void )
{ return _shadowTextureCoordIndex; }
void setTextureSize( const osg::Vec2s& textureSize )
{ _textureSize = textureSize; dirty(); }
osg::Vec2s getTextureSize( )
{ return _textureSize; }
void setLight( osg::Light* light )
{ _light = light; }
osg::Light* getLight( void )
{ return _light.get(); }
osg::Shader * getShadowVertexShader()
{ return _shadowVertexShader.get(); }
osg::Shader * getShadowFragmentShader()
{ return _shadowFragmentShader.get(); }
osg::Shader * getMainVertexShader( )
{ return _mainVertexShader.get(); }
osg::Shader * getMainFragmentShader( )
{ return _mainFragmentShader.get(); }
void setShadowVertexShader( osg::Shader * shader )
{ _shadowVertexShader = shader; }
void setShadowFragmentShader( osg::Shader * shader )
{ _shadowFragmentShader = shader; }
void setMainVertexShader( osg::Shader * shader )
{ _mainVertexShader = shader; }
void setMainFragmentShader( osg::Shader * shader )
{ _mainFragmentShader = shader; }
protected:
/** Classic protected OSG destructor */
virtual ~StandardShadowMap(void);
virtual void updateTextureCoordIndices
( unsigned int baseTexCoordIndex, unsigned int shadowTexCoordIndex );
virtual void searchAndReplaceShaderSource
( osg::Shader*, std::string fromString, std::string toString );
osg::ref_ptr< osg::Shader > _mainVertexShader;
osg::ref_ptr< osg::Shader > _mainFragmentShader;
osg::ref_ptr< osg::Shader > _shadowVertexShader;
osg::ref_ptr< osg::Shader > _shadowFragmentShader;
osg::ref_ptr< osg::Light > _light;
float _polygonOffsetFactor;
float _polygonOffsetUnits;
osg::Vec2s _textureSize;
unsigned int _baseTextureUnit;
unsigned int _shadowTextureUnit;
unsigned int _baseTextureCoordIndex;
unsigned int _shadowTextureCoordIndex;
struct ViewData: public BaseClass::ViewData
{
osg::ref_ptr< osg::Light > * _lightPtr;
unsigned int * _baseTextureUnitPtr;
unsigned int * _shadowTextureUnitPtr;
// ShadowMap texture is defined by base DebugShadowMap
// osg::ref_ptr<osg::Texture2D> _texture;
// ShadowMap camera is defined by base DebugShadowMap
// osg::ref_ptr<osg::Camera> _camera;
osg::ref_ptr<osg::TexGen> _texgen;
osg::ref_ptr<osg::StateSet> _stateset;
virtual void init( ThisClass * st, osgUtil::CullVisitor * cv );
virtual void cull( );
virtual void aimShadowCastingCamera(
const osg::BoundingSphere &bounds,
const osg::Light *light,
const osg::Vec4 &worldLightPos,
const osg::Vec3 &worldLightDir,
const osg::Vec3 &worldLightUp = osg::Vec3(0,1,0) );
virtual void cullShadowReceivingScene( );
virtual void cullShadowCastingScene( );
virtual void addShadowReceivingTexGen( );
virtual const osg::Light* selectLight( osg::Vec4 &viewLightPos,
osg::Vec3 &viewLightDir );
virtual void aimShadowCastingCamera( const osg::Light *light,
const osg::Vec4 &worldLightPos,
const osg::Vec3 &worldLightDir,
const osg::Vec3 &worldLightUp
= osg::Vec3(0,1,0) );
};
META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData )
};
} // namespace osgShadow
#endif

View File

@ -0,0 +1,229 @@
/* -*-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.
*/
#ifndef OSGSHADOW_VIEWDEPENDENTSHADOWTECHINIQUE
#define OSGSHADOW_VIEWDEPENDENTSHADOWTECHINIQUE 1
#include <osgShadow/ShadowTechnique>
#include <map>
#include <osgShadow/Export>
namespace osgShadow {
/**
META_ViewDependentShadowTechniqueData macro defines initViewDependentData
method used by derived shadow techniques to initialize their specific
ViewData objects. initViewDependentData will be called from
ViewDependentShadowTechnique base class to init derived class
*/
#define META_ViewDependentShadowTechniqueData( ShadowTechnique, TechniqueData )\
virtual ViewDependentShadowTechnique::ViewData * initViewDependentData \
( osgUtil::CullVisitor *cv, ViewDependentShadowTechnique::ViewData * vd ) \
{ \
TechniqueData* td = dynamic_cast<TechniqueData*>( vd ); \
if ( !td ) td = new TechniqueData; \
td->init( this, cv ); \
return td; \
}
/**
ViewDependentShadowTechnique is a base class for all
View Dependent Shadow techniques. It defines fundamental object structure
and methods to manage separate shadow resources for each view of the scene.
By view we understand osg::View or SceneView instance and their associated
Camera. Typical osg application has one or more such views. View Dependent
Shadow techniques manage shadow generation for them.
View Dependent Shadow techniques are used to optimize shadow algorithms for
part of the scene seen on the view. If rest of the scene is behind view
frustum, there is no sense in computing shadows for it. Since in practice we
often use 3d databases extending far beyond current camera frustum View
Dependent Shadow approach may produce much better shadows.
The other goal is to provide framework for thread safe rendering of
the shadows. It allows to use shadows with different OSG threading models.
Conceptually ViewDependentShadowTechnique is similar to osgSim::OverlayNode.
Its a container class for number of ViewData (or ViewData derived) objects
doing actual shadow work for each of the scene views.
But ViewDependentShadowTechnique is intended as foundation layer for all
derived classes so in some way it extends osgSim::OverlayNode approach a bit.
HOW IT WORKS:
ViewDependendentShadowTechnique is derived from osgShadow::ShadowTechnique
and as such overrides virtual methods of osgShadow::ShadowTechnique.
But most of the shadow dirty work is done by ViewData objects,
ViewDependendentShadowTechnique::cull is the only osgShadow::ShadowTechnique
method where ViewDependendentShadowTechnique does something significant:
What ViewDependentShadowTechnique::cull( CullVisitor & cv ) does ?
It identifies View. CullVisitor ptr is used as View identificator.
In practice we do not check and interpret what are actual Views and SceneViews
set up by application. We focus on Camera and CullVisitors as a identificators
of views. We can safely do this because each such view will have associated
unique CullVisitor used to cull the scene in every frame.
Based on CullVisitor ptr passed to cull method, associated Technique::ViewData
object is created (if neccessary) and then seleced. Then control is passed to
this ViewData object. So, each view has its associated unique ViewData
(or derived) object performing dirty work of shadow resources management and
shadow generation for the view.
To support creation of classes derived from ViewDependentShadowTechnique it
was neccessary to provide mechanism to override ViewData and allow for
initialization of new derived ViewData objects. Creation and initialization
is performed when ViewDependendentShadowTechnique::cull gets called with
CullVistor ptr which does not yet have associated ViewData object. When it
happens, virtual initViewDependentData method is called to give
derived techniques a chance to allocate and iniitalize its specific
resources as new ViewData derived instance. In practice initViewDependentData
in derived techniques should look the same as in base class so as a convenience
it was defined as META_ViewDependentShadowTechnique macro. Derived techniques
use this macro to override initViewDependentData method for their usage.
After ViewData derived object is construted and selected, control is passed
to this object by call to virtual ViewData::cull method. The rest of work
is the done by this object. ViewDependentShadowTechnique::ViewData is intended
as a base class so it does nothing. In practice the rest of dirty work will
do new ViewData classes implemented in derived techniques.
*/
class OSGSHADOW_EXPORT ViewDependentShadowTechnique
: public osgShadow::ShadowTechnique
{
public:
/**
osgShadow::ShadowTechnique equivalent methods for view dependent techniques
*/
/** Classic OSG constructor */
ViewDependentShadowTechnique( void );
/** Classic OSG cloning constructor */
ViewDependentShadowTechnique(
const ViewDependentShadowTechnique& vdst,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY );
/** Declaration of standard OSG object methods */
META_Object( ViewDependentShadow, ViewDependentShadowTechnique );
/** Dirty view data bits and force update of view data resources */
virtual void dirty();
/** Initialize the ShadowedScene and some data structures.*/
virtual void init();
/** Run the update traversal of the ShadowedScene and update any local cached data structures.*/
virtual void update(osg::NodeVisitor& nv);
/** Run the cull traversal of the ShadowedScene and set up the rendering for this ShadowTechnique.*/
virtual void cull(osgUtil::CullVisitor& cv);
/** Clean scene graph from any shadow technique specific nodes, state and drawables.*/
virtual void cleanSceneGraph();
/** Traverse shadow scene graph.*/
virtual void traverse(osg::NodeVisitor& nv);
protected:
/** Classic protected OSG destructor */
~ViewDependentShadowTechnique( void );
/**
Base container class for view dependent shadow resources.
Techniques based on ViewDependentShadowTechnique will usually define
similar struct and derive it from ViewData to contain their specufic resources.
*/
struct ViewData: public osg::Referenced
{
/**
Method called upon ViewData instance to initialize internal variables
*/
virtual void init
( ViewDependentShadowTechnique *st, osgUtil::CullVisitor *cv );
/**
Method called by ViewDependentShadowTechnique to allow ViewData
do the hard work computing shadows for its associated view
*/
virtual void cull();
/**
Dirty is called by parent ViewDependentShadowTechnique to force
update of resources after some of them were modified in parent technique
*/
virtual void dirty( bool flag );
/**
Simple constructor zeroing all variables.
*/
ViewData(): _dirty( true ), _cv( NULL ), _st( NULL ) { };
/**
Mutex used to guard _dirty flag from override in case when parent technique calls
dirty() simultaneously with ViewData while it is updating resources inside init method.
*/
OpenThreads::Mutex _mutex;
/**
View's CullVisitor associated with this ViewData instance
*/
osg::observer_ptr< osgUtil::CullVisitor > _cv;
/**
Parent ViewDependentShadowTechnique
*/
osg::observer_ptr< ViewDependentShadowTechnique > _st;
/**
Dirty flag tells this instance to update its resources
*/
bool _dirty;
};
/**
Map of view dependent data per view cull visitor (CVs are used as indices)
ViewDependentShadowTechnique uses this map to find VieData for each cull vitior
*/
typedef std::map< osg::ref_ptr< osgUtil::CullVisitor >,
osg::ref_ptr< ViewData > > ViewDataMap;
ViewDataMap _viewDataMap;
/**
Mutex used to serialize accesses to ViewDataMap
*/
OpenThreads::Mutex _viewDataMapMutex;
/** Return view dependent data for the cull visitor */
virtual ViewDependentShadowTechnique::ViewData * getViewDependentData( osgUtil::CullVisitor * cv );
/** Define view dependent data for the cull visitor */
virtual void setViewDependentData( osgUtil::CullVisitor * cv, ViewDependentShadowTechnique::ViewData * data );
/**
Declare standard initViewDependentData method.
*/
META_ViewDependentShadowTechniqueData( ViewDependentShadowTechnique, ViewData )
};
} // namespace osgShadow
#endif

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 )
{
}