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

Revision 11176, 17.9 kB (checked in by robert, 4 years ago)

From Wojcoech Lewandowski, "Attched are aimShadowCastingCamera() call changes, I have described in former post. Basically now MinimalShadowMap? overrides first variant and keeps second. So both variants of aimShadowCastingCamera are clearly defined in MinimalShadowMap::ViewData? scope. This way compilers have no problem and code looks less obscure. Changes made against the trunk."

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::BoundingSphere &bs,
119                                        const osg::Light *light,
120                                        const osg::Vec4 &lightPos,
121                                        const osg::Vec3 &lightDir,
122                                        const osg::Vec3 &lightUpVector
123                                        /* by default = osg::Vec3( 0, 1 0 )*/ )
124{
125    BaseClass::ViewData::aimShadowCastingCamera( bs, light, lightPos, lightDir, lightUpVector );
126}
127
128void MinimalShadowMap::ViewData::aimShadowCastingCamera
129 ( const osg::Light *light, const osg::Vec4 &lightPos,
130   const osg::Vec3 &lightDir, const osg::Vec3 &lightUp )
131{   
132    osg::BoundingBox bb = computeScenePolytopeBounds();
133    if( !bb.valid() ) { // empty scene or looking at the sky - substitute something
134        bb.expandBy( osg::BoundingSphere( _cv->getEyePoint(), 1 ) );
135    }
136
137    osg::Vec3 up = lightUp;
138
139    if( up.length2() <= 0 )
140    {
141    // This is extra step (not really needed but helpful in debuging)
142    // Compute such lightUp vector that shadow cam is intuitively aligned with eye
143    // We compute this vector on -ZY view plane, perpendicular to light direction
144    // Matrix m = ViewToWorld
145#if 0
146        osg::Matrix m = osg::Matrix::inverse( *cv.getModelViewMatrix() );
147        osg::Vec3 camFw( -m( 2, 0 ), -m( 2, 1 ), -m( 2, 2 ) );
148        camFw.normalize();
149
150        osg::Vec3 camUp( m( 1, 0 ), m( 1, 1 ), m( 1, 2 ) );
151        camUp.normalize();
152
153        up = camUp * ( camFw * lightDir ) - camFw * ( camUp * lightDir );
154        up.normalize();
155#else
156        osg::Matrix m = osg::Matrix::inverse( *_cv->getModelViewMatrix() );
157        // OpenGL std cam looks along -Z axis so Cam Fw = [ 0  0  -1  0 ] * m
158        up.set( -m( 2, 0 ), -m( 2, 1 ), -m( 2, 2 ) );
159#endif
160    }
161
162    aimShadowCastingCamera( osg::BoundingSphere( bb ), light, lightPos, lightDir, up );
163
164    // Intersect scene Receiving Shadow Polytope with shadow camera frustum
165    // Important for cases where Scene extend beyond shadow camera frustum
166    // From this moment shadowed scene portion is fully contained by both
167    // main camera frustum and shadow camera frustum
168    osg::Matrix mvp = _camera->getViewMatrix() * _camera->getProjectionMatrix();
169    cutScenePolytope( osg::Matrix::inverse( mvp ),  mvp );
170
171    MinimalShadowMap::ViewData::frameShadowCastingCamera
172            ( _cv->getRenderStage()->getCamera(), _camera.get(), 0 );
173}
174
175void MinimalShadowMap::ViewData::frameShadowCastingCamera
176     ( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass )
177{
178    osg::Matrix mvp =
179        cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix();
180
181    ConvexPolyhedron polytope = _sceneReceivingShadowPolytope;
182    std::vector<osg::Vec3d> points = _sceneReceivingShadowPolytopePoints;
183
184    osg::BoundingBox bb = computeScenePolytopeBounds( mvp );
185
186    // projection was trimmed above, need to recompute mvp
187    if( bb.valid() && *_minLightMarginPtr > 0 ) {
188        //        bb._max += osg::Vec3( 1, 1, 1 );
189        //        bb._min -= osg::Vec3( 1, 1, 1 );
190
191        osg::Matrix transform = osg::Matrix::inverse( mvp );
192
193        // Code below was working only for directional lights ie when projection was ortho
194        // osg::Vec3d normal = osg::Matrix::transform3x3( osg::Vec3d( 0,0,-1)., transfrom );
195
196        // So I replaced it with safer code working with spot lights as well
197        osg::Vec3d normal =
198            osg::Vec3d(0,0,-1) * transform - osg::Vec3d(0,0,1) * transform;
199
200        normal.normalize();
201        _sceneReceivingShadowPolytope.extrude( normal * *_minLightMarginPtr );
202
203        // Zero pass does crude shadowed scene hull approximation.
204        // Its important to cut it to coarse light frustum properly
205        // at this stage.
206        // If not cut and polytope extends beyond shadow projection clip
207        // space (-1..1), it may get "twisted" by precisely adjusted shadow cam
208        // projection in second pass.
209
210        if ( pass == 0 )
211        { // Make sure extruded polytope does not extend beyond light frustum
212            osg::Polytope lightFrustum;
213            lightFrustum.setToUnitFrustum();
214            lightFrustum.transformProvidingInverse( mvp );
215            _sceneReceivingShadowPolytope.cut( lightFrustum );
216        }
217
218        _sceneReceivingShadowPolytopePoints.clear();
219        _sceneReceivingShadowPolytope.getPoints
220            ( _sceneReceivingShadowPolytopePoints );
221
222        bb = computeScenePolytopeBounds( mvp );
223    }
224
225    setDebugPolytope( "extended",
226        _sceneReceivingShadowPolytope, osg::Vec4( 1, 0.5, 0, 1 ), osg::Vec4( 1, 0.5, 0, 0.1 ) );
227
228    _sceneReceivingShadowPolytope = polytope;
229    _sceneReceivingShadowPolytopePoints = points;
230   
231    // Warning: Trim light projection at near plane may remove shadowing
232    // from objects outside of view space but still casting shadows into it.
233    // I have not noticed this issue so I left mask at default: all bits set.
234    if( bb.valid() )
235        trimProjection( cameraShadow->getProjectionMatrix(), bb, 1|2|4|8|16|32 );
236
237    ///// Debuging stuff //////////////////////////////////////////////////////////
238    setDebugPolytope( "scene", _sceneReceivingShadowPolytope, osg::Vec4(0,1,0,1) );   
239
240
241#if PRINT_SHADOW_TEXEL_TO_PIXEL_ERROR
242    if( pass == 1 )
243        displayShadowTexelToPixelErrors
244            ( cameraMain, cameraShadow, &_sceneReceivingShadowPolytope );
245#endif
246
247}
248
249void MinimalShadowMap::ViewData::cullShadowReceivingScene( )
250{
251    BaseClass::ViewData::cullShadowReceivingScene( );
252
253    _clampedProjection = *_cv->getProjectionMatrix();
254
255    if( _cv->getComputeNearFarMode() ) {
256
257        // Redo steps from CullVisitor::popProjectionMatrix()
258        // which clamps projection matrix when Camera & Projection
259        // completes traversal of their children
260
261        // We have to do this now manually
262        // because we did not complete camera traversal yet but
263        // we need to know how this clamped projection matrix will be
264
265        _cv->computeNearPlane();
266       
267        osgUtil::CullVisitor::value_type n = _cv->getCalculatedNearPlane();
268        osgUtil::CullVisitor::value_type f = _cv->getCalculatedFarPlane();
269
270        if( n < f )
271            _cv->clampProjectionMatrix( _clampedProjection, n, f );
272    }
273
274    // Aditionally clamp far plane if shadows don't need to be cast as
275    // far as main projection far plane
276    if( 0 < *_maxFarPlanePtr )
277        clampProjection( _clampedProjection, 0.f, *_maxFarPlanePtr );
278
279    // Give derived classes chance to initialize _sceneReceivingShadowPolytope     
280    osg::BoundingBox bb = computeShadowReceivingCoarseBounds( );
281    if( bb.valid() )
282        _sceneReceivingShadowPolytope.setToBoundingBox( bb );
283    else 
284        _sceneReceivingShadowPolytope.clear();
285
286    // Cut initial scene using main camera frustum.
287    // Cutting will work correctly on empty polytope too.
288    // Take into consideration near far calculation and _maxFarPlane variable
289
290
291    osg::Matrix mvp = *_cv->getModelViewMatrix() * _clampedProjection;
292
293    cutScenePolytope( osg::Matrix::inverse( mvp ), mvp );
294
295    setDebugPolytope
296        ( "frustum", _sceneReceivingShadowPolytope, osg::Vec4(1,0,1,1));
297
298}
299
300void MinimalShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv )
301{
302    BaseClass::ViewData::init( st, cv );
303
304    _modellingSpaceToWorldPtr = &st->_modellingSpaceToWorld;
305    _minLightMarginPtr        = &st->_minLightMargin;
306    _maxFarPlanePtr           = &st->_maxFarPlane;
307}
308
309void MinimalShadowMap::ViewData::cutScenePolytope
310    ( const osg::Matrix & transform,
311      const osg::Matrix & inverse,
312      const osg::BoundingBox & bb )
313{   
314    _sceneReceivingShadowPolytopePoints.clear();
315
316    if( bb.valid() ) {
317        osg::Polytope polytope;
318        polytope.setToBoundingBox( bb );
319        polytope.transformProvidingInverse( inverse );
320        _sceneReceivingShadowPolytope.cut( polytope );       
321        _sceneReceivingShadowPolytope.getPoints
322                                ( _sceneReceivingShadowPolytopePoints );
323    } else
324        _sceneReceivingShadowPolytope.clear();
325}
326
327osg::BoundingBox
328    MinimalShadowMap::ViewData::computeScenePolytopeBounds( const osg::Matrix & m )
329{
330    osg::BoundingBox bb;
331
332    if( &m )
333        for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i )
334            bb.expandBy( _sceneReceivingShadowPolytopePoints[i] * m );
335    else 
336        for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i )
337            bb.expandBy( _sceneReceivingShadowPolytopePoints[i] );
338
339    return bb;
340}
341
342
343
344// Utility methods for adjusting projection matrices
345
346void MinimalShadowMap::ViewData::trimProjection
347    ( osg::Matrixd & projectionMatrix, osg::BoundingBox bb, unsigned int trimMask )
348{
349#if 1
350    if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return;
351    double l = -1, r = 1, b = -1, t = 1, n = 1, f = -1;
352
353#if 0
354    // make sure bounding box does not extend beyond unit frustum clip range
355    for( int i = 0; i < 3; i ++ ) {
356        if( bb._min[i] < -1 ) bb._min[i] = -1;
357        if( bb._max[i] >  1 ) bb._max[i] =  1;
358    }
359#endif
360
361    if( trimMask & 1 ) l = bb._min[0];
362    if( trimMask & 2 ) r = bb._max[0];
363    if( trimMask & 4 ) b = bb._min[1];
364    if( trimMask & 8 ) t = bb._max[1];
365    if( trimMask & 16 ) n = -bb._min[2];
366    if( trimMask & 32 ) f = -bb._max[2];
367
368    projectionMatrix.postMult( osg::Matrix::ortho( l,r,b,t,n,f ) );
369#else
370    if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return;
371    double l, r, t, b, n, f;
372    bool ortho = projectionMatrix.getOrtho( l, r, b, t, n, f );
373    if( !ortho && !projectionMatrix.getFrustum( l, r, b, t, n, f ) )
374        return; // rotated or skewed or other crooked projection - give up
375
376    // make sure bounding box does not extend beyond unit frustum clip range
377    for( int i = 0; i < 3; i ++ ) {
378        if( bb._min[i] < -1 ) bb._min[i] = -1;
379        if( bb._max[i] >  1 ) bb._max[i] =  1;
380    }
381
382    osg::Matrix projectionToView = osg::Matrix::inverse( projectionMatrix );
383   
384    osg::Vec3 min =
385        osg::Vec3( bb._min[0], bb._min[1], bb._min[2] ) * projectionToView;
386
387    osg::Vec3 max =
388        osg::Vec3( bb._max[0], bb._max[1], bb._max[2] ) * projectionToView;
389
390    if( trimMask & 16 ) { // trim near
391        if( !ortho ) { // recalc frustum corners on new near plane
392            l *= -min[2] / n;
393            r *= -min[2] / n;
394            b *= -min[2] / n;
395            t *= -min[2] / n;
396        }
397        n = -min[2];
398    }
399
400    if( trimMask & 32 ) // trim far
401        f = -max[2];
402
403    if( !ortho ) {
404        min[0] *=  -n / min[2];
405        min[1] *=  -n / min[2];
406        max[0] *=  -n / max[2];
407        max[1] *=  -n / max[2];
408    }
409
410    if( l < r ) { // check for inverted X range
411        if( l < min[0] && ( trimMask & 1 ) ) l = min[0];
412        if( r > max[0] && ( trimMask & 2 ) ) r = max[0];
413    } else {
414        if( l > min[0] && ( trimMask & 1 ) ) l = min[0];
415        if( r < max[0] && ( trimMask & 2 ) ) r = max[0];
416    }
417
418    if( b < t ) { // check for inverted Y range
419        if( b < min[1] && ( trimMask & 4 ) ) b = min[1];
420        if( t > max[1] && ( trimMask & 8 ) ) t = max[1];
421    } else {
422        if( b > min[1] && ( trimMask & 4 ) ) b = min[1];
423        if( t < max[1] && ( trimMask & 8 ) ) t = max[1];
424    }
425
426    if( ortho )
427        projectionMatrix.makeOrtho( l, r, b, t, n, f );
428    else 
429        projectionMatrix.makeFrustum( l, r, b, t, n, f );
430#endif
431}
432
433void MinimalShadowMap::ViewData::clampProjection
434                    ( osg::Matrixd & projection, float new_near, float new_far )
435{
436    double r, l, t, b, n, f;
437    bool perspective = projection.getFrustum( l, r, b, t, n, f );
438    if( !perspective && !projection.getOrtho( l, r, b, t, n, f ) )
439    {
440        // What to do here ?
441        osg::notify( osg::WARN )
442            << "MinimalShadowMap::clampProjectionFarPlane failed - non standard matrix"
443            << std::endl;
444
445    } else if( n < new_near || new_far < f ) {
446
447        if( n < new_near && new_near < f ) {
448            if( perspective ) {
449                l *= new_near / n;
450                r *= new_near / n;
451                b *= new_near / n;
452                t *= new_near / n;
453            }
454            n = new_near;
455        }
456
457        if( n < new_far && new_far < f ) {
458            f = new_far;             
459        }
460
461        if( perspective )
462            projection.makeFrustum( l, r, b, t, n, f );
463        else
464            projection.makeOrtho( l, r, b, t, n, f );                     
465    }
466}
467
468// Imagine following scenario:
469// We stand in the room and look through the window.
470// How should our view change if we were looking through larger window ?
471// In other words how should projection be adjusted if
472// window had grown by some margin ?
473// Method computes such new projection which maintains perpective/world ratio
474
475void MinimalShadowMap::ViewData::extendProjection
476    ( osg::Matrixd & projection, osg::Viewport * viewport, const osg::Vec2& margin )
477{
478  double l,r,b,t,n,f;
479
480  //osg::Matrix projection = camera.getProjectionMatrix();
481
482  bool frustum = projection.getFrustum( l,r,b,t,n,f );
483
484  if( !frustum && !projection.getOrtho( l,r,b,t,n,f ) ) {
485    osg::notify( osg::WARN )
486        << " Awkward projection matrix. ComputeExtendedProjection failed"
487        << std::endl;
488    return;
489  }
490
491  osg::Matrix window = viewport->computeWindowMatrix();
492 
493  osg::Vec3 vMin( viewport->x() - margin.x(),
494                 viewport->y() - margin.y(),
495                 0.0 );
496
497  osg::Vec3 vMax( viewport->width() + margin.x() * 2  + vMin.x(),
498                  viewport->height() + margin.y() * 2  + vMin.y(),
499                  0.0 );
500 
501  osg::Matrix inversePW = osg::Matrix::inverse( projection * window );
502
503  vMin = vMin * inversePW;
504  vMax = vMax * inversePW;
505 
506  l = vMin.x();
507  r = vMax.x();
508  b = vMin.y();
509  t = vMax.y();
510
511  if( frustum )
512    projection.makeFrustum( l,r,b,t,n,f );
513  else 
514    projection.makeOrtho( l,r,b,t,n,f );
515}
Note: See TracBrowser for help on using the browser.