From 12044a607bcad2f9d394dbb43b899635b8e29aed Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 10 Apr 2020 15:45:37 +0100 Subject: [PATCH 1/3] Only alpha-test shadows when necessary Previously we always discarded shadow map fragments if the alpha channel of the output would have been low, but there were some (modded) assets that have non-one alpha but have testing or blending disabled so end up opaque anyway. This lets the shadows of those objects match. --- components/sceneutil/mwshadowtechnique.cpp | 10 +++++ components/sceneutil/mwshadowtechnique.hpp | 1 + components/shader/shadermanager.cpp | 10 +++++ components/shader/shadermanager.hpp | 6 +++ components/shader/shadervisitor.cpp | 47 +++++++++++++++++++++- components/shader/shadervisitor.hpp | 3 ++ files/shaders/shadowcasting_fragment.glsl | 7 ++++ files/shaders/shadowcasting_vertex.glsl | 8 ++-- 8 files changed, 87 insertions(+), 5 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index f31d2faef..cb3a1b278 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -872,6 +872,15 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh _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)); + + _shadowMapAlphaTestDisableUniform = shaderManager.getShadowMapAlphaTestDisableUniform(); + _shadowMapAlphaTestDisableUniform->setName("alphaTestShadows"); + _shadowMapAlphaTestDisableUniform->setType(osg::Uniform::BOOL); + _shadowMapAlphaTestDisableUniform->set(false); + + shaderManager.getShadowMapAlphaTestEnableUniform()->setName("alphaTestShadows"); + shaderManager.getShadowMapAlphaTestEnableUniform()->setType(osg::Uniform::BOOL); + shaderManager.getShadowMapAlphaTestEnableUniform()->set(true); } MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) @@ -1570,6 +1579,7 @@ void MWShadowTechnique::createShaders() // 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->addUniform(_shadowMapAlphaTestDisableUniform); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 165613f3c..85e548b4b 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -286,6 +286,7 @@ namespace SceneUtil { osg::ref_ptr _debugHud; osg::ref_ptr _castingProgram; + osg::ref_ptr _shadowMapAlphaTestDisableUniform; }; } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 0a7345b97..92848de86 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -370,4 +370,14 @@ namespace Shader program.second->releaseGLObjects(state); } + const osg::ref_ptr ShaderManager::getShadowMapAlphaTestEnableUniform() + { + return mShadowMapAlphaTestEnableUniform; + } + + const osg::ref_ptr ShaderManager::getShadowMapAlphaTestDisableUniform() + { + return mShadowMapAlphaTestDisableUniform; + } + } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 05775edb6..4ea979c60 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -44,6 +44,9 @@ namespace Shader void releaseGLObjects(osg::State* state); + const osg::ref_ptr getShadowMapAlphaTestEnableUniform(); + const osg::ref_ptr getShadowMapAlphaTestDisableUniform(); + private: std::string mPath; @@ -61,6 +64,9 @@ namespace Shader ProgramMap mPrograms; OpenThreads::Mutex mMutex; + + const osg::ref_ptr mShadowMapAlphaTestEnableUniform = new osg::Uniform(); + const osg::ref_ptr mShadowMapAlphaTestDisableUniform = new osg::Uniform(); }; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 7fb5d53f5..b2a6d6f63 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,8 +1,10 @@ #include "shadervisitor.hpp" -#include -#include +#include +#include #include +#include +#include #include @@ -23,6 +25,8 @@ namespace Shader : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) + , mAlphaFuncOverridden(false) + , mBlendFuncOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) @@ -229,15 +233,21 @@ namespace Shader { if (!writableStateSet) writableStateSet = getWritableStateSet(node); + // We probably shouldn't construct a new version of this each time as StateSets only use pointer comparison by default. + // Also it should probably belong to the shader manager writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); } } + bool alphaSettingsChanged = false; + bool alphaTestShadows = false; + const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) { if (it->first.first == osg::StateAttribute::MATERIAL) { + // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) { if (it->second.second & osg::StateAttribute::OVERRIDE) @@ -269,6 +279,39 @@ namespace Shader mRequirements.back().mColorMode = colorMode; } } + else if (it->first.first == osg::StateAttribute::ALPHAFUNC) + { + if (!mRequirements.back().mAlphaFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mAlphaFuncOverridden = true; + + const osg::AlphaFunc* test = static_cast(it->second.first.get()); + if (test->getFunction() == osg::AlphaFunc::GREATER || test->getFunction() == osg::AlphaFunc::GEQUAL) + alphaTestShadows = true; + alphaSettingsChanged = true; + } + } + else if (it->first.first == osg::StateAttribute::BLENDFUNC) + { + if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mBlendFuncOverridden = true; + + const osg::BlendFunc* blend = static_cast(it->second.first.get()); + if (blend->getSource() == osg::BlendFunc::SRC_ALPHA || blend->getSource() == osg::BlendFunc::SRC_COLOR) + alphaTestShadows = true; + alphaSettingsChanged = true; + } + } + } + // we don't need to check for glEnable/glDisable of blending and testing as we always set it at the same time + if (alphaSettingsChanged) + { + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->addUniform(alphaTestShadows ? mShaderManager.getShadowMapAlphaTestEnableUniform() : mShaderManager.getShadowMapAlphaTestDisableUniform()); } } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index ac0ecc699..311f6213f 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -75,6 +75,9 @@ namespace Shader int mColorMode; bool mMaterialOverridden; + bool mAlphaFuncOverridden; + bool mBlendFuncOverridden; + bool mNormalHeight; // true if normal map has height info in alpha channel // -1 == no tangents required diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 336bfe4a4..47323cc6a 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -6,10 +6,17 @@ varying vec2 diffuseMapUV; varying float alphaPassthrough; uniform bool useDiffuseMapForShadowAlpha; +uniform bool alphaTestShadows; void main() { gl_FragData[0].rgb = vec3(1.0); + if (!alphaTestShadows) + { + gl_FragData[0].a = 1.0; + return; + } + if (useDiffuseMapForShadowAlpha) gl_FragData[0].a = texture2D(diffuseMap, diffuseMapUV).a * alphaPassthrough; else diff --git a/files/shaders/shadowcasting_vertex.glsl b/files/shaders/shadowcasting_vertex.glsl index d578e97b7..e19b587e5 100644 --- a/files/shaders/shadowcasting_vertex.glsl +++ b/files/shaders/shadowcasting_vertex.glsl @@ -6,6 +6,7 @@ varying float alphaPassthrough; uniform int colorMode; uniform bool useDiffuseMapForShadowAlpha; +uniform bool alphaTestShadows; void main(void) { @@ -14,12 +15,13 @@ void main(void) vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; - if (useDiffuseMapForShadowAlpha) + if (alphaTestShadows && useDiffuseMapForShadowAlpha) diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy; else diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions - - if (colorMode == 2) + if (!alphaTestShadows) + alphaPassthrough = 1.0; + else if (colorMode == 2) alphaPassthrough = gl_Color.a; else // This is uniform, so if it's too low, we might be able to put the position/clip vertex outside the view frustum and skip the fragment shader and rasteriser From 53b9b411591b2bc22e78ba2cc15fa422449d89a1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 21 Apr 2020 18:18:55 +0100 Subject: [PATCH 2/3] Rely on existing alpha test for non-blended shadow casting --- components/shader/shadervisitor.cpp | 17 ++--------------- components/shader/shadervisitor.hpp | 1 - files/shaders/shadowcasting_fragment.glsl | 10 ++-------- files/shaders/shadowcasting_vertex.glsl | 10 ++++------ 4 files changed, 8 insertions(+), 30 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index b2a6d6f63..639a7ecca 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -25,7 +25,6 @@ namespace Shader : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) - , mAlphaFuncOverridden(false) , mBlendFuncOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) @@ -279,19 +278,6 @@ namespace Shader mRequirements.back().mColorMode = colorMode; } } - else if (it->first.first == osg::StateAttribute::ALPHAFUNC) - { - if (!mRequirements.back().mAlphaFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) - { - if (it->second.second & osg::StateAttribute::OVERRIDE) - mRequirements.back().mAlphaFuncOverridden = true; - - const osg::AlphaFunc* test = static_cast(it->second.first.get()); - if (test->getFunction() == osg::AlphaFunc::GREATER || test->getFunction() == osg::AlphaFunc::GEQUAL) - alphaTestShadows = true; - alphaSettingsChanged = true; - } - } else if (it->first.first == osg::StateAttribute::BLENDFUNC) { if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) @@ -305,8 +291,9 @@ namespace Shader alphaSettingsChanged = true; } } + // Eventually, move alpha testing to discard in shader adn remove deprecated state here } - // we don't need to check for glEnable/glDisable of blending and testing as we always set it at the same time + // we don't need to check for glEnable/glDisable of blending as we always set it at the same time if (alphaSettingsChanged) { if (!writableStateSet) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 311f6213f..8e35f1d9c 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -75,7 +75,6 @@ namespace Shader int mColorMode; bool mMaterialOverridden; - bool mAlphaFuncOverridden; bool mBlendFuncOverridden; bool mNormalHeight; // true if normal map has height info in alpha channel diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 47323cc6a..a5410d008 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -11,18 +11,12 @@ uniform bool alphaTestShadows; void main() { gl_FragData[0].rgb = vec3(1.0); - if (!alphaTestShadows) - { - gl_FragData[0].a = 1.0; - return; - } - if (useDiffuseMapForShadowAlpha) gl_FragData[0].a = texture2D(diffuseMap, diffuseMapUV).a * alphaPassthrough; else gl_FragData[0].a = alphaPassthrough; - // Prevent translucent things casting shadow (including the player using an invisibility effect) - if (gl_FragData[0].a <= 0.5) + // Prevent translucent things casting shadow (including the player using an invisibility effect). For now, rely on the deprecated FF test for non-blended stuff. + if (alphaTestShadows && gl_FragData[0].a <= 0.5) discard; } diff --git a/files/shaders/shadowcasting_vertex.glsl b/files/shaders/shadowcasting_vertex.glsl index e19b587e5..e36f21a4d 100644 --- a/files/shaders/shadowcasting_vertex.glsl +++ b/files/shaders/shadowcasting_vertex.glsl @@ -5,8 +5,8 @@ varying vec2 diffuseMapUV; varying float alphaPassthrough; uniform int colorMode; -uniform bool useDiffuseMapForShadowAlpha; -uniform bool alphaTestShadows; +uniform bool useDiffuseMapForShadowAlpha = true; +uniform bool alphaTestShadows = true; void main(void) { @@ -15,13 +15,11 @@ void main(void) vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; - if (alphaTestShadows && useDiffuseMapForShadowAlpha) + if (useDiffuseMapForShadowAlpha) diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy; else diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions - if (!alphaTestShadows) - alphaPassthrough = 1.0; - else if (colorMode == 2) + if (colorMode == 2) alphaPassthrough = gl_Color.a; else // This is uniform, so if it's too low, we might be able to put the position/clip vertex outside the view frustum and skip the fragment shader and rasteriser From a3b032bf2bc3f4331f0b35220117978a2609eed3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 27 Apr 2020 23:49:48 +0100 Subject: [PATCH 3/3] Fix chameleon shadows --- apps/openmw/mwrender/animation.cpp | 12 ++++++++++-- components/shader/shadervisitor.cpp | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0f7548f05..627d36ac4 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -37,6 +38,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" @@ -502,8 +505,9 @@ namespace MWRender class TransparencyUpdater : public SceneUtil::StateSetUpdater { public: - TransparencyUpdater(const float alpha) + TransparencyUpdater(const float alpha, osg::ref_ptr shadowUniform) : mAlpha(alpha) + , mShadowUniform(shadowUniform) { } @@ -517,6 +521,9 @@ namespace MWRender { osg::BlendFunc* blendfunc (new osg::BlendFunc); stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + // TODO: don't do this anymore once custom shadow renderbin is handling it + if (mShadowUniform) + stateset->addUniform(mShadowUniform); // FIXME: overriding diffuse/ambient/emissive colors osg::Material* material = new osg::Material; @@ -535,6 +542,7 @@ namespace MWRender private: float mAlpha; + osg::ref_ptr mShadowUniform; }; struct Animation::AnimSource @@ -1744,7 +1752,7 @@ namespace MWRender { if (mTransparencyUpdater == nullptr) { - mTransparencyUpdater = new TransparencyUpdater(alpha); + mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform()); mObjectRoot->addUpdateCallback(mTransparencyUpdater); } else diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 639a7ecca..8165effb1 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -232,7 +232,7 @@ namespace Shader { if (!writableStateSet) writableStateSet = getWritableStateSet(node); - // We probably shouldn't construct a new version of this each time as StateSets only use pointer comparison by default. + // We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out. // Also it should probably belong to the shader manager writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); }