From a25c4e4ab2e58272506137773eb899fd6afcfe26 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Wed, 23 Feb 2011 16:46:34 +0000 Subject: [PATCH] From Wojciech Lewandowski, intial cut at new TrapezoidalShadowMap ShadowTechnique --- examples/osgshadow/osgshadow.cpp | 153 ++++-- include/osgShadow/TrapezoidalShadowMap | 123 +++++ src/osgShadow/CMakeLists.txt | 3 +- src/osgShadow/TrapezoidalShadowMap.cpp | 701 +++++++++++++++++++++++++ 4 files changed, 935 insertions(+), 45 deletions(-) create mode 100644 include/osgShadow/TrapezoidalShadowMap create mode 100644 src/osgShadow/TrapezoidalShadowMap.cpp diff --git a/examples/osgshadow/osgshadow.cpp b/examples/osgshadow/osgshadow.cpp index 7de794446..0c82dcb8c 100644 --- a/examples/osgshadow/osgshadow.cpp +++ b/examples/osgshadow/osgshadow.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -106,6 +107,35 @@ public: }; +class DumpShadowVolumesHandler : public osgGA::GUIEventHandler +{ +public: + DumpShadowVolumesHandler( ) + { + set( false ); + } + + bool get() { return _value; } + void set( bool value ) { _value = value; } + + /** Deprecated, Handle events, return true if handled, false otherwise. */ + virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) + { + if (ea.getEventType() == osgGA::GUIEventAdapter::KEYUP) + { + if (ea.getKey() == 'D' ) + { + set( true ); + return true; + } + } + + return false; + } + + bool _value; +}; + static int ReceivesShadowTraversalMask = 0x1; static int CastsShadowTraversalMask = 0x2; @@ -452,13 +482,13 @@ namespace ModelThree osg::ref_ptr geode_2 = new osg::Geode; osg::ref_ptr transform_2 = new osg::MatrixTransform; transform_2->addChild(geode_2.get()); - transform_2->setUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(0, 0, 0), osg::Z_AXIS, osg::inDegrees(45.0f))); +// transform_2->setUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(0, 0, 0), osg::Z_AXIS, osg::inDegrees(45.0f))); scene->addChild(transform_2.get()); osg::ref_ptr geode_3 = new osg::Geode; osg::ref_ptr transform_3 = new osg::MatrixTransform; transform_3->addChild(geode_3.get()); - transform_3->setUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(0, 0, 0), osg::Z_AXIS, osg::inDegrees(-22.5f))); +// transform_3->setUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(0, 0, 0), osg::Z_AXIS, osg::inDegrees(-22.5f))); scene->addChild(transform_3.get()); const float radius = 0.8f; @@ -571,13 +601,15 @@ int main(int argc, char** argv) arguments.getApplicationUsage()->addCommandLineOption("--PolyOffset-Unit", "ParallelSplitShadowMap set PolygonOffset unit.");//ADEGLI arguments.getApplicationUsage()->addCommandLineOption("--lispsm", "Select LightSpacePerspectiveShadowMap implementation."); - arguments.getApplicationUsage()->addCommandLineOption("--ViewBounds", "LiSPSM optimize shadow for view frustum (weakest option)"); - arguments.getApplicationUsage()->addCommandLineOption("--CullBounds", "LiSPSM optimize shadow for bounds of culled objects in view frustum (better option)."); - arguments.getApplicationUsage()->addCommandLineOption("--DrawBounds", "LiSPSM optimize shadow for bounds of predrawn pixels in view frustum (best & default)."); - arguments.getApplicationUsage()->addCommandLineOption("--mapres", "LiSPSM texture resolution."); - arguments.getApplicationUsage()->addCommandLineOption("--maxFarDist", "LiSPSM max far distance to shadow."); - arguments.getApplicationUsage()->addCommandLineOption("--moveVCamFactor", "LiSPSM move the virtual frustum behind the real camera, (also back ground object can cast shadow)."); - arguments.getApplicationUsage()->addCommandLineOption("--minLightMargin", "LiSPSM the same as --moveVCamFactor."); + arguments.getApplicationUsage()->addCommandLineOption("--tsm", "Select TrapezoidalShadowMap implementation."); + arguments.getApplicationUsage()->addCommandLineOption("--msm", "Select MinimalShadowMap implementation."); + arguments.getApplicationUsage()->addCommandLineOption("--ViewBounds", "MSM, LiSPSM & TSM optimize shadow for view frustum (weakest option)"); + arguments.getApplicationUsage()->addCommandLineOption("--CullBounds", "MSM, LiSPSM & TSM optimize shadow for bounds of culled objects in view frustum (better option)."); + arguments.getApplicationUsage()->addCommandLineOption("--DrawBounds", "MSM, LiSPSM & TSM optimize shadow for bounds of predrawn pixels in view frustum (best & default)."); + arguments.getApplicationUsage()->addCommandLineOption("--mapres", "MSM, LiSPSM & TSM texture resolution."); + arguments.getApplicationUsage()->addCommandLineOption("--maxFarDist", "MSM, LiSPSM & TSM max far distance to shadow."); + arguments.getApplicationUsage()->addCommandLineOption("--moveVCamFactor", "MSM, LiSPSM & TSM move the virtual frustum behind the real camera, (also back ground object can cast shadow)."); + arguments.getApplicationUsage()->addCommandLineOption("--minLightMargin", "MSM, LiSPSM t& TSM the same as --moveVCamFactor."); arguments.getApplicationUsage()->addCommandLineOption("-1", "Use test model one."); arguments.getApplicationUsage()->addCommandLineOption("-2", "Use test model two."); @@ -660,6 +692,7 @@ int main(int argc, char** argv) shadowedScene->setReceivesShadowTraversalMask(ReceivesShadowTraversalMask); shadowedScene->setCastsShadowTraversalMask(CastsShadowTraversalMask); + osg::ref_ptr msm = NULL; if (arguments.read("--sv")) { // hint to tell viewer to request stencil buffer when setting up windows @@ -731,45 +764,32 @@ int main(int argc, char** argv) osg::ref_ptr sm = new osgShadow::SoftShadowMap; shadowedScene->setShadowTechnique(sm.get()); } - else if ( arguments.read("--lispsm") ) + else if ( arguments.read("--lispsm") ) { - osg::ref_ptr sm = NULL; - if( arguments.read( "--ViewBounds" ) ) - sm = new osgShadow::LightSpacePerspectiveShadowMapVB; + msm = new osgShadow::LightSpacePerspectiveShadowMapVB; else if( arguments.read( "--CullBounds" ) ) - sm = new osgShadow::LightSpacePerspectiveShadowMapCB; + msm = new osgShadow::LightSpacePerspectiveShadowMapCB; else // if( arguments.read( "--DrawBounds" ) ) // default - sm = new osgShadow::LightSpacePerspectiveShadowMapDB; - - shadowedScene->setShadowTechnique( sm.get() ); - - if( sm.valid() ) - { - while( arguments.read("--debugHUD") ) - sm->setDebugDraw( true ); - - float minLightMargin = 10.f; - float maxFarPlane = 0; - unsigned int texSize = 1024; - unsigned int baseTexUnit = 0; - unsigned int shadowTexUnit = 1; - - while ( arguments.read("--moveVCamFactor", minLightMargin ) ); - while ( arguments.read("--minLightMargin", minLightMargin ) ); - while ( arguments.read("--maxFarDist", maxFarPlane ) ); - while ( arguments.read("--mapres", texSize )); - while ( arguments.read("--baseTextureUnit", baseTexUnit) ); - while ( arguments.read("--shadowTextureUnit", shadowTexUnit) ); - - sm->setMinLightMargin( minLightMargin ); - sm->setMaxFarPlane( maxFarPlane ); - sm->setTextureSize( osg::Vec2s( texSize, texSize ) ); - sm->setShadowTextureCoordIndex( shadowTexUnit ); - sm->setShadowTextureUnit( shadowTexUnit ); - sm->setBaseTextureCoordIndex( baseTexUnit ); - sm->setBaseTextureUnit( baseTexUnit ); - } + msm = new osgShadow::LightSpacePerspectiveShadowMapDB; + } + else if( arguments.read("--tsm") ) + { + if( arguments.read( "--ViewBounds" ) ) + msm = new osgShadow::TrapezoidalShadowMapVB; + else if( arguments.read( "--CullBounds" ) ) + msm = new osgShadow::TrapezoidalShadowMapCB; + else // if( arguments.read( "--DrawBounds" ) ) + msm = new osgShadow::TrapezoidalShadowMapDB; + } + else if( arguments.read("--msm") ) + { + if( arguments.read( "--ViewBounds" ) ) + msm = new osgShadow::MinimalShadowMap; + else if( arguments.read( "--CullBounds" ) ) + msm = new osgShadow::MinimalCullBoundsShadowMap; + else // if( arguments.read( "--DrawBounds" ) ) // default + msm = new osgShadow::MinimalDrawBoundsShadowMap; } else /* if (arguments.read("--sm")) */ { @@ -781,6 +801,34 @@ int main(int argc, char** argv) sm->setTextureSize(osg::Vec2s(mapres,mapres)); } + if( msm )// Set common MSM & TSM & LISPSM arguments + { + shadowedScene->setShadowTechnique( msm.get() ); + while( arguments.read("--debugHUD") ) + msm->setDebugDraw( true ); + + float minLightMargin = 10.f; + float maxFarPlane = 0; + unsigned int texSize = 1024; + unsigned int baseTexUnit = 0; + unsigned int shadowTexUnit = 1; + + while ( arguments.read("--moveVCamFactor", minLightMargin ) ); + while ( arguments.read("--minLightMargin", minLightMargin ) ); + while ( arguments.read("--maxFarDist", maxFarPlane ) ); + while ( arguments.read("--mapres", texSize )); + while ( arguments.read("--baseTextureUnit", baseTexUnit) ); + while ( arguments.read("--shadowTextureUnit", shadowTexUnit) ); + + msm->setMinLightMargin( minLightMargin ); + msm->setMaxFarPlane( maxFarPlane ); + msm->setTextureSize( osg::Vec2s( texSize, texSize ) ); + msm->setShadowTextureCoordIndex( shadowTexUnit ); + msm->setShadowTextureUnit( shadowTexUnit ); + msm->setBaseTextureCoordIndex( baseTexUnit ); + msm->setBaseTextureUnit( baseTexUnit ); + } + osg::ref_ptr model = osgDB::readNodeFiles(arguments); if (model.valid()) { @@ -857,7 +905,10 @@ int main(int argc, char** argv) viewer.setSceneData(shadowedScene.get()); + osg::ref_ptr< DumpShadowVolumesHandler > dumpShadowVolumes = new DumpShadowVolumesHandler; + viewer.addEventHandler(new ChangeFOVHandler(viewer.getCamera())); + viewer.addEventHandler( dumpShadowVolumes.get() ); // create the windows and run the threads. viewer.realize(); @@ -942,6 +993,20 @@ int main(int argc, char** argv) ls->getLight()->setDirection(lightDir); } + if( dumpShadowVolumes->get() ) + { + dumpShadowVolumes->set( false ); + + static int dumpFileNo = 0; + dumpFileNo ++; + char filename[256]; + std::sprintf( filename, "shadowDump%d.osg", dumpFileNo ); + + osgShadow::MinimalShadowMap * msm = dynamic_cast( shadowedScene->getShadowTechnique() ); + + if( msm ) msm->setDebugDump( filename ); + } + viewer.frame(); } diff --git a/include/osgShadow/TrapezoidalShadowMap b/include/osgShadow/TrapezoidalShadowMap new file mode 100644 index 000000000..71fa26dae --- /dev/null +++ b/include/osgShadow/TrapezoidalShadowMap @@ -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_TRAPEZOIDALSHADOWMAP +#define OSGSHADOW_TRAPEZOIDALSHADOWMAP 1 + +#include +#include +#include + +namespace osgShadow { + +// Class implements +// "Anti-aliasing and Continuity with Trapezoidal Shadow Maps" +// Tobias Martin (tobiasmartin@t-online.de) and Tiow-Seng Tan (tants@comp.nus.edu.sg) +// School of Computing, National University of Singapore +// http://www.comp.nus.edu.sg/~tants/tsm.html + +struct TrapezoidalMapping; + +class OSGSHADOW_EXPORT TrapezoidalShadowMapAlgorithm +{ + public: + TrapezoidalShadowMapAlgorithm(); + ~TrapezoidalShadowMapAlgorithm(); + + void operator() ( + const osgShadow::ConvexPolyhedron* hullShadowedView, + const osg::Camera* cameraMain, + osg::Camera* cameraShadow ) const; + + protected: + TrapezoidalMapping * tm; +}; + +// Optimized for draw traversal shadow bounds +class OSGSHADOW_EXPORT TrapezoidalShadowMapDB: public ProjectionShadowMap< MinimalDrawBoundsShadowMap, TrapezoidalShadowMapAlgorithm > +{ + public: + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef ProjectionShadowMap< MinimalDrawBoundsShadowMap, TrapezoidalShadowMapAlgorithm > BaseClass; + + /** Classic OSG constructor */ + TrapezoidalShadowMapDB() + { + } + + /** Classic OSG cloning constructor */ + TrapezoidalShadowMapDB( + const TrapezoidalShadowMapDB& copy, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop) + { + } + + /** Declaration of standard OSG object methods */ + META_Object( osgShadow, TrapezoidalShadowMapDB ); +}; + +// Optimized for cull traversal shadow bounds +class OSGSHADOW_EXPORT TrapezoidalShadowMapCB: public ProjectionShadowMap< MinimalCullBoundsShadowMap, TrapezoidalShadowMapAlgorithm > +{ + public: + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef ProjectionShadowMap< MinimalCullBoundsShadowMap, TrapezoidalShadowMapAlgorithm > BaseClass; + + /** Classic OSG constructor */ + TrapezoidalShadowMapCB() + { + } + + /** Classic OSG cloning constructor */ + TrapezoidalShadowMapCB( + const TrapezoidalShadowMapCB& copy, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop) + { + } + + /** Declaration of standard OSG object methods */ + META_Object( osgShadow, TrapezoidalShadowMapCB ); +}; + +// Optimized for view frustum bounds +class OSGSHADOW_EXPORT TrapezoidalShadowMapVB: public ProjectionShadowMap< MinimalShadowMap, TrapezoidalShadowMapAlgorithm > +{ + public: + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef ProjectionShadowMap< MinimalShadowMap, TrapezoidalShadowMapAlgorithm > BaseClass; + + /** Classic OSG constructor */ + TrapezoidalShadowMapVB() + { + } + + /** Classic OSG cloning constructor */ + TrapezoidalShadowMapVB( + const TrapezoidalShadowMapVB& copy, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop) + { + } + + /** Declaration of standard OSG object methods */ + META_Object( osgShadow, TrapezoidalShadowMapVB ); +}; + +typedef TrapezoidalShadowMapDB TrapezoidalShadowMap; + +} // namespace osgShadow + +#endif diff --git a/src/osgShadow/CMakeLists.txt b/src/osgShadow/CMakeLists.txt index 762eac1dc..f74da7587 100644 --- a/src/osgShadow/CMakeLists.txt +++ b/src/osgShadow/CMakeLists.txt @@ -18,7 +18,6 @@ SET(LIB_PUBLIC_HEADERS ${HEADER_PATH}/SoftShadowMap ${HEADER_PATH}/ParallelSplitShadowMap ${HEADER_PATH}/Version - ${HEADER_PATH}/ConvexPolyhedron ${HEADER_PATH}/DebugShadowMap ${HEADER_PATH}/LightSpacePerspectiveShadowMap @@ -27,6 +26,7 @@ SET(LIB_PUBLIC_HEADERS ${HEADER_PATH}/MinimalShadowMap ${HEADER_PATH}/ProjectionShadowMap ${HEADER_PATH}/StandardShadowMap + ${HEADER_PATH}/TrapezoidalShadowMap ${HEADER_PATH}/ViewDependentShadowTechnique ) @@ -50,6 +50,7 @@ ADD_LIBRARY(${LIB_NAME} MinimalDrawBoundsShadowMap.cpp MinimalShadowMap.cpp StandardShadowMap.cpp + TrapezoidalShadowMap.cpp ViewDependentShadowTechnique.cpp ${OPENSCENEGRAPH_VERSIONINFO_RC} ) diff --git a/src/osgShadow/TrapezoidalShadowMap.cpp b/src/osgShadow/TrapezoidalShadowMap.cpp new file mode 100644 index 000000000..7385a6c11 --- /dev/null +++ b/src/osgShadow/TrapezoidalShadowMap.cpp @@ -0,0 +1,701 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + + +#include +#include +#include +#include + + +namespace ConvexHull2D { +//=================================================================== +// Copyright 2001, softSurfer (www.softsurfer.com) +// This code may be freely used and modified for any purpose +// providing that this copyright notice is included with it. +// SoftSurfer makes no warranty for this code, and cannot be held +// liable for any real or imagined damage resulting from its use. +// Users of this code must verify correctness for their application. +//=================================================================== +///////////////////////////////////////////////////////////////////// +// Wojtek Lewandowski: +// Original Algorithm code adjusted to templates and indices. +// .x, .y, .z fields access changed to indices for usage with OSG +///////////////////////////////////////////////////////////////////// +//=================================================================== +// isLeft(): tests if a point is Left|On|Right of an infinite line. +// Input: three points P0, P1, and P2 +// Return: >0 for P2 left of the line through P0 and P1 +// =0 for P2 on the line +// <0 for P2 right of the line +// See: the January 2001 Algorithm on Area of Triangles +//=================================================================== +template< typename Point > +inline typename Point::value_type isLeft( Point P0, Point P1, Point P2 ) +{ + return (P1[0] - P0[0])*(P2[1] - P0[1]) - + (P2[0] - P0[0])*(P1[1] - P0[1]); +} +//=================================================================== +// chainHull_2D(): Andrew's monotone chain 2D convex hull algorithm +// Input: P[] = an array of 2D points +// presorted by increasing x- and y-coordinates +// n = the number of points in P[] +// Output: H[] = an array of the convex hull vertices (max is n) +// Return: the number of points in H[] +//=================================================================== +template< typename Point > +int chainConvexHull2D( Point* P, int n, Point* H ) +{ + // the output array H[] will be used as the stack + int bot=0, top=(-1); // indices for bottom and top of the stack + int i; // array scan index + + // Get the indices of points with min x-coord and min|max y-coord + int minmin = 0, minmax; + typename Point::value_type xmin = P[0][0]; + for (i=1; i=0; i--) + if (P[i][0] != xmax) break; + maxmin = i+1; + + // Compute the lower hull on the stack H + H[++top] = P[minmin]; // push minmin point onto stack + i = minmax; + while (++i <= maxmin) + { + // the lower line joins P[minmin] with P[maxmin] + if (isLeft( P[minmin], P[maxmin], P[i]) >= 0 && i < maxmin) + continue; // ignore P[i] above or on the lower line + + while (top > 0) // there are at least 2 points on the stack + { + // test if P[i] is left of the line at the stack top + if (isLeft( H[top-1], H[top], P[i]) > 0) + break; // P[i] is a new hull vertex + else + top--; // pop top point off stack + } + H[++top] = P[i]; // push P[i] onto stack + } + + // Next, compute the upper hull on the stack H above the bottom hull + if (maxmax != maxmin) // if distinct xmax points + H[++top] = P[maxmax]; // push maxmax point onto stack + bot = top; // the bottom point of the upper hull stack + i = maxmin; + while (--i >= minmax) + { + // the upper line joins P[maxmax] with P[minmax] + if (isLeft( P[maxmax], P[minmax], P[i]) >= 0 && i > minmax) + continue; // ignore P[i] below or on the upper line + + while (top > bot) // at least 2 points on the upper stack + { + // test if P[i] is left of the line at the stack top + if (isLeft( H[top-1], H[top], P[i]) > 0) + break; // P[i] is a new hull vertex + else + top--; // pop top point off stack + } + H[++top] = P[i]; // push P[i] onto stack + } + if (minmax != minmin) + H[++top] = P[minmin]; // push joining endpoint onto stack + + return top+1; +} + +} // namespace ConvexHull2D + +namespace osgShadow +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TrapezoidalShadowMapAlgorithm +// + +//////////////////////////////////////////////////////////////////////////////// +struct TrapezoidalMapping : public osg::Referenced +{ + TrapezoidalMapping( unsigned resolution = 2048, + float focusDistance = 1000.f, + float focusStart = 0.8f, + float focusEnd = 0.0f, + float maxNearFarDist = 10000.f, + float maxShadowSize = 1000.f, + float scale = 1.f, + float duellingFrustaAngle = 15.f, + float focusDistanceMin = 50.f, + float focusDistanceMax = 10000.f, + float focusDistanceStep = 10.f, + float focusBase = 1000.f, + float frustumNear = -1.f, + float frustumFar = 1.f ) : + resolution( resolution ), + handler( false ), + maxNearFarDist( maxNearFarDist ), + maxShadowSize( maxShadowSize ), + focusDistance( focusDistance ), + focusStart( focusStart ), + focusEnd( focusEnd ), + scale( scale ), + duellingFrustaAngle( duellingFrustaAngle ), + focusDistanceMin( focusDistanceMin ), + focusDistanceMax( focusDistanceMax ), + focusDistanceStep( focusDistanceStep ), + focusBase( focusBase ), + frustumNear( frustumNear ), + frustumFar( frustumFar ) + { + } + + bool AdjustFocus( float distance ) + { + if( handler ) return false; + + float scaledFocusDistance = FocusDistance(); + float minStepDistance = scaledFocusDistance - focusDistanceStep; + float maxStepDistance = scaledFocusDistance + focusDistanceStep; + + if( distance < minStepDistance ) distance = minStepDistance; + if( distance > maxStepDistance ) distance = maxStepDistance; + if( distance < focusDistanceMin ) distance = focusDistanceMin; + if( distance > focusDistanceMax ) distance = focusDistanceMax; + + scale = distance / focusBase; + return true; + } + + unsigned resolution; + bool handler; + + float maxNearFarDist; + float maxShadowSize; + float focusDistance; + float focusStart; + float focusEnd; + float scale; + float duellingFrustaAngle; + float focusDistanceMin; + float focusDistanceMax; + float focusDistanceStep; + float focusBase; + float frustumNear; + float frustumFar; + + float FocusStart() { return focusStart; } + float FocusEnd() { return focusEnd; } + float FocusDistance() { return focusDistance * scale; } + float ShadowRange() { return maxNearFarDist * scale; } + float ShadowLength() { return maxShadowSize * scale; } + unsigned Resolution() { return resolution; } + + void ComputeTrapezoidMapping + ( osg::Matrix camPostPerspectiveToLightViewSpaceTransform, + osg::Matrix &projection, + osg::Matrix &trapezoidalMapping ); + + bool ComputeViewFrustumProjection + ( osg::Matrix camPostPerspectiveToLightViewSpaceTransform, + osg::Matrix & projection, + osg::Vec3 polygon[8], int & count, + osg::Vec3 &top, osg::Vec3 &bottom, + float &dotLightViewDir ); + +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TrapezoidalShadowMapAlgorithm +// +bool useIdentity = false; +float fraction = 1.f / 10240; +/////////////////////////////////////////////////////////////////////////////// +using namespace osg; +using namespace osgShadow; +/////////////////////////////////////////////////////////////////////////////// +// Auxilliary functions +/////////////////////////////////////////////////////////////////////////////// +static double +ComputeConvexPolygonArea( Vec3 points[], int count, Matrix * matrix ) +{ + double area = 0.0; + + Vec3 av[9]; + + if( matrix ) + for( int i = 0; i < count; i++ ) av[i] = points[i] * *matrix; + else + for( int i = 0; i < count; i++ ) av[i] = points[i]; + + for( Vec3 *p0 = av + count - 1, *p1 = av; count--; p0 = p1, p1++ ) + area += p0->x() * p1->y() - p0->y() * p1->x(); + + return abs( area ) * 0.5; +} +//////////////////////////////////////////////////////////////////////////////// +// Code taken from TSM Recipe +/////////////////////////////////////////////////////////////////////////////// +#define ASSIGN_MAT(M, u0, u3, u6, u1, u4, u7, u2, u5, u8) { \ + M[0][0] = u0; M[0][1] = u3; M[0][2] = u6; \ + M[1][0] = u1; M[1][1] = u4; M[1][2] = u7; \ + M[2][0] = u2; M[2][1] = u5; M[2][2] = u8; \ +} +//////////////////////////////////////////////////////////////////////////////// +#define DET2(a, b, c, d) ((a) * (d) - (b) * (c)) + +#define DOT2(u, v) (u[0] * v[0] + u[1] * v[1]) +//////////////////////////////////////////////////////////////////////////////// +static void intersect +(double i[2], double g0[3], double g1[3], double h0[3], double h1[3]) +{ + double a, b; + + i[0] = i[1] = + 1.0f / DET2(g0[0] - g1[0], g0[1] - g1[1], h0[0] - h1[0], h0[1] - h1[1]); + + a = DET2(g0[0], g0[1], g1[0], g1[1]); + b = DET2(h0[0], h0[1], h1[0], h1[1]); + + i[0] *= DET2(a, g0[0] - g1[0], b, h0[0] - h1[0]); + i[1] *= DET2(a, g0[1] - g1[1], b, h0[1] - h1[1]); +} +//////////////////////////////////////////////////////////////////////////////// +static void map_Trapezoid_To_Square +(double TR[3][3], double t0[2], double t1[2], double t2[2], double t3[2]) +{ + double i[2], a, b, c, d; + + //M1 = R * T1 + a = 0.5f * (t2[0] - t3[0]); + b = 0.5f * (t2[1] - t3[1]); + + ASSIGN_MAT(TR, a , b , a * a + b * b, + b , -a , a * b - b * a, + 0.0f, 0.0f, 1.0f); + + //M2 = T2 * M1 = T2 * R * T1 + intersect(i, t0, t3, t1, t2); + + TR[0][2] = -DOT2(TR[0], i); + TR[1][2] = -DOT2(TR[1], i); + + //M1 = H * M2 = H * T2 * R * T1 + a = DOT2(TR[0], t2) + TR[0][2]; + b = DOT2(TR[1], t2) + TR[1][2]; + c = DOT2(TR[0], t3) + TR[0][2]; + d = DOT2(TR[1], t3) + TR[1][2]; + + a = -(a + c) / (b + d); + + TR[0][0] += TR[1][0] * a; + TR[0][1] += TR[1][1] * a; + TR[0][2] += TR[1][2] * a; + + //M2 = S1 * M1 = S1 * H * T2 * R * T1 + a = 1.0f / (DOT2(TR[0], t2) + TR[0][2]); + b = 1.0f / (DOT2(TR[1], t2) + TR[1][2]); + + TR[0][0] *= a; TR[0][1] *= a; TR[0][2] *= a; + TR[1][0] *= b; TR[1][1] *= b; TR[1][2] *= b; + + //M1 = N * M2 = N * S1 * H * T2 * R * T1 + TR[2][0] = TR[1][0]; TR[2][1] = TR[1][1]; TR[2][2] = TR[1][2]; + TR[1][2] += 1.0f; + + //M2 = T3 * M1 = T3 * N * S1 * H * T2 * R * T1 + a = DOT2(TR[1], t0) + TR[1][2]; + b = DOT2(TR[2], t0) + TR[2][2]; + c = DOT2(TR[1], t2) + TR[1][2]; + d = DOT2(TR[2], t2) + TR[2][2]; + + a = -0.5f * (a / b + c / d); + + TR[1][0] += TR[2][0] * a; + TR[1][1] += TR[2][1] * a; + TR[1][2] += TR[2][2] * a; + + //M1 = S2 * M2 = S2 * T3 * N * S1 * H * T2 * R * T1 + a = DOT2(TR[1], t0) + TR[1][2]; + b = DOT2(TR[2], t0) + TR[2][2]; + + c = -b / a; + + TR[1][0] *= c; TR[1][1] *= c; TR[1][2] *= c; +} +//////////////////////////////////////////////////////////////////////////////// +static osg::Matrix TrapezoidToSquare +( const Vec2 & t0, const Vec2 & t1, const Vec2 &t2, const Vec2 &t3 ) +{ + double at[4][3] = + { {t0[0],t0[1],0}, {t1[0],t1[1],0}, {t2[0],t2[1],0}, {t3[0],t3[1],0} }; + + double TR[3][3]; + map_Trapezoid_To_Square( TR, at[0], at[1], at[2], at[3] ); + + double N_T[16]; + N_T[0] = TR[0][0]; N_T[4] = TR[0][1]; N_T[ 8] = 0.0f; N_T[12] = TR[0][2]; + N_T[1] = TR[1][0]; N_T[5] = TR[1][1]; N_T[ 9] = 0.0f; N_T[13] = TR[1][2]; + N_T[2] = 0.0f; N_T[6] = 0.0f; N_T[10] = 1.0f; N_T[14] = 0.0f; + N_T[3] = TR[2][0]; N_T[7] = TR[2][1]; N_T[11] = 0.0f; N_T[15] = TR[2][2]; + + osg::Matrix trapezoidalMapping( N_T ); + +#if 0 + Vec3 check[] = { + + ( Vec3( t0, 0 ) * trapezoidalMapping ), + ( Vec3( t1, 0 ) * trapezoidalMapping ), + ( Vec3( t2, 0 ) * trapezoidalMapping ), + ( Vec3( t3, 0 ) * trapezoidalMapping ), + + ( Vec3( ( t0 + t2 ) * 0.5f, 0 ) * trapezoidalMapping ), + ( Vec3( ( t1 + t3 ) * 0.5f, 0 ) * trapezoidalMapping ) + }; +#endif + + return trapezoidalMapping; +} +//////////////////////////////////////////////////////////////////////////////// +static inline bool SortByXY( const Vec3& p1, const Vec3& p2 ) +{ + return (p1[0] < p2[0]) || ((p1[0] == p2[0]) && (p1[1] < p2[1])); +} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +void AlignProjection( Matrix & projection, Vec3 polygon[8], + int & count, Vec3 & top, Vec3 & bottom ) +{ + Vec3 origin = top * projection; + Vec3 vertVec = -( bottom * projection - top * projection ); + + assert( 0.0 < vertVec.length() ); + vertVec.normalize(); + Vec2 horzVec = Vec2( vertVec[1], -vertVec[0] ); + + // Rotation matrix to reorient projection to aligned with vertical axis + Matrix m( horzVec[0], vertVec[0], 0, 0, + horzVec[1], vertVec[1], 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 ); + + osg::BoundingBox bb; + for( int i = 0; i < count; i++ ) { + polygon[i] = polygon[i] * m; + bb.expandBy( polygon[i] ); + } + + projection = projection * m; + + m = Matrix::ortho( bb.xMin(), bb.xMax(), bb.yMin(), bb.yMax(), 1, -1 ); + + for( int i = 0; i < count; i++ ) + polygon[i] = polygon[i] * m; + + projection = projection * m; +} +//////////////////////////////////////////////////////////////////////////////// +bool TrapezoidalMapping::ComputeViewFrustumProjection + ( osg::Matrix camPostPerspectiveToLightViewSpaceTransform, + Matrix & projection, Vec3 polygon[8], int & count, Vec3 & top, Vec3 & bottom, + float &dotLightViewDir ) +{ +#if 1 // Shortened frustum + //float limit = cos( osg::inDegrees( this->duellingFrustaAngle ) ); + + Vec3 vecNear = Vec3(0, 0,-1) * camPostPerspectiveToLightViewSpaceTransform; + Vec3 vecFar = Vec3(0, 0, 1) * camPostPerspectiveToLightViewSpaceTransform; + Vec3 vecNearFar = vecFar - vecNear; + + float nearDist = vecNear.length(); + float farDist = vecFar.length(); + + float currNearFarDist = farDist - nearDist; + + // dot( vecNearFar.normal, lighVector ) can be used to check angle between + // camera dir and light vectors hence find duelling frusta case. + // Note that light vector in light space is 0,0,1 so + // dot( vecNearFar.normal, lighVector ) == vecNearFar.z / vecNearFar.length() + dotLightViewDir = vecNearFar[2] / currNearFarDist; + + // compute new shortened far distance + vecFar = vecNear + vecNearFar * this->ShadowRange() / currNearFarDist; + + // This is correct because light origin is the same as camera origin + + float n = -1.f, f = n + 2.f * this->ShadowRange() / currNearFarDist; + f = (vecFar * Matrix::inverse( camPostPerspectiveToLightViewSpaceTransform ))[2]; + + this->frustumNear = n; + this->frustumFar = f; + +#if 0 + float dist = ( Vec3( 0, 0, f ) * camPostPerspectiveToLightViewSpaceTransform - + Vec3( 0, 0, n ) * camPostPerspectiveToLightViewSpaceTransform ).length(); +#endif + + Vec3 frustum[8], cube[8] = { + Vec3( -1,-1,n ), Vec3( -1,1,n ), Vec3( 1,-1,n ), Vec3( 1,1,n ), + Vec3( -1,-1,f ), Vec3( -1,1,f ), Vec3( 1,-1,f ), Vec3( 1,1,f ), + }; + + BoundingBox bb; + for( int i = 0; i < 8; i++ ) { + frustum[i] = cube[i] * camPostPerspectiveToLightViewSpaceTransform; + bb.expandBy( frustum[i] ); + } + + // Note negative z range - its because when we set positive n & f + // both perspective or ortho matrix is constructed such that it looks into + // -n ... -f range + + projection.makeOrtho( bb.xMin(), bb.xMax(), + bb.yMin(), bb.yMax(), + -( bb.zMax() + this->ShadowLength() ), -bb.zMin() ); + + for( int i = 0; i < 8; i++ ) { + frustum[i] = frustum[i] * projection; + frustum[i][2] = cube[i][2]; //used later to identify duelling frusta + } + + std::sort( frustum, frustum+8, SortByXY ); + count = ConvexHull2D::chainConvexHull2D( frustum, 8, cube ); + + assert( 0 < count && count <= 9 ); + + // We don't need last vertex being the same as first one + count--; + + + // Projected near plane rect fits completely within far rect or opposite + bool bNoDuellingFrusta = ( count > 4 || + cube[0][2] != cube[1][2] || + cube[1][2] != cube[2][2] || + cube[2][2] != cube[3][2] ); +// fabs( dotLightViewDir ) > limit /*obey degrees limit*/ ); + + if( bNoDuellingFrusta ) { // Compute center line through near and far center + top = Vec3( 0,0,n ) * camPostPerspectiveToLightViewSpaceTransform; + bottom = Vec3( 0,0,f ) * camPostPerspectiveToLightViewSpaceTransform; + } else { // Compute center line through far bottom and top center + float f = cube[0][2]; // near or far plane corresponding to convex hull pts + top = Vec3( 0,-1, f ) * camPostPerspectiveToLightViewSpaceTransform; + bottom = Vec3( 0,1, f ) * camPostPerspectiveToLightViewSpaceTransform; + } + + for( int i = 0; i < count; i++ ) + polygon[i] = cube[i]; + + AlignProjection( projection, polygon, count, top, bottom ); + + return bNoDuellingFrusta; +#else + + +#endif +} +//////////////////////////////////////////////////////////////////////////////// +void TrapezoidalMapping::ComputeTrapezoidMapping +( osg::Matrix camPostPerspectiveToLightViewSpaceTransform, + osg::Matrix &projection, + osg::Matrix &trapezoidalMapping ) +{ + float dotLightViewDir; + Vec3 poly[8], top, bottom; + int count = 0; + + bool bNoDuellingFrusta = ComputeViewFrustumProjection + ( camPostPerspectiveToLightViewSpaceTransform, + projection, poly, count, top, bottom, dotLightViewDir ); + + double dfWorldTopBottomDistance = ( bottom - top ).length(); + + top = top * projection; + bottom = bottom * projection; + + Vec3 vertVec3 = bottom - top; + Vec2 vertVec( vertVec3.x(), vertVec3.y()); + + assert( 0.0 < vertVec.length() ); + + double dfLightProjectionSpaceToWorldRatio = + vertVec.length() / dfWorldTopBottomDistance; + + vertVec.normalize(); + Vec2 horzVec = Vec2( -vertVec[1], vertVec[0] ); + + // Compute trapezoid + int iBottom = 0, iTop = 0, iLeft = 0, iRight = 0; + Vec2 av[8]; + + for( int i = 0; i < count; i++ ) + { + Vec3 v3 = ( poly[i] - top ); + Vec2 v( v3.x(), v3.y() ); + av[i] = Vec2( horzVec * v, vertVec * v ); + if( av[iTop][1] > av[i][1] ) iTop = i; + if( av[iBottom][1] < av[i][1] ) iBottom = i; + } + + if( bNoDuellingFrusta ) { + + // Distance from lowest to furthest point + double lambda = av[ iBottom ][1] - av[ iTop ][1]; + double stdnear = 1, stdfar = -1; + + // Distance of focus in world units + double delta = this->FocusDistance() * dfLightProjectionSpaceToWorldRatio; + + // 0..lambda temporary set to 20% of distance from near + + double lastArea = 0.0; // Area of focus + float focusEnd = dotLightViewDir < 0 ? this->FocusEnd() : this->FocusStart(); + for( double focus = this->FocusStart(), step = 1.0 / this->Resolution(); + focus >= focusEnd; focus -= step ) { + + // Point of focus on std unitary frustum (near = 1 > sigma > -1 = far) + double sigma = ( stdfar - stdnear ) * focus + stdnear; // Eighty percent rule + // Focal length neccesary to compute trapezoid + double eta = lambda * delta * ( 1 + sigma ) / + ( lambda - 2 * delta - lambda * sigma ); + +#if 0 // Check if computed eta(near) gives projection which returns focus distance (d=sigma) + double d = -( lambda + 2 * eta ) * ( eta + delta ) + 2 * ( lambda + eta ) * eta; + d /= lambda * (eta + delta); +#endif + + double dfLeft = av[iTop][0] / eta, dfRight = dfLeft; + double vertOffset = eta - av[iTop][1]; + for( int i = 0; i < count; i++ ) { // Projective transform uses tangents + double df = av[i][0] / ( av[i][1] + vertOffset ); + if( dfLeft > df ) dfLeft = df; + if( dfRight < df ) dfRight = df; + } + + Vec2 centerTop = Vec2(top.x(),top.y()) + vertVec * av[iTop][1]; + Vec2 centerBottom = Vec2(top.x(),top.y()) + vertVec * av[iBottom][1]; + + osg::Matrix m = TrapezoidToSquare + ( /*lower left*/ centerBottom + horzVec * ( dfLeft * ( eta + lambda ) ), + /* lower right*/ centerBottom + horzVec * ( dfRight * ( eta + lambda ) ), + /* upper right */ centerTop + horzVec * ( dfRight * eta ), + /* upper left */ centerTop + horzVec * ( dfLeft * eta ) ); + + double area = ComputeConvexPolygonArea( poly, count, &m ); + + // Stop condition for matrix optimization + // Ignore if Trapezoidal matrix reverts orientation + // this happens sometimes TrapezoidToSquare does some funky stuff + if( area <= lastArea || eta <= 0 ) + break; + + trapezoidalMapping = m; + lastArea = area; + } + + } else { + + for( int i = 0; i < count; i++ ) { + if( av[iLeft][0] > av[i][0] ) iLeft = i; + if( av[iRight][0] < av[i][0] ) iRight = i; + } + + Vec2 center = Vec2(top.x(),top.y()) + vertVec * ( av[iBottom][1] + av[iTop][1] ) * 0.5f; + Vec2 vertvec = vertVec * ( av[iTop][1] - av[iBottom][1] ) * 0.5f; + Vec2 horzvec = horzVec * ( av[iRight][0] - av[iLeft][0] ) * 0.5f; + + trapezoidalMapping.set( horzvec[0], horzvec[1], 0, 0, + vertvec[0], vertvec[1], 0, 0, + 0, 0, 1, 0, + center[0], center[1], 0, 1 ); + + trapezoidalMapping.invert( trapezoidalMapping ); +#if 0 + { + float f = fraction, nf = 0.5, n = nf * f; + Vec3 c = + Vec3( 0,0,-1 ) * camPostPerspectiveToLightViewSpaceTransform * projection; + + trapezoidalMapping = + osg::Matrix::scale( osg::Vec3( 1, 1, nf * fraction ) ) * + osg::Matrix::translate( osg::Vec3( -c[0],-c[1], -f ) ) * + osg::Matrix::frustum( -( c[0] - -1 )*nf, ( 1 - c[0] )*nf, + -( c[1] - -1 )*nf, ( 1 - c[1] )*nf, n, 2 * f ); + } +#endif + } + + if( useIdentity ) + trapezoidalMapping.makeIdentity(); + + return; +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TrapezoidalShadowMapAlgorithm +// +TrapezoidalShadowMapAlgorithm::TrapezoidalShadowMapAlgorithm() +{ + tm = new TrapezoidalMapping; +} + +TrapezoidalShadowMapAlgorithm::~TrapezoidalShadowMapAlgorithm() +{ + delete tm; +} + +void TrapezoidalShadowMapAlgorithm::operator() + ( const osgShadow::ConvexPolyhedron* hullShadowedView, + const osg::Camera* cameraMain, + osg::Camera* cameraShadow ) const +{ + + osg::Matrix invCamView = cameraMain->getInverseViewMatrix(); + osg::Matrix view = cameraShadow->getViewMatrix(); + + osg::Matrix proj, camInvViewProj, trapezoidMapping; + + camInvViewProj = + osg::Matrix::inverse( cameraMain->getProjectionMatrix() ) * invCamView; + + tm->ComputeTrapezoidMapping( camInvViewProj * view, proj, trapezoidMapping ); + +// TrapezoidMappingUniform->set( trapezoidMapping ); + + cameraShadow->setProjectionMatrix( proj * trapezoidMapping ); +} + +} // end of osgShadow namespace \ No newline at end of file