diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index b5bf0b6320..4a114490c6 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -608,9 +608,9 @@ printf "OSG 3.4.1-scrawl... " SUFFIX="" fi add_runtime_dlls "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng*}${SUFFIX}.dll \ - "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer}${SUFFIX}.dll + "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll - add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll + add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll echo Done. } cd $DEPS diff --git a/CMakeLists.txt b/CMakeLists.txt index aaf269eefc..795a7d3090 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,7 +256,7 @@ if(NOT HAVE_STDINT_H) endif() -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) set(USED_OSG_PLUGINS diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0bb6e900de..554a3ac861 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -126,6 +126,7 @@ target_link_libraries(openmw ${OSGDB_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} + ${OSGSHADOW_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index ce6a27389a..a3679e844c 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -136,6 +137,7 @@ namespace MWRender mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); mCamera->setName("CharacterPreview"); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + mCamera->setCullMask(~(Mask_UpdateVisitor)); mCamera->setNodeMask(Mask_RenderToTexture); @@ -152,6 +154,8 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog (new osg::Fog); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 8ea4e39917..a7f9247f71 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" @@ -177,7 +178,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setCullMask(Mask_Scene|Mask_SimpleWater|Mask_Terrain); + camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object); camera->setNodeMask(Mask_RenderToTexture); osg::ref_ptr stateset = new osg::StateSet; @@ -209,6 +210,8 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + camera->addChild(lightSource); camera->setStateSet(stateset); camera->setViewport(0, 0, mMapResolution, mMapResolution); @@ -377,7 +380,7 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) { osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain); + computeBoundsVisitor.setTraversalMask(Mask_Scene | Mask_Terrain | Mask_Object); mSceneRoot->accept(computeBoundsVisitor); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 920e09a69a..05b8a281fd 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -362,11 +362,16 @@ public: if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar)) { fov = mFov; - osg::RefMatrix* newProjectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); + osg::ref_ptr newProjectionMatrix = new osg::RefMatrix(); newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar); - cv->pushProjectionMatrix(newProjectionMatrix); + osg::ref_ptr invertedOldMatrix = cv->getProjectionMatrix(); + invertedOldMatrix = new osg::RefMatrix(osg::RefMatrix::inverse(*invertedOldMatrix)); + osg::ref_ptr viewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + viewMatrix->postMult(*newProjectionMatrix); + viewMatrix->postMult(*invertedOldMatrix); + cv->pushModelViewMatrix(viewMatrix, osg::Transform::ReferenceFrame::ABSOLUTE_RF); traverse(node, nv); - cv->popProjectionMatrix(); + cv->popModelViewMatrix(); } else traverse(node, nv); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 882c9856f2..ec1c4397bf 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -71,6 +71,7 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool animated, bool allowLight) { insertBegin(ptr); + ptr.getRefData().getBaseNode()->setNodeMask(Mask_Object); osg::ref_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 322c310a21..51a92a280c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -219,7 +220,7 @@ namespace MWRender { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); - resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders")); + resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows")); // Shadows have problems with fixed-function mode resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); @@ -233,7 +234,28 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mRootNode->addChild(mSceneRoot); + int shadowCastingTraversalMask = Mask_Scene; + if (Settings::Manager::getBool("actor shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Actor; + if (Settings::Manager::getBool("player shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Player; + if (Settings::Manager::getBool("terrain shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Terrain; + + int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; + if (Settings::Manager::getBool("object shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Object; + + mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); + + Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); + Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); + + for (auto itr = shadowDefines.begin(); itr != shadowDefines.end(); itr++) + globalDefines[itr->first] = itr->second; + + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. + mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); @@ -443,7 +465,7 @@ namespace MWRender osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(diffuse); - mSunLight->setDirection(osg::Vec3f(1.f,-1.f,-1.f)); + mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f)); } void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular) @@ -491,6 +513,10 @@ namespace MWRender void RenderingManager::setSkyEnabled(bool enabled) { mSky->setEnabled(enabled); + if (enabled) + mShadowManager->enableOutdoorMode(); + else + mShadowManager->enableIndoorMode(); } bool RenderingManager::toggleBorders() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index e04404f2b0..3daf810716 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -53,6 +53,7 @@ namespace Fallback namespace SceneUtil { + class ShadowManager; class WorkQueue; class UnrefQueue; } @@ -267,6 +268,7 @@ namespace MWRender TerrainStorage* mTerrainStorage; std::unique_ptr mSky; std::unique_ptr mEffectManager; + std::unique_ptr mShadowManager; osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 0ea0905a61..194646f2aa 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -1127,7 +1128,8 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana skyroot->setName("Sky Root"); // Assign empty program to specify we don't want shaders // The shaders generated by the SceneManager can't handle everything we need - skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE); + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); + SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); skyroot->setNodeMask(Mask_Sky); parentNode->addChild(skyroot); diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index d52c7c232e..1d94b4bf9d 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -33,25 +33,26 @@ namespace MWRender Mask_SimpleWater = (1<<7), Mask_Terrain = (1<<8), Mask_FirstPerson = (1<<9), + Mask_Object = (1<<10), // child of Sky - Mask_Sun = (1<<10), - Mask_WeatherParticles = (1<<11), + Mask_Sun = (1<<11), + Mask_WeatherParticles = (1<<12), // top level masks - Mask_Scene = (1<<12), - Mask_GUI = (1<<13), + Mask_Scene = (1<<13), + Mask_GUI = (1<<14), // Set on a ParticleSystem Drawable - Mask_ParticleSystem = (1<<14), + Mask_ParticleSystem = (1<<15), // Set on cameras within the main scene graph - Mask_RenderToTexture = (1<<15), + Mask_RenderToTexture = (1<<16), - Mask_PreCompile = (1<<16), + Mask_PreCompile = (1<<17), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<17) + Mask_Lighting = (1<<18) }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index f668b0820f..376924d82d 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -224,7 +225,7 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("RefractionCamera"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -263,6 +264,8 @@ public: mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); + + SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setScene(osg::Node* scene) @@ -315,7 +318,7 @@ public: bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -341,6 +344,8 @@ public: mClipCullNode = new ClipCullNode; addChild(mClipCullNode); + + SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setWaterLevel(float waterLevel) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 50fd590068..d26a92d448 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -51,7 +51,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer - actorutil detourdebugdraw navmesh agentpath + actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique ) add_component_dir (nif @@ -228,6 +228,7 @@ target_link_libraries(components ${OSGVIEWER_LIBRARIES} ${OSGTEXT_LIBRARIES} ${OSGGA_LIBRARIES} + ${OSGSHADOW_LIBRARIES} ${OSGANIMATION_LIBRARIES} ${Bullet_LIBRARIES} ${SDL2_LIBRARIES} diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index d45f37b256..a2dad247d9 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1284,6 +1284,7 @@ namespace NifOsg boundTextures.clear(); } + // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the shadow casting shader will need to be updated accordingly. for (int i=0; itextures[i].inUse) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index e102653b90..cb6a12c879 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -399,7 +399,7 @@ namespace SceneUtil return false; } - if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask())) + if (!(cv->getTraversalMask() & mLightManager->getLightingMask())) return false; // Possible optimizations: diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp new file mode 100644 index 0000000000..64c2e6e55f --- /dev/null +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -0,0 +1,3180 @@ +/* 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 + +using namespace osgShadow; +using namespace SceneUtil; + +////////////////////////////////////////////////////////////////// +// 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); + + virtual void operator()(osg::Node*, osg::NodeVisitor* nv); + + 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 + if (_vdsm->getShadowedScene()) + { + _vdsm->getShadowedScene()->osg::Group::traverse(*nv); + } +#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(); +} + +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); + + //_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) +{ + _shadowRecievingPlaceholderStateSet = new osg::StateSet; +} + +MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): + ShadowTechnique(vdsm,copyop) +{ + _shadowRecievingPlaceholderStateSet = new osg::StateSet; +} + +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::enableFrontFaceCulling() +{ + _useFrontFaceCulling = true; + + if (_shadowCastingStateSet) + _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); +} + +void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() +{ + _useFrontFaceCulling = false; + + if (_shadowCastingStateSet) + _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 + + _castingProgram = new osg::Program(); + + _castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); + _castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); +} + +MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) +{ + return new ViewDependentData(this); +} + +MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(osgUtil::CullVisitor* cv) +{ + OpenThreads::ScopedLock 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) + { + _shadowedScene->osg::Group::traverse(cv); + 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; + + OpenThreads::ScopedLock lock(_accessUniformsAndProgramMutex); + + for (auto uniform : _uniforms) + { + if (uniform->getName() == validRegionUniformName) + validRegionUniform = uniform; + } + + if (!validRegionUniform) + { + validRegionUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, validRegionUniformName); + _uniforms.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) + { + decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd)); + } + + // 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"< lock(_accessUniformsAndProgramMutex); + + _shadowCastingStateSet = new osg::StateSet; + + ShadowSettings* settings = getShadowedScene()->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); + + + _uniforms.clear(); + osg::ref_ptr baseTextureSampler = new osg::Uniform("baseTexture",(int)_baseTextureUnit); + _uniforms.push_back(baseTextureSampler.get()); + + osg::ref_ptr baseTextureUnit = new osg::Uniform("baseTextureUnit",(int)_baseTextureUnit); + _uniforms.push_back(baseTextureUnit.get()); + + 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)); + _uniforms.push_back(shadowTextureSampler.get()); + } + + { + std::stringstream sstr; + sstr<<"shadowTextureUnit"< shadowTextureUnit = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); + _uniforms.push_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); + + } + + if (!_castingProgram) + OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; + + _shadowCastingStateSet->setAttributeAndModes(_castingProgram, 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", false)); + + _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); + + _shadowCastingStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); + + // TODO: compare performance when alpha testing is handled here versus using a discard in the fragment shader + // TODO: compare performance when we set a bunch of GL state to the default here with OVERRIDE set so that there are fewer pointless state switches +} + +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.push_back(osg::Plane(0.0, -1.0, 0.0, yMax)); + planeList.push_back(osg::Plane(0.0, 1.0, 0.0, -yMin)); + planeList.push_back(osg::Plane(-1.0, 0.0, 0.0, xMax)); + planeList.push_back(osg::Plane(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.push_back(osg::Plane(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::selectStateSetForRenderingShadow(ViewDependentData& vdd) const +{ + OSG_INFO<<" selectStateSetForRenderingShadow() "< stateset = vdd.getStateSet(); + + OpenThreads::ScopedLock lock(_accessUniformsAndProgramMutex); + + vdd.getStateSet()->clear(); + + vdd.getStateSet()->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); + + for(Uniforms::const_iterator itr=_uniforms.begin(); + itr!=_uniforms.end(); + ++itr) + { + OSG_INFO<<"addUniform("<<(*itr)->getName()<<")"<addUniform(itr->get()); + } + + 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 vdd.getStateSet(); +} + +void MWShadowTechnique::resizeGLObjectBuffers(unsigned int /*maxSize*/) +{ + // the way that ViewDependentData is mapped shouldn't +} + +void MWShadowTechnique::releaseGLObjects(osg::State* state) const +{ + OpenThreads::ScopedLock 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) {} + + virtual 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 (int i = 0; i < 2; ++i) + { + mFrustumGeometries.emplace_back(new osg::Geometry()); + mFrustumGeometries[i]->setCullingActive(false); + + mFrustumGeometries[i]->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(); + + mFrustumUniforms[shadowMapNumber]->set(matrix); + + osg::ref_ptr stateSet = mDebugGeometry[shadowMapNumber]->getOrCreateStateSet(); + stateSet->setTextureAttributeAndModes(sDebugTextureUnit, texture, osg::StateAttribute::ON); + + // 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.push_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]); + + mFrustumUniforms.push_back(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "transform")); + mFrustumTransforms[shadowMapNumber]->getOrCreateStateSet()->addUniform(mFrustumUniforms[shadowMapNumber]); +} diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp new file mode 100644 index 0000000000..6210667990 --- /dev/null +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -0,0 +1,287 @@ +/* This file is based on OpenSceneGraph's include/osgShadow/ViewDependentShadowMap. + * 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. +*/ + +#ifndef COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H +#define COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H 1 + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace SceneUtil { + + /** ViewDependentShadowMap provides an base implementation of view dependent shadow mapping techniques.*/ + class MWShadowTechnique : public osgShadow::ShadowTechnique + { + public: + MWShadowTechnique(); + + MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_Object(SceneUtil, MWShadowTechnique); + + /** initialize the ShadowedScene and local cached data structures.*/ + virtual void init(); + + /** run the update traversal of the ShadowedScene and update any loca chached data structures.*/ + virtual void update(osg::NodeVisitor& nv); + + /** run the cull traversal of the ShadowedScene and set up the rendering for this ShadowTechnique.*/ + virtual void cull(osgUtil::CullVisitor& cv); + + /** Resize any per context GLObject buffers to specified size. */ + virtual void resizeGLObjectBuffers(unsigned int maxSize); + + /** If State is non-zero, this function releases any associated OpenGL objects for + * the specified graphics context. Otherwise, releases OpenGL objects + * for all graphics contexts. */ + virtual void releaseGLObjects(osg::State* = 0) const; + + /** Clean scene graph from any shadow technique specific nodes, state and drawables.*/ + virtual void cleanSceneGraph(); + + virtual void enableShadows(); + + virtual void disableShadows(); + + virtual void enableDebugHUD(); + + virtual void disableDebugHUD(); + + virtual void setSplitPointUniformLogarithmicRatio(double ratio); + + virtual void setSplitPointDeltaBias(double bias); + + virtual void setPolygonOffset(float factor, float units); + + virtual void enableFrontFaceCulling(); + + virtual void disableFrontFaceCulling(); + + virtual void setupCastingShader(Shader::ShaderManager &shaderManager); + + class ComputeLightSpaceBounds : public osg::NodeVisitor, public osg::CullStack + { + public: + ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix); + + void apply(osg::Node& node); + + void apply(osg::Drawable& drawable); + + void apply(Terrain::QuadTreeWorld& quadTreeWorld); + + void apply(osg::Billboard&); + + void apply(osg::Projection&); + + void apply(osg::Transform& transform); + + void apply(osg::Camera&); + + void updateBound(const osg::BoundingBox& bb); + + void update(const osg::Vec3& v); + + osg::BoundingBox _bb; + }; + + struct Frustum + { + Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar); + + osg::Matrixd projectionMatrix; + osg::Matrixd modelViewMatrix; + + typedef std::vector Vertices; + Vertices corners; + + typedef std::vector Indices; + typedef std::vector Faces; + Faces faces; + + typedef std::vector Edges; + Edges edges; + + osg::Vec3d eye; + osg::Vec3d centerNearPlane; + osg::Vec3d centerFarPlane; + osg::Vec3d center; + osg::Vec3d frustumCenterLine; + }; + + // forward declare + class ViewDependentData; + + struct LightData : public osg::Referenced + { + LightData(ViewDependentData* vdd); + + virtual void setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix); + + ViewDependentData* _viewDependentData; + + osg::ref_ptr lightMatrix; + osg::ref_ptr light; + + osg::Vec4d lightPos; + osg::Vec3d lightPos3; + osg::Vec3d lightDir; + bool directionalLight; + + typedef std::vector ActiveTextureUnits; + ActiveTextureUnits textureUnits; + }; + + typedef std::list< osg::ref_ptr > LightDataList; + + struct ShadowData : public osg::Referenced + { + ShadowData(ViewDependentData* vdd); + + virtual void releaseGLObjects(osg::State* = 0) const; + + ViewDependentData* _viewDependentData; + + unsigned int _textureUnit; + osg::ref_ptr _texture; + osg::ref_ptr _texgen; + osg::ref_ptr _camera; + }; + + typedef std::list< osg::ref_ptr > ShadowDataList; + + + class ViewDependentData : public osg::Referenced + { + public: + ViewDependentData(MWShadowTechnique* vdsm); + + const MWShadowTechnique* getViewDependentShadowMap() const { return _viewDependentShadowMap; } + + LightDataList& getLightDataList() { return _lightDataList; } + + ShadowDataList& getShadowDataList() { return _shadowDataList; } + + osg::StateSet* getStateSet() { return _stateset.get(); } + + virtual void releaseGLObjects(osg::State* = 0) const; + + protected: + virtual ~ViewDependentData() {} + + MWShadowTechnique* _viewDependentShadowMap; + + osg::ref_ptr _stateset; + + LightDataList _lightDataList; + ShadowDataList _shadowDataList; + }; + + virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); + + ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); + + + + virtual void createShaders(); + + virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; + + virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); + + virtual bool computeShadowCameraSettings(Frustum& frustum, LightData& positionedLight, osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix); + + virtual bool cropShadowCameraToMainFrustum(Frustum& frustum, osg::Camera* camera, double viewNear, double viewFar, std::vector& planeList); + + virtual bool adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& positionedLight, osg::Camera* camera, double viewNear, double viewFar); + + virtual bool assignTexGenSettings(osgUtil::CullVisitor* cv, osg::Camera* camera, unsigned int textureUnit, osg::TexGen* texgen); + + virtual void cullShadowReceivingScene(osgUtil::CullVisitor* cv) const; + + virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; + + virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd) const; + + protected: + virtual ~MWShadowTechnique(); + + typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr > ViewDependentDataMap; + mutable OpenThreads::Mutex _viewDependentDataMapMutex; + ViewDependentDataMap _viewDependentDataMap; + + osg::ref_ptr _shadowRecievingPlaceholderStateSet; + + osg::ref_ptr _shadowCastingStateSet; + osg::ref_ptr _polygonOffset; + osg::ref_ptr _fallbackBaseTexture; + osg::ref_ptr _fallbackShadowMapTexture; + + typedef std::vector< osg::ref_ptr > Uniforms; + mutable OpenThreads::Mutex _accessUniformsAndProgramMutex; + Uniforms _uniforms; + osg::ref_ptr _program; + + bool _enableShadows; + + double _splitPointUniformLogRatio = 0.5; + double _splitPointDeltaBias = 0.0; + + float _polygonOffsetFactor = 1.1; + float _polygonOffsetUnits = 4.0; + + bool _useFrontFaceCulling = true; + + class DebugHUD : public osg::Referenced + { + public: + DebugHUD(int numberOfShadowMapsPerLight); + + virtual void draw(osg::ref_ptr texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv); + + virtual void releaseGLObjects(osg::State* state = 0) const; + + virtual void setFrustumVertices(osg::ref_ptr vertices, unsigned int traversalNumber); + protected: + virtual void addAnotherShadowMap(); + + static const int sDebugTextureUnit = 0; + + std::vector> mDebugCameras; + osg::ref_ptr mDebugProgram; + std::vector> mDebugGeometry; + std::vector> mFrustumTransforms; + std::vector> mFrustumUniforms; + std::vector> mFrustumGeometries; + }; + + osg::ref_ptr _debugHud; + osg::ref_ptr _castingProgram; + }; + +} + +#endif diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index f32f215e7d..f1acf33e99 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -71,6 +71,8 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) to.setSupportsDisplayList(false); to.setUseVertexBufferObjects(true); to.setCullingActive(false); // make sure to disable culling since that's handled by this class + to.setComputeBoundingBoxCallback(new CopyBoundingBoxCallback()); + to.setComputeBoundingSphereCallback(new CopyBoundingSphereCallback()); // vertices and normals are modified every frame, so we need to deep copy them. // assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO. @@ -296,6 +298,14 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) _boundingSphereComputed = true; for (unsigned int i=0; idirtyBound(); + + for (unsigned int i = 0; i < 2; ++i) + { + osg::Geometry& geom = *mGeometry[i]; + static_cast(geom.getComputeBoundingBoxCallback())->boundingBox = _boundingBox; + static_cast(geom.getComputeBoundingSphereCallback())->boundingSphere = _boundingSphere; + geom.dirtyBound(); + } } } diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 267c709f9f..a393530ec2 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -50,6 +50,20 @@ namespace SceneUtil virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } virtual void accept(osg::PrimitiveFunctor&) const; + struct CopyBoundingBoxCallback : osg::Drawable::ComputeBoundingBoxCallback + { + osg::BoundingBox boundingBox; + + virtual osg::BoundingBox computeBound(const osg::Drawable&) const override { return boundingBox; } + }; + + struct CopyBoundingSphereCallback : osg::Node::ComputeBoundingSphereCallback + { + osg::BoundingSphere boundingSphere; + + virtual osg::BoundingSphere computeBound(const osg::Node&) const override { return boundingSphere; } + }; + private: void cull(osg::NodeVisitor* nv); void updateBounds(osg::NodeVisitor* nv); diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp new file mode 100644 index 0000000000..a69fd80909 --- /dev/null +++ b/components/sceneutil/shadow.cpp @@ -0,0 +1,158 @@ +#include "shadow.hpp" + +#include + +#include + +namespace SceneUtil +{ + using namespace osgShadow; + + void ShadowManager::setupShadowSettings() + { + mEnableShadows = Settings::Manager::getBool("enable shadows", "Shadows"); + + if (!mEnableShadows) + { + mShadowTechnique->disableShadows(); + return; + } + + mShadowTechnique->enableShadows(); + + mShadowSettings->setLightNum(0); + mShadowSettings->setReceivesShadowTraversalMask(~0u); + + int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); + mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight); + mShadowSettings->setBaseShadowTextureUnit(8 - numberOfShadowMapsPerLight); + + mShadowSettings->setMinimumShadowMapNearFarRatio(Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); + if (Settings::Manager::getBool("compute tight scene bounds", "Shadows")) + mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); + + int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); + mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres)); + + mShadowTechnique->setSplitPointUniformLogarithmicRatio(Settings::Manager::getFloat("split point uniform logarithmic ratio", "Shadows")); + mShadowTechnique->setSplitPointDeltaBias(Settings::Manager::getFloat("split point bias", "Shadows")); + + mShadowTechnique->setPolygonOffset(Settings::Manager::getFloat("polygon offset factor", "Shadows"), Settings::Manager::getFloat("polygon offset units", "Shadows")); + + if (Settings::Manager::getBool("use front face culling", "Shadows")) + mShadowTechnique->enableFrontFaceCulling(); + else + mShadowTechnique->disableFrontFaceCulling(); + + if (Settings::Manager::getBool("allow shadow map overlap", "Shadows")) + mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED); + else + mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT); + + if (Settings::Manager::getBool("enable debug hud", "Shadows")) + mShadowTechnique->enableDebugHUD(); + else + mShadowTechnique->disableDebugHUD(); + } + + void ShadowManager::disableShadowsForStateSet(osg::ref_ptr stateset) + { + int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); + int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; + + osg::ref_ptr fakeShadowMapImage = new osg::Image(); + fakeShadowMapImage->allocateImage(1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); + *(float*)fakeShadowMapImage->data() = std::numeric_limits::infinity(); + osg::ref_ptr fakeShadowMapTexture = new osg::Texture2D(fakeShadowMapImage); + fakeShadowMapTexture->setShadowComparison(true); + fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); + for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) + { + stateset->setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); + stateset->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + stateset->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + } + } + + ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), + mShadowTechnique(new MWShadowTechnique), + mOutdoorShadowCastingMask(outdoorShadowCastingMask), + mIndoorShadowCastingMask(indoorShadowCastingMask) + { + mShadowedScene->setShadowTechnique(mShadowTechnique); + + mShadowedScene->addChild(sceneRoot); + rootNode->addChild(mShadowedScene); + + mShadowSettings = mShadowedScene->getShadowSettings(); + setupShadowSettings(); + + mShadowTechnique->setupCastingShader(shaderManager); + + enableOutdoorMode(); + } + + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() + { + if (!mEnableShadows) + return getShadowsDisabledDefines(); + + Shader::ShaderManager::DefineMap definesWithShadows; + + definesWithShadows["shadows_enabled"] = "1"; + + for (unsigned int i = 0; i < mShadowSettings->getNumShadowMapsPerLight(); ++i) + definesWithShadows["shadow_texture_unit_list"] += std::to_string(i) + ","; + // remove extra comma + definesWithShadows["shadow_texture_unit_list"] = definesWithShadows["shadow_texture_unit_list"].substr(0, definesWithShadows["shadow_texture_unit_list"].length() - 1); + + definesWithShadows["shadowMapsOverlap"] = Settings::Manager::getBool("allow shadow map overlap", "Shadows") ? "1" : "0"; + + definesWithShadows["useShadowDebugOverlay"] = Settings::Manager::getBool("enable debug overlay", "Shadows") ? "1" : "0"; + + // switch this to reading settings if it's ever exposed to the user + definesWithShadows["perspectiveShadowMaps"] = mShadowSettings->getShadowMapProjectionHint() == ShadowSettings::PERSPECTIVE_SHADOW_MAP ? "1" : "0"; + + definesWithShadows["disableNormalOffsetShadows"] = Settings::Manager::getFloat("normal offset distance", "Shadows") == 0.0 ? "1" : "0"; + + definesWithShadows["shadowNormalOffset"] = std::to_string(Settings::Manager::getFloat("normal offset distance", "Shadows")); + + return definesWithShadows; + } + + Shader::ShaderManager::DefineMap ShadowManager::getShadowsDisabledDefines() + { + Shader::ShaderManager::DefineMap definesWithoutShadows; + + definesWithoutShadows["shadows_enabled"] = "0"; + + definesWithoutShadows["shadow_texture_unit_list"] = ""; + + definesWithoutShadows["shadowMapsOverlap"] = "0"; + + definesWithoutShadows["useShadowDebugOverlay"] = "0"; + + definesWithoutShadows["perspectiveShadowMaps"] = "0"; + + definesWithoutShadows["disableNormalOffsetShadows"] = "0"; + + definesWithoutShadows["shadowNormalOffset"] = "0.0"; + + return definesWithoutShadows; + } + + void ShadowManager::enableIndoorMode() + { + if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask); + else + mShadowTechnique->disableShadows(); + } + + void ShadowManager::enableOutdoorMode() + { + if (mEnableShadows) + mShadowTechnique->enableShadows(); + mShadowSettings->setCastsShadowTraversalMask(mOutdoorShadowCastingMask); + } +} diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp new file mode 100644 index 0000000000..24deff2531 --- /dev/null +++ b/components/sceneutil/shadow.hpp @@ -0,0 +1,43 @@ +#ifndef COMPONENTS_SCENEUTIL_SHADOW_H +#define COMPONENTS_SCENEUTIL_SHADOW_H + +#include +#include + +#include + +#include "mwshadowtechnique.hpp" + +namespace SceneUtil +{ + class ShadowManager + { + public: + static void disableShadowsForStateSet(osg::ref_ptr stateSet); + + ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager); + + virtual ~ShadowManager() = default; + + virtual void setupShadowSettings(); + + virtual Shader::ShaderManager::DefineMap getShadowDefines(); + + virtual Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); + + virtual void enableIndoorMode(); + + virtual void enableOutdoorMode(); + protected: + bool mEnableShadows; + + osg::ref_ptr mShadowedScene; + osg::ref_ptr mShadowSettings; + osg::ref_ptr mShadowTechnique; + + unsigned int mOutdoorShadowCastingMask; + unsigned int mIndoorShadowCastingMask; + }; +} + +#endif //COMPONENTS_SCENEUTIL_SHADOW_H diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index f3d52f5f04..0f4dc2e738 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -21,6 +21,44 @@ namespace Shader mPath = path; } + bool addLineDirectivesAfterConditionalBlocks(std::string& source) + { + for (size_t position = 0; position < source.length(); ) + { + size_t foundPos = source.find("#endif", position); + foundPos = std::min(foundPos, source.find("#elif", position)); + foundPos = std::min(foundPos, source.find("#else", position)); + + if (foundPos == std::string::npos) + break; + + foundPos = source.find_first_of("\n\r", foundPos); + foundPos = source.find_first_not_of("\n\r", foundPos); + + size_t lineDirectivePosition = source.rfind("#line", foundPos); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString) - 1; + } + else + { + lineDirectivePosition = 0; + lineNumber = 1; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); + + source.replace(foundPos, 0, "#line " + std::to_string(lineNumber) + "\n"); + + position = foundPos; + } + + return true; + } + bool parseIncludes(boost::filesystem::path shaderPath, std::string& source) { boost::replace_all(source, "\r\n", "\n"); @@ -54,14 +92,30 @@ namespace Shader std::stringstream buffer; buffer << includeFstream.rdbuf(); + std::string stringRepresentation = buffer.str(); + addLineDirectivesAfterConditionalBlocks(stringRepresentation); // insert #line directives so we get correct line numbers in compiler errors int includedFileNumber = fileNumber++; - int lineNumber = std::count(source.begin(), source.begin() + foundPos, '\n'); + size_t lineDirectivePosition = source.rfind("#line", foundPos); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString) - 1; + } + else + { + lineDirectivePosition = 0; + lineNumber = 1; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); std::stringstream toInsert; - toInsert << "#line 0 " << includedFileNumber << "\n" << buffer.str() << "\n#line " << lineNumber << " 0\n"; + toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n"; source.replace(foundPos, (end-foundPos+1), toInsert.str()); @@ -74,13 +128,97 @@ namespace Shader return true; } - bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines) + bool parseFors(std::string& source) { - const char escapeCharacter = '@'; + const char escapeCharacter = '$'; size_t foundPos = 0; while ((foundPos = source.find(escapeCharacter)) != std::string::npos) { - size_t endPos = source.find_first_of(" \n\r()[].;", foundPos); + size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); + if (endPos == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string command = source.substr(foundPos + 1, endPos - (foundPos + 1)); + if (command != "foreach") + { + Log(Debug::Error) << "Unknown shader directive: $" << command; + return false; + } + + size_t iterNameStart = endPos + 1; + size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); + if (iterNameEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart); + + size_t listStart = iterNameEnd + 1; + size_t listEnd = source.find_first_of("\n\r", listStart); + if (listEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string list = source.substr(listStart, listEnd - listStart); + std::vector listElements; + if (list != "") + boost::split(listElements, list, boost::is_any_of(",")); + + size_t contentStart = source.find_first_not_of("\n\r", listEnd); + size_t contentEnd = source.find("$endforeach", contentStart); + if (contentEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string content = source.substr(contentStart, contentEnd - contentStart); + + size_t overallEnd = contentEnd + std::string("$endforeach").length(); + + size_t lineDirectivePosition = source.rfind("#line", overallEnd); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString); + } + else + { + lineDirectivePosition = 0; + lineNumber = 2; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + overallEnd, '\n'); + + std::string replacement = ""; + for (std::vector::const_iterator element = listElements.cbegin(); element != listElements.cend(); element++) + { + std::string contentInstance = content; + size_t foundIterator; + while ((foundIterator = contentInstance.find(iteratorName)) != std::string::npos) + contentInstance.replace(foundIterator, iteratorName.length(), *element); + replacement += contentInstance; + } + replacement += "\n#line " + std::to_string(lineNumber); + source.replace(foundPos, overallEnd - foundPos, replacement); + } + + return true; + } + + bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines) + { + const char escapeCharacter = '@'; + size_t foundPos = 0; + std::vector forIterators; + while ((foundPos = source.find(escapeCharacter)) != std::string::npos) + { + size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); if (endPos == std::string::npos) { Log(Debug::Error) << "Unexpected EOF"; @@ -88,14 +226,46 @@ namespace Shader } std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); - if (defineFound == defines.end()) + ShaderManager::DefineMap::const_iterator globalDefineFound = globalDefines.find(define); + if (define == "foreach") { - Log(Debug::Error) << "Undefined " << define; - return false; + source.replace(foundPos, 1, "$"); + size_t iterNameStart = endPos + 1; + size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); + if (iterNameEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + forIterators.push_back(source.substr(iterNameStart, iterNameEnd - iterNameStart)); + } + else if (define == "endforeach") + { + source.replace(foundPos, 1, "$"); + if (forIterators.empty()) + { + Log(Debug::Error) << "endforeach without foreach"; + return false; + } + else + forIterators.pop_back(); + } + else if (std::find(forIterators.begin(), forIterators.end(), define) != forIterators.end()) + { + source.replace(foundPos, 1, "$"); + } + else if (defineFound != defines.end()) + { + source.replace(foundPos, endPos - foundPos, defineFound->second); + } + else if (globalDefineFound != globalDefines.end()) + { + source.replace(foundPos, endPos - foundPos, globalDefineFound->second); } else { - source.replace(foundPos, endPos-foundPos, defineFound->second); + Log(Debug::Error) << "Undefined " << define; + return false; } } return true; @@ -122,7 +292,7 @@ namespace Shader // parse includes std::string source = buffer.str(); - if (!parseIncludes(boost::filesystem::path(mPath), source)) + if (!addLineDirectivesAfterConditionalBlocks(source) || !parseIncludes(boost::filesystem::path(mPath), source)) return nullptr; templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first; @@ -132,7 +302,7 @@ namespace Shader if (shaderIt == mShaders.end()) { std::string shaderSource = templateIt->second; - if (!parseDefines(shaderSource, defines)) + if (!parseDefines(shaderSource, defines, mGlobalDefines) || !parseFors(shaderSource)) { // Add to the cache anyway to avoid logging the same error over and over. mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), nullptr)); @@ -164,11 +334,39 @@ namespace Shader return found->second; } + ShaderManager::DefineMap ShaderManager::getGlobalDefines() + { + return DefineMap(mGlobalDefines); + } + + void ShaderManager::setGlobalDefines(DefineMap & globalDefines) + { + mGlobalDefines = globalDefines; + for (auto shaderMapElement: mShaders) + { + std::string templateId = shaderMapElement.first.first; + ShaderManager::DefineMap defines = shaderMapElement.first.second; + osg::ref_ptr shader = shaderMapElement.second; + if (shader == nullptr) + // I'm not sure how to handle a shader that was already broken as there's no way to get a potential replacement to the nodes that need it. + continue; + std::string shaderSource = mShaderTemplates[templateId]; + if (!parseDefines(shaderSource, defines, mGlobalDefines) || !parseFors(shaderSource)) + // We just broke the shader and there's no way to force existing objects back to fixed-function mode as we would when creating the shader. + // If we put a nullptr in the shader map, we just lose the ability to put a working one in later. + continue; + shader->setShaderSource(shaderSource); + } + } + void ShaderManager::releaseGLObjects(osg::State *state) { OpenThreads::ScopedLock lock(mMutex); for (auto shader : mShaders) - shader.second->releaseGLObjects(state); + { + if (shader.second != nullptr) + shader.second->releaseGLObjects(state); + } for (auto program : mPrograms) program.second->releaseGLObjects(state); } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 6b2e021242..05775edb69 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -8,6 +8,8 @@ #include +#include + #include namespace Shader @@ -32,11 +34,21 @@ namespace Shader osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + /// Get (a copy of) the DefineMap used to construct all shaders + DefineMap getGlobalDefines(); + + /// Set the DefineMap used to construct all shaders + /// @param defines The DefineMap to use + /// @note This will change the source code for any shaders already created, potentially causing problems if they're being used to render a frame. It is recommended that any associated Viewers have their threading stopped while this function is running if any shaders are in use. + void setGlobalDefines(DefineMap & globalDefines); + void releaseGLObjects(osg::State* state); private: std::string mPath; + DefineMap mGlobalDefines; + // typedef std::map TemplateMap; TemplateMap mShaderTemplates; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a0a6832d06..3080e1318a 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -23,8 +23,7 @@ namespace Shader ShaderVisitor::ShaderRequirements::ShaderRequirements() : mShaderRequired(false) - , mColorMaterial(false) - , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) + , mColorMode(0) , mMaterialOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) @@ -217,6 +216,13 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } + + if (diffuseMap) + { + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); + } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); @@ -230,8 +236,29 @@ namespace Shader mRequirements.back().mMaterialOverridden = true; const osg::Material* mat = static_cast(it->second.first.get()); - mRequirements.back().mColorMaterial = (mat->getColorMode() != osg::Material::OFF); - mRequirements.back().mVertexColorMode = mat->getColorMode(); + + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + + int colorMode; + switch (mat->getColorMode()) + { + case osg::Material::OFF: + colorMode = 0; + break; + case GL_AMBIENT: + colorMode = 3; + break; + default: + case GL_AMBIENT_AND_DIFFUSE: + colorMode = 2; + break; + case GL_EMISSION: + colorMode = 1; + break; + } + + mRequirements.back().mColorMode = colorMode; } } } @@ -272,30 +299,13 @@ namespace Shader defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } - if (!reqs.mColorMaterial) - defineMap["colorMode"] = "0"; - else - { - switch (reqs.mVertexColorMode) - { - case GL_AMBIENT: - defineMap["colorMode"] = "3"; - break; - default: - case GL_AMBIENT_AND_DIFFUSE: - defineMap["colorMode"] = "2"; - break; - case GL_EMISSION: - defineMap["colorMode"] = "1"; - break; - } - } - defineMap["forcePPL"] = mForcePerPixelLighting ? "1" : "0"; defineMap["clamp"] = mClampLighting ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; + writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index cb0538d9d3..8737baf599 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -81,9 +81,8 @@ namespace Shader bool mShaderRequired; - bool mColorMaterial; - // osg::Material::ColorMode - int mVertexColorMode; + int mColorMode; + bool mMaterialOverridden; bool mNormalHeight; // true if normal map has height info in alpha channel diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index a0f051524b..83684b5c91 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -218,7 +218,6 @@ namespace Terrain defineMap["clamp"] = clampLighting ? "1" : "0"; defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; defineMap["blendMap"] = !firstLayer ? "1" : "0"; - defineMap["colorMode"] = "2"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; @@ -231,6 +230,7 @@ namespace Terrain } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); + stateset->addUniform(new osg::Uniform("colorMode", 2)); } else { diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 2222cbb844..4b4df43659 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -134,7 +134,7 @@ ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv) { osgUtil::CullVisitor* cv = static_cast(&nv); ViewData* vd = mViewDataMap->getViewData(cv->getCurrentCamera()); - vd->setEyePoint(nv.getEyePoint()); + vd->setEyePoint(nv.getViewPoint()); return vd; } else // INTERSECTION_VISITOR diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 40f7a6fb0c..6ec89721ad 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -332,7 +333,19 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunk void QuadTreeWorld::accept(osg::NodeVisitor &nv) { if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR) + { + if (nv.getName().find("AcceptedByComponentsTerrainQuadTreeWorld") != std::string::npos) + { + if (nv.getName().find("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds") != std::string::npos) + { + SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds* clsb = static_cast(&nv); + clsb->apply(*this); + } + else + nv.apply(*mRootNode); + } return; + } ViewData* vd = mRootNode->getView(nv); @@ -350,7 +363,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) traverseToCell(mRootNode.get(), vd, x,y); } else - traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getEyePoint(), true); + traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true); } else mRootNode->traverse(nv); diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index f3080e31cd..f216bb33bb 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -47,10 +47,22 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) osg::RefMatrix& matrix = *cv->getModelViewMatrix(); + if (cv->getComputeNearFarMode() && bb.valid()) + { + if (!cv->updateCalculatedNearFar(matrix, *this, false)) + return; + } + float depth = bb.valid() ? distance(bb.center(),matrix) : 0.0f; if (osg::isNaN(depth)) return; + if (cv->getCurrentCamera()->getName() == "ShadowCamera") + { + cv->addDrawableAndDepth(this, &matrix, depth); + return; + } + bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index f7c86b5678..b4bf1d2d37 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -47,6 +47,7 @@ The ranges included with each setting are the physically possible ranges, not re game general shaders + shadows input saves sound diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index b36f642852..b8a3b45b9f 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -1,6 +1,7 @@ Shaders Settings ################ +.. _force-shaders-label: force shaders ------------- diff --git a/docs/source/reference/modding/settings/shadows.rst b/docs/source/reference/modding/settings/shadows.rst new file mode 100644 index 0000000000..c8d3250a0e --- /dev/null +++ b/docs/source/reference/modding/settings/shadows.rst @@ -0,0 +1,205 @@ +Shadow Settings +############### + +Main settings +************* + +enable shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the rendering of shadows. +Unlike in the original Morrowind engine, 'Shadow Mapping' is used, which can have a performance impact, but has more realistic results. +Bear in mind that this will force OpenMW to use shaders as if :ref:`force-shaders-label` was enabled. +A keen developer may be able to implement compatibility with fixed-function mode using the advice of `this post `_, but it may be more difficult than it seems. + + +number of shadow maps +--------------------- + +:Type: integer +:Range: 1 to 8, but higher values may conflict with other texture effects +:Default: 3 + +Control how many shadow maps to use - more of these means each shadow map texel covers less area, producing better-looking shadows, but may decrease performance. +Using too many shadow maps will lead to them overriding texture slots used for other effects, producing unpleasant artefacts. +A value of three is recommended in most cases, but other values may produce better results or performance. + +allow shadow map overlap +------------------------ + +:Type: boolean +:Range: True/False +:Default: True + +If true, allow shadow maps to overlap. +Counter-intuitively, will produce much better results when the light is behind the camera. +When enabled, OpenMW uses Cascaded Shadow Maps and when disabled, it uses Parallel Split Shadow Maps. + +enable debug hud +---------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the debug hud to see what the shadow map(s) contain. +This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings. + +enable debug overlay +---------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the debug overlay to see the area covered by each shadow map. +This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings. + +compute tight scene bounds +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +With this setting enabled, attempt to better use the shadow map(s) by making them cover a smaller area. +This can be especially helpful when looking downwards with a high viewing distance but will be less useful with the default value. +The performance impact of this may be very large. + +shadow map resolution +--------------------- + +:Type: integer +:Range: Dependent on GPU/driver combination +:Default: 1024 + +Control How large to make the shadow map(s). +Higher values increase GPU load but can produce better-looking results. +Power-of-two values may turn out to be faster than smaller values which are not powers of two on some GPU/driver combinations. + +actor shadows +------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow actors to cast shadows. +Potentially decreases performance. + +player shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow the player to cast shadows. +Potentially decreases performance. + +terrain shadows +--------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow terrain to cast shadows. +Potentially decreases performance. + +object shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow static objects to cast shadows. +Potentially decreases performance. + +enable indoor shadows +--------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow shadows indoors. +Due to limitations with Morrowind's data, only actors can cast shadows indoors without the ceiling casting a shadow everywhere. +Some might feel this is distracting as shadows can be cast through other objects, so indoor shadows can be disabled completely. + +Expert settings +*************** + +These settings are probably too complicated for regular users to judge what might be good values to set them to. +If you've got a good understanding of how shadow mapping works, or you've got enough time to try a large set of values, you may get better results tuning these yourself. +Copying values from another user who's done careful tuning is the recommended way of arriving at an optimal value for these settings. + +Understanding what some of these do might be easier for people who've read `this paper on Parallel Split Shadow Maps `_ and understood how they interact with the transformation used with Light Space Perspective Shadow Maps. + +polygon offset factor +--------------------- + +:Type: float +:Range: Theoretically the whole range of 32-bit floating point, but values just above 1.0 are most sensible. +:Default: 1.1 + +Used as the factor parameter for the polygon offset used for shadow map rendering. +Higher values reduce shadow flicker, but risk increasing Peter Panning. +See `the OpenGL documentation for glPolygonOffset `_ for details. + +polygon offset units +--------------------- + +:Type: float +:Range: Theoretically the whole range of 32-bit floating point, but values between 1 and 10 are most sensible. +:Default: 4.0 + +Used as the units parameter for the polygon offset used for shadow map rendering. +Higher values reduce shadow flicker, but risk increasing Peter Panning. +See `the OpenGL documentation for glPolygonOffset `_ for details. + +use front face culling +---------------------- + +:Type: boolean +:Range: True/False +:Default: True + +Excludes theoretically unnecessary faces from shadow maps, slightly increasing performance. +In practice, Peter Panning can be much less visible with these faces included, so if you have high polygon offset values, disabling this may help minimise the side effects. + +split point uniform logarithmic ratio +------------------------------------- + +:Type: float +:Range: 0.0-1.0 for sensible results. Other values may 'work' but could behave bizarrely. +:Default: 0.5 + +Controls the ratio of :math:`C_i^{log}` versus :math:`C_i^{uniform}` used to form the Practical Split Scheme as described in the linked paper. +When using a larger-than-default viewing distance and distant terrain, and you have `allow shadow map overlap`_ enabled, larger values will prevent nearby shadows losing quality. +It is therefore recommended that this isn't left at the default when the viewing distance is changed. + +split point bias +---------------- + +:Type: float +:Range: Any value supported by C++ floats on your platform, although undesirable behaviour is more likely to appear the further the value is from zero. +:Default: 0.0 + +The :math:`\delta_{bias}` parameter used to form the Practical Split Scheme as described in the linked paper. + +minimum lispsm near far ratio +----------------------------- + +:Type: float +:Range: Must be greater than zero. +:Default: 0.25 + +Controls the minimum near/far ratio for the Light Space Perspective Shadow Map transformation. +Helps prevent too much detail being brought towards the camera at the expense of detail further from the camera. +Increasing this pushes detail further away by moving the frustum apex further from the near plane. \ No newline at end of file diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 81df700cc5..89f4f82ea6 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -647,3 +647,62 @@ enable nav mesh render = false # Render agents paths (true, false) enable agents paths render = false + +[Shadows] + +# Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. +enable shadows = false + +# How many shadow maps to use - more of these means each shadow map texel covers less area, producing better looking shadows, but may decrease performance. +number of shadow maps = 3 + +# If true, allow shadow maps to overlap. Counter-intuitively, will produce better results when the light is behind the camera. When enabled, OpenMW uses Cascaded Shadow Maps and when disabled, it uses Parallel Split Shadow Maps. +allow shadow map overlap = true + +# Indirectly controls where to split the shadow map(s). Values closer to 1.0 bring more detail closer to the camera (potentially excessively so), and values closer to 0.0 spread it more evenly across the whole viewing distance. 0.5 is recommended for most viewing distances by the original Parallel Split Shadow Maps paper, but this does not take into account use of a Light Space Perspective transformation, so other values may be preferable. If some of the terms used here go over your head, you might not want to change this, especially not without reading the associated papers first. When "allow shadow map overlap" is combined with a higher-than-default viewing distance, values closer to 1.0 will prevent nearby shadows losing a lot of quality. +split point uniform logarithmic ratio = 0.5 + +# Indirectly controls where to split the shadow map(s). Positive values move split points away from the camera and negative values move them towards the camera. Intended to be used in conjunction with changes to 'split point uniform logarithmic ratio' to counteract side effects, but may cause additional, more serious side effects. Read the Parallel Split Shadow Maps paper by F Zhang et al before changing. +split point bias = 0.0 + +# Enable the debug hud to see what the shadow map(s) contain. +enable debug hud = false + +# Enable the debug overlay to see where each shadow map affects. +enable debug overlay = false + +# Attempt to better use the shadow map by making them cover a smaller area. Especially helpful when looking downwards with a high viewing distance. The performance impact of this may be very large. +compute tight scene bounds = false + +# How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations. +shadow map resolution = 1024 + +# Controls the minimum near/far ratio for the Light Space Perspective Shadow Map transformation. Helps prevent too much detail being brought towards the camera at the expense of detail further from the camera. Increasing this pushes detail further away. +minimum lispsm near far ratio = 0.25 + +# Used as the factor parameter for the polygon offset used for shadow map rendering. Higher values reduce shadow flicker, but risk increasing Peter Panning. See https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml for details. +polygon offset factor = 1.1 + +# Used as the units parameter for the polygon offset used for shadow map rendering. Higher values reduce shadow flicker, but risk increasing Peter Panning. See https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml for details. +polygon offset units = 4.0 + +# How far along the surface normal to project shadow coordinates. Higher values significantly reduce shadow flicker, usually with a lower increase of Peter Panning than the Polygon Offset settings. This value is in in-game units, so 1.0 is roughly 1.4 cm. +normal offset distance = 1.0 + +# Excludes theoretically unnecessary faces from shadow maps, slightly increasing performance. In practice, Peter Panning can be much less visible with these faces included, so if you have high polygon offset values, disabling this may help minimise the side effects. +use front face culling = true + +# Allow actors to cast shadows. Potentially decreases performance. +actor shadows = false + +# Allow the player to cast shadows. Potentially decreases performance. +player shadows = false + +# Allow terrain to cast shadows. Potentially decreases performance. +terrain shadows = false + +# Allow world objects to cast shadows. Potentially decreases performance. +object shadows = false + +# Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting. +enable indoor shadows = true diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 7baca78ef1..8012c2bc10 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -18,6 +18,10 @@ set(SHADER_FILES parallax.glsl s360_fragment.glsl s360_vertex.glsl + shadows_vertex.glsl + shadows_fragment.glsl + shadowcasting_vertex.glsl + shadowcasting_fragment.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 06eebea68e..90d849472b 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -1,38 +1,66 @@ #define MAX_LIGHTS 8 -vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) +uniform int colorMode; + +void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal, vec4 diffuse, vec3 ambient) { vec3 lightDir; - float d; + float lightDistance; -#if @colorMode == 3 - vec4 diffuse = gl_FrontMaterial.diffuse; - vec3 ambient = vertexColor.xyz; -#elif @colorMode == 2 - vec4 diffuse = vertexColor; - vec3 ambient = vertexColor.xyz; + lightDir = gl_LightSource[lightIndex].position.xyz - (viewPos.xyz * gl_LightSource[lightIndex].position.w); + lightDistance = length(lightDir); + lightDir = normalize(lightDir); + float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); + + ambientOut = ambient * gl_LightSource[lightIndex].ambient.xyz * illumination; + diffuseOut = diffuse.xyz * gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal.xyz, lightDir), 0.0) * illumination; +} + +#if PER_PIXEL_LIGHTING +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, float shadowing) #else - vec4 diffuse = gl_FrontMaterial.diffuse; - vec3 ambient = gl_FrontMaterial.ambient.xyz; +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, out vec3 shadowDiffuse) #endif +{ + vec4 diffuse; + vec3 ambient; + if (colorMode == 3) + { + diffuse = gl_FrontMaterial.diffuse; + ambient = vertexColor.xyz; + } + else if (colorMode == 2) + { + diffuse = vertexColor; + ambient = vertexColor.xyz; + } + else + { + diffuse = gl_FrontMaterial.diffuse; + ambient = gl_FrontMaterial.ambient.xyz; + } vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); + vec3 diffuseLight, ambientLight; + perLight(ambientLight, diffuseLight, 0, viewPos, viewNormal, diffuse, ambient); +#if PER_PIXEL_LIGHTING + lightResult.xyz += diffuseLight * shadowing - diffuseLight; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. +#else + shadowDiffuse = diffuseLight; + lightResult.xyz -= shadowDiffuse; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. +#endif for (int i=0; i non-linear depth value and returns <0,1> linearized value @@ -163,7 +165,7 @@ void main(void) vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; UV.y *= -1.0; - float shadow = 1.0; + float shadow = unshadowedLightRatio(); vec2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; screenCoords.y = (1.0-screenCoords.y); @@ -288,4 +290,6 @@ void main(void) #else gl_FragData[0].w = clamp(fresnel*6.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); #endif + + applyShadowDebugOverlay(); } diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 7d7b7b18aa..575f8f3c2f 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -4,14 +4,16 @@ varying vec3 screenCoordsPassthrough; varying vec4 position; varying float depthPassthrough; +#include "shadows_vertex.glsl" + void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, - 0.0, -0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); + 0.0, -0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); vec4 texcoordProj = ((scalemat) * ( gl_Position)); screenCoordsPassthrough = vec3(texcoordProj.x, texcoordProj.y, texcoordProj.w); @@ -19,4 +21,6 @@ void main(void) position = gl_Vertex; depthPassthrough = gl_Position.z; + + setupShadowCoords(gl_ModelViewMatrix * gl_Vertex, normalize((gl_NormalMatrix * gl_Normal).xyz)); }