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

Revision 11176, 17.9 kB (checked in by robert, 4 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::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 | |

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

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

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

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

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

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

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

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

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