From 8c3b00164ebc5783c54050550d8e661e3358b028 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 20 Oct 2021 09:42:18 -0700 Subject: [PATCH] soft particles --- apps/openmw/mwrender/postprocessor.cpp | 79 ++++++++++++++++--- apps/openmw/mwrender/postprocessor.hpp | 2 + apps/openmw/mwrender/renderingmanager.cpp | 13 +++ components/nifosg/nifloader.cpp | 1 + components/resource/scenemanager.cpp | 6 ++ components/resource/scenemanager.hpp | 4 + components/shader/shadervisitor.cpp | 20 +++++ components/shader/shadervisitor.hpp | 4 + .../reference/modding/settings/shaders.rst | 13 +++ files/settings-default.cfg | 2 + files/shaders/CMakeLists.txt | 1 + files/shaders/objects_fragment.glsl | 9 +++ files/shaders/softparticles.glsl | 32 ++++++++ 13 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 files/shaders/softparticles.glsl diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 1ee5745cb4..7405098a03 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -93,6 +93,41 @@ namespace MWRender::PostProcessor* mPostProcessor; }; + + // Copies the currently bound depth attachment to a new texture so drawables in transparent renderbin can safely sample from depth. + class OpaqueDepthCopyCallback : public osgUtil::RenderBin::DrawCallback + { + public: + OpaqueDepthCopyCallback(osg::ref_ptr opaqueDepthTex, osg::ref_ptr sourceFbo) + : mOpaqueDepthFbo(new osg::FrameBufferObject) + , mSourceFbo(sourceFbo) + , mOpaqueDepthTex(opaqueDepthTex) + { + mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER, osg::FrameBufferAttachment(opaqueDepthTex)); + } + + void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override + { + if (bin->getStage()->getFrameBufferObject() == mSourceFbo) + { + osg::State& state = *renderInfo.getState(); + osg::GLExtensions* ext = state.get(); + + mSourceFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); + mOpaqueDepthFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + ext->glBlitFramebuffer(0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), 0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); + + mSourceFbo->apply(state); + } + + bin->drawImplementation(renderInfo, previous); + } + private: + osg::ref_ptr mOpaqueDepthFbo; + osg::ref_ptr mSourceFbo; + osg::ref_ptr mOpaqueDepthTex; + }; } namespace MWRender @@ -103,7 +138,9 @@ namespace MWRender , mDepthFormat(GL_DEPTH_COMPONENT24) , mRendering(rendering) { - if (!SceneUtil::getReverseZ()) + bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); + + if (!SceneUtil::getReverseZ() && !softParticles) return; osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); @@ -124,17 +161,22 @@ namespace MWRender return; } - if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) - mDepthFormat = GL_DEPTH_COMPONENT32F; - else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) - mDepthFormat = GL_DEPTH_COMPONENT32F_NV; - else + if (SceneUtil::getReverseZ()) { - // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. - // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no - // benefits if no floating point depth formats are supported. - Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; - return; + if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) + mDepthFormat = GL_DEPTH_COMPONENT32F; + else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) + mDepthFormat = GL_DEPTH_COMPONENT32F_NV; + else + { + // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. + // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no + // benefits if no floating point depth formats are supported. + Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; + + if (!softParticles) + return; + } } int width = viewer->getCamera()->getViewport()->width(); @@ -165,6 +207,12 @@ namespace MWRender mDepthTex->dirtyTextureObject(); mSceneTex->dirtyTextureObject(); + if (mOpaqueDepthTex) + { + mOpaqueDepthTex->setTextureSize(width, height); + mOpaqueDepthTex->dirtyTextureObject(); + } + int samples = Settings::Manager::getInt("antialiasing", "Video"); mFbo = new osg::FrameBufferObject; @@ -186,6 +234,9 @@ namespace MWRender if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY")) mFirstPersonDepthRBProxy = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); + if (Settings::Manager::getBool("soft particles", "Shaders")) + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(new OpaqueDepthCopyCallback(mOpaqueDepthTex, mMsaaFbo ? mMsaaFbo : mFbo)); + mViewer->getCamera()->resize(width, height); mHUDCamera->resize(width, height); mRendering.updateProjectionMatrix(); @@ -204,6 +255,12 @@ namespace MWRender mDepthTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mDepthTex->setResizeNonPowerOfTwoHint(false); + if (Settings::Manager::getBool("soft particles", "Shaders")) + { + mOpaqueDepthTex = new osg::Texture2D(*mDepthTex); + mOpaqueDepthTex->setName("opaqueTexMap"); + } + mSceneTex = new osg::Texture2D; mSceneTex->setTextureSize(width, height); mSceneTex->setSourceFormat(GL_RGB); diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 0d03d4b500..cc5128d8f9 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -26,6 +26,7 @@ namespace MWRender auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; } int getDepthFormat() { return mDepthFormat; } + osg::ref_ptr getOpaqueDepthTex() { return mOpaqueDepthTex; } void resize(int width, int height); @@ -42,6 +43,7 @@ namespace MWRender osg::ref_ptr mSceneTex; osg::ref_ptr mDepthTex; + osg::ref_ptr mOpaqueDepthTex; int mDepthFormat; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7b8393f690..a129543828 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -91,6 +91,7 @@ namespace MWRender stateset->addUniform(new osg::Uniform("linearFac", 0.f)); stateset->addUniform(new osg::Uniform("near", 0.f)); stateset->addUniform(new osg::Uniform("far", 0.f)); + stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{})); if (mUsePlayerUniforms) { stateset->addUniform(new osg::Uniform("windSpeed", 0.0f)); @@ -116,6 +117,10 @@ namespace MWRender if (uFar) uFar->set(mFar); + auto* uScreenRes = stateset->getUniform("screenRes"); + if (uScreenRes) + uScreenRes->set(mScreenRes); + if (mUsePlayerUniforms) { auto* windSpeed = stateset->getUniform("windSpeed"); @@ -148,6 +153,11 @@ namespace MWRender mFar = far; } + void setScreenRes(float width, float height) + { + mScreenRes = osg::Vec2f(width, height); + } + void setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; @@ -167,6 +177,7 @@ namespace MWRender bool mUsePlayerUniforms; float mWindSpeed; osg::Vec3f mPlayerPos; + osg::Vec2f mScreenRes; }; class StateUpdater : public SceneUtil::StateSetUpdater @@ -453,6 +464,7 @@ namespace MWRender mPostProcessor = new PostProcessor(*this, viewer, mRootNode); resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat()); + resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex()); if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(mPostProcessor->getDepthFormat())) Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it."; @@ -1166,6 +1178,7 @@ namespace MWRender mSharedUniformStateUpdater->setNear(mNearClip); mSharedUniformStateUpdater->setFar(mViewDistance); + mSharedUniformStateUpdater->setScreenRes(mViewer->getCamera()->getViewport()->width(), mViewer->getCamera()->getViewport()->height()); // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 838895eb47..a745d50eda 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1045,6 +1045,7 @@ 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/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 4184a77c54..b6608ab631 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -429,6 +429,11 @@ namespace Resource mConvertAlphaTestToAlphaToCoverage = convert; } + void SceneManager::setOpaqueDepthTex(osg::ref_ptr texture) + { + mOpaqueDepthTex = texture; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -891,6 +896,7 @@ namespace Resource shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); + shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex); return shaderVisitor; } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 85e012071d..58ae8fdb8b 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "resourcemanager.hpp" @@ -111,6 +112,8 @@ namespace Resource void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; + void setOpaqueDepthTex(osg::ref_ptr texture); + enum class UBOBinding { // If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate @@ -209,6 +212,7 @@ namespace Resource SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; GLenum mDepthFormat; + osg::ref_ptr mOpaqueDepthTex; osg::ref_ptr mSharedStateManager; mutable std::mutex mSharedStateMutex; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 9877ab1863..9b54060730 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include @@ -19,6 +21,7 @@ #include #include #include +#include #include "removedalphafunc.hpp" #include "shadermanager.hpp" @@ -554,6 +557,18 @@ namespace Shader updateAddedState(*writableUserData, addedState); } + if (auto partsys = dynamic_cast(&node)) + { + writableStateSet->setDefine("SOFT_PARTICLES", "1", osg::StateAttribute::ON); + + auto depth = SceneUtil::createDepth(); + 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); + } + std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -769,6 +784,11 @@ namespace Shader mConvertAlphaTestToAlphaToCoverage = convert; } + void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr texture) + { + mOpaqueDepthTex = texture; + } + ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 5f9739ea90..d80e697fd8 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Resource { @@ -45,6 +46,8 @@ namespace Shader void setConvertAlphaTestToAlphaToCoverage(bool convert); + void setOpaqueDepthTex(osg::ref_ptr texture); + void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -110,6 +113,7 @@ namespace Shader bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); osg::ref_ptr mProgramTemplate; + osg::ref_ptr mOpaqueDepthTex; }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 03b7805de6..c62c44e936 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -269,3 +269,16 @@ antialias alpha test Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage when :ref:`antialiasing` is on. This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. When MSAA is off, this setting will have no visible effect, but might have a performance cost. + +soft particles +------------------------ + +:Type: boolean +:Range: True/False +:Default: False + +Enables soft particles for almost all particle effects, excluding precipitation. +This technique softens the 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. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 27a9544ea6..2df6eaa0a7 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -494,6 +494,8 @@ minimum interior brightness = 0.08 # When MSAA is off, this setting will have no visible effect, but might have a performance cost. antialias alpha test = false +soft particles = true + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index d86719f318..6e19263a38 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -39,6 +39,7 @@ set(SHADER_FILES sky_vertex.glsl sky_fragment.glsl skypasses.glsl + softparticles.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 6f6cede4e4..cb435c7790 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,5 +1,6 @@ #version 120 #pragma import_defines(FORCE_OPAQUE) +#pragma import_defines(SOFT_PARTICLES) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -80,6 +81,10 @@ varying vec3 passNormal; #include "parallax.glsl" #include "alpha.glsl" +#if defined(SOFT_PARTICLES) && SOFT_PARTICLES +#include "softparticles.glsl" +#endif + void main() { #if @diffuseMap @@ -220,6 +225,10 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +#if defined(SOFT_PARTICLES) && SOFT_PARTICLES + gl_FragData[0].a *= calcSoftParticleFade(); +#endif + #if defined(FORCE_OPAQUE) && FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque gl_FragData[0].a = 1.0; diff --git a/files/shaders/softparticles.glsl b/files/shaders/softparticles.glsl new file mode 100644 index 0000000000..fa8b4de4c1 --- /dev/null +++ b/files/shaders/softparticles.glsl @@ -0,0 +1,32 @@ +uniform float near; +uniform float far; +uniform sampler2D opaqueDepthTex; +uniform vec2 screenRes; +uniform float particleSize; + +float viewDepth(float depth) +{ +#if @reverseZ + depth = 1.0 - depth; +#endif + return (near * far) / ((far - near) * depth - far); +} + +float calcSoftParticleFade() +{ + const float falloffMultiplier = 0.33; + const float contrast = 1.30; + + vec2 screenCoords = gl_FragCoord.xy / screenRes; + float sceneDepth = viewDepth(texture2D(opaqueDepthTex, screenCoords).x); + float particleDepth = viewDepth(gl_FragCoord.z); + float falloff = particleSize * falloffMultiplier; + float delta = particleDepth - sceneDepth; + + if (delta < 0.0) + discard; + + const float shift = 0.845; + + return shift * pow(clamp(delta/falloff, 0.0, 1.0), contrast); +}