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

Revision 11136, 17.4 kB (checked in by robert, 5 years ago) |
---|

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 | |

23 | using namespace osgShadow; |

24 | |

25 | #define PRINT_SHADOW_TEXEL_TO_PIXEL_ERROR 0 |

26 | |

27 | MinimalShadowMap::MinimalShadowMap(): |

28 | BaseClass(), |

29 | _maxFarPlane( FLT_MAX ), |

30 | _minLightMargin( 0 ), |

31 | _shadowReceivingCoarseBoundAccuracy( BOUNDING_BOX ) |

32 | { |

33 | |

34 | } |

35 | |

36 | MinimalShadowMap::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 | |

45 | MinimalShadowMap::~MinimalShadowMap() |

46 | { |

47 | } |

48 | |

49 | osg::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 | |

117 | void 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 | |

165 | void 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 | |

239 | void 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 | |

290 | void 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 | |

299 | void 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 | |

317 | osg::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 | |

336 | void 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 | |

423 | void 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 | |

465 | void 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.