/* This file is based on OpenSceneGraph's src/osgShadow/ViewDependentShadowMap.cpp. * Where applicable, any changes made are covered by OpenMW's GPL 3 license, not the OSGPL. * The original copyright notice is listed below. */ /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2011 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #include "mwshadowtechnique.hpp" #include #include #include #include #include #include #include "shadowsbin.hpp" namespace { using namespace osgShadow; using namespace SceneUtil; #define dbl_max std::numeric_limits::max() ////////////////////////////////////////////////////////////////// // fragment shader // #if 0 static const char fragmentShaderSource_withBaseTexture[] = "uniform sampler2D baseTexture; \n" "uniform sampler2DShadow shadowTexture; \n" " \n" "void main(void) \n" "{ \n" " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" " vec4 color = texture2D( baseTexture, gl_TexCoord[0].xy ); \n" " color *= mix( colorAmbientEmissive, gl_Color, shadow2DProj( shadowTexture, gl_TexCoord[1] ).r ); \n" " gl_FragColor = color; \n" "} \n"; #else static const char fragmentShaderSource_withBaseTexture[] = "uniform sampler2D baseTexture; \n" "uniform int baseTextureUnit; \n" "uniform sampler2DShadow shadowTexture0; \n" "uniform int shadowTextureUnit0; \n" " \n" "void main(void) \n" "{ \n" " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" " vec4 color = texture2D( baseTexture, gl_TexCoord[baseTextureUnit].xy ); \n" " color *= mix( colorAmbientEmissive, gl_Color, shadow2DProj( shadowTexture0, gl_TexCoord[shadowTextureUnit0] ).r ); \n" " gl_FragColor = color; \n" "} \n"; static const char fragmentShaderSource_withBaseTexture_twoShadowMaps[] = "uniform sampler2D baseTexture; \n" "uniform int baseTextureUnit; \n" "uniform sampler2DShadow shadowTexture0; \n" "uniform int shadowTextureUnit0; \n" "uniform sampler2DShadow shadowTexture1; \n" "uniform int shadowTextureUnit1; \n" " \n" "void main(void) \n" "{ \n" " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" " vec4 color = texture2D( baseTexture, gl_TexCoord[baseTextureUnit].xy ); \n" " float shadow0 = shadow2DProj( shadowTexture0, gl_TexCoord[shadowTextureUnit0] ).r; \n" " float shadow1 = shadow2DProj( shadowTexture1, gl_TexCoord[shadowTextureUnit1] ).r; \n" " color *= mix( colorAmbientEmissive, gl_Color, shadow0*shadow1 ); \n" " gl_FragColor = color; \n" "} \n"; #endif std::string debugVertexShaderSource = "void main(void){gl_Position = gl_Vertex; gl_TexCoord[0]=gl_MultiTexCoord0;}"; std::string debugFragmentShaderSource = "uniform sampler2D texture; \n" " \n" "void main(void) \n" "{ \n" #if 1 " float f = texture2D(texture, gl_TexCoord[0].xy).r; \n" " \n" " f = 256.0 * f; \n" " float fC = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fS = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fH = floor( f ) / 256.0; \n" " \n" " fS *= 0.5; \n" " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" " \n" " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" " abs( fC - 0.333333 ), \n" " abs( fC - 0.666667 ) ); \n" " \n" " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" " \n" " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" " fMax = 1.0 / fMax; \n" " \n" " vec3 color = fMax * rgb; \n" " \n" " gl_FragColor = vec4( fS + fH * color, 1 ); \n" #else " gl_FragColor = texture2D(texture, gl_TexCoord[0].xy); \n" #endif "} \n"; std::string debugFrustumVertexShaderSource = "varying float depth; uniform mat4 transform; void main(void){gl_Position = transform * gl_Vertex; depth = gl_Position.z / gl_Position.w;}"; std::string debugFrustumFragmentShaderSource = "varying float depth; \n" " \n" "void main(void) \n" "{ \n" #if 1 " float f = depth; \n" " \n" " f = 256.0 * f; \n" " float fC = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fS = floor( f ) / 256.0; \n" " \n" " f = 256.0 * fract( f ); \n" " float fH = floor( f ) / 256.0; \n" " \n" " fS *= 0.5; \n" " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" " \n" " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" " abs( fC - 0.333333 ), \n" " abs( fC - 0.666667 ) ); \n" " \n" " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" " \n" " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" " fMax = 1.0 / fMax; \n" " \n" " vec3 color = fMax * rgb; \n" " \n" " gl_FragColor = vec4( fS + fH * color, 1 ); \n" #else " gl_FragColor = vec4(0.0, 0.0, 1.0, 0.0); \n" #endif "} \n"; template class RenderLeafTraverser : public T { public: RenderLeafTraverser() { } void traverse(const osgUtil::RenderStage* rs) { traverse(static_cast(rs)); } void traverse(const osgUtil::RenderBin* renderBin) { const osgUtil::RenderBin::RenderBinList& rbl = renderBin->getRenderBinList(); for(osgUtil::RenderBin::RenderBinList::const_iterator itr = rbl.begin(); itr != rbl.end(); ++itr) { traverse(itr->second.get()); } const osgUtil::RenderBin::RenderLeafList& rll = renderBin->getRenderLeafList(); for(osgUtil::RenderBin::RenderLeafList::const_iterator itr = rll.begin(); itr != rll.end(); ++itr) { handle(*itr); } const osgUtil::RenderBin::StateGraphList& rgl = renderBin->getStateGraphList(); for(osgUtil::RenderBin::StateGraphList::const_iterator itr = rgl.begin(); itr != rgl.end(); ++itr) { traverse(*itr); } } void traverse(const osgUtil::StateGraph* stateGraph) { const osgUtil::StateGraph::ChildList& cl = stateGraph->_children; for(osgUtil::StateGraph::ChildList::const_iterator itr = cl.begin(); itr != cl.end(); ++itr) { traverse(itr->second.get()); } const osgUtil::StateGraph::LeafList& ll = stateGraph->_leaves; for(osgUtil::StateGraph::LeafList::const_iterator itr = ll.begin(); itr != ll.end(); ++itr) { handle(itr->get()); } } inline void handle(const osgUtil::RenderLeaf* renderLeaf) { this->operator()(renderLeaf); } }; /////////////////////////////////////////////////////////////////////////////////////////////// // // VDSMCameraCullCallback // class VDSMCameraCullCallback : public osg::NodeCallback { public: VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope); void operator()(osg::Node*, osg::NodeVisitor* nv) override; osg::RefMatrix* getProjectionMatrix() { return _projectionMatrix.get(); } osgUtil::RenderStage* getRenderStage() { return _renderStage.get(); } protected: MWShadowTechnique* _vdsm; osg::ref_ptr _projectionMatrix; osg::ref_ptr _renderStage; osg::Polytope _polytope; }; VDSMCameraCullCallback::VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope): _vdsm(vdsm), _polytope(polytope) { } void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) { osgUtil::CullVisitor* cv = static_cast(nv); osg::Camera* camera = node->asCamera(); OSG_INFO<<"VDSMCameraCullCallback::operator()(osg::Node* "<getProjectionCullingStack().back(); cs.setFrustum(_polytope); cv->pushCullingSet(); } #endif // bin has to go inside camera cull or the rendertexture stage will override it static osg::ref_ptr ss; if (!ss) { ShadowsBinAdder adder("ShadowsBin", _vdsm->getCastingPrograms()); ss = new osg::StateSet; ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); } cv->pushStateSet(ss); if (_vdsm->getShadowedScene()) { _vdsm->getShadowedScene()->osg::Group::traverse(*nv); } cv->popStateSet(); #if 1 if (!_polytope.empty()) { OSG_INFO<<"Popping custom Polytope"<popCullingSet(); } #endif _renderStage = cv->getCurrentRenderBin()->getStage(); OSG_INFO<<"VDSM second : _renderStage = "<<_renderStage<getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { // make sure that the near plane is computed correctly. cv->computeNearPlane(); osg::Matrixd projection = *(cv->getProjectionMatrix()); OSG_INFO<<"RTT Projection matrix "<setProjectionMatrix(projection); } _projectionMatrix = cv->getProjectionMatrix(); } } // namespace MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) { setCullingMode(osg::CullSettings::VIEW_FRUSTUM_CULLING); pushViewport(viewport); pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); setName("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds,AcceptedByComponentsTerrainQuadTreeWorld"); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Node& node) { if (isCulled(node)) return; // push the culling mode. pushCurrentMask(); traverse(node); // pop the culling mode. popCurrentMask(); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Drawable& drawable) { if (isCulled(drawable)) return; // push the culling mode. pushCurrentMask(); updateBound(drawable.getBoundingBox()); // pop the culling mode. popCurrentMask(); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(Terrain::QuadTreeWorld & quadTreeWorld) { // For now, just expand the bounds fully as terrain will fill them up and possible ways to detect which terrain definitely won't cast shadows aren't implemented. update(osg::Vec3(-1.0, -1.0, 0.0)); update(osg::Vec3(1.0, 1.0, 0.0)); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Billboard&) { OSG_INFO << "Warning Billboards not yet supported" << std::endl; return; } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Projection&) { // projection nodes won't affect a shadow map so their subgraphs should be ignored return; } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Transform& transform) { if (isCulled(transform)) return; // push the culling mode. pushCurrentMask(); // absolute transforms won't affect a shadow map so their subgraphs should be ignored. if (transform.getReferenceFrame() == osg::Transform::RELATIVE_RF) { osg::ref_ptr matrix = new osg::RefMatrix(*getModelViewMatrix()); transform.computeLocalToWorldMatrix(*matrix, this); pushModelViewMatrix(matrix.get(), transform.getReferenceFrame()); traverse(transform); popModelViewMatrix(); } // pop the culling mode. popCurrentMask(); } void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Camera&) { // camera nodes won't affect a shadow map so their subgraphs should be ignored return; } void MWShadowTechnique::ComputeLightSpaceBounds::updateBound(const osg::BoundingBox& bb) { if (!bb.valid()) return; const osg::Matrix& matrix = *getModelViewMatrix() * *getProjectionMatrix(); update(bb.corner(0) * matrix); update(bb.corner(1) * matrix); update(bb.corner(2) * matrix); update(bb.corner(3) * matrix); update(bb.corner(4) * matrix); update(bb.corner(5) * matrix); update(bb.corner(6) * matrix); update(bb.corner(7) * matrix); } void MWShadowTechnique::ComputeLightSpaceBounds::update(const osg::Vec3& v) { if (v.z()<-1.0f) { //OSG_NOTICE<<"discarding("<1.0f) x = 1.0f; float y = v.y(); if (y<-1.0f) y = -1.0f; if (y>1.0f) y = 1.0f; _bb.expandBy(osg::Vec3(x, y, v.z())); } /////////////////////////////////////////////////////////////////////////////////////////////// // // LightData // MWShadowTechnique::LightData::LightData(MWShadowTechnique::ViewDependentData* vdd): _viewDependentData(vdd), directionalLight(false) { } void MWShadowTechnique::LightData::setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix) { lightMatrix = lm; light = l; lightPos = light->getPosition(); directionalLight = (light->getPosition().w()== 0.0); if (directionalLight) { lightPos3.set(0.0, 0.0, 0.0); // directional light has no destinct position lightDir.set(-lightPos.x(), -lightPos.y(), -lightPos.z()); lightDir.normalize(); OSG_INFO<<" Directional light, lightPos="<setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); #ifndef __APPLE__ // workaround shadow issue on macOS, https://gitlab.com/OpenMW/openmw/-/issues/6057 _camera->setImplicitBufferAttachmentMask(0, 0); #endif //_camera->setClearColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); _camera->setClearColor(osg::Vec4(0.0f,0.0f,0.0f,0.0f)); //_camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); //_camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_PRIMITIVES); // Now we are using Depth Clamping, we want to not cull things on the wrong side of the near plane. // When the near and far planes are computed, OSG always culls anything on the wrong side of the near plane, even if it's told not to. // Even if that weren't an issue, the near plane can't go past any shadow receivers or the depth-clamped fragments which ended up on the near plane can't cast shadows on those receivers. // Unfortunately, this change will make shadows have less depth precision when there are no casters outside the view frustum. // TODO: Find a better solution. E.g. detect when there are no casters outside the view frustum, write a new cull visitor that does all the wacky things we'd need it to. _camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); // switch off small feature culling as this can cull out geometry that will still be large enough once perspective correction takes effect. _camera->setCullingMode(_camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING); // set viewport _camera->setViewport(0,0,textureSize.x(),textureSize.y()); if (debug) { // clear just the depth buffer _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // render after the main camera _camera->setRenderOrder(osg::Camera::POST_RENDER); // attach the texture and use it as the color buffer. //_camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); _camera->attach(osg::Camera::COLOR_BUFFER, _texture.get()); } else { // clear the depth and colour bufferson each clear. _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // set the camera to render before the main camera. _camera->setRenderOrder(osg::Camera::PRE_RENDER); // tell the camera to use OpenGL frame buffer object where supported. _camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); // attach the texture and use it as the color buffer. _camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); //_camera->attach(osg::Camera::COLOR_BUFFER, _texture.get()); } } void MWShadowTechnique::ShadowData::releaseGLObjects(osg::State* state) const { OSG_INFO<<"MWShadowTechnique::ShadowData::releaseGLObjects"<releaseGLObjects(state); _camera->releaseGLObjects(state); } /////////////////////////////////////////////////////////////////////////////////////////////// // // Frustum // MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar): corners(8), faces(6), edges(12) { projectionMatrix = *(cv->getProjectionMatrix()); modelViewMatrix = *(cv->getModelViewMatrix()); OSG_INFO<<"Projection matrix "<getComputeNearFarMode()!=osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { osg::Matrix::value_type zNear = osg::maximum(cv->getCalculatedNearPlane(),minZNear); osg::Matrix::value_type zFar = osg::minimum(cv->getCalculatedFarPlane(),maxZFar); cv->clampProjectionMatrix(projectionMatrix, zNear, zFar); OSG_INFO<<"zNear = "<releaseGLObjects(state); } } /////////////////////////////////////////////////////////////////////////////////////////////// // // MWShadowTechnique // MWShadowTechnique::MWShadowTechnique(): ShadowTechnique(), _enableShadows(false), _debugHud(nullptr), _castingPrograms{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } { _shadowRecievingPlaceholderStateSet = new osg::StateSet; mSetDummyStateWhenDisabled = false; } MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): ShadowTechnique(vdsm,copyop) , _castingPrograms(vdsm._castingPrograms) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; _enableShadows = vdsm._enableShadows; mSetDummyStateWhenDisabled = vdsm.mSetDummyStateWhenDisabled; } MWShadowTechnique::~MWShadowTechnique() { } void MWShadowTechnique::init() { if (!_shadowedScene) return; OSG_INFO<<"MWShadowTechnique::init()"<getShadowSettings()->getNumShadowMapsPerLight()); } void SceneUtil::MWShadowTechnique::disableDebugHUD() { _debugHud = nullptr; } void SceneUtil::MWShadowTechnique::setSplitPointUniformLogarithmicRatio(double ratio) { _splitPointUniformLogRatio = ratio; } void SceneUtil::MWShadowTechnique::setSplitPointDeltaBias(double bias) { _splitPointDeltaBias = bias; } void SceneUtil::MWShadowTechnique::setPolygonOffset(float factor, float units) { _polygonOffsetFactor = factor; _polygonOffsetUnits = units; if (_polygonOffset) { _polygonOffset->setFactor(factor); _polygonOffset->setUnits(units); } } void SceneUtil::MWShadowTechnique::setShadowFadeStart(float shadowFadeStart) { _shadowFadeStart = shadowFadeStart; } void SceneUtil::MWShadowTechnique::enableFrontFaceCulling() { _useFrontFaceCulling = true; if (_shadowCastingStateSet) { _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); } } void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() { _useFrontFaceCulling = false; if (_shadowCastingStateSet) { _shadowCastingStateSet->removeAttribute(osg::StateAttribute::CULLFACE); _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); } } void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) { // This can't be part of the constructor as OSG mandates that there be a trivial constructor available osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX); osg::ref_ptr exts = osg::GLExtensions::Get(0, false); std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) { auto& program = _castingPrograms[alphaFunc - GL_NEVER]; program = new osg::Program(); program->addShader(castingVertexShader); program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"}, {"useGPUShader4", useGPUShader4} }, osg::Shader::FRAGMENT)); } } MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) { return new ViewDependentData(this); } MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(osgUtil::CullVisitor* cv) { std::lock_guard lock(_viewDependentDataMapMutex); ViewDependentDataMap::iterator itr = _viewDependentDataMap.find(cv); if (itr!=_viewDependentDataMap.end()) return itr->second.get(); osg::ref_ptr vdd = createViewDependentData(cv); _viewDependentDataMap[cv] = vdd; return vdd.release(); } void MWShadowTechnique::update(osg::NodeVisitor& nv) { OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<osg::Group::traverse(nv); } void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) { if (!_enableShadows) { if (mSetDummyStateWhenDisabled) { osg::ref_ptr dummyState = new osg::StateSet(); ShadowSettings* settings = getShadowedScene()->getShadowSettings(); int baseUnit = settings->getBaseShadowTextureUnit(); int endUnit = baseUnit + settings->getNumShadowMapsPerLight(); for (int i = baseUnit; i < endUnit; ++i) { dummyState->setTextureAttributeAndModes(i, _fallbackShadowMapTexture, osg::StateAttribute::ON); dummyState->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseUnit)).c_str(), i)); dummyState->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseUnit)).c_str(), i)); } cv.pushStateSet(dummyState); } _shadowedScene->osg::Group::traverse(cv); if (mSetDummyStateWhenDisabled) cv.popStateSet(); return; } OSG_INFO<osg::Group::traverse(cv); return; } ViewDependentData* vdd = getViewDependentData(&cv); if (!vdd) { OSG_INFO<<"Warning, now ViewDependentData created, unable to create shadows."<osg::Group::traverse(cv); return; } ShadowSettings* settings = getShadowedScene()->getShadowSettings(); OSG_INFO<<"cv->getProjectionMatrix()="<<*cv.getProjectionMatrix()<getMaximumShadowMapDistance(),maxZFar); if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio(); //OSG_NOTICE<<"maxZFar "< vertexArray = new osg::Vec3Array(); for (osg::Vec3d &vertex : frustum.corners) vertexArray->push_back((osg::Vec3)vertex); _debugHud->setFrustumVertices(vertexArray, cv.getTraversalNumber()); } double reducedNear, reducedFar; if (cv.getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { reducedNear = osg::maximum(cv.getCalculatedNearPlane(), minZNear); reducedFar = osg::minimum(cv.getCalculatedFarPlane(), maxZFar); } else { reducedNear = minZNear; reducedFar = maxZFar; } // return compute near far mode back to it's original settings cv.setComputeNearFarMode(cachedNearFarMode); OSG_INFO<<"frustum.eye="<1 &&*/ _shadowedScene->getCastsShadowTraversalMask()!=0xffffffff) { // osg::ElapsedTime timer; osg::ref_ptr viewport = new osg::Viewport(0,0,2048,2048); ComputeLightSpaceBounds clsb(viewport.get(), projectionMatrix, viewMatrix); clsb.setTraversalMask(_shadowedScene->getCastsShadowTraversalMask()); osg::Matrixd invertModelView; invertModelView.invert(viewMatrix); osg::Polytope local_polytope(polytope); local_polytope.transformProvidingInverse(invertModelView); osg::CullingSet& cs = clsb.getProjectionCullingStack().back(); cs.setFrustum(local_polytope); clsb.pushCullingSet(); _shadowedScene->accept(clsb); // OSG_NOTICE<<"Extents of LightSpace "<(maxZ, -corner.z()); minZ = osg::minimum(minZ, -corner.z()); } reducedNear = osg::maximum(reducedNear, minZ); reducedFar = osg::minimum(reducedFar, maxZ); // OSG_NOTICE<<" xMid="< camera = sd->_camera; camera->setProjectionMatrix(projectionMatrix); camera->setViewMatrix(viewMatrix); if (settings->getDebugDraw()) { camera->getViewport()->x() = pos_x; pos_x += static_cast(camera->getViewport()->width()) + 40; } // transform polytope in model coords into light spaces eye coords. osg::Matrixd invertModelView; invertModelView.invert(camera->getViewMatrix()); osg::Polytope local_polytope(polytope); local_polytope.transformProvidingInverse(invertModelView); double cascaseNear = reducedNear; double cascadeFar = reducedFar; if (numShadowMapsPerLight>1) { // compute the start and end range in non-dimensional coords #if 0 double r_start = (sm_i==0) ? -1.0 : (double(sm_i)/double(numShadowMapsPerLight)*2.0-1.0); double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : (double(sm_i+1)/double(numShadowMapsPerLight)*2.0-1.0); #elif 0 // hardwired for 2 splits double r_start = (sm_i==0) ? -1.0 : splitPoint; double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : splitPoint; #else double r_start, r_end; // split system based on the original Parallel Split Shadow Maps paper. double n = reducedNear; double f = reducedFar; double i = double(sm_i); double m = double(numShadowMapsPerLight); if (sm_i == 0) r_start = -1.0; else { // compute the split point in main camera view double ciLog = n * pow(f / n, i / m); double ciUniform = n + (f - n) * i / m; double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias; cascaseNear = ci; // work out where this is in light space osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci; osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix; r_start = lightSpacePos.y(); } if (sm_i + 1 == numShadowMapsPerLight) r_end = 1.0; else { // compute the split point in main camera view double ciLog = n * pow(f / n, (i + 1) / m); double ciUniform = n + (f - n) * (i + 1) / m; double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias; cascadeFar = ci; // work out where this is in light space osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci; osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix; r_end = lightSpacePos.y(); } #endif // for all by the last shadowmap shift the r_end so that it overlaps slightly with the next shadowmap // to prevent a seam showing through between the shadowmaps if (sm_i+1getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i>0) { // not the first shadowmap so insert a polytope to clip the scene from before r_start // plane in clip space coords osg::Plane plane(0.0,1.0,0.0,-r_start); // transform into eye coords plane.transformProvidingInverse(projectionMatrix); local_polytope.getPlaneList().push_back(plane); //OSG_NOTICE<<"Adding r_start plane "<getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i+1getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT) { // OSG_NOTICE<<"Need to adjust RTT camera projection and view matrix here, r_start="< validRegionUniform; for (auto uniform : _uniforms[cv.getTraversalNumber() % 2]) { if (uniform->getName() == validRegionUniformName) validRegionUniform = uniform; } if (!validRegionUniform) { validRegionUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, validRegionUniformName); _uniforms[cv.getTraversalNumber() % 2].push_back(validRegionUniform); } validRegionUniform->set(validRegionMatrix); } if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED) adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), cascaseNear, cascadeFar); else adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), reducedNear, reducedFar); if (vdsmCallback->getProjectionMatrix()) { vdsmCallback->getProjectionMatrix()->set(camera->getProjectionMatrix()); } } // 4.4 compute main scene graph TexGen + uniform settings + setup state // assignTexGenSettings(&cv, camera.get(), textureUnit, sd->_texgen.get()); // mark the light as one that has active shadows and requires shaders pl.textureUnits.push_back(textureUnit); // pass on shadow data to ShadowDataList sd->_textureUnit = textureUnit; if (textureUnit >= 8) { OSG_NOTICE<<"Shadow texture unit is invalid for texgen, will not be used."<draw(sd->_texture, sm_i, camera->getViewMatrix() * camera->getProjectionMatrix(), cv); } } if (numValidShadows>0) { prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); } // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()<getLightDataList(); LightDataList previous_ldl; previous_ldl.swap(pll); //MR testing giving a specific light osgUtil::RenderStage * rs = cv->getCurrentRenderBin()->getStage(); OSG_INFO<<"selectActiveLights osgUtil::RenderStage="<getModelViewMatrix()); osgUtil::PositionalStateContainer::AttrMatrixList& aml = rs->getPositionalStateContainer()->getAttrMatrixList(); const ShadowSettings* settings = getShadowedScene()->getShadowSettings(); for(osgUtil::PositionalStateContainer::AttrMatrixList::reverse_iterator itr = aml.rbegin(); itr != aml.rend(); ++itr) { const osg::Light* light = dynamic_cast(itr->first.get()); if (light && light->getLightNum() >= 0) { // is LightNum matched to that defined in settings if (settings && settings->getLightNum()>=0 && light->getLightNum()!=settings->getLightNum()) continue; LightDataList::iterator pll_itr = pll.begin(); for(; pll_itr != pll.end(); ++pll_itr) { if ((*pll_itr)->light->getLightNum()==light->getLightNum()) break; } if (pll_itr==pll.end()) { OSG_INFO<<"Light num "<getLightNum()<setLightData(itr->second.get(), light, modelViewMatrix); pll.push_back(ld); } else { OSG_INFO<<"Light num "<getLightNum()<<" already used, ignore light"<getShadowSettings(); if (!settings->getDebugDraw()) { // note soft (attribute only no mode override) setting. When this works ? // 1. for objects prepared for backface culling // because they usually also set CullFace and CullMode on in their state // For them we override CullFace but CullMode remains set by them // 2. For one faced, trees, and similar objects which cannot use // backface nor front face so they usually use CullMode off set here. // In this case we will draw them in their entirety. if (_useFrontFaceCulling) { _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); // make sure GL_CULL_FACE is off by default // we assume that if object has cull face attribute set to back // it will also set cull face mode ON so no need for override _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); } else _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); } _polygonOffset = new osg::PolygonOffset(_polygonOffsetFactor, _polygonOffsetUnits); _shadowCastingStateSet->setAttribute(_polygonOffset.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_POLYGON_OFFSET_FILL, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); osg::ref_ptr baseTextureSampler = new osg::Uniform("baseTexture",(int)_baseTextureUnit); osg::ref_ptr baseTextureUnit = new osg::Uniform("baseTextureUnit",(int)_baseTextureUnit); osg::ref_ptr maxDistance = new osg::Uniform("maximumShadowMapDistance", (float)settings->getMaximumShadowMapDistance()); osg::ref_ptr fadeStart = new osg::Uniform("shadowFadeStart", (float)_shadowFadeStart); for (auto& perFrameUniformList : _uniforms) { perFrameUniformList.clear(); perFrameUniformList.push_back(baseTextureSampler); perFrameUniformList.emplace_back(baseTextureUnit.get()); perFrameUniformList.push_back(maxDistance); perFrameUniformList.push_back(fadeStart); } for(unsigned int sm_i=0; sm_igetNumShadowMapsPerLight(); ++sm_i) { { std::stringstream sstr; sstr<<"shadowTexture"< shadowTextureSampler = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); for (auto& perFrameUniformList : _uniforms) perFrameUniformList.emplace_back(shadowTextureSampler.get()); } { std::stringstream sstr; sstr<<"shadowTextureUnit"< shadowTextureUnit = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); for (auto& perFrameUniformList : _uniforms) perFrameUniformList.emplace_back(shadowTextureUnit.get()); } } switch(settings->getShaderHint()) { case(ShadowSettings::NO_SHADERS): { OSG_INFO<<"No shaders provided by, user must supply own shaders"< fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_noBaseTexture); if (settings->getNumShadowMapsPerLight()==2) { _program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture_twoShadowMaps)); } else { _program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture)); } break; } } { osg::ref_ptr image = new osg::Image; image->allocateImage( 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE ); *(osg::Vec4ub*)image->data() = osg::Vec4ub( 0xFF, 0xFF, 0xFF, 0xFF ); _fallbackBaseTexture = new osg::Texture2D(image.get()); _fallbackBaseTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); _fallbackBaseTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); _fallbackBaseTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); _fallbackBaseTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); _fallbackShadowMapTexture = new osg::Texture2D(image.get()); _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); _fallbackShadowMapTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); _fallbackShadowMapTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); _fallbackShadowMapTexture->setShadowComparison(true); _fallbackShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); } if (!_castingPrograms[GL_ALWAYS - GL_NEVER]) OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; // Always use the GL_ALWAYS shader as the shadows bin will change it if necessary _shadowCastingStateSet->setAttributeAndModes(_castingPrograms[GL_ALWAYS - GL_NEVER], osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); _shadowCastingStateSet->addUniform(new osg::Uniform("alphaTestShadows", false)); osg::ref_ptr depth = new osg::Depth; depth->setWriteMask(true); _shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); // TODO: compare performance when alpha testing is handled here versus using a discard in the fragment shader } osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight) { OSG_INFO<<"computeLightViewFrustumPolytope()"<getShadowSettings(); double dotProduct_v = positionedLight.lightDir * frustum.frustumCenterLine; double gamma_v = acos(dotProduct_v); if (gamma_vgetPerspectiveShadowMapCutOffAngle()) || gamma_v>osg::DegreesToRadians(180.0-settings->getPerspectiveShadowMapCutOffAngle())) { OSG_INFO<<"View direction and Light direction below tolerance"<=0.0 && d1>=0.0) { // OSG_NOTICE<<" Edge completely inside"<first; osg::Vec3d& v1 = itr->second; osg::Vec3d intersection = v0 - (v1-v0)*(d0/(d1-d0)); intersections.push_back(intersection); // OSG_NOTICE<<" Edge across clip plane, v0="<=side_y.length2()) ? side_x : side_y; side.normalize(); osg::Vec3d up = side ^ normal; up.normalize(); osg::Vec3d center; for(Vertices::iterator itr = intersections.begin(); itr != intersections.end(); ++itr) { center += *itr; center.x() = osg::maximum(center.x(), -dbl_max); center.y() = osg::maximum(center.y(), -dbl_max); center.z() = osg::maximum(center.z(), -dbl_max); center.x() = osg::minimum(center.x(), dbl_max); center.y() = osg::minimum(center.y(), dbl_max); center.z() = osg::minimum(center.z(), dbl_max); } center /= double(intersections.size()); typedef std::map>> VertexMap; VertexMap vertexMap; for(Vertices::iterator itr = intersections.begin(); itr != intersections.end(); ++itr) { osg::Vec3d dv = (*itr-center); double h = dv * side; double v = dv * up; double angle = atan2(h,v); // OSG_NOTICE<<"angle = "<_modelview.get()!=previous_modelview) { previous_modelview = renderLeaf->_modelview.get(); if (previous_modelview) { light_mvp.mult(*renderLeaf->_modelview, light_p); } else { // no modelview matrix (such as when LightPointNode is in the scene graph) so assume // that modelview matrix is indentity. light_mvp = light_p; } // OSG_INFO<<"Computing new light_mvp "<_drawable->getBoundingBox(); if (bb.valid()) { // OSG_NOTICE<<"checked extents of "<_drawable->getName()<max_z) { max_z=ls.z(); /* OSG_NOTICE<<" + ";*/ } // OSG_NOTICE<<" bb.z() in ls = "<& planeList) { osg::Matrixd light_p = camera->getProjectionMatrix(); osg::Matrixd light_v = camera->getViewMatrix(); osg::Matrixd light_vp = light_v * light_p; osg::Matrixd oldLightP = light_p; ConvexHull convexHull; convexHull.setToFrustum(frustum); osg::Vec3d nearPoint = frustum.eye + frustum.frustumCenterLine * viewNear; osg::Vec3d farPoint = frustum.eye + frustum.frustumCenterLine * viewFar; double nearDist = -frustum.frustumCenterLine * nearPoint; double farDist = frustum.frustumCenterLine * farPoint; convexHull.clip(osg::Plane(frustum.frustumCenterLine, nearDist)); convexHull.clip(osg::Plane(-frustum.frustumCenterLine, farDist)); convexHull.transform(light_vp); double xMin = -1.0, xMax = 1.0; double yMin = -1.0, yMax = 1.0; double zMin = -1.0, zMax = 1.0; if (convexHull.valid()) { xMin = osg::maximum(-1.0, convexHull.min(0)); xMax = osg::minimum(1.0, convexHull.max(0)); yMin = osg::maximum(-1.0, convexHull.min(1)); yMax = osg::minimum(1.0, convexHull.max(1)); zMin = osg::maximum(-1.0, convexHull.min(2)); zMax = osg::minimum(1.0, convexHull.max(2)); } else return false; if (xMin != -1.0 || yMin != -1.0 || zMin != -1.0 || xMax != 1.0 || yMax != 1.0 || zMax != 1.0) { osg::Matrix m; m.makeTranslate(osg::Vec3d(-0.5*(xMax + xMin), -0.5*(yMax + yMin), -0.5*(zMax + zMin))); m.postMultScale(osg::Vec3d(2.0 / (xMax - xMin), 2.0 / (yMax - yMin), 2.0 / (zMax - zMin))); light_p.postMult(m); camera->setProjectionMatrix(light_p); convexHull.transform(osg::Matrixd::inverse(oldLightP)); xMin = convexHull.min(0); xMax = convexHull.max(0); yMin = convexHull.min(1); yMax = convexHull.max(1); zMin = convexHull.min(2); planeList.emplace_back(0.0, -1.0, 0.0, yMax); planeList.emplace_back(0.0, 1.0, 0.0, -yMin); planeList.emplace_back(-1.0, 0.0, 0.0, xMax); planeList.emplace_back(1.0, 0.0, 0.0, -xMin); // In view space, the light is at the most positive value, and we want to cull stuff beyond the minimum value. planeList.emplace_back(0.0, 0.0, 1.0, -zMin); // Don't add a zMax culling plane - we still want those objects, but don't care about their depth buffer value. } return true; } bool MWShadowTechnique::adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& /*positionedLight*/, osg::Camera* camera, double viewNear, double viewFar) { const ShadowSettings* settings = getShadowedScene()->getShadowSettings(); //frustum.projectionMatrix; //frustum.modelViewMatrix; osg::Matrixd light_p = camera->getProjectionMatrix(); osg::Matrixd light_v = camera->getViewMatrix(); osg::Matrixd light_vp = light_v * light_p; osg::Vec3d lightdir(0.0,0.0,-1.0); // check whether this light space projection is perspective or orthographic. bool orthographicLightSpaceProjection = light_p(0,3)==0.0 && light_p(1,3)==0.0 && light_p(2,3)==0.0; if (!orthographicLightSpaceProjection) { OSG_INFO<<"perspective light space projection not yet supported."<setProjectionMatrix(light_p); } #endif osg::Vec3d eye_v = frustum.eye * light_v; //osg::Vec3d centerNearPlane_v = frustum.centerNearPlane * light_v; osg::Vec3d center_v = frustum.center * light_v; osg::Vec3d viewdir_v = center_v-eye_v; viewdir_v.normalize(); double dotProduct_v = lightdir * viewdir_v; double gamma_v = acos(dotProduct_v); if (gamma_vgetPerspectiveShadowMapCutOffAngle()) || gamma_v>osg::DegreesToRadians(180-settings->getPerspectiveShadowMapCutOffAngle())) { // OSG_NOTICE<<"Light and view vectors near parallel - use standard shadow map."<getTraversalMask(); cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getReceivesShadowTraversalMask() ); _shadowedScene->osg::Group::traverse(*cv); cv->setTraversalMask( traversalMask ); return; } void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const { OSG_INFO<<"cullShadowCastingScene()"<getTraversalMask(); cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getCastsShadowTraversalMask() ); if (camera) camera->accept(*cv); cv->setTraversalMask( traversalMask ); return; } osg::StateSet* MWShadowTechnique::prepareStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const { OSG_INFO<<" prepareStateSetForRenderingShadow() "< stateset = vdd.getStateSet(traversalNumber); stateset->clear(); stateset->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); for(const auto& uniform : _uniforms[traversalNumber % 2]) { OSG_INFO<<"addUniform("<getName()<<")"<addUniform(uniform); } if (_program.valid()) { stateset->setAttribute(_program.get()); } LightDataList& pll = vdd.getLightDataList(); for(LightDataList::iterator itr = pll.begin(); itr != pll.end(); ++itr) { // 3. create per light/per shadow map division of lightspace/frustum // create a list of light/shadow map data structures LightData& pl = (**itr); // if no texture units have been activated for this light then no shadow state required. if (pl.textureUnits.empty()) continue; for(LightData::ActiveTextureUnits::iterator atu_itr = pl.textureUnits.begin(); atu_itr != pl.textureUnits.end(); ++atu_itr) { OSG_INFO<<" Need to assign state for "<<*atu_itr<getShadowSettings(); unsigned int shadowMapModeValue = settings->getUseOverrideForShadowMapTexture() ? osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE : osg::StateAttribute::ON; ShadowDataList& sdl = vdd.getShadowDataList(); for(ShadowDataList::iterator itr = sdl.begin(); itr != sdl.end(); ++itr) { // 3. create per light/per shadow map division of lightspace/frustum // create a list of light/shadow map data structures ShadowData& sd = (**itr); OSG_INFO<<" ShadowData for "<setTextureAttributeAndModes(sd._textureUnit, sd._texture.get(), shadowMapModeValue); stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_S,osg::StateAttribute::ON); stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_T,osg::StateAttribute::ON); stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_R,osg::StateAttribute::ON); stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_Q,osg::StateAttribute::ON); } return stateset; } void MWShadowTechnique::resizeGLObjectBuffers(unsigned int /*maxSize*/) { // the way that ViewDependentData is mapped shouldn't } void MWShadowTechnique::releaseGLObjects(osg::State* state) const { std::lock_guard lock(_viewDependentDataMapMutex); for(ViewDependentDataMap::const_iterator itr = _viewDependentDataMap.begin(); itr != _viewDependentDataMap.end(); ++itr) { ViewDependentData* vdd = itr->second.get(); if (vdd) { vdd->releaseGLObjects(state); } } if (_debugHud) _debugHud->releaseGLObjects(state); } class DoubleBufferCallback : public osg::Callback { public: DoubleBufferCallback(osg::NodeList &children) : mChildren(children) {} bool run(osg::Object* node, osg::Object* visitor) override { // We can't use a static cast as NodeVisitor virtually inherits from Object osg::ref_ptr nodeVisitor = visitor->asNodeVisitor(); unsigned int traversalNumber = nodeVisitor->getTraversalNumber(); mChildren[traversalNumber % 2]->accept(*nodeVisitor); return true; } protected: osg::NodeList mChildren; }; SceneUtil::MWShadowTechnique::DebugHUD::DebugHUD(int numberOfShadowMapsPerLight) : mDebugProgram(new osg::Program) { osg::ref_ptr vertexShader = new osg::Shader(osg::Shader::VERTEX, debugVertexShaderSource); mDebugProgram->addShader(vertexShader); osg::ref_ptr fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, debugFragmentShaderSource); mDebugProgram->addShader(fragmentShader); osg::ref_ptr frustumProgram = new osg::Program; vertexShader = new osg::Shader(osg::Shader::VERTEX, debugFrustumVertexShaderSource); frustumProgram->addShader(vertexShader); fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, debugFrustumFragmentShaderSource); frustumProgram->addShader(fragmentShader); for (auto& frustumGeometry : mFrustumGeometries) { frustumGeometry = new osg::Geometry(); frustumGeometry->setCullingActive(false); frustumGeometry->getOrCreateStateSet()->setAttributeAndModes(frustumProgram, osg::StateAttribute::ON); } osg::ref_ptr frustumDrawElements = new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP); for (auto & geom : mFrustumGeometries) geom->addPrimitiveSet(frustumDrawElements); frustumDrawElements->push_back(0); frustumDrawElements->push_back(1); frustumDrawElements->push_back(2); frustumDrawElements->push_back(3); frustumDrawElements->push_back(0); frustumDrawElements->push_back(4); frustumDrawElements->push_back(5); frustumDrawElements->push_back(6); frustumDrawElements->push_back(7); frustumDrawElements->push_back(4); frustumDrawElements = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES); for (auto & geom : mFrustumGeometries) geom->addPrimitiveSet(frustumDrawElements); frustumDrawElements->push_back(1); frustumDrawElements->push_back(5); frustumDrawElements->push_back(2); frustumDrawElements->push_back(6); frustumDrawElements->push_back(3); frustumDrawElements->push_back(7); for (int i = 0; i < numberOfShadowMapsPerLight; ++i) addAnotherShadowMap(); } void SceneUtil::MWShadowTechnique::DebugHUD::draw(osg::ref_ptr texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv) { // It might be possible to change shadow settings at runtime if (shadowMapNumber > mDebugCameras.size()) addAnotherShadowMap(); osg::ref_ptr stateSet = new osg::StateSet(); stateSet->setTextureAttributeAndModes(sDebugTextureUnit, texture, osg::StateAttribute::ON); auto frustumUniform = mFrustumUniforms[cv.getTraversalNumber() % 2][shadowMapNumber]; frustumUniform->set(matrix); stateSet->addUniform(frustumUniform); // Some of these calls may be superfluous. unsigned int traversalMask = cv.getTraversalMask(); cv.setTraversalMask(mDebugGeometry[shadowMapNumber]->getNodeMask()); cv.pushStateSet(stateSet); mDebugCameras[shadowMapNumber]->accept(cv); cv.popStateSet(); cv.setTraversalMask(traversalMask); // cv.getState()->setCheckForGLErrors(osg::State::ONCE_PER_ATTRIBUTE); } void SceneUtil::MWShadowTechnique::DebugHUD::releaseGLObjects(osg::State* state) const { for (auto const& camera : mDebugCameras) camera->releaseGLObjects(state); mDebugProgram->releaseGLObjects(state); for (auto const& node : mDebugGeometry) node->releaseGLObjects(state); for (auto const& node : mFrustumTransforms) node->releaseGLObjects(state); for (auto const& node : mFrustumGeometries) node->releaseGLObjects(state); } void SceneUtil::MWShadowTechnique::DebugHUD::setFrustumVertices(osg::ref_ptr vertices, unsigned int traversalNumber) { mFrustumGeometries[traversalNumber % 2]->setVertexArray(vertices); } void SceneUtil::MWShadowTechnique::DebugHUD::addAnotherShadowMap() { unsigned int shadowMapNumber = mDebugCameras.size(); mDebugCameras.push_back(new osg::Camera); mDebugCameras[shadowMapNumber]->setViewport(200 * shadowMapNumber, 0, 200, 200); mDebugCameras[shadowMapNumber]->setRenderOrder(osg::Camera::POST_RENDER); mDebugCameras[shadowMapNumber]->setClearColor(osg::Vec4(1.0, 1.0, 0.0, 1.0)); mDebugCameras[shadowMapNumber]->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); mDebugGeometry.emplace_back(osg::createTexturedQuadGeometry(osg::Vec3(-1, -1, 0), osg::Vec3(2, 0, 0), osg::Vec3(0, 2, 0))); mDebugGeometry[shadowMapNumber]->setCullingActive(false); mDebugCameras[shadowMapNumber]->addChild(mDebugGeometry[shadowMapNumber]); osg::ref_ptr stateSet = mDebugGeometry[shadowMapNumber]->getOrCreateStateSet(); stateSet->setAttributeAndModes(mDebugProgram, osg::StateAttribute::ON); osg::ref_ptr textureUniform = new osg::Uniform("texture", sDebugTextureUnit); //textureUniform->setType(osg::Uniform::SAMPLER_2D); stateSet->addUniform(textureUniform.get()); mFrustumTransforms.push_back(new osg::Group); osg::NodeList frustumGeometryNodeList(mFrustumGeometries.cbegin(), mFrustumGeometries.cend()); mFrustumTransforms[shadowMapNumber]->setCullCallback(new DoubleBufferCallback(frustumGeometryNodeList)); mFrustumTransforms[shadowMapNumber]->setCullingActive(false); mDebugCameras[shadowMapNumber]->addChild(mFrustumTransforms[shadowMapNumber]); for(auto& uniformVector : mFrustumUniforms) uniformVector.push_back(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "transform")); }