root/OpenSceneGraph/trunk/src/osgShadow/MinimalShadowMap.cpp @ 11136

Revision 11136, 17.4 kB (checked in by robert, 4 years ago)

From Wojciech Lewandowski, "Here comes a list of small fixes in StandardShadowMap? and derived classes affecting LispSM techniques. Changes made against SVN trunk.

Fixes in StandardShadowMap?.cpp & MinimalShadowMap?.cpp were made for spotlight issues. There were cases when further located spotlights were not shadowing properly.

Small tweak in DebugShadowMap? & StandardShadowMap?.cpp to not limit shadow maps to texture2D (which should also allow texture2D arrays and cube maps). I simply replaced ptr to osg::Texture2D with pointer to osg::Texture. Interpretation of this member could be now changed with change of shaders in derived classes. This may be useful for guys who override LispSM or MinimalBoundsShadowMaps? techniques. Could be useful for implementation of PerspectiveCascadedShadowMaps? technique for example.

ConvexPolyhedron?.cpp & DebugShadowMap?.cpp contain debug HUD tweaks.

Change in ConvexPolyhedron?.cpp overcomes the regression problem with color per primitive binding which caused that shadow volume outlines stopped to draw. I simply changed PER_PRIMITIVE to PER_PRIMITIVE_SET and it works again.

Other adition is dump method I added to DebugShadowMap? which can be used in shadow debugging mode to dump current frame shadow volumes & scene to osg file. It could be then loaded into viewer and freely examined from different angles (which is difficult inside the application if shadow adopts to view and projection). "

