diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index b08cfe1f71..19655c5cdb 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -102,8 +102,15 @@ namespace : mOpaqueDepthFbo(new osg::FrameBufferObject) , mSourceFbo(sourceFbo) , mOpaqueDepthTex(opaqueDepthTex) + , mColorAttached(false) { mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER, osg::FrameBufferAttachment(opaqueDepthTex)); + +#ifdef __APPLE__ + // Mac OS drivers complain that a FBO is incomplete if it has no color attachment + mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_RGB))); + mColorAttached = true; +#endif } void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override @@ -114,7 +121,10 @@ namespace osg::GLExtensions* ext = state.get(); mSourceFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); + postBindOperation(state); + mOpaqueDepthFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + postBindOperation(state); ext->glBlitFramebuffer(0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), 0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); @@ -124,9 +134,20 @@ namespace bin->drawImplementation(renderInfo, previous); } private: + void postBindOperation(osg::State& state) + { + if (mColorAttached) + return; + #if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE) + state.glDrawBuffer(GL_NONE); + state.glReadBuffer(GL_NONE); + #endif + } + osg::ref_ptr mOpaqueDepthFbo; osg::ref_ptr mSourceFbo; osg::ref_ptr mOpaqueDepthTex; + bool mColorAttached; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 30be74c839..9abd2e7487 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -316,6 +316,7 @@ namespace MWRender resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); // Shadows and radial fog have problems with fixed-function mode bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") + || Settings::Manager::getBool("soft particles", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index fcb7447cf3..8d24ada5be 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -636,7 +636,6 @@ namespace MWRender mParticleNode = new osg::PositionAttitudeTransform; mParticleNode->addCullCallback(mUnderwaterSwitch); mParticleNode->setNodeMask(Mask_WeatherParticles); - mParticleNode->getOrCreateStateSet(); mRootNode->addChild(mParticleNode); } @@ -668,7 +667,6 @@ namespace MWRender ps->getParticle(particleIndex)->update(0, true); } - ps->getOrCreateStateSet(); ps->setUserValue("simpleLighting", true); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 91d2300161..f85946f7bd 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1049,7 +1049,6 @@ namespace NifOsg void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); - partsys->getOrCreateStateSet(); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); const Nif::NiParticleSystemController* partctrl = nullptr; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 95e0a75f18..2063546034 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,6 +1,7 @@ #include "shadervisitor.hpp" #include +#include #include #include @@ -37,12 +38,14 @@ namespace Shader , mUniforms(rhs.mUniforms) , mModes(rhs.mModes) , mAttributes(rhs.mAttributes) + , mTextureModes(rhs.mTextureModes) { } void addUniform(const std::string& name) { mUniforms.emplace(name); } void setMode(osg::StateAttribute::GLMode mode) { mModes.emplace(mode); } void setAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { mAttributes.emplace(typeMemberPair); } + void setTextureMode(int unit, osg::StateAttribute::GLMode mode) { mTextureModes[unit].emplace(mode); } void setAttribute(const osg::StateAttribute* attribute) { @@ -64,12 +67,20 @@ namespace Shader bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } bool hasAttribute(const osg::StateAttribute::TypeMemberPair &typeMemberPair) { return mAttributes.count(typeMemberPair); } bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); } + bool hasTextureMode(int unit, osg::StateAttribute::GLMode mode) + { + auto it = mTextureModes.find(unit); + if (it == mTextureModes.cend()) + return false; + + return it->second.count(mode); + } const std::set& getAttributes() { return mAttributes; } bool empty() { - return mUniforms.empty() && mModes.empty() && mAttributes.empty(); + return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureModes.empty(); } META_Object(Shader, AddedState) @@ -86,9 +97,12 @@ namespace Shader AddedState* mTracker; }; + using ModeSet = std::unordered_set; + std::unordered_set mUniforms; - std::unordered_set mModes; + ModeSet mModes; std::set mAttributes; + std::unordered_map mTextureModes; }; ShaderVisitor::ShaderRequirements::ShaderRequirements() @@ -102,6 +116,8 @@ namespace Shader , mAlphaBlend(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) + , mSoftParticles(false) + , mSoftParticleSize(0.f) , mNode(nullptr) { } @@ -213,6 +229,8 @@ namespace Shader if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) mRequirements.back().mShaderRequired = true; + osg::ref_ptr addedState = getAddedState(*stateset); + if (!texAttributes.empty()) { const osg::Texture* diffuseMap = nullptr; @@ -224,6 +242,9 @@ namespace Shader const osg::StateAttribute *attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { + if (addedState && addedState->hasTextureMode(unit, GL_TEXTURE_2D)) + continue; + const osg::Texture* texture = attr->asTexture(); if (texture) { @@ -350,7 +371,6 @@ namespace Shader osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList(); - osg::ref_ptr addedState = getAddedState(*stateset); for (const auto* attributeMap : std::initializer_list{ &attributes, &removedAttributes }) { @@ -545,6 +565,25 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } + if (reqs.mSoftParticles) + { + osg::ref_ptr depth = new SceneUtil::AutoDepth; + depth->setWriteMask(false); + writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + addedState->setAttribute(depth); + + writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); + addedState->addUniform("particleSize"); + + writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); + addedState->addUniform("opaqueDepthTex"); + + writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); + addedState->setTextureMode(2, GL_TEXTURE_2D); + } + + defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; + if (!addedState->empty()) { // user data is normally shallow copied so shared with the original stateset @@ -557,27 +596,6 @@ namespace Shader updateAddedState(*writableUserData, addedState); } - bool softParticles = false; - - if (mOpaqueDepthTex) - { - auto partsys = dynamic_cast(&node); - - if (partsys) - { - softParticles = true; - - auto depth = new SceneUtil::AutoDepth; - depth->setWriteMask(false); - writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - writableStateSet->addUniform(new osg::Uniform("particleSize", partsys->getDefaultParticleTemplate().getSizeRange().maximum)); - writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); - writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); - } - } - - defineMap["softParticles"] = softParticles ? "1" : "0"; - std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -719,13 +737,22 @@ namespace Shader void ShaderVisitor::apply(osg::Drawable& drawable) { - // non-Geometry drawable (e.g. particle system) - bool needPop = (drawable.getStateSet() != nullptr); + auto partsys = dynamic_cast(&drawable); + + bool needPop = drawable.getStateSet() || partsys; - if (drawable.getStateSet()) + if (needPop) { pushRequirements(drawable); - applyStateSet(drawable.getStateSet(), drawable); + + if (partsys && mOpaqueDepthTex) + { + mRequirements.back().mSoftParticles = true; + mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum; + } + + if (drawable.getStateSet()) + applyStateSet(drawable.getStateSet(), drawable); } if (!mRequirements.empty()) @@ -831,6 +858,12 @@ namespace Shader for (const auto& attribute : removedState->getAttributeList()) writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + + for (unsigned int unit = 0; unit < removedState->getTextureModeList().size(); ++unit) + { + for (const auto&[mode, value] : removedState->getTextureModeList()[unit]) + writableStateSet->setTextureMode(unit, mode, value); + } } } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index d80e697fd8..72dec05b5e 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -101,6 +101,9 @@ namespace Shader // -1 == no tangents required int mTexStageRequiringTangents; + bool mSoftParticles; + float mSoftParticleSize; + // the Node that requested these requirements osg::Node* mNode; }; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index b9d8cfe1b9..5629e321d0 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -282,3 +282,6 @@ intersection between individual particles and other opaque geometry by blending between them. Note, this relies on overriding specific properties of particle systems that potentially differ from the source content, this setting may change the look of some particle systems. + +Note that the rendering will act as if you have 'force shaders' option enabled. +This means that shaders will be used to render all objects and the terrain. \ No newline at end of file diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 99ed44919b..bae9ff7951 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -226,7 +226,7 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if @softParticles +#if !defined(FORCE_OPAQUE) && @softParticles gl_FragData[0].a *= calcSoftParticleFade(); #endif