diff --git a/CHANGELOG.md b/CHANGELOG.md index 551a7d9aa6..8ae19018e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -219,6 +219,7 @@ Feature #5198: Implement "Magic effect expired" event Feature #5454: Clear active spells from actor when he disappears from scene Feature #5489: MCP: Telekinesis fix for activators + Feature #5492: Let rain and snow collide with statics Feature #5701: Convert osgAnimation::RigGeometry to double-buffered custom version Feature #5737: OpenMW-CS: Handle instance move from one cell to another Feature #5928: Allow Glow in the Dahrk to be disabled diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index bfd12d27c1..9fe478f94a 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -126,6 +126,7 @@ bool Launcher::AdvancedPage::loadSettings() antialiasAlphaTestCheckBox->setCheckState(Qt::Unchecked); } loadSettingBool(adjustCoverageForAlphaTestCheckBox, "adjust coverage for alpha test", "Shaders"); + loadSettingBool(weatherParticleOcclusionCheckBox, "weather particle occlusion", "Shaders"); loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); connect(animSourcesCheckBox, &QCheckBox::toggled, this, &AdvancedPage::slotAnimSourcesToggled); loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); @@ -285,6 +286,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); saveSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); saveSettingBool(adjustCoverageForAlphaTestCheckBox, "adjust coverage for alpha test", "Shaders"); + saveSettingBool(weatherParticleOcclusionCheckBox, "weather particle occlusion", "Shaders"); saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game"); saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game"); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ced1060ce9..e9683cc40a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -23,7 +23,7 @@ add_openmw_dir (mwrender creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover - postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode + postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode precipitationocclusion ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/precipitationocclusion.cpp b/apps/openmw/mwrender/precipitationocclusion.cpp new file mode 100644 index 0000000000..40eddea270 --- /dev/null +++ b/apps/openmw/mwrender/precipitationocclusion.cpp @@ -0,0 +1,170 @@ +#include "precipitationocclusion.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "vismask.hpp" + +namespace +{ + class PrecipitationOcclusionUpdater : public SceneUtil::StateSetUpdater + { + public: + PrecipitationOcclusionUpdater(osg::ref_ptr depthTexture) + : mDepthTexture(depthTexture) + { + } + + private: + void setDefaults(osg::StateSet* stateset) override + { + stateset->setTextureAttributeAndModes(3, mDepthTexture); + stateset->addUniform(new osg::Uniform("orthoDepthMap", 3)); + stateset->addUniform(new osg::Uniform("depthSpaceMatrix", mDepthSpaceMatrix)); + } + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + osg::Camera* camera = nv->asCullVisitor()->getCurrentCamera(); + stateset->getUniform("depthSpaceMatrix")->set(camera->getViewMatrix() * camera->getProjectionMatrix()); + } + + osg::Matrixf mDepthSpaceMatrix; + osg::ref_ptr mDepthTexture; + }; + + class DepthCameraUpdater : public SceneUtil::StateSetUpdater + { + public: + DepthCameraUpdater() + : mDummyTexture(new osg::Texture2D) + { + mDummyTexture->setInternalFormat(GL_RGB); + mDummyTexture->setTextureSize(1, 1); + + Shader::ShaderManager& shaderMgr + = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager(); + osg::ref_ptr vertex + = shaderMgr.getShader("precipitationdepth_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr fragment + = shaderMgr.getShader("precipitationdepth_fragment.glsl", {}, osg::Shader::FRAGMENT); + mProgram = shaderMgr.getProgram(vertex, fragment); + } + + private: + void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf())); + stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(0, mDummyTexture); + stateset->setRenderBinDetails( + osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); + } + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + osg::Camera* camera = nv->asCullVisitor()->getCurrentCamera(); + stateset->getUniform("projectionMatrix")->set(camera->getProjectionMatrix()); + } + + osg::Matrixf mProjectionMatrix; + osg::ref_ptr mDummyTexture; + osg::ref_ptr mProgram; + }; +} + +namespace MWRender +{ + PrecipitationOccluder::PrecipitationOccluder( + osg::Group* skyNode, osg::Group* sceneNode, osg::Group* rootNode, osg::Camera* camera) + : mSkyNode(skyNode) + , mSceneNode(sceneNode) + , mRootNode(rootNode) + , mSceneCamera(camera) + { + constexpr int rttSize = 256; + + mDepthTexture = new osg::Texture2D; + mDepthTexture->setTextureSize(rttSize, rttSize); + mDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); + mDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24); + mDepthTexture->setSourceType(GL_UNSIGNED_INT); + mDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); + mDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); + mDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mDepthTexture->setBorderColor( + SceneUtil::AutoDepth::isReversed() ? osg::Vec4(0, 0, 0, 0) : osg::Vec4(1, 1, 1, 1)); + + mCamera = new osg::Camera; + mCamera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + mCamera->setRenderOrder(osg::Camera::PRE_RENDER); + mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + mCamera->setNodeMask(Mask_RenderToTexture); + mCamera->setCullMask(Mask_Scene | Mask_Object | Mask_Static); + mCamera->setViewport(0, 0, rttSize, rttSize); + mCamera->attach(osg::Camera::DEPTH_BUFFER, mDepthTexture); + mCamera->addChild(mSceneNode); + + SceneUtil::setCameraClearDepth(mCamera); + } + + void PrecipitationOccluder::update() + { + const osg::Vec3 pos = mSceneCamera->getInverseViewMatrix().getTrans(); + + const float zmin = pos.z() - mRange.z() - Constants::CellSizeInUnits; + const float zmax = pos.z() + mRange.z() + Constants::CellSizeInUnits; + const float near = 0; + const float far = zmax - zmin; + + const float left = -mRange.x() / 2; + const float right = -left; + const float top = mRange.y() / 2; + const float bottom = -top; + + if (SceneUtil::AutoDepth::isReversed()) + { + mCamera->setProjectionMatrix( + SceneUtil::getReversedZProjectionMatrixAsOrtho(left, right, bottom, top, near, far)); + } + else + { + mCamera->setProjectionMatrix(osg::Matrixf::ortho(left, right, bottom, top, near, far)); + } + + mCamera->setViewMatrixAsLookAt( + osg::Vec3(pos.x(), pos.y(), zmax), osg::Vec3(pos.x(), pos.y(), zmin), osg::Vec3(0, 1, 0)); + } + + void PrecipitationOccluder::enable() + { + mSkyNode->setCullCallback(new PrecipitationOcclusionUpdater(mDepthTexture)); + mCamera->setCullCallback(new DepthCameraUpdater); + + mRootNode->removeChild(mCamera); + mRootNode->addChild(mCamera); + } + + void PrecipitationOccluder::disable() + { + mSkyNode->setCullCallback(nullptr); + mCamera->setCullCallback(nullptr); + + mRootNode->removeChild(mCamera); + } + + void PrecipitationOccluder::updateRange(const osg::Vec3f range) + { + const osg::Vec3f margin = { -50, -50, 0 }; + mRange = range - margin; + } +} diff --git a/apps/openmw/mwrender/precipitationocclusion.hpp b/apps/openmw/mwrender/precipitationocclusion.hpp new file mode 100644 index 0000000000..9910777449 --- /dev/null +++ b/apps/openmw/mwrender/precipitationocclusion.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_MWRENDER_PRECIPITATIONOCCLUSION_H +#define OPENMW_MWRENDER_PRECIPITATIONOCCLUSION_H + +#include +#include + +namespace MWRender +{ + class PrecipitationOccluder + { + public: + PrecipitationOccluder(osg::Group* skyNode, osg::Group* sceneNode, osg::Group* rootNode, osg::Camera* camera); + + void update(); + + void enable(); + + void disable(); + + void updateRange(const osg::Vec3f range); + + private: + osg::Group* mSkyNode; + osg::Group* mSceneNode; + osg::Group* mRootNode; + osg::ref_ptr mCamera; + osg::ref_ptr mSceneCamera; + osg::ref_ptr mDepthTexture; + osg::Vec3f mRange; + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ca71532828..30cf10dada 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -586,8 +586,8 @@ namespace MWRender mFog = std::make_unique(); - mSky = std::make_unique(sceneRoot, resourceSystem->getSceneManager(), mSkyBlending); - mSky->setCamera(mViewer->getCamera()); + mSky = std::make_unique( + sceneRoot, mRootNode, mViewer->getCamera(), resourceSystem->getSceneManager(), mSkyBlending); if (mSkyBlending) { int skyTextureUnit = mResourceSystem->getSceneManager()->getShaderManager().reserveGlobalTextureUnits( diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index af0b161ee1..dd45a8d560 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -228,9 +228,10 @@ namespace namespace MWRender { - SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager, bool enableSkyRTT) + SkyManager::SkyManager(osg::Group* parentNode, osg::Group* rootNode, osg::Camera* camera, + Resource::SceneManager* sceneManager, bool enableSkyRTT) : mSceneManager(sceneManager) - , mCamera(nullptr) + , mCamera(camera) , mAtmosphereNightRoll(0.f) , mCreated(false) , mIsStorm(false) @@ -289,6 +290,8 @@ namespace MWRender mRootNode->setNodeMask(Mask_Sky); mRootNode->addChild(mEarlyRenderBinRoot); mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); + + mPrecipitationOccluder = std::make_unique(skyroot, parentNode, rootNode, camera); } void SkyManager::create() @@ -382,11 +385,6 @@ namespace MWRender mCreated = true; } - void SkyManager::setCamera(osg::Camera* camera) - { - mCamera = camera; - } - void SkyManager::createRain() { if (mRainNode) @@ -466,9 +464,11 @@ namespace MWRender mRainNode->setNodeMask(Mask_WeatherParticles); mRainParticleSystem->setUserValue("simpleLighting", true); + mRainParticleSystem->setUserValue("particleOcclusion", true); mSceneManager->recreateShaders(mRainNode); mRootNode->addChild(mRainNode); + mPrecipitationOccluder->enable(); } void SkyManager::destroyRain() @@ -482,6 +482,7 @@ namespace MWRender mCounter = nullptr; mRainParticleSystem = nullptr; mRainShooter = nullptr; + mPrecipitationOccluder->disable(); } SkyManager::~SkyManager() @@ -563,6 +564,7 @@ namespace MWRender * osg::DegreesToRadians(360.f) / (3600 * 96.f); if (mAtmosphereNightNode->getNodeMask() != 0) mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1))); + mPrecipitationOccluder->update(); } void SkyManager::setEnabled(bool enabled) @@ -606,6 +608,7 @@ namespace MWRender mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops / mRainEntranceSpeed * 20); + mPrecipitationOccluder->updateRange(rainRange); } } @@ -671,6 +674,10 @@ namespace MWRender mRootNode->removeChild(mParticleNode); mParticleNode = nullptr; } + if (mRainEffect.empty()) + { + mPrecipitationOccluder->disable(); + } } else { @@ -693,6 +700,8 @@ namespace MWRender SceneUtil::FindByClassVisitor findPSVisitor("ParticleSystem"); mParticleEffect->accept(findPSVisitor); + const osg::Vec3 defaultWrapRange = osg::Vec3(1024, 1024, 800); + for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) { osgParticle::ParticleSystem* ps @@ -700,7 +709,7 @@ namespace MWRender osg::ref_ptr program = new osgParticle::ModularProgram; if (!mIsStorm) - program->addOperator(new WrapAroundOperator(mCamera, osg::Vec3(1024, 1024, 800))); + program->addOperator(new WrapAroundOperator(mCamera, defaultWrapRange)); program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); program->setParticleSystem(ps); mParticleNode->addChild(program); @@ -713,9 +722,16 @@ namespace MWRender } ps->setUserValue("simpleLighting", true); + ps->setUserValue("particleOcclusion", true); } mSceneManager->recreateShaders(mParticleNode); + + if (mCurrentParticleEffect == "meshes\\snow.nif") + { + mPrecipitationOccluder->enable(); + mPrecipitationOccluder->updateRange(defaultWrapRange); + } } } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index a365d32342..16cc0df7bd 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -8,6 +8,7 @@ #include #include +#include "precipitationocclusion.hpp" #include "skyutil.hpp" namespace osg @@ -43,7 +44,8 @@ namespace MWRender class SkyManager { public: - SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager, bool enableSkyRTT); + SkyManager(osg::Group* parentNode, osg::Group* rootNode, osg::Camera* camera, + Resource::SceneManager* sceneManager, bool enableSkyRTT); ~SkyManager(); void update(float duration); @@ -98,8 +100,6 @@ namespace MWRender void listAssetsToPreload(std::vector& models, std::vector& textures); - void setCamera(osg::Camera* camera); - float getBaseWindSpeed() const; void setSunglare(bool enabled); @@ -151,6 +151,8 @@ namespace MWRender osg::ref_ptr mCounter; osg::ref_ptr mRainShooter; + std::unique_ptr mPrecipitationOccluder; + bool mCreated; bool mIsStorm; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 4e5f68edf5..2a2429ff79 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -675,6 +676,11 @@ namespace Shader if (simpleLighting || dynamic_cast(&node)) defineMap["forcePPL"] = "0"; + bool particleOcclusion = false; + node.getUserValue("particleOcclusion", particleOcclusion); + defineMap["particleOcclusion"] + = particleOcclusion && Settings::Manager::getBool("weather particle occlusion", "Shaders") ? "1" : "0"; + if (reqs.mAlphaBlend && mSupportsNormalsRT) { if (reqs.mSoftParticles) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index bd74d01fdd..e46e9ebb99 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -287,3 +287,18 @@ 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. + +weather particle occlusion +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enables particle occlusion for rain and snow particle effects. +When enabled, rain and snow will not clip through ceilings and overhangs. +Currently this relies on an additional render pass, which may lead to a performance hit. + +.. warning:: + This is an experimental feature that may cause visual oddities, especially when using default rain settings. + It is recommended to at least double the rain diameter through `openmw.cfg`.` diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 7c6c2e62fb..0981ecacc0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -499,6 +499,9 @@ adjust coverage for alpha test = true # Soften intersection of blended particle systems with opaque geometry soft particles = false +# Rain and snow particle occlusion +weather particle occlusion = false + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index d688f9e7d9..9215f78da0 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -34,8 +34,8 @@ set(SHADER_FILES shadowcasting_vertex.glsl shadowcasting_fragment.glsl vertexcolors.glsl - multiview_resolve_vertex.glsl - multiview_resolve_fragment.glsl + multiview_resolve_vertex.glsl + multiview_resolve_fragment.glsl nv_default_vertex.glsl nv_default_fragment.glsl nv_nolighting_vertex.glsl @@ -55,6 +55,8 @@ set(SHADER_FILES fullscreen_tri_vertex.glsl fullscreen_tri_fragment.glsl fog.glsl + precipitation_vertex.glsl + precipitation_fragment.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index caf8b672ca..f05ff8eba8 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -92,8 +92,30 @@ varying vec3 passNormal; #include "softparticles.glsl" #endif +#if @particleOcclusion +uniform sampler2D orthoDepthMap; + +varying vec3 orthoDepthMapCoord; + +void precipitationOcclusion() +{ + float sceneDepth = texture2D(orthoDepthMap, orthoDepthMapCoord.xy * 0.5 + 0.5).r; +#if @reverseZ + if (orthoDepthMapCoord.z < sceneDepth) + discard; +#else + if (orthoDepthMapCoord.z * 0.5 + 0.5 > sceneDepth) + discard; +#endif +} +#endif + void main() { +#if @particleOcclusion + precipitationOcclusion(); +#endif + #if @diffuseMap vec2 adjustedDiffuseUV = diffuseMapUV; #endif diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 971b95d8e0..12af0fa003 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -66,8 +66,20 @@ varying vec3 passNormal; #include "lighting.glsl" #include "depth.glsl" +#if @particleOcclusion +varying vec3 orthoDepthMapCoord; + +uniform mat4 depthSpaceMatrix; +uniform mat4 osg_ViewMatrixInverse; +#endif + void main(void) { +#if @particleOcclusion + mat4 model = osg_ViewMatrixInverse * gl_ModelViewMatrix; + orthoDepthMapCoord = ((depthSpaceMatrix * model) * vec4(gl_Vertex.xyz, 1.0)).xyz; +#endif + gl_Position = mw_modelToClip(gl_Vertex); vec4 viewPos = mw_modelToView(gl_Vertex); diff --git a/files/shaders/precipitation_fragment.glsl b/files/shaders/precipitation_fragment.glsl new file mode 100644 index 0000000000..b16eb0fd99 --- /dev/null +++ b/files/shaders/precipitation_fragment.glsl @@ -0,0 +1,15 @@ +#version 120 + +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; + +#include "vertexcolors.glsl" + +void main() +{ + gl_FragData[0].rgb = vec3(1.0); + gl_FragData[0].a = texture2D(diffuseMap, diffuseMapUV).a * getDiffuseColor().a; + + if (gl_FragData[0].a <= 0.5) + discard; +} \ No newline at end of file diff --git a/files/shaders/precipitation_vertex.glsl b/files/shaders/precipitation_vertex.glsl new file mode 100644 index 0000000000..a202d9cd96 --- /dev/null +++ b/files/shaders/precipitation_vertex.glsl @@ -0,0 +1,13 @@ +#version 120 + +uniform mat4 projectionMatrix; +varying vec2 diffuseMapUV; + +#include "vertexcolors.glsl" + +void main() +{ + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy; + passColor = gl_Color; +} \ No newline at end of file diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index d1da187fbe..f373f5532a 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -485,6 +485,16 @@ + + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + + + Weather Particle Occlusion + + +