Line 
1/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
2 *
3 * This library is open source and may be redistributed and/or modified under 
4 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
5 * (at your option) any later version.  The full license is in LICENSE file
6 * included with this distribution, and on the openscenegraph.org website.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * OpenSceneGraph Public License for more details.
12 *
13 * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski
14 * Thanks to to my company http://www.ai.com.pl for allowing me free this work.
15*/
16
17#include <osgShadow/MinimalShadowMap>
18#include <osgShadow/ConvexPolyhedron>
19#include <osg/MatrixTransform>
20#include <osgShadow/ShadowedScene>
21#include <osg/ComputeBoundsVisitor>
22
23using namespace osgShadow;
24
25#define PRINT_SHADOW_TEXEL_TO_PIXEL_ERROR 0
26
27MinimalShadowMap::MinimalShadowMap():
28    BaseClass(),
29    _maxFarPlane( FLT_MAX ),
30    _minLightMargin( 0 ),
31    _shadowReceivingCoarseBoundAccuracy( BOUNDING_BOX )
32{
33   
34}
35
36MinimalShadowMap::MinimalShadowMap
37(const MinimalShadowMap& copy, const osg::CopyOp& copyop) :
38    BaseClass(copy,copyop),
39    _maxFarPlane( copy._maxFarPlane ),
40    _minLightMargin( copy._minLightMargin ),
41    _shadowReceivingCoarseBoundAccuracy( copy._shadowReceivingCoarseBoundAccuracy )
42{
43}
44
45MinimalShadowMap::~MinimalShadowMap()
46{
47}
48
49osg::BoundingBox MinimalShadowMap::ViewData::computeShadowReceivingCoarseBounds()
50{
51    // Default slowest but most precise
52    ShadowReceivingCoarseBoundAccuracy accuracy = DEFAULT_ACCURACY;
53
54    MinimalShadowMap * msm = dynamic_cast< MinimalShadowMap* >( _st.get() );
55    if( msm ) accuracy = msm->getShadowReceivingCoarseBoundAccuracy();
56
57    if( accuracy == MinimalShadowMap::EMPTY_BOX )
58    {
59        // One may skip coarse scene bounds computation if light is infinite.
60        // Empty box will be intersected with view frustum so in the end
61        // view frustum will be used as bounds approximation.
62        // But if light is nondirectional and bounds come out too large
63        // they may bring the effect of almost 180 deg perspective set
64        // up for shadow camera. Such projection will significantly impact
65        // precision of further math.
66
67        return osg::BoundingBox();
68    }
69
70    if( accuracy == MinimalShadowMap::BOUNDING_SPHERE )
71    {
72        // faster but less precise rough scene bound computation
73        // however if compute near far is active it may bring quite good result
74        osg::Camera * camera = _cv->getRenderStage()->getCamera();
75        osg::Matrix m = camera->getViewMatrix() * _clampedProjection;
76
77        ConvexPolyhedron frustum;
78        frustum.setToUnitFrustum();
79        frustum.transform( osg::Matrix::inverse( m ), m );
80
81        osg::BoundingSphere bs =_st->getShadowedScene()->getBound();
82        osg::BoundingBox bb;
83        bb.expandBy( bs );
84        osg::Polytope box;
85        box.setToBoundingBox( bb );
86
87        frustum.cut( box );
88
89        // approximate sphere with octahedron. Ie first cut by box then
90        // additionaly cut with the same box rotated 45, 45, 45 deg.
91        box.transform( // rotate box around its center
92            osg::Matrix::translate( -bs.center() ) *
93            osg::Matrix::rotate( osg::PI_4, 0, 0, 1 ) *
94            osg::Matrix::rotate( osg::PI_4, 1, 1, 0 ) *
95            osg::Matrix::translate( bs.center() ) );
96        frustum.cut( box );
97   
98        return frustum.computeBoundingBox( );   
99    }
100   
101    if( accuracy == MinimalShadowMap::BOUNDING_BOX ) // Default
102    {
103        // more precise method but slower method
104        // bound visitor traversal takes lot of time for complex scenes
105        // (note that this adds to cull time)
106
107        osg::ComputeBoundsVisitor cbbv(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN);
108        cbbv.setTraversalMask(_st->getShadowedScene()->getCastsShadowTraversalMask());
109        _st->getShadowedScene()->osg::Group::traverse(cbbv);
110
111        return cbbv.getBoundingBox();
112    }
113
114    return osg::BoundingBox();
115}
116
117void MinimalShadowMap::ViewData::aimShadowCastingCamera
118 ( const osg::Light *light, const osg::Vec4 &lightPos,
119   const osg::Vec3 &lightDir, const osg::Vec3 &lightUp )
120{   
121    osg::BoundingBox bb = computeScenePolytopeBounds();
122    if( !bb.valid() ) { // empty scene or looking at the sky - substitute something
123        bb.expandBy( osg::BoundingSphere( _cv->getEyePoint(), 1 ) );
124    }
125
126    osg::Vec3 up = lightUp;
127
128    if( up.length2() <= 0 )
129    {
130    // This is extra step (not really needed but helpful in debuging)
131    // Compute such lightUp vector that shadow cam is intuitively aligned with eye
132    // We compute this vector on -ZY view plane, perpendicular to light direction
133    // Matrix m = ViewToWorld
134#if 0
135        osg::Matrix m = osg::Matrix::inverse( *cv.getModelViewMatrix() );
136        osg::Vec3 camFw( -m( 2, 0 ), -m( 2, 1 ), -m( 2, 2 ) );
137        camFw.normalize();
138
139        osg::Vec3 camUp( m( 1, 0 ), m( 1, 1 ), m( 1, 2 ) );
140        camUp.normalize();
141
142        up = camUp * ( camFw * lightDir ) - camFw * ( camUp * lightDir );
143        up.normalize();
144#else
145        osg::Matrix m = osg::Matrix::inverse( *_cv->getModelViewMatrix() );
146        // OpenGL std cam looks along -Z axis so Cam Fw = [ 0  0  -1  0 ] * m
147        up.set( -m( 2, 0 ), -m( 2, 1 ), -m( 2, 2 ) );
148#endif
149    }
150
151    BaseClass::ViewData::aimShadowCastingCamera
152                                    ( bb, light, lightPos, lightDir, up );
153
154    // Intersect scene Receiving Shadow Polytope with shadow camera frustum
155    // Important for cases where Scene extend beyond shadow camera frustum
156    // From this moment shadowed scene portion is fully contained by both
157    // main camera frustum and shadow camera frustum
158    osg::Matrix mvp = _camera->getViewMatrix() * _camera->getProjectionMatrix();
159    cutScenePolytope( osg::Matrix::inverse( mvp ),  mvp );
160
161    MinimalShadowMap::ViewData::frameShadowCastingCamera
162            ( _cv->getRenderStage()->getCamera(), _camera.get(), 0 );
163}
164
165void MinimalShadowMap::ViewData::frameShadowCastingCamera
166     ( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass )
167{
168    osg::Matrix mvp =
169        cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix();
170
171    ConvexPolyhedron polytope = _sceneReceivingShadowPolytope;
172    std::vector<osg::Vec3d> points = _sceneReceivingShadowPolytopePoints;
173
174    osg::BoundingBox bb = computeScenePolytopeBounds( mvp );
175
176    // projection was trimmed above, need to recompute mvp
177    if( bb.valid() && *_minLightMarginPtr > 0 ) {
178        //        bb._max += osg::Vec3( 1, 1, 1 );
179        //        bb._min -= osg::Vec3( 1, 1, 1 );
180
181        osg::Matrix transform = osg::Matrix::inverse( mvp );
182
183        // Code below was working only for directional lights ie when projection was ortho
184        // osg::Vec3d normal = osg::Matrix::transform3x3( osg::Vec3d( 0,0,-1)., transfrom );
185
186        // So I replaced it with safer code working with spot lights as well
187        osg::Vec3d normal =
188            osg::Vec3d(0,0,-1) * transform - osg::Vec3d(0,0,1) * transform;
189
190        normal.normalize();
191        _sceneReceivingShadowPolytope.extrude( normal * *_minLightMarginPtr );
192
193        // Zero pass does crude shadowed scene hull approximation.
194        // Its important to cut it to coarse light frustum properly
195        // at this stage.
196        // If not cut and polytope extends beyond shadow projection clip
197        // space (-1..1), it may get "twisted" by precisely adjusted shadow cam
198        // projection in second pass.
199
200        if ( pass == 0 )
201        { // Make sure extruded polytope does not extend beyond light frustum
202            osg::Polytope lightFrustum;
203            lightFrustum.setToUnitFrustum();
204            lightFrustum.transformProvidingInverse( mvp );
205            _sceneReceivingShadowPolytope.cut( lightFrustum );
206        }
207
208        _sceneReceivingShadowPolytopePoints.clear();
209        _sceneReceivingShadowPolytope.getPoints
210            ( _sceneReceivingShadowPolytopePoints );
211
212        bb = computeScenePolytopeBounds( mvp );
213    }
214
215    setDebugPolytope( "extended",
216        _sceneReceivingShadowPolytope, osg::Vec4( 1, 0.5, 0, 1 ), osg::Vec4( 1, 0.5, 0, 0.1 ) );
217
218    _sceneReceivingShadowPolytope = polytope;
219    _sceneReceivingShadowPolytopePoints = points;
220   
221    // Warning: Trim light projection at near plane may remove shadowing
222    // from objects outside of view space but still casting shadows into it.
223    // I have not noticed this issue so I left mask at default: all bits set.
224    if( bb.valid() )
225        trimProjection( cameraShadow->getProjectionMatrix(), bb, 1|2|4|8|16|32 );
226
227    ///// Debuging stuff //////////////////////////////////////////////////////////
228    setDebugPolytope( "scene", _sceneReceivingShadowPolytope, osg::Vec4(0,1,0,1) );   
229
230
231#if PRINT_SHADOW_TEXEL_TO_PIXEL_ERROR
232    if( pass == 1 )
233        displayShadowTexelToPixelErrors
234            ( cameraMain, cameraShadow, &_sceneReceivingShadowPolytope );
235#endif
236
237}
238
239void MinimalShadowMap::ViewData::cullShadowReceivingScene( )
240{
241    BaseClass::ViewData::cullShadowReceivingScene( );
242
243    _clampedProjection = *_cv->getProjectionMatrix();
244
245    if( _cv->getComputeNearFarMode() ) {
246
247        // Redo steps from CullVisitor::popProjectionMatrix()
248        // which clamps projection matrix when Camera & Projection
249        // completes traversal of their children
250
251        // We have to do this now manually
252        // because we did not complete camera traversal yet but
253        // we need to know how this clamped projection matrix will be
254
255        _cv->computeNearPlane();
256       
257        osgUtil::CullVisitor::value_type n = _cv->getCalculatedNearPlane();
258        osgUtil::CullVisitor::value_type f = _cv->getCalculatedFarPlane();
259
260        if( n < f )
261            _cv->clampProjectionMatrix( _clampedProjection, n, f );
262    }
263
264    // Aditionally clamp far plane if shadows don't need to be cast as
265    // far as main projection far plane
266    if( 0 < *_maxFarPlanePtr )
267        clampProjection( _clampedProjection, 0.f, *_maxFarPlanePtr );
268
269    // Give derived classes chance to initialize _sceneReceivingShadowPolytope     
270    osg::BoundingBox bb = computeShadowReceivingCoarseBounds( );
271    if( bb.valid() )
272        _sceneReceivingShadowPolytope.setToBoundingBox( bb );
273    else 
274        _sceneReceivingShadowPolytope.clear();
275
276    // Cut initial scene using main camera frustum.
277    // Cutting will work correctly on empty polytope too.
278    // Take into consideration near far calculation and _maxFarPlane variable
279
280
281    osg::Matrix mvp = *_cv->getModelViewMatrix() * _clampedProjection;
282
283    cutScenePolytope( osg::Matrix::inverse( mvp ), mvp );
284
285    setDebugPolytope
286        ( "frustum", _sceneReceivingShadowPolytope, osg::Vec4(1,0,1,1));
287
288}
289
290void MinimalShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv )
291{
292    BaseClass::ViewData::init( st, cv );
293
294    _modellingSpaceToWorldPtr = &st->_modellingSpaceToWorld;
295    _minLightMarginPtr        = &st->_minLightMargin;
296    _maxFarPlanePtr           = &st->_maxFarPlane;
297}
298
299void MinimalShadowMap::ViewData::cutScenePolytope
300    ( const osg::Matrix & transform,
301      const osg::Matrix & inverse,
302      const osg::BoundingBox & bb )
303{   
304    _sceneReceivingShadowPolytopePoints.clear();
305
306    if( bb.valid() ) {
307        osg::Polytope polytope;
308        polytope.setToBoundingBox( bb );
309        polytope.transformProvidingInverse( inverse );
310        _sceneReceivingShadowPolytope.cut( polytope );       
311        _sceneReceivingShadowPolytope.getPoints
312                                ( _sceneReceivingShadowPolytopePoints );
313    } else
314        _sceneReceivingShadowPolytope.clear();
315}
316
317osg::BoundingBox
318    MinimalShadowMap::ViewData::computeScenePolytopeBounds( const osg::Matrix & m )
319{
320    osg::BoundingBox bb;
321
322    if( &m )
323        for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i )
324            bb.expandBy( _sceneReceivingShadowPolytopePoints[i] * m );
325    else 
326        for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i )
327            bb.expandBy( _sceneReceivingShadowPolytopePoints[i] );
328
329    return bb;
330}
331
332
333
334// Utility methods for adjusting projection matrices
335
336void MinimalShadowMap::ViewData::trimProjection
337    ( osg::Matrixd & projectionMatrix, osg::BoundingBox bb, unsigned int trimMask )
338{
339#if 1
340    if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return;
341    double l = -1, r = 1, b = -1, t = 1, n = 1, f = -1;
342
343#if 0
344    // make sure bounding box does not extend beyond unit frustum clip range
345    for( int i = 0; i < 3; i ++ ) {
346        if( bb._min[i] < -1 ) bb._min[i] = -1;
347        if( bb._max[i] >  1 ) bb._max[i] =  1;
348    }
349#endif
350
351    if( trimMask & 1 ) l = bb._min[0];
352    if( trimMask & 2 ) r = bb._max[0];
353    if( trimMask & 4 ) b = bb._min[1];
354    if( trimMask & 8 ) t = bb._max[1];
355    if( trimMask & 16 ) n = -bb._min[2];
356    if( trimMask & 32 ) f = -bb._max[2];
357
358    projectionMatrix.postMult( osg::Matrix::ortho( l,r,b,t,n,f ) );
359#else
360    if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return;
361    double l, r, t, b, n, f;
362    bool ortho = projectionMatrix.getOrtho( l, r, b, t, n, f );
363    if( !ortho && !projectionMatrix.getFrustum( l, r, b, t, n, f ) )
364        return; // rotated or skewed or other crooked projection - give up
365
366    // make sure bounding box does not extend beyond unit frustum clip range
367    for( int i = 0; i < 3; i ++ ) {
368        if( bb._min[i] < -1 ) bb._min[i] = -1;
369        if( bb._max[i] >  1 ) bb._max[i] =  1;
370    }
371
372    osg::Matrix projectionToView = osg::Matrix::inverse( projectionMatrix );
373   
374    osg::Vec3 min =
375        osg::Vec3( bb._min[0], bb._min[1], bb._min[2] ) * projectionToView;
376
377    osg::Vec3 max =
378        osg::Vec3( bb._max[0], bb._max[1], bb._max[2] ) * projectionToView;
379
380    if( trimMask & 16 ) { // trim near
381        if( !ortho ) { // recalc frustum corners on new near plane
382            l *= -min[2] / n;
383            r *= -min[2] / n;
384            b *= -min[2] / n;
385            t *= -min[2] / n;
386        }
387        n = -min[2];
388    }
389
390    if( trimMask & 32 ) // trim far
391        f = -max[2];
392
393    if( !ortho ) {
394        min[0] *=  -n / min[2];
395        min[1] *=  -n / min[2];
396        max[0] *=  -n / max[2];
397        max[1] *=  -n / max[2];
398    }
399
400    if( l < r ) { // check for inverted X range
401        if( l < min[0] && ( trimMask & 1 ) ) l = min[0];
402        if( r > max[0] && ( trimMask & 2 ) ) r = max[0];
403    } else {
404        if( l > min[0] && ( trimMask & 1 ) ) l = min[0];
405        if( r < max[0] && ( trimMask & 2 ) ) r = max[0];
406    }
407
408    if( b < t ) { // check for inverted Y range
409        if( b < min[1] && ( trimMask & 4 ) ) b = min[1];
410        if( t > max[1] && ( trimMask & 8 ) ) t = max[1];
411    } else {
412        if( b > min[1] && ( trimMask & 4 ) ) b = min[1];
413        if( t < max[1] && ( trimMask & 8 ) ) t = max[1];
414    }
415
416    if( ortho )
417        projectionMatrix.makeOrtho( l, r, b, t, n, f );
418    else 
419        projectionMatrix.makeFrustum( l, r, b, t, n, f );
420#endif
421}
422
423void MinimalShadowMap::ViewData::clampProjection
424                    ( osg::Matrixd & projection, float new_near, float new_far )
425{
426    double r, l, t, b, n, f;
427    bool perspective = projection.getFrustum( l, r, b, t, n, f );
428    if( !perspective && !projection.getOrtho( l, r, b, t, n, f ) )
429    {
430        // What to do here ?
431        osg::notify( osg::WARN )
432            << "MinimalShadowMap::clampProjectionFarPlane failed - non standard matrix"
433            << std::endl;
434
435    } else if( n < new_near || new_far < f ) {
436
437        if( n < new_near && new_near < f ) {
438            if( perspective ) {
439                l *= new_near / n;
440                r *= new_near / n;
441                b *= new_near / n;
442                t *= new_near / n;
443            }
444            n = new_near;
445        }
446
447        if( n < new_far && new_far < f ) {
448            f = new_far;             
449        }
450
451        if( perspective )
452            projection.makeFrustum( l, r, b, t, n, f );
453        else
454            projection.makeOrtho( l, r, b, t, n, f );                     
455    }
456}
457
458// Imagine following scenario:
459// We stand in the room and look through the window.
460// How should our view change if we were looking through larger window ?
461// In other words how should projection be adjusted if
462// window had grown by some margin ?
463// Method computes such new projection which maintains perpective/world ratio
464
465void MinimalShadowMap::ViewData::extendProjection
466    ( osg::Matrixd & projection, osg::Viewport * viewport, const osg::Vec2& margin )
467{
468  double l,r,b,t,n,f;
469
470  //osg::Matrix projection = camera.getProjectionMatrix();
471
472  bool frustum = projection.getFrustum( l,r,b,t,n,f );
473
474  if( !frustum && !projection.getOrtho( l,r,b,t,n,f ) ) {
475    osg::notify( osg::WARN )
476        << " Awkward projection matrix. ComputeExtendedProjection failed"
477        << std::endl;
478    return;
479  }
480
481  osg::Matrix window = viewport->computeWindowMatrix();
482 
483  osg::Vec3 vMin( viewport->x() - margin.x(),
484                 viewport->y() - margin.y(),
485                 0.0 );
486
487  osg::Vec3 vMax( viewport->width() + margin.x() * 2  + vMin.x(),
488                  viewport->height() + margin.y() * 2  + vMin.y(),
489                  0.0 );
490 
491  osg::Matrix inversePW = osg::Matrix::inverse( projection * window );
492
493  vMin = vMin * inversePW;
494  vMax = vMax * inversePW;
495 
496  l = vMin.x();
497  r = vMax.x();
498  b = vMin.y();
499  t = vMax.y();
500
501  if( frustum )
502    projection.makeFrustum( l,r,b,t,n,f );
503  else 
504    projection.makeOrtho( l,r,b,t,n,f );
505}
Note: See TracBrowser for help on using the browser.