From 3bf18c601c0d0b58a85d60fee0cabc0bfb6dfce8 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 6 Jun 2022 22:40:38 +0200 Subject: [PATCH] Better fog --- apps/launcher/advancedpage.cpp | 4 +- apps/openmw/engine.cpp | 16 +++++- apps/openmw/engine.hpp | 1 + apps/openmw/mwrender/characterpreview.cpp | 6 +++ apps/openmw/mwrender/localmap.cpp | 6 +++ apps/openmw/mwrender/renderingmanager.cpp | 45 ++++++++++++++--- apps/openmw/mwrender/renderingmanager.hpp | 1 + apps/openmw/mwrender/sky.cpp | 33 +++++++++++- apps/openmw/mwrender/sky.hpp | 11 +++- apps/openmw_test_suite/fx/technique.cpp | 2 +- components/fx/pass.cpp | 2 +- components/sceneutil/waterutil.cpp | 3 -- components/shader/shadermanager.cpp | 19 +++++++ components/shader/shadermanager.hpp | 9 ++++ .../source/reference/modding/settings/fog.rst | 50 +++++++++++++++++++ .../reference/modding/settings/shaders.rst | 13 +---- files/settings-default.cfg | 21 ++++++-- files/shaders/CMakeLists.txt | 1 + files/shaders/fog.glsl | 34 +++++++++++++ files/shaders/groundcover_fragment.glsl | 10 ++-- files/shaders/nv_default_fragment.glsl | 11 ++-- files/shaders/nv_nolighting_fragment.glsl | 13 ++--- files/shaders/objects_fragment.glsl | 22 ++------ files/shaders/objects_vertex.glsl | 6 --- files/shaders/softparticles.glsl | 2 - files/shaders/terrain_fragment.glsl | 10 ++-- files/shaders/water_fragment.glsl | 12 ++--- 27 files changed, 268 insertions(+), 95 deletions(-) create mode 100644 files/shaders/fog.glsl diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 5e01443008..fe94c945f6 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -117,7 +117,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(autoUseTerrainNormalMapsCheckBox, "auto use terrain normal maps", "Shaders"); loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); - loadSettingBool(radialFogCheckBox, "radial fog", "Shaders"); + loadSettingBool(radialFogCheckBox, "radial fog", "Fog"); loadSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); loadSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); if (Settings::Manager::getInt("antialiasing", "Video") == 0) { @@ -265,7 +265,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(autoUseTerrainNormalMapsCheckBox, "auto use terrain normal maps", "Shaders"); saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders"); saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders"); - saveSettingBool(radialFogCheckBox, "radial fog", "Shaders"); + saveSettingBool(radialFogCheckBox, "radial fog", "Fog"); saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders"); saveSettingBool(antialiasAlphaTestCheckBox, "antialias alpha test", "Shaders"); saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game"); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index b2b1dbd0d6..4234662d31 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -256,7 +256,18 @@ namespace Log(Debug::Info) << "OpenGL Vendor: " << glGetString(GL_VENDOR); Log(Debug::Info) << "OpenGL Renderer: " << glGetString(GL_RENDERER); Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxTextureImageUnits); } + + int getMaxTextureImageUnits() const + { + if (mMaxTextureImageUnits == 0) + throw std::logic_error("mMaxTextureImageUnits is not initialized"); + return mMaxTextureImageUnits; + } + + private: + int mMaxTextureImageUnits = 0; }; class InitializeStereoOperation final : public osg::GraphicsOperation @@ -664,7 +675,8 @@ void OMW::Engine::createWindow() osg::ref_ptr realizeOperations = new SceneUtil::OperationSequence(false); mViewer->setRealizeOperation(realizeOperations); - realizeOperations->add(new IdentifyOpenGLOperation()); + osg::ref_ptr identifyOp = new IdentifyOpenGLOperation(); + realizeOperations->add(identifyOp); if (Debug::shouldDebugOpenGL()) realizeOperations->add(new Debug::EnableGLDebugOperation()); @@ -679,6 +691,7 @@ void OMW::Engine::createWindow() } mViewer->realize(); + mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits(); mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); } @@ -724,6 +737,7 @@ void OMW::Engine::prepareEngine() VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); mResourceSystem = std::make_unique(mVFS.get()); + mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index e8db06a33e..ba39132b04 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -264,6 +264,7 @@ namespace OMW private: Files::ConfigurationManager& mCfgMgr; class LuaWorker; + int mGlMaxTextureImageUnits; }; } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 3a6d2df7ff..f986c71045 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -268,6 +268,12 @@ namespace MWRender fog->setEnd(10000000); stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // turn of sky blending + stateset->addUniform(new osg::Uniform("far", 10000000.0f)); + stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); + stateset->addUniform(new osg::Uniform("sky", 0)); + stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{1, 1})); + // Opaque stuff must have 1 as its fragment alpha as the FBO is translucent, so having blending off isn't enough osg::ref_ptr noBlendAlphaEnv = new osg::TexEnvCombine(); noBlendAlphaEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index ccd3220efe..ecfb2d6387 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -720,6 +720,12 @@ void LocalMapRenderToTexture::setDefaults(osg::Camera* camera) fog->setEnd(10000000); stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + // turn of sky blending + stateset->addUniform(new osg::Uniform("far", 10000000.0f)); + stateset->addUniform(new osg::Uniform("skyBlendingStart", 8000000.0f)); + stateset->addUniform(new osg::Uniform("sky", 0)); + stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{1, 1})); + osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5464992544..e639880b37 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -81,7 +82,6 @@ namespace MWRender { class PerViewUniformStateUpdater final : public SceneUtil::StateSetUpdater { - public: public: PerViewUniformStateUpdater() { @@ -90,6 +90,8 @@ namespace MWRender void setDefaults(osg::StateSet* stateset) override { stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); + if (mSkyRTT) + stateset->addUniform(new osg::Uniform("sky", mSkyTextureUnit)); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override @@ -97,6 +99,11 @@ namespace MWRender auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(mProjectionMatrix); + if (mSkyRTT && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + { + osg::Texture* skyTexture = mSkyRTT->getColorTexture(static_cast(nv)); + stateset->setTextureAttribute(mSkyTextureUnit, skyTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } } void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override @@ -123,8 +130,16 @@ namespace MWRender return mProjectionMatrix; } + void enableSkyRTT(int skyTextureUnit, SceneUtil::RTTNode* skyRTT) + { + mSkyTextureUnit = skyTextureUnit; + mSkyRTT = skyRTT; + } + private: osg::Matrixf mProjectionMatrix; + int mSkyTextureUnit = -1; + SceneUtil::RTTNode* mSkyRTT = nullptr; }; class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater @@ -144,6 +159,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("skyBlendingStart", 0.f)); stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{})); if (mUsePlayerUniforms) { @@ -166,6 +182,11 @@ namespace MWRender if (uFar) uFar->set(mFar); + static const float mSkyBlendingStartCoef = Settings::Manager::getFloat("sky blending start", "Fog"); + auto* uSkyBlendingStart = stateset->getUniform("skyBlendingStart"); + if (uSkyBlendingStart) + uSkyBlendingStart->set(mFar * mSkyBlendingStartCoef); + auto* uScreenRes = stateset->getUniform("screenRes"); if (uScreenRes) uScreenRes->set(mScreenRes); @@ -337,7 +358,8 @@ namespace MWRender RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore) - : mViewer(viewer) + : mSkyBlending(Settings::Manager::getBool("sky blending", "Fog")) + , mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) @@ -358,12 +380,14 @@ 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") + bool forceShaders = Settings::Manager::getBool("radial fog", "Fog") + || Settings::Manager::getBool("exponential fog", "Fog") || Settings::Manager::getBool("soft particles", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ + || mSkyBlending || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); @@ -413,7 +437,10 @@ namespace MWRender globalDefines["forcePPL"] = Settings::Manager::getBool("force per pixel lighting", "Shaders") ? "1" : "0"; globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; - globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + bool exponentialFog = Settings::Manager::getBool("exponential fog", "Fog"); + globalDefines["radialFog"] = (exponentialFog || Settings::Manager::getBool("radial fog", "Fog")) ? "1" : "0"; + globalDefines["exponentialFog"] = exponentialFog ? "1" : "0"; + globalDefines["skyBlending"] = mSkyBlending ? "1" : "0"; globalDefines["refraction_enabled"] = "0"; globalDefines["useGPUShader4"] = "0"; globalDefines["useOVR_multiview"] = "0"; @@ -546,8 +573,14 @@ namespace MWRender mFog = std::make_unique(); - mSky = std::make_unique(sceneRoot, resourceSystem->getSceneManager()); + mSky = std::make_unique(sceneRoot, resourceSystem->getSceneManager(), mSkyBlending); mSky->setCamera(mViewer->getCamera()); + if (mSkyBlending) + { + int skyTextureUnit = mResourceSystem->getSceneManager()->getShaderManager().reserveGlobalTextureUnits(1); + Log(Debug::Info) << "Reserving texture unit for sky RTT: " << skyTextureUnit; + mPerViewUniformStateUpdater->enableSkyRTT(skyTextureUnit, mSky->getSkyRTT()); + } source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); @@ -573,8 +606,6 @@ namespace MWRender mStateUpdater->setFogEnd(mViewDistance); - mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); - // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); // The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and breaks things. diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 128ef43195..12f5bb073d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -267,6 +267,7 @@ namespace MWRender void updateRecastMesh(); + const bool mSkyBlending; osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 754810fc8c..7a37a69a3d 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -215,11 +216,35 @@ namespace private: const float &mAlpha; }; + + class SkyRTT : public SceneUtil::RTTNode + { + public: + SkyRTT(osg::Vec2f size, osg::Group* earlyRenderBinRoot) : + RTTNode(static_cast(size.x()), static_cast(size.y()), 0, false, 1, StereoAwareness::Aware), + mEarlyRenderBinRoot(earlyRenderBinRoot) + { + setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); + } + + void setDefaults(osg::Camera* camera) override + { + camera->setReferenceFrame(osg::Camera::RELATIVE_RF); + camera->setName("SkyCamera"); + camera->setNodeMask(MWRender::Mask_RenderToTexture); + camera->addChild(mEarlyRenderBinRoot); + SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + } + + private: + osg::ref_ptr mEarlyRenderBinRoot; + }; + } namespace MWRender { - SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) + SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager, bool enableSkyRTT) : mSceneManager(sceneManager) , mCamera(nullptr) , mAtmosphereNightRoll(0.f) @@ -271,6 +296,12 @@ namespace MWRender mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); mRootNode->addChild(mEarlyRenderBinRoot); + if (enableSkyRTT) + { + mSkyRTT = new SkyRTT(Settings::Manager::getVector2("sky rtt resolution", "Fog"), mEarlyRenderBinRoot); + mRootNode->addChild(mSkyRTT); + } + mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 1fdf476bd5..8682a6f17f 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -30,6 +30,11 @@ namespace Resource class SceneManager; } +namespace SceneUtil +{ + class RTTNode; +} + namespace MWRender { ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to be rendered @@ -37,7 +42,7 @@ namespace MWRender class SkyManager { public: - SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager); + SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager, bool enableSkyRTT); ~SkyManager(); void update(float duration); @@ -98,6 +103,8 @@ namespace MWRender void setSunglare(bool enabled); + SceneUtil::RTTNode* getSkyRTT() { return mSkyRTT.get(); } + private: void create(); ///< no need to call this, automatically done on first enable() @@ -192,6 +199,8 @@ namespace MWRender bool mDirtyParticlesEffect; osg::Vec4f mMoonScriptColor; + + osg::ref_ptr mSkyRTT; }; } diff --git a/apps/openmw_test_suite/fx/technique.cpp b/apps/openmw_test_suite/fx/technique.cpp index d4b5bb0f8c..3273d69eb2 100644 --- a/apps/openmw_test_suite/fx/technique.cpp +++ b/apps/openmw_test_suite/fx/technique.cpp @@ -104,7 +104,7 @@ TestingOpenMW::VFSTestFile repeated_shared_block{R"( })) , mImageManager(mVFS.get()) { - Settings::Manager::setBool("radial fog", "Shaders", true); + Settings::Manager::setBool("radial fog", "Fog", true); Settings::Manager::setBool("stereo enabled", "Stereo", false); } diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index b2fb8b2ffb..d264ceae2a 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -182,7 +182,7 @@ float omw_GetPointLightRadius(int index) {"@ubo", mUBO ? "1" : "0"}, {"@normals", technique.getNormals() ? "1" : "0"}, {"@reverseZ", SceneUtil::AutoDepth::isReversed() ? "1" : "0"}, - {"@radialFog", Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"}, + {"@radialFog", Settings::Manager::getBool("radial fog", "Fog") ? "1" : "0"}, {"@hdr", technique.getHDR() ? "1" : "0"}, {"@in", mLegacyGLSL ? "varying" : "in"}, {"@out", mLegacyGLSL ? "varying" : "out"}, diff --git a/components/sceneutil/waterutil.cpp b/components/sceneutil/waterutil.cpp index e22070f40f..10a0d4e885 100644 --- a/components/sceneutil/waterutil.cpp +++ b/components/sceneutil/waterutil.cpp @@ -84,9 +84,6 @@ namespace SceneUtil stateset->setRenderBinDetails(renderBin, "RenderBin"); - // Let the shader know we're dealing with simple water here. - stateset->addUniform(new osg::Uniform("simpleWater", true)); - return stateset; } } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index c4d45a9958..0ac96ce4dc 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace Shader { @@ -509,4 +510,22 @@ namespace Shader program->addShader(linkedShader); } + int ShaderManager::reserveGlobalTextureUnits(int count) + { + { + // Texture units from `8 - numberOfShadowMaps` to `8` are used for shadows, so we skip them here. + // TODO: Maybe instead of fixed texture units use `reserveGlobalTextureUnits` for shadows as well. + static const int numberOfShadowMaps = Settings::Manager::getBool("enable shadows", "Shadows") ? + std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8) : + 0; + if (getAvailableTextureUnits() >= 8 && getAvailableTextureUnits() - count < 8) + mReservedTextureUnits = mMaxTextureUnits - (8 - numberOfShadowMaps); + } + + if (getAvailableTextureUnits() < count + 1) + throw std::runtime_error("Can't reserve texture unit; no available units"); + mReservedTextureUnits += count; + return mMaxTextureUnits - mReservedTextureUnits; + } + } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 598dde85bb..05c1b17bb4 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -54,6 +54,12 @@ namespace Shader bool createSourceFromTemplate(std::string& source, std::vector& linkedShaderTemplateNames, const std::string& templateName, const ShaderManager::DefineMap& defines); + void setMaxTextureUnits(int maxTextureUnits) { mMaxTextureUnits = maxTextureUnits; } + int getMaxTextureUnits() const { return mMaxTextureUnits; } + int getAvailableTextureUnits() const { return mMaxTextureUnits - mReservedTextureUnits; } + + int reserveGlobalTextureUnits(int count); + private: void getLinkedShaders(osg::ref_ptr shader, const std::vector& linkedShaderNames, const DefineMap& defines); void addLinkedShaders(osg::ref_ptr shader, osg::ref_ptr program); @@ -80,6 +86,9 @@ namespace Shader std::mutex mMutex; osg::ref_ptr mProgramTemplate; + + int mMaxTextureUnits = 0; + int mReservedTextureUnits = 0; }; bool parseForeachDirective(std::string& source, const std::string& templateName, size_t foundPos); diff --git a/docs/source/reference/modding/settings/fog.rst b/docs/source/reference/modding/settings/fog.rst index 10ab0bbe30..b05112162e 100644 --- a/docs/source/reference/modding/settings/fog.rst +++ b/docs/source/reference/modding/settings/fog.rst @@ -113,3 +113,53 @@ distant interior fog end :Default: 16384 (2 cells) This is the base fog end distance used for distant fog calculations in interior locations. + +radial fog +---------- + +:Type: boolean +:Range: True/False +:Default: False + +By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. +This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. +Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. + +exponential fog +--------------- + +:Type: boolean +:Range: True/False +:Default: False + +Similar to "radial fog" but uses an exponential formula for the fog. +Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. + +sky blending +------------ + +:Type: boolean +:Range: True/False +:Default: False + +Whether to use blending with the sky for everything that is close to the clipping plane. +If enabled the clipping plane becomes invisible. +Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. + +sky blending start +------------------ + +:Type: floating point +:Range: from 0.0 (including) to 1.0 (excluding) +:Default: 0.8 + +The fraction of the maximum distance at which blending with the sky starts. + +sky rtt resolution +------------------ + +:Type: two positive integers +:Default: 512 256 + +The sky RTT texture size, used only for sky blending. Smaller values +reduce quality of the sky blending, but can have slightly better performance. diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 5629e321d0..10752d8ba4 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -137,17 +137,6 @@ Normally environment map reflections aren't affected by lighting, which makes en Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. Affected objects will use shaders. -radial fog ----------- - -:Type: boolean -:Range: True/False -:Default: False - -By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. -This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. -Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. - lighting method --------------- @@ -284,4 +273,4 @@ 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 +This means that shaders will be used to render all objects and the terrain. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index fd9826104a..807f00b41f 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -137,6 +137,23 @@ distant interior fog start = 0 distant interior fog end = 16384 +# Determine fog intensity based on the distance from the eye point. +# This makes fogging independent from the viewing angle. Shaders will be used to render all objects. +radial fog = false + +# Whether to use exponential formula for fog. +exponential fog = false + +# Whether to hide the clipping plane by blending with sky. +sky blending = false + +# Fraction of the maximum distance at which blending with the sky starts. +sky blending start = 0.8 + +# The sky RTT texture size, used only for sky blending. Smaller values +# reduce quality of the sky blending, but can have slightly better performance. +sky rtt resolution = 512 256 + [Map] # Size of each exterior cell in pixels in the world map. (e.g. 12 to 24). @@ -427,10 +444,6 @@ terrain specular map pattern = _diffusespec # Affected objects use shaders. apply lighting to environment maps = false -# Determine fog intensity based on the distance from the eye point. -# This makes fogging independent from the viewing angle. Shaders will be used to render all objects. -radial fog = false - # Internal handling of lights, ignored if 'force shaders' is off. "legacy" # provides fixed function pipeline emulation."shaders compatibility" (default) # uncaps the light limit, enables groundcover lighting, and uses a modified diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 88fd6d45ca..affb3fc940 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -52,6 +52,7 @@ set(SHADER_FILES hdr_luminance_fragment.glsl fullscreen_tri_vertex.glsl fullscreen_tri_fragment.glsl + fog.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/fog.glsl b/files/shaders/fog.glsl new file mode 100644 index 0000000000..d327d6a345 --- /dev/null +++ b/files/shaders/fog.glsl @@ -0,0 +1,34 @@ +uniform float far; + +#if @skyBlending +uniform sampler2D sky; +uniform float skyBlendingStart; +#endif + +vec4 applyFogAtDist(vec4 color, float euclideanDist, float linearDist) +{ +#if @radialFog + float dist = euclideanDist; +#else + float dist = abs(linearDist); +#endif +#if @exponentialFog + float fogValue = 1.0 - exp(-2.0 * max(0.0, dist - gl_Fog.start/2.0) / (gl_Fog.end - gl_Fog.start/2.0)); +#else + float fogValue = clamp((dist - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif + color.xyz = mix(color.xyz, gl_Fog.color.xyz, fogValue); + +#if @skyBlending && !@useOVR_multiview + float fadeValue = clamp((far - dist) / (far - skyBlendingStart), 0.0, 1.0); + vec3 skyColor = texture2D(sky, gl_FragCoord.xy / screenRes).xyz; + color.xyz = mix(skyColor, color.xyz, fadeValue * fadeValue); +#endif + + return color; +} + +vec4 applyFogAtPos(vec4 color, vec3 pos) +{ + return applyFogAtDist(color, length(pos), pos.z); +} diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index b7ff102079..0bdc3d915d 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -27,6 +27,7 @@ varying vec4 passTangent; varying float euclideanDepth; varying float linearDepth; +uniform vec2 screenRes; #if PER_PIXEL_LIGHTING varying vec3 passViewPos; @@ -40,6 +41,7 @@ varying vec3 passNormal; #include "shadows_fragment.glsl" #include "lighting.glsl" #include "alpha.glsl" +#include "fog.glsl" void main() { @@ -82,13 +84,7 @@ void main() clampLightingResult(lighting); gl_FragData[0].xyz *= lighting; - -#if @radialFog - float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#else - float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#endif - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth); #if !@disableNormals gl_FragData[1].xyz = worldNormal * 0.5 + 0.5; diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 9cb784ab98..57ef7b14df 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -33,10 +33,13 @@ varying float linearDepth; varying vec3 passViewPos; varying vec3 passNormal; +uniform vec2 screenRes; + #include "vertexcolors.glsl" #include "shadows_fragment.glsl" #include "lighting.glsl" #include "alpha.glsl" +#include "fog.glsl" uniform float emissiveMult; uniform float specStrength; @@ -91,12 +94,8 @@ void main() if (matSpec != vec3(0.0)) gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; -#if @radialFog - float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#else - float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#endif - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + + gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth); #if defined(FORCE_OPAQUE) && FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl index 7c4f4737e0..59dfda9ee8 100644 --- a/files/shaders/nv_nolighting_fragment.glsl +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -10,18 +10,17 @@ uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; #endif -#if @radialFog varying float euclideanDepth; -#else varying float linearDepth; -#endif uniform bool useFalloff; +uniform vec2 screenRes; varying float passFalloff; #include "vertexcolors.glsl" #include "alpha.glsl" +#include "fog.glsl" void main() { @@ -39,15 +38,9 @@ void main() alphaTest(); -#if @radialFog - float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#else - float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#endif - #if defined(FORCE_OPAQUE) && FORCE_OPAQUE gl_FragData[0].a = 1.0; #endif - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth); } diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 16cbf9bbbd..6ebc7cbd62 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -63,10 +63,7 @@ uniform sampler2D glossMap; varying vec2 glossMapUV; #endif -uniform bool simpleWater; - -varying float euclideanDepth; -varying float linearDepth; +uniform vec2 screenRes; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) @@ -85,6 +82,7 @@ varying vec3 passNormal; #include "lighting.glsl" #include "parallax.glsl" #include "alpha.glsl" +#include "fog.glsl" #if @softParticles #include "softparticles.glsl" @@ -190,7 +188,7 @@ void main() #endif - float shadowing = unshadowedLightRatio(linearDepth); + float shadowing = unshadowedLightRatio(passViewPos.z); vec3 lighting; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; @@ -230,18 +228,8 @@ void main() #endif gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; } -#if @radialFog - float depth; - // For the less detailed mesh of simple water we need to recalculate depth on per-pixel basis - if (simpleWater) - depth = length(passViewPos); - else - depth = euclideanDepth; - float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#else - float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#endif - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + + gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos); #if !defined(FORCE_OPAQUE) && @softParticles gl_FragData[0].a *= calcSoftParticleFade(); diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 5c94417491..971b95d8e0 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -50,9 +50,6 @@ varying vec2 specularMapUV; varying vec2 glossMapUV; #endif -varying float euclideanDepth; -varying float linearDepth; - #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING @@ -74,10 +71,7 @@ void main(void) gl_Position = mw_modelToClip(gl_Vertex); vec4 viewPos = mw_modelToView(gl_Vertex); - gl_ClipVertex = viewPos; - euclideanDepth = length(viewPos.xyz); - linearDepth = getLinearDepth(gl_Position.z, viewPos.z); #if (@envMap || !PER_PIXEL_LIGHTING || @shadows_enabled) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); diff --git a/files/shaders/softparticles.glsl b/files/shaders/softparticles.glsl index fa8b4de4c1..f2ed36a8ad 100644 --- a/files/shaders/softparticles.glsl +++ b/files/shaders/softparticles.glsl @@ -1,7 +1,5 @@ uniform float near; -uniform float far; uniform sampler2D opaqueDepthTex; -uniform vec2 screenRes; uniform float particleSize; float viewDepth(float depth) diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index 7ff696e083..1e72cf5828 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -32,10 +32,13 @@ centroid varying vec3 shadowDiffuseLighting; varying vec3 passViewPos; varying vec3 passNormal; +uniform vec2 screenRes; + #include "vertexcolors.glsl" #include "shadows_fragment.glsl" #include "lighting.glsl" #include "parallax.glsl" +#include "fog.glsl" void main() { @@ -116,12 +119,7 @@ void main() gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos), shininess, matSpec) * shadowing; } -#if @radialFog - float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#else - float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#endif - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth); #if !@disableNormals && @writeNormals gl_FragData[1].xyz = worldNormal.xyz * 0.5 + 0.5; diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 51d30be2d4..f60ec765b1 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -212,7 +212,6 @@ uniform sampler2D normalMap; uniform float osg_SimulationTime; uniform float near; -uniform float far; uniform vec3 nodePosition; uniform float rainIntensity; @@ -223,6 +222,7 @@ uniform vec2 screenRes; #include "shadows_fragment.glsl" #include "lighting.glsl" +#include "fog.glsl" float frustumDepth; @@ -291,6 +291,8 @@ void main(void) // TODO: Figure out how to properly radialise refraction depth and thus underwater fog // while avoiding oddities when the water plane is close to the clipping plane // radialise = radialDepth / linearDepth; +#else + float radialDepth = 0.0; #endif vec2 screenCoordsOffset = normal.xy * REFL_BUMP; @@ -355,13 +357,7 @@ void main(void) gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); #endif - // fog -#if @radialFog - float fogValue = clamp((radialDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#else - float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); -#endif - gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + gl_FragData[0] = applyFogAtDist(gl_FragData[0], radialDepth, linearDepth); #if !@disableNormals gl_FragData[1].rgb = normal * 0.5 + 0.5;