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

Revision 13041, 18.6 kB (checked in by robert, 2 years ago)

Ran script to remove trailing spaces and tabs

  • Property svn:eol-style set to native
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    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 && _frameShadowCastingCameraPasses > 1 )
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    if( pass == _frameShadowCastingCameraPasses - 1 )
248    {
249#if 1
250        {
251            osg::Matrix mvp = cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix();
252            ConvexPolyhedron frustum;
253            frustum.setToUnitFrustum();
254            frustum.transform( osg::Matrix::inverse( mvp ), mvp );
255
256            setDebugPolytope( "shadowCamFrustum", frustum, osg::Vec4(0,0,1,1) );
257        }
258
259        {
260            osg::Matrix mvp = cameraMain->getViewMatrix() * cameraMain->getProjectionMatrix();
261            ConvexPolyhedron frustum;
262            frustum.setToUnitFrustum();
263            frustum.transform( osg::Matrix::inverse( mvp ), mvp );
264
265            setDebugPolytope( "mainCamFrustum", frustum, osg::Vec4(1,1,1,1) );
266        }
267#endif
268        std::string * filename = getDebugDump( );
269        if( filename && !filename->empty() )
270        {
271            dump( *filename );
272            filename->clear();
273        }
274    }
275}
276
277void MinimalShadowMap::ViewData::cullShadowReceivingScene( )
278{
279    BaseClass::ViewData::cullShadowReceivingScene( );
280
281    _clampedProjection = *_cv->getProjectionMatrix();
282
283    if( _cv->getComputeNearFarMode() ) {
284
285        // Redo steps from CullVisitor::popProjectionMatrix()
286        // which clamps projection matrix when Camera & Projection
287        // completes traversal of their children
288
289        // We have to do this now manually
290        // because we did not complete camera traversal yet but
291        // we need to know how this clamped projection matrix will be
292
293        _cv->computeNearPlane();
294
295        osgUtil::CullVisitor::value_type n = _cv->getCalculatedNearPlane();
296        osgUtil::CullVisitor::value_type f = _cv->getCalculatedFarPlane();
297
298        if( n < f )
299            _cv->clampProjectionMatrix( _clampedProjection, n, f );
300    }
301
302    // Aditionally clamp far plane if shadows don't need to be cast as
303    // far as main projection far plane
304    if( 0 < *_maxFarPlanePtr )
305        clampProjection( _clampedProjection, 0.f, *_maxFarPlanePtr );
306
307    // Give derived classes chance to initialize _sceneReceivingShadowPolytope
308    osg::BoundingBox bb = computeShadowReceivingCoarseBounds( );
309    if( bb.valid() )
310        _sceneReceivingShadowPolytope.setToBoundingBox( bb );
311    else
312        _sceneReceivingShadowPolytope.clear();
313
314    // Cut initial scene using main camera frustum.
315    // Cutting will work correctly on empty polytope too.
316    // Take into consideration near far calculation and _maxFarPlane variable
317
318
319    osg::Matrix mvp = *_cv->getModelViewMatrix() * _clampedProjection;
320
321    cutScenePolytope( osg::Matrix::inverse( mvp ), mvp );
322
323    setDebugPolytope
324        ( "frustum", _sceneReceivingShadowPolytope, osg::Vec4(1,0,1,1));
325}
326
327void MinimalShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv )
328{
329    BaseClass::ViewData::init( st, cv );
330
331    _modellingSpaceToWorldPtr = &st->_modellingSpaceToWorld;
332    _minLightMarginPtr        = &st->_minLightMargin;
333    _maxFarPlanePtr           = &st->_maxFarPlane;
334
335    _frameShadowCastingCameraPasses = 1;
336}
337
338void MinimalShadowMap::ViewData::cutScenePolytope
339    ( const osg::Matrix & transform,
340      const osg::Matrix & inverse,
341      const osg::BoundingBox & bb )
342{
343    _sceneReceivingShadowPolytopePoints.clear();
344
345    if( bb.valid() ) {
346        osg::Polytope polytope;
347        polytope.setToBoundingBox( bb );
348        polytope.transformProvidingInverse( inverse );
349        _sceneReceivingShadowPolytope.cut( polytope );
350        _sceneReceivingShadowPolytope.getPoints
351                                ( _sceneReceivingShadowPolytopePoints );
352    } else
353        _sceneReceivingShadowPolytope.clear();
354}
355
356osg::BoundingBox
357    MinimalShadowMap::ViewData::computeScenePolytopeBounds( const osg::Matrix & m )
358{
359    osg::BoundingBox bb;
360
361    if( &m )
362        for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i )
363            bb.expandBy( _sceneReceivingShadowPolytopePoints[i] * m );
364    else
365        for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i )
366            bb.expandBy( _sceneReceivingShadowPolytopePoints[i] );
367
368    return bb;
369}
370
371
372
373// Utility methods for adjusting projection matrices
374
375void MinimalShadowMap::ViewData::trimProjection
376    ( osg::Matrixd & projectionMatrix, osg::BoundingBox bb, unsigned int trimMask )
377{
378#if 1
379    if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return;
380    double l = -1, r = 1, b = -1, t = 1, n = 1, f = -1;
381
382#if 0
383    // make sure bounding box does not extend beyond unit frustum clip range
384    for( int i = 0; i < 3; i ++ ) {
385        if( bb._min[i] < -1 ) bb._min[i] = -1;
386        if( bb._max[i] >  1 ) bb._max[i] =  1;
387    }
388#endif
389
390    if( trimMask & 1 ) l = bb._min[0];
391    if( trimMask & 2 ) r = bb._max[0];
392    if( trimMask & 4 ) b = bb._min[1];
393    if( trimMask & 8 ) t = bb._max[1];
394    if( trimMask & 16 ) n = -bb._min[2];
395    if( trimMask & 32 ) f = -bb._max[2];
396
397    projectionMatrix.postMult( osg::Matrix::ortho( l,r,b,t,n,f ) );
398#else
399    if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return;
400    double l, r, t, b, n, f;
401    bool ortho = projectionMatrix.getOrtho( l, r, b, t, n, f );
402    if( !ortho && !projectionMatrix.getFrustum( l, r, b, t, n, f ) )
403        return; // rotated or skewed or other crooked projection - give up
404
405    // make sure bounding box does not extend beyond unit frustum clip range
406    for( int i = 0; i < 3; i ++ ) {
407        if( bb._min[i] < -1 ) bb._min[i] = -1;
408        if( bb._max[i] >  1 ) bb._max[i] =  1;
409    }
410
411    osg::Matrix projectionToView = osg::Matrix::inverse( projectionMatrix );
412
413    osg::Vec3 min =
414        osg::Vec3( bb._min[0], bb._min[1], bb._min[2] ) * projectionToView;
415
416    osg::Vec3 max =
417        osg::Vec3( bb._max[0], bb._max[1], bb._max[2] ) * projectionToView;
418
419    if( trimMask & 16 ) { // trim near
420        if( !ortho ) { // recalc frustum corners on new near plane
421            l *= -min[2] / n;
422            r *= -min[2] / n;
423            b *= -min[2] / n;
424            t *= -min[2] / n;
425        }
426        n = -min[2];
427    }
428
429    if( trimMask & 32 ) // trim far
430        f = -max[2];
431
432    if( !ortho ) {
433        min[0] *=  -n / min[2];
434        min[1] *=  -n / min[2];
435        max[0] *=  -n / max[2];
436        max[1] *=  -n / max[2];
437    }
438
439    if( l < r ) { // check for inverted X range
440        if( l < min[0] && ( trimMask & 1 ) ) l = min[0];
441        if( r > max[0] && ( trimMask & 2 ) ) r = max[0];
442    } else {
443        if( l > min[0] && ( trimMask & 1 ) ) l = min[0];
444        if( r < max[0] && ( trimMask & 2 ) ) r = max[0];
445    }
446
447    if( b < t ) { // check for inverted Y range
448        if( b < min[1] && ( trimMask & 4 ) ) b = min[1];
449        if( t > max[1] && ( trimMask & 8 ) ) t = max[1];
450    } else {
451        if( b > min[1] && ( trimMask & 4 ) ) b = min[1];
452        if( t < max[1] && ( trimMask & 8 ) ) t = max[1];
453    }
454
455    if( ortho )
456        projectionMatrix.makeOrtho( l, r, b, t, n, f );
457    else
458        projectionMatrix.makeFrustum( l, r, b, t, n, f );
459#endif
460}
461
462void MinimalShadowMap::ViewData::clampProjection
463                    ( osg::Matrixd & projection, float new_near, float new_far )
464{
465    double r, l, t, b, n, f;
466    bool perspective = projection.getFrustum( l, r, b, t, n, f );
467    if( !perspective && !projection.getOrtho( l, r, b, t, n, f ) )
468    {
469        // What to do here ?
470        OSG_WARN << "MinimalShadowMap::clampProjectionFarPlane failed - non standard matrix" << std::endl;
471
472    } else if( n < new_near || new_far < f ) {
473
474        if( n < new_near && new_near < f ) {
475            if( perspective ) {
476                l *= new_near / n;
477                r *= new_near / n;
478                b *= new_near / n;
479                t *= new_near / n;
480            }
481            n = new_near;
482        }
483
484        if( n < new_far && new_far < f ) {
485            f = new_far;
486        }
487
488        if( perspective )
489            projection.makeFrustum( l, r, b, t, n, f );
490        else
491            projection.makeOrtho( l, r, b, t, n, f );
492    }
493}
494
495// Imagine following scenario:
496// We stand in the room and look through the window.
497// How should our view change if we were looking through larger window ?
498// In other words how should projection be adjusted if
499// window had grown by some margin ?
500// Method computes such new projection which maintains perpective/world ratio
501
502void MinimalShadowMap::ViewData::extendProjection
503    ( osg::Matrixd & projection, osg::Viewport * viewport, const osg::Vec2& margin )
504{
505  double l,r,b,t,n,f;
506
507  //osg::Matrix projection = camera.getProjectionMatrix();
508
509  bool frustum = projection.getFrustum( l,r,b,t,n,f );
510
511  if( !frustum && !projection.getOrtho( l,r,b,t,n,f ) ) {
512    OSG_WARN << " Awkward projection matrix. ComputeExtendedProjection failed" << std::endl;
513    return;
514  }
515
516  osg::Matrix window = viewport->computeWindowMatrix();
517
518  osg::Vec3 vMin( viewport->x() - margin.x(),
519                 viewport->y() - margin.y(),
520                 0.0 );
521
522  osg::Vec3 vMax( viewport->width() + margin.x() * 2  + vMin.x(),
523                  viewport->height() + margin.y() * 2  + vMin.y(),
524                  0.0 );
525
526  osg::Matrix inversePW = osg::Matrix::inverse( projection * window );
527
528  vMin = vMin * inversePW;
529  vMax = vMax * inversePW;
530
531  l = vMin.x();
532  r = vMax.x();
533  b = vMin.y();
534  t = vMax.y();
535
536  if( frustum )
537    projection.makeFrustum( l,r,b,t,n,f );
538  else
539    projection.makeOrtho( l,r,b,t,n,f );
540}
Note: See TracBrowser for help on using the browser.