From dda735c54a0e4676c4d0c99b4b0fe14ebeab79d8 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 21 Feb 2021 10:38:15 -0800 Subject: [PATCH 01/24] initial commit --- apps/opencs/view/render/scenewidget.cpp | 1 - apps/openmw/engine.cpp | 1 + apps/openmw/mwrender/characterpreview.cpp | 3 +- apps/openmw/mwrender/localmap.cpp | 11 + apps/openmw/mwrender/renderingmanager.cpp | 13 +- apps/openmw/mwrender/water.cpp | 3 + components/resource/scenemanager.cpp | 13 +- components/resource/scenemanager.hpp | 4 + components/sceneutil/lightmanager.cpp | 575 +++++++++++++++++----- components/sceneutil/lightmanager.hpp | 110 +++-- components/shader/shadermanager.cpp | 13 + components/shader/shadermanager.hpp | 10 + files/settings-default.cfg | 5 + files/shaders/CMakeLists.txt | 1 + files/shaders/groundcover_fragment.glsl | 1 + files/shaders/groundcover_vertex.glsl | 2 + files/shaders/lighting.glsl | 83 +++- files/shaders/objects_fragment.glsl | 4 +- files/shaders/objects_vertex.glsl | 3 +- files/shaders/sun.glsl | 14 + files/shaders/terrain_fragment.glsl | 4 +- files/shaders/terrain_vertex.glsl | 3 +- files/shaders/water_fragment.glsl | 11 +- 23 files changed, 710 insertions(+), 178 deletions(-) create mode 100644 files/shaders/sun.glsl diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 4d73cde153..61707967b9 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -72,7 +72,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; - lightMgr->setStartLight(1); lightMgr->setLightingMask(Mask_Lighting); mRootNode = lightMgr; diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d7c315323d..019e9f67a9 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -31,6 +31,7 @@ #include #include +#include #include diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 262b03229f..a44b51db83 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -152,7 +152,7 @@ namespace MWRender mCamera->setNodeMask(Mask_RenderToTexture); - osg::ref_ptr lightManager = new SceneUtil::LightManager; + osg::ref_ptr lightManager = new SceneUtil::LightManager(mResourceSystem->getSceneManager()->getFFPLighting()); lightManager->setStartLight(1); osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); @@ -215,6 +215,7 @@ namespace MWRender light->setConstantAttenuation(1.f); light->setLinearAttenuation(0.f); light->setQuadraticAttenuation(0.f); + lightManager->setSunlight(light); osg::ref_ptr lightSource = new osg::LightSource; lightSource->setLight(light); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 5fa1a0e299..8b1dda841a 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -18,7 +18,10 @@ #include #include #include +#include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -219,6 +222,14 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + // override sun for local map + if (!MWBase::Environment::get().getResourceSystem()->getSceneManager()->getFFPLighting()) + { + osg::ref_ptr sun = new SceneUtil::SunlightStateAttribute; + sun->setFromLight(light); + sun->setStateSet(stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + camera->addChild(lightSource); camera->setStateSet(stateset); camera->setViewport(0, 0, mMapResolution, mMapResolution); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a3aee5c0fa..733e1d287a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -203,17 +203,19 @@ namespace MWRender resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); // Shadows and radial fog have problems with fixed-function mode bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows"); + bool clampLighting = Settings::Manager::getBool("clamp lighting", "Shaders"); resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped - resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); + resourceSystem->getSceneManager()->setClampLighting(clampLighting); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::Manager::getString("normal height map pattern", "Shaders")); resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); + resourceSystem->getSceneManager()->setFFPLighting(clampLighting || !forceShaders || !SceneUtil::LightManager::queryNonFFPLightingSupport()); - osg::ref_ptr sceneRoot = new SceneUtil::LightManager; + osg::ref_ptr sceneRoot = new SceneUtil::LightManager(mResourceSystem->getSceneManager()->getFFPLighting()); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); @@ -235,8 +237,12 @@ namespace MWRender mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); + Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); + for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) + globalDefines[itr->first] = itr->second; + for (auto itr = shadowDefines.begin(); itr != shadowDefines.end(); itr++) globalDefines[itr->first] = itr->second; @@ -248,7 +254,7 @@ namespace MWRender float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); - + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); @@ -354,6 +360,7 @@ namespace MWRender mSunLight->setAmbient(osg::Vec4f(0,0,0,1)); mSunLight->setSpecular(osg::Vec4f(0,0,0,0)); mSunLight->setConstantAttenuation(1.f); + sceneRoot->setSunlight(mSunLight); sceneRoot->addChild(source); sceneRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 1cc5a3cb7c..4eac2fe4cd 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -27,6 +27,7 @@ #include #include +#include #include @@ -644,6 +645,8 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); + if (!mResourceSystem->getSceneManager()->getFFPLighting()) + program->addBindUniformBlock("SunlightBuffer", 9); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); node->setStateSet(shaderStateset); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 19cc96433b..6755db2557 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -220,12 +220,13 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) - , mShaderManager(new Shader::ShaderManager) + , mShaderManager(new Shader::ShaderManager(this)) , mForceShaders(false) , mClampLighting(true) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) + , mFFPLighting(true) , mInstanceCache(new MultiObjectCache) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) @@ -297,6 +298,16 @@ namespace Resource mApplyLightingToEnvMaps = apply; } + void SceneManager::setFFPLighting(bool apply) + { + mFFPLighting = apply; + } + + bool SceneManager::getFFPLighting() const + { + return mFFPLighting; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 9da6bc500d..92800df9fb 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -100,6 +100,9 @@ namespace Resource void setApplyLightingToEnvMaps(bool apply); + void setFFPLighting(bool apply); + bool getFFPLighting() const; + void setShaderPath(const std::string& path); /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded @@ -184,6 +187,7 @@ namespace Resource bool mAutoUseSpecularMaps; std::string mSpecularMapPattern; bool mApplyLightingToEnvMaps; + bool mFFPLighting; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 2ebce241d5..fa9dc4fda8 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1,11 +1,145 @@ #include "lightmanager.hpp" +#include + #include #include +#include + +#include + +#include + +namespace +{ + /* similar to the boost::hash_combine */ + template + inline void hash_combine(std::size_t& seed, const T& v) + { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + + bool sortLights(const SceneUtil::LightManager::LightSourceViewBound* left, const SceneUtil::LightManager::LightSourceViewBound* right) + { + return left->mViewBound.center().length2() - left->mViewBound.radius2()*81 < right->mViewBound.center().length2() - right->mViewBound.radius2()*81; + } +} + namespace SceneUtil { + static int sLightId = 0; + + class LightBuffer : public osg::Referenced + { + public: + LightBuffer(int elements, int count=1) + : mNumElements(elements) + , mData(new osg::Vec4Array(elements * count)) + , mDirty(false) + {} + + virtual ~LightBuffer() {} + + osg::ref_ptr getData() + { + return mData; + } + + bool isDirty() const + { + return mDirty; + } + + void dirty() + { + mData->dirty(); + mDirty = false; + } + + int blockSize() const + { + return mData->getNumElements() * sizeof(GL_FLOAT) * osg::Vec4::num_components; + } + + protected: + size_t mNumElements; + osg::ref_ptr mData; + bool mDirty; + }; + + /* + * struct: + * vec4 diffuse + * vec4 ambient + * vec4 specular + * vec4 direction + */ + class SunlightBuffer : public LightBuffer + { + public: + + enum Type {Diffuse, Ambient, Specular, Direction}; + + SunlightBuffer() + : LightBuffer(4) + {} + + void setValue(Type type, const osg::Vec4& value) + { + if (getValue(type) == value) + return; + + (*mData)[type] = value; + + mDirty = true; + } + + osg::Vec4 getValue(Type type) + { + return (*mData)[type]; + } + }; + + /* + * struct: + * vec4 diffuse + * vec4 ambient + * vec4 position + * vec4 illumination (constant, linear, quadratic) + */ + class PointLightBuffer : public LightBuffer + { + public: + + enum Type {Diffuse, Ambient, Position, Attenuation}; + + PointLightBuffer(int count) + : LightBuffer(4, count) + {} + + void setValue(int index, Type type, const osg::Vec4& value) + { + if (getValue(index, type) == value) + return; + + (*mData)[mNumElements * index + type] = value; + + mDirty = true; + } + + osg::Vec4 getValue(int index, Type type) + { + return (*mData)[mNumElements * index + type]; + } + + static constexpr int queryBlockSize(int sz) + { + return 4 * osg::Vec4::num_components * sizeof(GL_FLOAT) * sz; + } + }; class LightStateCache { @@ -13,7 +147,7 @@ namespace SceneUtil osg::Light* lastAppliedLight[8]; }; - LightStateCache* getLightStateCache(unsigned int contextid) + LightStateCache* getLightStateCache(size_t contextid) { static std::vector cacheVector; if (cacheVector.size() < contextid+1) @@ -21,15 +155,53 @@ namespace SceneUtil return &cacheVector[contextid]; } - // Resets the modelview matrix to just the view matrix before applying lights. - class LightStateAttribute : public osg::StateAttribute + SunlightStateAttribute::SunlightStateAttribute() + : mBuffer(new SunlightBuffer) + { + osg::ref_ptr ubo = new osg::UniformBufferObject; + mBuffer->getData()->setBufferObject(ubo); + mUbb = new osg::UniformBufferBinding(9 , mBuffer->getData().get(), 0, mBuffer->blockSize()); + } + + SunlightStateAttribute::SunlightStateAttribute(const SunlightStateAttribute ©, const osg::CopyOp ©op) + : osg::StateAttribute(copy, copyop), mBuffer(copy.mBuffer) + {} + + int SunlightStateAttribute::compare(const StateAttribute &sa) const + { + throw std::runtime_error("LightStateAttribute::compare: unimplemented"); + } + + void SunlightStateAttribute::setFromLight(const osg::Light* light) + { + mBuffer->setValue(SunlightBuffer::Diffuse, light->getDiffuse()); + mBuffer->setValue(SunlightBuffer::Ambient, light->getAmbient()); + mBuffer->setValue(SunlightBuffer::Specular, light->getSpecular()); + mBuffer->setValue(SunlightBuffer::Direction, light->getPosition()); + } + + void SunlightStateAttribute::setStateSet(osg::StateSet* stateset, int mode) + { + stateset->setAttributeAndModes(mUbb, mode); + stateset->setAssociatedModes(this, mode); + mBuffer->dirty(); + } + + class DisableLight : public osg::StateAttribute { public: - LightStateAttribute() : mIndex(0) {} - LightStateAttribute(unsigned int index, const std::vector >& lights) : mIndex(index), mLights(lights) {} + DisableLight() : mIndex(0) {} + DisableLight(int index) : mIndex(index) {} - LightStateAttribute(const LightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex), mLights(copy.mLights) {} + DisableLight(const DisableLight& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex) {} + + osg::Object* cloneType() const override { return new DisableLight(mIndex); } + osg::Object* clone(const osg::CopyOp& copyop) const override { return new DisableLight(*this,copyop); } + bool isSameKindAs(const osg::Object* obj) const override { return dynamic_cast(obj)!=nullptr; } + const char* libraryName() const override { return "SceneUtil"; } + const char* className() const override { return "DisableLight"; } + Type getType() const override { return LIGHT; } unsigned int getMember() const override { @@ -38,11 +210,40 @@ namespace SceneUtil bool getModeUsage(ModeUsage & usage) const override { - for (unsigned int i=0; ilastAppliedLight[mIndex] = nullptr; + } + + private: + size_t mIndex; + osg::Vec4f mnullptr; + }; + + class LightStateAttribute : public osg::StateAttribute + { + public: + LightStateAttribute() : mBuffer(nullptr) {} + LightStateAttribute(const std::vector >& lights, const osg::ref_ptr& buffer) : mLights(lights), mBuffer(buffer) {} + + LightStateAttribute(const LightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mLights(copy.mLights), mBuffer(copy.mBuffer) {} + int compare(const StateAttribute &sa) const override { throw std::runtime_error("LightStateAttribute::compare: unimplemented"); @@ -50,6 +251,68 @@ namespace SceneUtil META_StateAttribute(NifOsg, LightStateAttribute, osg::StateAttribute::LIGHT) + void apply(osg::State& state) const override + { + if (mLights.empty()) + return; + osg::Matrix modelViewMatrix = state.getModelViewMatrix(); + + state.applyModelViewMatrix(state.getInitialViewMatrix()); + + if (mBuffer) + { + for (size_t i = 0; i < mLights.size(); ++i) + { + + mBuffer->setValue(i, PointLightBuffer::Diffuse, mLights[i]->getDiffuse()); + mBuffer->setValue(i, PointLightBuffer::Ambient, mLights[i]->getAmbient()); + mBuffer->setValue(i, PointLightBuffer::Position, mLights[i]->getPosition() * state.getModelViewMatrix()); + mBuffer->setValue(i, PointLightBuffer::Attenuation, + osg::Vec4(mLights[i]->getConstantAttenuation(), + mLights[i]->getLinearAttenuation(), + mLights[i]->getQuadraticAttenuation(), 0.0)); + } + + if (mBuffer && mBuffer->isDirty()) + mBuffer->dirty(); + } + + state.applyModelViewMatrix(modelViewMatrix); + } + + private: + std::vector> mLights; + osg::ref_ptr mBuffer; + }; + + class FFPLightStateAttribute : public osg::StateAttribute + { + public: + FFPLightStateAttribute() : mIndex(0) {} + FFPLightStateAttribute(size_t index, const std::vector >& lights) : mIndex(index), mLights(lights) {} + + FFPLightStateAttribute(const FFPLightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex), mLights(copy.mLights) {} + + unsigned int getMember() const override + { + return mIndex; + } + + bool getModeUsage(ModeUsage & usage) const override + { + for (size_t i=0; ilastAppliedLight[i+mIndex]; if (current != mLights[i].get()) @@ -90,14 +353,14 @@ namespace SceneUtil } private: - unsigned int mIndex; + size_t mIndex; - std::vector > mLights; + std::vector> mLights; }; LightManager* findLightManager(const osg::NodePath& path) { - for (unsigned int i=0;i(path[i])) return lightManager; @@ -160,33 +423,161 @@ namespace SceneUtil } }; - LightManager::LightManager() + class SunlightCallback : public osg::NodeCallback + { + public: + SunlightCallback(LightManager* lightManager) : mLightManager(lightManager) {} + + void operator()(osg::Node* node, osg::NodeVisitor* nv) override + { + osgUtil::CullVisitor* cv = static_cast(nv); + + if (mLastFrameNumber != cv->getTraversalNumber()) + { + mLastFrameNumber = cv->getTraversalNumber(); + + auto sun = mLightManager->getSunlight(); + + if (!sun) + return; + + auto buf = mLightManager->getSunBuffer(); + + buf->setValue(SunlightBuffer::Diffuse, sun->getDiffuse()); + buf->setValue(SunlightBuffer::Ambient, sun->getAmbient()); + buf->setValue(SunlightBuffer::Specular, sun->getSpecular()); + buf->setValue(SunlightBuffer::Direction, sun->getPosition() * *cv->getCurrentRenderStage()->getInitialViewMatrix()); + + if (buf->isDirty()) + buf->dirty(); + } + + traverse(node, nv); + } + + private: + LightManager* mLightManager; + size_t mLastFrameNumber; + }; + + LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) + , mSun(nullptr) + , mSunBuffer(nullptr) + , mFFP(ffp) { setUpdateCallback(new LightManagerUpdateCallback); - for (unsigned int i=0; i<8; ++i) - mDummies.push_back(new LightStateAttribute(i, std::vector >())); + + if (usingFFP()) + { + for (int i=0; i >())); + return; + } + + auto* stateset = getOrCreateStateSet(); + + mSunBuffer = new SunlightBuffer(); + osg::ref_ptr ubo = new osg::UniformBufferObject; + mSunBuffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(9 , mSunBuffer->getData().get(), 0, mSunBuffer->blockSize()); + stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("PointLightCount", 0)); + + addCullCallback(new SunlightCallback(this)); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) , mStartLight(copy.mStartLight) , mLightingMask(copy.mLightingMask) + , mSun(copy.mSun) + , mSunBuffer(copy.mSunBuffer) + , mFFP(copy.mFFP) { - } - void LightManager::setLightingMask(unsigned int mask) + bool LightManager::usingFFP() const + { + return mFFP; + } + + int LightManager::getMaxLights() const + { + if (usingFFP()) return LightManager::mFFPMaxLights; + return std::clamp(Settings::Manager::getInt("max lights", "Shaders"), 0, getMaxLightsInScene()); + } + + int LightManager::getMaxLightsInScene() const + { + static constexpr int max = 16384 / PointLightBuffer::queryBlockSize(1); + return max; + } + + Shader::ShaderManager::DefineMap LightManager::getLightDefines() const + { + Shader::ShaderManager::DefineMap defines; + + bool ffp = usingFFP(); + + defines["ffpLighting"] = ffp ? "1" : "0"; + defines["sunDirection"] = ffp ? "gl_LightSource[0].position" : "Sun.direction"; + defines["sunAmbient"] = ffp ? "gl_LightSource[0].ambient" : "Sun.ambient"; + defines["sunDiffuse"] = ffp ? "gl_LightSource[0].diffuse" : "Sun.diffuse"; + defines["sunSpecular"] = ffp ? "gl_LightSource[0].specular" : "Sun.specular"; + defines["maxLights"] = std::to_string(getMaxLights()); + defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene()); + + return defines; + } + + bool LightManager::queryNonFFPLightingSupport() + { + osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); + if (!exts || !exts->isUniformBufferObjectSupported) + { + auto ffpWarning = Misc::StringUtils::format("GL_ARB_uniform_buffer_object not supported: Falling back to FFP %zu light limit. Can not set lights to %i." + , LightManager::mFFPMaxLights + , Settings::Manager::getInt("max lights", "Shaders")); + Log(Debug::Warning) << ffpWarning; + return false; + } + return true; + } + + void LightManager::setLightingMask(size_t mask) { mLightingMask = mask; } - unsigned int LightManager::getLightingMask() const + size_t LightManager::getLightingMask() const { return mLightingMask; } + void LightManager::setStartLight(int start) + { + if (!usingFFP()) return; + + mStartLight = start; + + // Set default light state to zero + // This is necessary because shaders don't respect glDisable(GL_LIGHTX) so in addition to disabling + // we'll have to set a light state that has no visible effect + for (int i=start; i defaultLight (new DisableLight(i)); + getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); + } + } + + int LightManager::getStartLight() const + { + return mStartLight; + } + void LightManager::update() { mLights.clear(); @@ -200,7 +591,7 @@ namespace SceneUtil } } - void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum) + void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum) { LightSourceTransform l; l.mLightSource = lightSource; @@ -211,19 +602,28 @@ namespace SceneUtil mLights.push_back(l); } - /* similar to the boost::hash_combine */ - template - inline void hash_combine(std::size_t& seed, const T& v) + void LightManager::setSunlight(osg::ref_ptr sun) { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + if (usingFFP()) return; + + mSun = sun; } - osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, unsigned int frameNum) + osg::ref_ptr LightManager::getSunlight() + { + return mSun; + } + + osg::ref_ptr LightManager::getSunBuffer() + { + return mSunBuffer; + } + + osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, size_t frameNum) { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; - for (unsigned int i=0; imLightSource->getId()); LightStateSetMap& stateSetCache = mStateSetCache[frameNum%2]; @@ -236,21 +636,35 @@ namespace SceneUtil osg::ref_ptr stateset = new osg::StateSet; std::vector > lights; lights.reserve(lightList.size()); - for (unsigned int i=0; imLightSource->getLight(frameNum)); - // the first light state attribute handles the actual state setting for all lights - // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary - // don't use setAttributeAndModes, that does not support light indices! - stateset->setAttribute(new LightStateAttribute(mStartLight, std::move(lights)), osg::StateAttribute::ON); + if (usingFFP()) + { + // the first light state attribute handles the actual state setting for all lights + // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary + // don't use setAttributeAndModes, that does not support light indices! + stateset->setAttribute(new FFPLightStateAttribute(mStartLight, std::move(lights)), osg::StateAttribute::ON); - for (unsigned int i=0; isetMode(GL_LIGHT0 + mStartLight + i, osg::StateAttribute::ON); + for (size_t i=0; isetMode(GL_LIGHT0 + mStartLight + i, osg::StateAttribute::ON); - // need to push some dummy attributes to ensure proper state tracking - // lights need to reset to their default when the StateSet is popped - for (unsigned int i=1; isetAttribute(mDummies[i+mStartLight].get(), osg::StateAttribute::ON); + // need to push some dummy attributes to ensure proper state tracking + // lights need to reset to their default when the StateSet is popped + for (size_t i=1; isetAttribute(mDummies[i+mStartLight].get(), osg::StateAttribute::ON); + } + else + { + osg::ref_ptr buffer = new PointLightBuffer(lights.size()); + osg::ref_ptr ubo = new osg::UniformBufferObject; + buffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(8 ,buffer->getData().get(), 0, buffer->blockSize()); + stateset->addUniform(new osg::Uniform("PointLightCount", (int)lights.size())); + stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + + stateset->setAttribute(new LightStateAttribute(std::move(lights), buffer), osg::StateAttribute::ON); + } stateSetCache.emplace(hash, stateset); return stateset; @@ -262,7 +676,7 @@ namespace SceneUtil return mLights; } - const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix) + const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { osg::observer_ptr camPtr (camera); std::map, LightSourceViewBoundCollection>::iterator it = mLightsInViewSpace.find(camPtr); @@ -286,75 +700,6 @@ namespace SceneUtil return it->second; } - class DisableLight : public osg::StateAttribute - { - public: - DisableLight() : mIndex(0) {} - DisableLight(int index) : mIndex(index) {} - - DisableLight(const DisableLight& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex) {} - - osg::Object* cloneType() const override { return new DisableLight(mIndex); } - osg::Object* clone(const osg::CopyOp& copyop) const override { return new DisableLight(*this,copyop); } - bool isSameKindAs(const osg::Object* obj) const override { return dynamic_cast(obj)!=nullptr; } - const char* libraryName() const override { return "SceneUtil"; } - const char* className() const override { return "DisableLight"; } - Type getType() const override { return LIGHT; } - - unsigned int getMember() const override - { - return mIndex; - } - - bool getModeUsage(ModeUsage & usage) const override - { - usage.usesMode(GL_LIGHT0 + mIndex); - return true; - } - - int compare(const StateAttribute &sa) const override - { - throw std::runtime_error("DisableLight::compare: unimplemented"); - } - - void apply(osg::State& state) const override - { - int lightNum = GL_LIGHT0 + mIndex; - glLightfv( lightNum, GL_AMBIENT, mnullptr.ptr() ); - glLightfv( lightNum, GL_DIFFUSE, mnullptr.ptr() ); - glLightfv( lightNum, GL_SPECULAR, mnullptr.ptr() ); - - LightStateCache* cache = getLightStateCache(state.getContextID()); - cache->lastAppliedLight[mIndex] = nullptr; - } - - private: - unsigned int mIndex; - osg::Vec4f mnullptr; - }; - - void LightManager::setStartLight(int start) - { - mStartLight = start; - - // Set default light state to zero - // This is necessary because shaders don't respect glDisable(GL_LIGHTX) so in addition to disabling - // we'll have to set a light state that has no visible effect - for (int i=start; i<8; ++i) - { - osg::ref_ptr defaultLight (new DisableLight(i)); - getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); - } - } - - int LightManager::getStartLight() const - { - return mStartLight; - } - - static int sLightId = 0; - LightSource::LightSource() : mRadius(0.f) { @@ -372,12 +717,6 @@ namespace SceneUtil mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } - - bool sortLights (const LightManager::LightSourceViewBound* left, const LightManager::LightSourceViewBound* right) - { - return left->mViewBound.center().length2() - left->mViewBound.radius2()*81 < right->mViewBound.center().length2() - right->mViewBound.radius2()*81; - } - void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) { osgUtil::CullVisitor* cv = static_cast(nv); @@ -413,7 +752,7 @@ namespace SceneUtil // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); - const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix); + const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); // get the node bounds in view space // NB do not node->getBound() * modelView, that would apply the node's transformation twice @@ -421,7 +760,7 @@ namespace SceneUtil osg::Transform* transform = node->asTransform(); if (transform) { - for (unsigned int i=0; igetNumChildren(); ++i) + for (size_t i=0; igetNumChildren(); ++i) nodeBound.expandBy(transform->getChild(i)->getBound()); } else @@ -430,7 +769,7 @@ namespace SceneUtil transformBoundingSphere(mat, nodeBound); mLightList.clear(); - for (unsigned int i=0; i (8 - mLightManager->getStartLight()); + size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight(); osg::StateSet* stateset = nullptr; diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index c370f1b7f0..7ceb725aef 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -8,6 +8,9 @@ #include #include #include +#include + +#include namespace osgUtil { @@ -16,6 +19,27 @@ namespace osgUtil namespace SceneUtil { + class SunlightBuffer; + + // Used to override sun. Rarely useful but necassary for local map. + class SunlightStateAttribute : public osg::StateAttribute + { + public: + SunlightStateAttribute(); + SunlightStateAttribute(const SunlightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + int compare(const StateAttribute &sa) const override; + + META_StateAttribute(NifOsg, SunlightStateAttribute, osg::StateAttribute::LIGHT) + + void setFromLight(const osg::Light* light); + + void setStateSet(osg::StateSet* stateset, int mode=osg::StateAttribute::ON); + + private: + osg::ref_ptr mBuffer; + osg::ref_ptr mUbb; + }; /// LightSource managed by a LightManager. /// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole scene @@ -57,7 +81,7 @@ namespace SceneUtil /// Get the osg::Light safe for modification in the given frame. /// @par May be used externally to animate the light's color/attenuation properties, /// and is used internally to synchronize the light's position with the position of the LightSource. - osg::Light* getLight(unsigned int frame) + osg::Light* getLight(size_t frame) { return mLight[frame % 2]; } @@ -83,51 +107,62 @@ namespace SceneUtil class LightManager : public osg::Group { public: - - META_Node(SceneUtil, LightManager) - - LightManager(); - - LightManager(const LightManager& copy, const osg::CopyOp& copyop); - - /// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired. - /// By default, it's ~0u i.e. always on. - /// If you have some views that do not require lighting, then set the Camera's cull mask to not include - /// the lightingMask for a much faster cull and rendering. - void setLightingMask (unsigned int mask); - - unsigned int getLightingMask() const; - - /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. - void setStartLight(int start); - - int getStartLight() const; - - /// Internal use only, called automatically by the LightManager's UpdateCallback - void update(); - - /// Internal use only, called automatically by the LightSource's UpdateCallback - void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum); - struct LightSourceTransform { LightSource* mLightSource; osg::Matrixf mWorldMatrix; }; - const std::vector& getLights() const; - struct LightSourceViewBound { LightSource* mLightSource; osg::BoundingSphere mViewBound; }; - const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix); - typedef std::vector LightList; - osg::ref_ptr getLightListStateSet(const LightList& lightList, unsigned int frameNum); + static bool queryNonFFPLightingSupport(); + + META_Node(SceneUtil, LightManager) + + LightManager(bool ffp = true); + + LightManager(const LightManager& copy, const osg::CopyOp& copyop); + + /// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired. + /// By default, it's ~0u i.e. always on. + /// If you have some views that do not require lighting, then set the Camera's cull mask to not include + /// the lightingMask for a much faster cull and rendering. + void setLightingMask (size_t mask); + size_t getLightingMask() const; + + /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. + void setStartLight(int start); + int getStartLight() const; + + /// Internal use only, called automatically by the LightManager's UpdateCallback + void update(); + + /// Internal use only, called automatically by the LightSource's UpdateCallback + void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); + + const std::vector& getLights() const; + + const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum); + + osg::ref_ptr getLightListStateSet(const LightList& lightList, size_t frameNum); + + void setSunlight(osg::ref_ptr sun); + osg::ref_ptr getSunlight(); + + osg::ref_ptr getSunBuffer(); + + bool usingFFP() const; + + int getMaxLights() const; + int getMaxLightsInScene() const; + + Shader::ShaderManager::DefineMap getLightDefines() const; private: // Lights collected from the scene graph. Only valid during the cull traversal. @@ -144,7 +179,14 @@ namespace SceneUtil int mStartLight; - unsigned int mLightingMask; + size_t mLightingMask; + + osg::ref_ptr mSun; + osg::ref_ptr mSunBuffer; + + bool mFFP; + + static constexpr int mFFPMaxLights = 8; }; /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via @@ -180,7 +222,7 @@ namespace SceneUtil private: LightManager* mLightManager; - unsigned int mLastFrameNumber; + size_t mLastFrameNumber; LightManager::LightList mLightList; std::set mIgnoredLightSources; }; diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 4f887e659b..96671bf8b4 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -9,12 +9,20 @@ #include #include +#include +#include #include #include namespace Shader { + ShaderManager::ShaderManager(Resource::SceneManager* sceneManager) + : mSceneManager(sceneManager) + { + + } + void ShaderManager::setShaderPath(const std::string &path) { mPath = path; @@ -344,6 +352,11 @@ namespace Shader program->addShader(fragmentShader); program->addBindAttribLocation("aOffset", 6); program->addBindAttribLocation("aRotation", 7); + if (!mSceneManager->getFFPLighting()) + { + program->addBindUniformBlock("PointLightBuffer", 8); + program->addBindUniformBlock("SunlightBuffer", 9); + } found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 13db30b019..b9bb005b11 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -11,6 +11,11 @@ #include +namespace Resource +{ + class SceneManager; +} + namespace Shader { @@ -19,6 +24,9 @@ namespace Shader class ShaderManager { public: + + ShaderManager(Resource::SceneManager* sceneManager); + void setShaderPath(const std::string& path); typedef std::map DefineMap; @@ -59,6 +67,8 @@ namespace Shader typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; + Resource::SceneManager* mSceneManager; + std::mutex mMutex; }; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 67d944d195..368d74f6bd 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -442,6 +442,11 @@ apply lighting to environment maps = false # This makes fogging independent from the viewing angle. Shaders will be used to render all objects. radial fog = false +# Set maximum number of lights per object, does not include the sun. +# This feature may not work on your device, in which case it will fall back to the previous OpenGL limit of 8 lights. +# "[Shaders]/clamp lighting" must be set to 'false' and "[Shaders]/force shaders" must be set to 'true' for this to take effect. +max lights = 16 + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index a4e898e4b0..1a0c5f1a54 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -25,6 +25,7 @@ set(SHADER_FILES shadowcasting_vertex.glsl shadowcasting_fragment.glsl vertexcolors.glsl + sun.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 77fd32e58b..392419d92b 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -1,4 +1,5 @@ #version 120 +#extension GL_ARB_uniform_buffer_object : enable #define GROUNDCOVER diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 407599effd..3238bc864f 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -1,5 +1,7 @@ #version 120 +#extension GL_ARB_uniform_buffer_object : enable + #define GROUNDCOVER attribute vec4 aOffset; diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index a2dcc758a8..b9f426fc9d 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -1,13 +1,62 @@ -#define MAX_LIGHTS 8 +#if !@ffpLighting -void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) +#include "sun.glsl" + +#define getLight PointLights + +struct PointLight { - vec3 lightDir = gl_LightSource[lightIndex].position.xyz - viewPos * gl_LightSource[lightIndex].position.w; + vec4 diffuse; + vec4 ambient; + vec4 position; + vec4 attenuation; +}; + +uniform mat4 osg_ViewMatrix; +uniform int PointLightCount; +uniform int PointLightIndex[@maxLights]; + +layout(std140) uniform PointLightBuffer +{ + PointLight PointLights[@maxLights]; +}; + +#else +#define getLight gl_LightSource +#endif + +void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal) +{ + vec3 lightDir = @sunDirection.xyz; + lightDir = normalize(lightDir); + + ambientOut = @sunAmbient.xyz; + + float lambert = dot(viewNormal.xyz, lightDir); +#ifndef GROUNDCOVER + lambert = max(lambert, 0.0); +#else + float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); + if (lambert < 0.0) + { + lambert = -lambert; + eyeCosine = -eyeCosine; + } + lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); +#endif + diffuseOut = @sunDiffuse.xyz * lambert; +} + +void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) +{ + vec3 lightDir = getLight[lightIndex].position.xyz - viewPos; + //vec3 lightDir = (osg_ViewMatrix * vec4(getLight[lightIndex].position, 1.0)).xyz - viewPos; + float lightDistance = length(lightDir); lightDir = normalize(lightDir); - float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); - ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination; + float illumination = clamp(1.0 / (getLight[lightIndex].attenuation.x + getLight[lightIndex].attenuation.y * lightDistance + getLight[lightIndex].attenuation.z * lightDistance * lightDistance), 0.0, 1.0); + ambientOut = getLight[lightIndex].ambient.xyz * illumination; float lambert = dot(viewNormal.xyz, lightDir) * illumination; #ifndef GROUNDCOVER @@ -21,7 +70,7 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie } lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif - diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; + diffuseOut = getLight[lightIndex].diffuse.xyz * lambert; } #if PER_PIXEL_LIGHTING @@ -32,7 +81,8 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a { vec3 ambientOut, diffuseOut; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. - perLight(ambientOut, diffuseOut, 0, viewPos, viewNormal); + perLightSun(ambientOut, diffuseOut, viewPos, viewNormal); + #if PER_PIXEL_LIGHTING diffuseLight = diffuseOut * shadowing - diffuseOut; #else @@ -40,22 +90,31 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a diffuseLight = -diffuseOut; #endif ambientLight = gl_LightModel.ambient.xyz; - for (int i=0; i Date: Mon, 1 Mar 2021 23:30:54 -0800 Subject: [PATCH 02/24] Add shared UBO --- apps/opencs/model/world/data.cpp | 1 + apps/opencs/view/render/scenewidget.cpp | 1 + apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 8 +- apps/openmw/mwrender/water.cpp | 2 +- components/resource/scenemanager.cpp | 2 +- components/sceneutil/lightcontroller.cpp | 9 +- components/sceneutil/lightcontroller.hpp | 3 +- components/sceneutil/lightmanager.cpp | 233 +++++++++++++--------- components/sceneutil/lightmanager.hpp | 47 ++++- components/sceneutil/lightutil.cpp | 12 +- components/sceneutil/lightutil.hpp | 4 +- components/shader/shadermanager.cpp | 16 +- components/shader/shadermanager.hpp | 6 +- files/shaders/lighting.glsl | 34 +++- 15 files changed, 253 insertions(+), 127 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 70c496e3f2..25ac051f41 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -83,6 +83,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat defines["clamp"] = "1"; // Clamp lighting defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["radialFog"] = "0"; + defines["ffpLighting"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 61707967b9..4d73cde153 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -72,6 +72,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; + lightMgr->setStartLight(1); lightMgr->setLightingMask(Mask_Lighting); mRootNode = lightMgr; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index d8c7599358..7ccf653ea1 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1613,7 +1613,7 @@ namespace MWRender { bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); - SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior); + SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior, mResourceSystem->getSceneManager()->getFFPLighting()); } void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 733e1d287a..bbd7d06925 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -213,9 +213,11 @@ namespace MWRender resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); - resourceSystem->getSceneManager()->setFFPLighting(clampLighting || !forceShaders || !SceneUtil::LightManager::queryNonFFPLightingSupport()); - - osg::ref_ptr sceneRoot = new SceneUtil::LightManager(mResourceSystem->getSceneManager()->getFFPLighting()); + bool useFFP = clampLighting || !forceShaders || !SceneUtil::LightManager::queryNonFFPLightingSupport(); + resourceSystem->getSceneManager()->setFFPLighting(useFFP); + resourceSystem->getSceneManager()->getShaderManager().setFFPLighting(useFFP); + + osg::ref_ptr sceneRoot = new SceneUtil::LightManager(useFFP); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 4eac2fe4cd..51792d1baa 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -646,7 +646,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R program->addShader(vertexShader); program->addShader(fragmentShader); if (!mResourceSystem->getSceneManager()->getFFPLighting()) - program->addBindUniformBlock("SunlightBuffer", 9); + program->addBindUniformBlock("SunlightBuffer", 0); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); node->setStateSet(shaderStateset); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 6755db2557..ef53bea0d4 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -220,7 +220,7 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) - , mShaderManager(new Shader::ShaderManager(this)) + , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) , mClampLighting(true) , mAutoUseNormalMaps(false) diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index c759fabc79..cc95fe2e4f 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -11,13 +11,14 @@ namespace SceneUtil { - LightController::LightController() + LightController::LightController(bool useFFPLighting) : mType(LT_Normal) , mPhase(0.25f + Misc::Rng::rollClosedProbability() * 0.75f) , mBrightness(0.675f) , mStartTime(0.0) , mLastTime(0.0) , mTicksToAdvance(0.f) + , mFFPLighting(useFFPLighting) { } @@ -62,7 +63,11 @@ namespace SceneUtil mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } - static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness); + auto* lightSource = static_cast(node); + if (mFFPLighting) + lightSource->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness); + else + lightSource->setBrightness(nv->getTraversalNumber(), mBrightness); traverse(node, nv); } diff --git a/components/sceneutil/lightcontroller.hpp b/components/sceneutil/lightcontroller.hpp index 36b2e868e5..f416be7240 100644 --- a/components/sceneutil/lightcontroller.hpp +++ b/components/sceneutil/lightcontroller.hpp @@ -20,7 +20,7 @@ namespace SceneUtil LT_PulseSlow }; - LightController(); + LightController(bool useFFPLighting=true); void setType(LightType type); @@ -36,6 +36,7 @@ namespace SceneUtil double mStartTime; double mLastTime; float mTicksToAdvance; + bool mFFPLighting; }; } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index fa9dc4fda8..987a95e20e 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1,6 +1,7 @@ #include "lightmanager.hpp" #include +#include #include @@ -12,6 +13,8 @@ #include +#include "apps/openmw/mwrender/vismask.hpp" + namespace { /* similar to the boost::hash_combine */ @@ -32,6 +35,10 @@ namespace SceneUtil { static int sLightId = 0; +//////////////////////////////////////////////////////////////////////////////// +// Internal Data Structures +//////////////////////////////////////////////////////////////////////////////// + class LightBuffer : public osg::Referenced { public: @@ -59,11 +66,6 @@ namespace SceneUtil mDirty = false; } - int blockSize() const - { - return mData->getNumElements() * sizeof(GL_FLOAT) * osg::Vec4::num_components; - } - protected: size_t mNumElements; osg::ref_ptr mData; @@ -114,7 +116,7 @@ namespace SceneUtil { public: - enum Type {Diffuse, Ambient, Position, Attenuation}; + enum Type {Position, Diffuse, Ambient, Attenuation}; PointLightBuffer(int count) : LightBuffer(4, count) @@ -124,9 +126,10 @@ namespace SceneUtil { if (getValue(index, type) == value) return; - - (*mData)[mNumElements * index + type] = value; - + + int indexOffset = mNumElements * index + type; + (*mData)[indexOffset] = value; + mDirty = true; } @@ -155,12 +158,16 @@ namespace SceneUtil return &cacheVector[contextid]; } +//////////////////////////////////////////////////////////////////////////////// +// State Attributes +//////////////////////////////////////////////////////////////////////////////// + SunlightStateAttribute::SunlightStateAttribute() : mBuffer(new SunlightBuffer) { osg::ref_ptr ubo = new osg::UniformBufferObject; mBuffer->getData()->setBufferObject(ubo); - mUbb = new osg::UniformBufferBinding(9 , mBuffer->getData().get(), 0, mBuffer->blockSize()); + mUbb = new osg::UniformBufferBinding(0, mBuffer->getData().get(), 0, mBuffer->getData()->getTotalDataSize()); } SunlightStateAttribute::SunlightStateAttribute(const SunlightStateAttribute ©, const osg::CopyOp ©op) @@ -235,56 +242,6 @@ namespace SceneUtil osg::Vec4f mnullptr; }; - class LightStateAttribute : public osg::StateAttribute - { - public: - LightStateAttribute() : mBuffer(nullptr) {} - LightStateAttribute(const std::vector >& lights, const osg::ref_ptr& buffer) : mLights(lights), mBuffer(buffer) {} - - LightStateAttribute(const LightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop), mLights(copy.mLights), mBuffer(copy.mBuffer) {} - - int compare(const StateAttribute &sa) const override - { - throw std::runtime_error("LightStateAttribute::compare: unimplemented"); - } - - META_StateAttribute(NifOsg, LightStateAttribute, osg::StateAttribute::LIGHT) - - void apply(osg::State& state) const override - { - if (mLights.empty()) - return; - osg::Matrix modelViewMatrix = state.getModelViewMatrix(); - - state.applyModelViewMatrix(state.getInitialViewMatrix()); - - if (mBuffer) - { - for (size_t i = 0; i < mLights.size(); ++i) - { - - mBuffer->setValue(i, PointLightBuffer::Diffuse, mLights[i]->getDiffuse()); - mBuffer->setValue(i, PointLightBuffer::Ambient, mLights[i]->getAmbient()); - mBuffer->setValue(i, PointLightBuffer::Position, mLights[i]->getPosition() * state.getModelViewMatrix()); - mBuffer->setValue(i, PointLightBuffer::Attenuation, - osg::Vec4(mLights[i]->getConstantAttenuation(), - mLights[i]->getLinearAttenuation(), - mLights[i]->getQuadraticAttenuation(), 0.0)); - } - - if (mBuffer && mBuffer->isDirty()) - mBuffer->dirty(); - } - - state.applyModelViewMatrix(modelViewMatrix); - } - - private: - std::vector> mLights; - osg::ref_ptr mBuffer; - }; - class FFPLightStateAttribute : public osg::StateAttribute { public: @@ -368,6 +325,10 @@ namespace SceneUtil return nullptr; } +//////////////////////////////////////////////////////////////////////////////// +// Node Callbacks +//////////////////////////////////////////////////////////////////////////////// + // Set on a LightSource. Adds the light source to its light manager for the current frame. // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager. class CollectLightCallback : public osg::NodeCallback @@ -460,6 +421,42 @@ namespace SceneUtil size_t mLastFrameNumber; }; + class LightManagerStateAttribute : public osg::StateAttribute + { + public: + LightManagerStateAttribute() : mLightManager(nullptr) {} + LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} + + LightManagerStateAttribute(const LightManagerStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop),mLightManager(copy.mLightManager) {} + + int compare(const StateAttribute &sa) const override + { + throw std::runtime_error("LightManagerStateAttribute::compare: unimplemented"); + } + + META_StateAttribute(NifOsg, LightManagerStateAttribute, osg::StateAttribute::LIGHT) + + void apply(osg::State& state) const override + { + osg::Matrix modelViewMatrix = state.getModelViewMatrix(); + + state.applyModelViewMatrix(state.getInitialViewMatrix()); + + for (size_t i = 0; i < mLightManager->mPointLightProxyData.size(); i++) + { + auto& data = mLightManager->mPointLightProxyData[i]; + auto pos = data.mPosition * state.getInitialViewMatrix(); + pos[3] = data.mBrightness; + mLightManager->mPointBuffer->setValue(i, PointLightBuffer::Position, pos); + } + state.applyModelViewMatrix(modelViewMatrix); + mLightManager->mPointBuffer->dirty(); + } + + LightManager* mLightManager; + }; + LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) @@ -467,28 +464,44 @@ namespace SceneUtil , mSunBuffer(nullptr) , mFFP(ffp) { - setUpdateCallback(new LightManagerUpdateCallback); - if (usingFFP()) { for (int i=0; i >())); + setUpdateCallback(new LightManagerUpdateCallback); return; } auto* stateset = getOrCreateStateSet(); - mSunBuffer = new SunlightBuffer(); - osg::ref_ptr ubo = new osg::UniformBufferObject; - mSunBuffer->getData()->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(9 , mSunBuffer->getData().get(), 0, mSunBuffer->blockSize()); - stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + { // sunlight UBO data + mSunBuffer = new SunlightBuffer(); + osg::ref_ptr ubo = new osg::UniformBufferObject; + mSunBuffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(0, mSunBuffer->getData().get(), 0, mSunBuffer->getData()->getTotalDataSize()); + stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + } + { // point lights UBO data + mPointBuffer = new PointLightBuffer(getMaxLightsInScene()); + osg::ref_ptr ubo = new osg::UniformBufferObject; + mPointBuffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(1, mPointBuffer->getData().get(), 0, mPointBuffer->getData()->getTotalDataSize()); + stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + } + + mPointLightProxyData.resize(getMaxLightsInScene()); + + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + stateset->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("PointLightCount", 0)); + setUpdateCallback(new LightManagerUpdateCallback); addCullCallback(new SunlightCallback(this)); } +//////////////////////////////////////////////////////////////////////////////// + LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) , mStartLight(copy.mStartLight) @@ -507,7 +520,7 @@ namespace SceneUtil int LightManager::getMaxLights() const { if (usingFFP()) return LightManager::mFFPMaxLights; - return std::clamp(Settings::Manager::getInt("max lights", "Shaders"), 0, getMaxLightsInScene()); + return std::clamp(Settings::Manager::getInt("max lights", "Shaders"),LightManager::mFFPMaxLights , getMaxLightsInScene()); } int LightManager::getMaxLightsInScene() const @@ -579,16 +592,18 @@ namespace SceneUtil } void LightManager::update() - { + { mLights.clear(); mLightsInViewSpace.clear(); - // do an occasional cleanup for orphaned lights + // Do an occasional cleanup for orphaned lights. for (int i=0; i<2; ++i) { - if (mStateSetCache[i].size() > 5000) + if (mStateSetCache[i].size() > 5000 || mIndexNeedsRecompiling) mStateSetCache[i].clear(); } + + mIndexNeedsRecompiling = false; } void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum) @@ -599,7 +614,8 @@ namespace SceneUtil lightSource->getLight(frameNum)->setPosition(osg::Vec4f(worldMat.getTrans().x(), worldMat.getTrans().y(), worldMat.getTrans().z(), 1.f)); - mLights.push_back(l); + if (mLights.size() < static_cast(getMaxLightsInScene())) + mLights.emplace_back(l); } void LightManager::setSunlight(osg::ref_ptr sun) @@ -627,20 +643,21 @@ namespace SceneUtil hash_combine(hash, lightList[i]->mLightSource->getId()); LightStateSetMap& stateSetCache = mStateSetCache[frameNum%2]; - + LightStateSetMap::iterator found = stateSetCache.find(hash); if (found != stateSetCache.end()) return found->second; else { osg::ref_ptr stateset = new osg::StateSet; - std::vector > lights; - lights.reserve(lightList.size()); - for (size_t i=0; imLightSource->getLight(frameNum)); if (usingFFP()) { + std::vector > lights; + lights.reserve(lightList.size()); + for (size_t i=0; imLightSource->getLight(frameNum)); + // the first light state attribute handles the actual state setting for all lights // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary // don't use setAttributeAndModes, that does not support light indices! @@ -656,14 +673,18 @@ namespace SceneUtil } else { - osg::ref_ptr buffer = new PointLightBuffer(lights.size()); - osg::ref_ptr ubo = new osg::UniformBufferObject; - buffer->getData()->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(8 ,buffer->getData().get(), 0, buffer->blockSize()); - stateset->addUniform(new osg::Uniform("PointLightCount", (int)lights.size())); - stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); - - stateset->setAttribute(new LightStateAttribute(std::move(lights), buffer), osg::StateAttribute::ON); + osg::ref_ptr indices = new osg::IntArray(getMaxLights()); + osg::ref_ptr indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", indices->size()); + int validCount = 0; + for (size_t i = 0; i < lightList.size(); ++i) + { + auto it = mLightData.find(lightList[i]->mLightSource->getId()); + if (it != mLightData.end()) + indices->at(validCount++) = it->second; + } + indicesUni->setArray(indices); + stateset->addUniform(indicesUni); + stateset->addUniform(new osg::Uniform("PointLightCount", validCount)); } stateSetCache.emplace(hash, stateset); @@ -671,11 +692,6 @@ namespace SceneUtil } } - const std::vector& LightManager::getLights() const - { - return mLights; - } - const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { osg::observer_ptr camPtr (camera); @@ -695,13 +711,47 @@ namespace SceneUtil l.mLightSource = lightIt->mLightSource; l.mViewBound = viewBound; it->second.push_back(l); + + if (usingFFP()) continue; + + auto* light = l.mLightSource->getLight(frameNum); + + auto dataIt = mLightData.find(l.mLightSource->getId()); + if (dataIt != mLightData.end()) + { + mPointLightProxyData[dataIt->second].mPosition = light->getPosition(); + mPointLightProxyData[dataIt->second].mBrightness = l.mLightSource->getBrightness(frameNum); + mPointBuffer->setValue(dataIt->second, PointLightBuffer::Diffuse, light->getDiffuse()); + continue; + } + + if (mLightData.size() >= static_cast(getMaxLightsInScene())) + { + mIndexNeedsRecompiling = true; + mLightData.clear(); + } + + int index = mLightData.size(); + updateGPUPointLight(index, l.mLightSource, frameNum); + mLightData.emplace(l.mLightSource->getId(), index); } } return it->second; } + void LightManager::updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum) + { + auto* light = lightSource->getLight(frameNum); + mPointLightProxyData[index].mPosition = light->getPosition(); + mPointLightProxyData[index].mBrightness = lightSource->getBrightness(frameNum); + mPointBuffer->setValue(index, PointLightBuffer::Diffuse, light->getDiffuse()); + mPointBuffer->setValue(index, PointLightBuffer::Ambient, light->getAmbient()); + mPointBuffer->setValue(index, PointLightBuffer::Attenuation, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); + } + LightSource::LightSource() : mRadius(0.f) + , mBrightness{1.0,1.0} { setUpdateCallback(new CollectLightCallback); mId = sLightId++; @@ -753,7 +803,7 @@ namespace SceneUtil // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); - + // get the node bounds in view space // NB do not node->getBound() * modelView, that would apply the node's transformation twice osg::BoundingSphere nodeBound; @@ -818,7 +868,6 @@ namespace SceneUtil else stateset = mLightManager->getLightListStateSet(mLightList, cv->getTraversalNumber()); - cv->pushStateSet(stateset); return true; } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 7ceb725aef..9c478b294b 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -2,13 +2,15 @@ #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H #include +#include +#include +#include #include #include #include #include -#include #include @@ -17,9 +19,16 @@ namespace osgUtil class CullVisitor; } +namespace osg +{ + class UniformBufferBinding; + class UniformBufferObject; +} + namespace SceneUtil { class SunlightBuffer; + class PointLightBuffer; // Used to override sun. Rarely useful but necassary for local map. class SunlightStateAttribute : public osg::StateAttribute @@ -59,6 +68,8 @@ namespace SceneUtil int mId; + float mBrightness[2]; + public: META_Node(SceneUtil, LightSource) @@ -78,6 +89,16 @@ namespace SceneUtil mRadius = radius; } + float getBrightness(size_t frame) + { + return mBrightness[frame % 2]; + } + + void setBrightness(size_t frame, float brightness) + { + mBrightness[frame % 2] = brightness; + } + /// Get the osg::Light safe for modification in the given frame. /// @par May be used externally to animate the light's color/attenuation properties, /// and is used internally to synchronize the light's position with the position of the LightSource. @@ -119,7 +140,7 @@ namespace SceneUtil osg::BoundingSphere mViewBound; }; - typedef std::vector LightList; + using LightList = std::vector; static bool queryNonFFPLightingSupport(); @@ -146,8 +167,6 @@ namespace SceneUtil /// Internal use only, called automatically by the LightSource's UpdateCallback void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); - const std::vector& getLights() const; - const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum); osg::ref_ptr getLightListStateSet(const LightList& lightList, size_t frameNum); @@ -165,6 +184,11 @@ namespace SceneUtil Shader::ShaderManager::DefineMap getLightDefines() const; private: + + friend class LightManagerStateAttribute; + + void updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum); + // Lights collected from the scene graph. Only valid during the cull traversal. std::vector mLights; @@ -184,6 +208,21 @@ namespace SceneUtil osg::ref_ptr mSun; osg::ref_ptr mSunBuffer; + struct PointLightProxyData + { + osg::Vec4 mPosition; + float mBrightness; + }; + + std::vector mPointLightProxyData; + osg::ref_ptr mPointBuffer; + + // < Light ID , Buffer Index > + using LightDataMap = std::unordered_map; + LightDataMap mLightData; + + bool mIndexNeedsRecompiling; + bool mFFP; static constexpr int mFFPMaxLights = 8; diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index e9be05908e..6c17117d01 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -58,7 +58,7 @@ namespace SceneUtil light->setQuadraticAttenuation(quadraticAttenuation); } - void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior) + void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior, bool useFFPLighting) { SceneUtil::FindByNameVisitor visitor("AttachLight"); node->accept(visitor); @@ -85,17 +85,21 @@ namespace SceneUtil attachTo = trans; } - osg::ref_ptr lightSource = createLightSource(esmLight, lightMask, isExterior); + osg::ref_ptr lightSource = createLightSource(esmLight, lightMask, isExterior, osg::Vec4f(0,0,0,1), useFFPLighting); attachTo->addChild(lightSource); } - osg::ref_ptr createLightSource(const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient) + osg::ref_ptr createLightSource(const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient, bool useFFPLighting) { osg::ref_ptr lightSource (new SceneUtil::LightSource); osg::ref_ptr light (new osg::Light); lightSource->setNodeMask(lightMask); float radius = esmLight->mData.mRadius; + // arbitrary multipler to reduce light popping, this is hard to avoid with per-object lighting + // we offset this multipler in shaders + if (!useFFPLighting) + radius *= 2.0; lightSource->setRadius(radius); configureLight(light, radius, isExterior); @@ -112,7 +116,7 @@ namespace SceneUtil lightSource->setLight(light); - osg::ref_ptr ctrl (new SceneUtil::LightController); + osg::ref_ptr ctrl (new SceneUtil::LightController(useFFPLighting)); ctrl->setDiffuse(light->getDiffuse()); if (esmLight->mData.mFlags & ESM::Light::Flicker) ctrl->setType(SceneUtil::LightController::LT_Flicker); diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp index 7096c38b20..bb28276e9b 100644 --- a/components/sceneutil/lightutil.hpp +++ b/components/sceneutil/lightutil.hpp @@ -32,14 +32,14 @@ namespace SceneUtil /// @param partsysMask Node mask to ignore when computing the sub graph's bounding box. /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. - void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior); + void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior, bool useFFPLighting=true); /// @brief Convert an ESM::Light to a SceneUtil::LightSource, and return it. /// @param esmLight The light definition coming from the game files containing radius, color, flicker, etc. /// @param lightMask Mask to assign to the newly created LightSource. /// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use. /// @param ambient Ambient component of the light. - osg::ref_ptr createLightSource (const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient=osg::Vec4f(0,0,0,1)); + osg::ref_ptr createLightSource (const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient=osg::Vec4f(0,0,0,1), bool useFFPLighting=true); } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 96671bf8b4..b1dc145f78 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -17,10 +17,9 @@ namespace Shader { - ShaderManager::ShaderManager(Resource::SceneManager* sceneManager) - : mSceneManager(sceneManager) + ShaderManager::ShaderManager() + : mFFPLighting(false) { - } void ShaderManager::setShaderPath(const std::string &path) @@ -28,6 +27,11 @@ namespace Shader mPath = path; } + void ShaderManager::setFFPLighting(bool useFFP) + { + mFFPLighting = useFFP; + } + bool addLineDirectivesAfterConditionalBlocks(std::string& source) { for (size_t position = 0; position < source.length(); ) @@ -352,10 +356,10 @@ namespace Shader program->addShader(fragmentShader); program->addBindAttribLocation("aOffset", 6); program->addBindAttribLocation("aRotation", 7); - if (!mSceneManager->getFFPLighting()) + if (!mFFPLighting) { - program->addBindUniformBlock("PointLightBuffer", 8); - program->addBindUniformBlock("SunlightBuffer", 9); + program->addBindUniformBlock("SunlightBuffer", 0); + program->addBindUniformBlock("PointLightBuffer", 1); } found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index b9bb005b11..c70085485d 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -25,10 +25,12 @@ namespace Shader { public: - ShaderManager(Resource::SceneManager* sceneManager); + ShaderManager(); void setShaderPath(const std::string& path); + void setFFPLighting(bool useFFP); + typedef std::map DefineMap; /// Create or retrieve a shader instance. @@ -67,7 +69,7 @@ namespace Shader typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; - Resource::SceneManager* mSceneManager; + bool mFFPLighting; std::mutex mMutex; }; diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index b9f426fc9d..b243a90c4e 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -6,19 +6,21 @@ struct PointLight { + vec4 position; vec4 diffuse; vec4 ambient; - vec4 position; - vec4 attenuation; + float constantAttenuation; + float linearAttenuation; + float quadraticAttenuation; + float radius; }; -uniform mat4 osg_ViewMatrix; uniform int PointLightCount; uniform int PointLightIndex[@maxLights]; layout(std140) uniform PointLightBuffer { - PointLight PointLights[@maxLights]; + PointLight PointLights[@maxLightsInScene]; }; #else @@ -47,15 +49,26 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi diffuseOut = @sunDiffuse.xyz * lambert; } + +uniform float osg_SimulationTime; void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) { - vec3 lightDir = getLight[lightIndex].position.xyz - viewPos; - //vec3 lightDir = (osg_ViewMatrix * vec4(getLight[lightIndex].position, 1.0)).xyz - viewPos; + vec4 pos = getLight[lightIndex].position; + vec3 lightDir = pos.xyz - viewPos; float lightDistance = length(lightDir); lightDir = normalize(lightDir); - float illumination = clamp(1.0 / (getLight[lightIndex].attenuation.x + getLight[lightIndex].attenuation.y * lightDistance + getLight[lightIndex].attenuation.z * lightDistance * lightDistance), 0.0, 1.0); + float illumination = clamp(1.0 / (getLight[lightIndex].constantAttenuation + getLight[lightIndex].linearAttenuation * lightDistance + getLight[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); + +// Add an artificial cutoff, otherwise effected objects will be brightly lit and adjacent objects not effected by this light will be dark by contrast +// This causes nasty artifacts, especially with active grid so it is necassary for now. +#if !@ffpLighting + float cutoff = getLight[lightIndex].radius * 0.5; + illumination *= 1.0 - smoothstep(0.0, 1.0, ((lightDistance / cutoff) - 1.0) * 0.887); + illumination = max(0.0, illumination); +#endif + ambientOut = getLight[lightIndex].ambient.xyz * illumination; float lambert = dot(viewNormal.xyz, lightDir) * illumination; @@ -70,7 +83,12 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec } lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif + +#if @ffpLighting diffuseOut = getLight[lightIndex].diffuse.xyz * lambert; +#else + diffuseOut = (getLight[lightIndex].diffuse.xyz * pos.w) * lambert; +#endif } #if PER_PIXEL_LIGHTING @@ -97,7 +115,7 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a diffuseLight += diffuseOut; for (int i=0; i Date: Sat, 27 Mar 2021 12:47:41 -0700 Subject: [PATCH 03/24] Brighter point lights and light fade --- components/sceneutil/lightmanager.cpp | 35 ++++++++++++++++++++++++--- components/sceneutil/lightmanager.hpp | 2 ++ files/settings-default.cfg | 8 +++++- files/shaders/lighting.glsl | 29 +++++++++++----------- 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 7215459202..d4275e5d28 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -56,7 +56,15 @@ namespace SceneUtil void setDiffuse(int index, const osg::Vec4& value) { - *(unsigned int*)(&(*mData)[3*index][0]) = asRGBA(value); + auto signedValue = value; + float signBit = 1.0; + if (value[0] < 0) + { + signedValue *= -1.0; + signBit = -1.0; + } + *(unsigned int*)(&(*mData)[3*index][0]) = asRGBA(signedValue); + *(int*)(&(*mData)[3*index][3]) = signBit; } void setAmbient(int index, const osg::Vec4& value) @@ -587,8 +595,16 @@ namespace SceneUtil : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) - , mPointLightRadiusMultiplier(std::max(0.0f, Settings::Manager::getFloat("light bounds multiplier", "Shaders"))) + , mPointLightRadiusMultiplier(std::max(0.f, Settings::Manager::getFloat("light bounds multiplier", "Shaders"))) + , mPointLightFadeStart(0.f) { + mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); + if (mPointLightFadeEnd > 0) + { + mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); + mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; + } + auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); bool validLightingModel = isValidLightingModelString(lightingModelString); if (!validLightingModel) @@ -869,6 +885,19 @@ namespace SceneUtil osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius * mPointLightRadiusMultiplier); transformBoundingSphere(worldViewMat, viewBound); + static const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; + + if (mPointLightFadeEnd != 0.f) + { + float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); + + if (fade == 0.f) + continue; + + auto* light = transform.mLightSource->getLight(frameNum); + light->setDiffuse(light->getDiffuse() * fade); + } + LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; @@ -896,7 +925,7 @@ namespace SceneUtil auto* light = lightSource->getLight(frameNum); auto& buf = getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); - buf->setAmbient(index, light->getSpecular()); + buf->setAmbient(index, light->getAmbient()); buf->setAttenuation(index, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation()); buf->setRadius(index, lightSource->getRadius()); buf->setPosition(index, light->getPosition() * (*viewMatrix)); diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index b430153b27..214ffa1385 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -215,6 +215,8 @@ namespace SceneUtil LightingMethod mLightingMethod; float mPointLightRadiusMultiplier; + float mPointLightFadeEnd; + float mPointLightFadeStart; int mMaxLights; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index cb23f7df73..b74d3ce02d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -448,7 +448,13 @@ lighting method = experimental # Sets the bounding sphere multiplier of light sources, which are used to determine if an object should # receive lighting. Higher values will allow for smoother transitions of light sources, but may have a performance cost and # requires a higher number of 'max lights' set. It is recommended to keep this at 1.0 with 'legacy' lighting enabled. -light bounds multiplier = 1.9 +light bounds multiplier = 2.0 + +# The distance from the camera at which lights fade away completely. Set to 0 to disable fading. +maximum light distance = 8192 + +# Fraction of the maximum distance at which lights begin to gradually fade away. +light fade start = 0.85 # Set maximum number of lights per object. # Only used when 'lighting method' is not set to 'legacy' diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 36be081efc..257c0513af 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -16,22 +16,21 @@ float quickstep(float x) #if @useUBO const uint mask = uint(0xff); +const uvec4 shift = uvec4(uint(0), uint(8), uint(16), uint(24)); -vec3 unpackRGB(float data) +vec3 unpackRGB(uint data) { - uint colors = uint(data); - return vec3( (((colors >> 0) & mask) / 255.0) - ,(((colors >> 8) & mask) / 255.0) - ,(((colors >> 16) & mask) / 255.0)); + return vec3( (float(((data >> shift.x) & mask)) / 255.0) + ,(float(((data >> shift.y) & mask)) / 255.0) + ,(float(((data >> shift.z) & mask)) / 255.0)); } -vec4 unpackRGBA(float data) +vec4 unpackRGBA(uint data) { - uint colors = uint(data); - return vec4( (((colors >> 0) & mask) / 255.0) - ,(((colors >> 8) & mask) / 255.0) - ,(((colors >> 16) & mask) / 255.0) - ,(((colors >> 24) & mask) / 255.0)); + return vec4( (float(((data >> shift.x) & mask)) / 255.0) + ,(float(((data >> shift.y) & mask)) / 255.0) + ,(float(((data >> shift.z) & mask)) / 255.0) + ,(float(((data >> shift.w) & mask)) / 255.0)); } struct LightData @@ -74,7 +73,7 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi vec3 lightDir = normalize(getLight[0].position.xyz); #if @lightingModel == LIGHTING_MODEL_SINGLE_UBO - vec4 data = getLight[0].packedColors; + uvec4 data = getLight[0].packedColors; ambientOut = unpackRGB(data.y); vec3 sunDiffuse = unpackRGB(data.x); #else @@ -108,11 +107,11 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec float illumination = clamp(1.0 / (getLight[lightIndex].constantAttenuation + getLight[lightIndex].linearAttenuation * lightDistance + getLight[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); #else float illumination = clamp(1.0 / (getLight[lightIndex].attenuation.x + getLight[lightIndex].attenuation.y * lightDistance + getLight[lightIndex].attenuation.z * lightDistance * lightDistance), 0.0, 1.0); - illumination *= 1.0 - quickstep((lightDistance * 0.887 / getLight[lightIndex].attenuation.w) - 0.887); + illumination *= 1.0 - quickstep((lightDistance / (getLight[lightIndex].attenuation.w)) - 1.0); #endif #if @useUBO - vec4 data = getLight[lightIndex].packedColors; + uvec4 data = getLight[lightIndex].packedColors; ambientOut = unpackRGB(data.y) * illumination; #else ambientOut = getLight[lightIndex].ambient.xyz * illumination; @@ -133,7 +132,7 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec #endif #if @useUBO - diffuseOut = unpackRGB(data.x) * lambert; + diffuseOut = unpackRGB(data.x) * lambert * float(int(data.w)); #else diffuseOut = getLight[lightIndex].diffuse.xyz * lambert; #endif From ec27e60284511a11ce9f775f2b4d27f5548cfd7e Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 27 Mar 2021 23:41:22 -0700 Subject: [PATCH 04/24] Cutoff conditional in light shader --- files/settings-default.cfg | 2 +- files/shaders/lighting.glsl | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index b74d3ce02d..102c1aa66c 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -448,7 +448,7 @@ lighting method = experimental # Sets the bounding sphere multiplier of light sources, which are used to determine if an object should # receive lighting. Higher values will allow for smoother transitions of light sources, but may have a performance cost and # requires a higher number of 'max lights' set. It is recommended to keep this at 1.0 with 'legacy' lighting enabled. -light bounds multiplier = 2.0 +light bounds multiplier = 1.0 # The distance from the camera at which lights fade away completely. Set to 0 to disable fading. maximum light distance = 8192 diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 257c0513af..4b7e233146 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -101,6 +101,17 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec vec3 lightDir = getLight[lightIndex].position.xyz - viewPos; float lightDistance = length(lightDir); + +#if !@ffpLighting + // This has a *considerable* performance uplift where GPU is a bottleneck + if (lightDistance > getLight[lightIndex].attenuation.w * 2.0) + { + ambientOut = vec3(0.0); + diffuseOut = vec3(0.0); + return; + } +#endif + lightDir = normalize(lightDir); #if @ffpLighting From 24454a1698d8331f8b822b6f0f004f432f7964bb Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 27 Mar 2021 23:52:05 -0700 Subject: [PATCH 05/24] Switch to integer, uint not reliable in GLSL 120 --- files/shaders/lighting.glsl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 4b7e233146..c6c03f2232 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -15,17 +15,17 @@ float quickstep(float x) #if @useUBO -const uint mask = uint(0xff); -const uvec4 shift = uvec4(uint(0), uint(8), uint(16), uint(24)); +const int mask = int(0xff); +const ivec4 shift = ivec4(int(0), int(8), int(16), int(24)); -vec3 unpackRGB(uint data) +vec3 unpackRGB(int data) { return vec3( (float(((data >> shift.x) & mask)) / 255.0) ,(float(((data >> shift.y) & mask)) / 255.0) ,(float(((data >> shift.z) & mask)) / 255.0)); } -vec4 unpackRGBA(uint data) +vec4 unpackRGBA(int data) { return vec4( (float(((data >> shift.x) & mask)) / 255.0) ,(float(((data >> shift.y) & mask)) / 255.0) @@ -35,7 +35,7 @@ vec4 unpackRGBA(uint data) struct LightData { - uvec4 packedColors; // diffuse, ambient, specular + ivec4 packedColors; // diffuse, ambient, specular vec4 position; vec4 attenuation; // constant, linear, quadratic, radius }; @@ -73,7 +73,7 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi vec3 lightDir = normalize(getLight[0].position.xyz); #if @lightingModel == LIGHTING_MODEL_SINGLE_UBO - uvec4 data = getLight[0].packedColors; + ivec4 data = getLight[0].packedColors; ambientOut = unpackRGB(data.y); vec3 sunDiffuse = unpackRGB(data.x); #else @@ -122,7 +122,7 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec #endif #if @useUBO - uvec4 data = getLight[lightIndex].packedColors; + ivec4 data = getLight[lightIndex].packedColors; ambientOut = unpackRGB(data.y) * illumination; #else ambientOut = getLight[lightIndex].ambient.xyz * illumination; From 328ec85757b4dae0ca7b9466148781989c2bdfd6 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 28 Mar 2021 11:00:52 -0700 Subject: [PATCH 06/24] Code review cleanup, add setting documentation --- apps/openmw/engine.cpp | 1 - components/sceneutil/lightmanager.cpp | 102 +++++++++--------- .../reference/modding/settings/shaders.rst | 63 +++++++++++ files/shaders/objects_fragment.glsl | 2 +- files/shaders/terrain_fragment.glsl | 2 +- 5 files changed, 117 insertions(+), 53 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 019e9f67a9..d7c315323d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -31,7 +31,6 @@ #include #include -#include #include diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index d4275e5d28..3c1f31364f 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -16,7 +16,7 @@ #include "apps/openmw/mwrender/vismask.hpp" -namespace +namespace { /* similar to the boost::hash_combine */ template @@ -136,33 +136,35 @@ namespace SceneUtil switch (method) { case LightingMethod::FFP: - break; + { + break; + } case LightingMethod::PerObjectUniform: - { - stateset->addUniform(new osg::Uniform("LightBuffer[0].diffuse", light->getDiffuse()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].ambient", light->getAmbient()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].specular", light->getSpecular()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].position", light->getPosition()), mode); + { + stateset->addUniform(new osg::Uniform("LightBuffer[0].diffuse", light->getDiffuse()), mode); + stateset->addUniform(new osg::Uniform("LightBuffer[0].ambient", light->getAmbient()), mode); + stateset->addUniform(new osg::Uniform("LightBuffer[0].specular", light->getSpecular()), mode); + stateset->addUniform(new osg::Uniform("LightBuffer[0].position", light->getPosition()), mode); - break; - } + break; + } case LightingMethod::SingleUBO: - { - osg::ref_ptr buffer = new LightBuffer(1); + { + osg::ref_ptr buffer = new LightBuffer(1); - buffer->setDiffuse(0, light->getDiffuse()); - buffer->setAmbient(0, light->getAmbient()); - buffer->setSpecular(0, light->getSpecular()); - buffer->setPosition(0, light->getPosition()); + buffer->setDiffuse(0, light->getDiffuse()); + buffer->setAmbient(0, light->getAmbient()); + buffer->setSpecular(0, light->getSpecular()); + buffer->setPosition(0, light->getPosition()); - osg::ref_ptr ubo = new osg::UniformBufferObject; - buffer->mData->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->mData.get(), 0, buffer->mData->getTotalDataSize()); + osg::ref_ptr ubo = new osg::UniformBufferObject; + buffer->mData->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->mData.get(), 0, buffer->mData->getTotalDataSize()); - stateset->setAttributeAndModes(ubb, mode); + stateset->setAttributeAndModes(ubb, mode); - break; - } + break; + } } } @@ -201,9 +203,9 @@ namespace SceneUtil void apply(osg::State& state) const override { int lightNum = GL_LIGHT0 + mIndex; - glLightfv( lightNum, GL_AMBIENT, mnullptr.ptr() ); - glLightfv( lightNum, GL_DIFFUSE, mnullptr.ptr() ); - glLightfv( lightNum, GL_SPECULAR, mnullptr.ptr() ); + glLightfv(lightNum, GL_AMBIENT, mnullptr.ptr()); + glLightfv(lightNum, GL_DIFFUSE, mnullptr.ptr()); + glLightfv(lightNum, GL_SPECULAR, mnullptr.ptr()); LightStateCache* cache = getLightStateCache(state.getContextID()); cache->lastAppliedLight[mIndex] = nullptr; @@ -334,7 +336,7 @@ namespace SceneUtil cache->lastAppliedLight[i] = mLights[i]; } } - + state.applyModelViewMatrix(modelViewMatrix); } @@ -344,7 +346,7 @@ namespace SceneUtil }; struct StateSetGenerator - { + { LightManager* mLightManager; virtual ~StateSetGenerator() {} @@ -475,9 +477,9 @@ namespace SceneUtil mLightManager = findLightManager(nv->getNodePath()); if (!mLightManager) - throw std::runtime_error("can't find parent LightManager"); + throw std::runtime_error("can't find parent LightManager"); } - + mLightManager->addLight(static_cast(node), osg::computeLocalToWorld(nv->getNodePath()), nv->getTraversalNumber()); traverse(node, nv); @@ -523,7 +525,7 @@ namespace SceneUtil mLastFrameNumber = cv->getTraversalNumber(); if (mLightManager->getLightingMethod() == LightingMethod::SingleUBO) - { + { auto stateset = mLightManager->getStateSet(); auto bo = mLightManager->getLightBuffer(mLastFrameNumber); osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData().get(), 0, bo->getData()->getTotalDataSize()); @@ -548,7 +550,7 @@ namespace SceneUtil buf->setDiffuse(0, sun->getDiffuse()); buf->setAmbient(0, sun->getAmbient()); buf->setSpecular(0, sun->getSpecular()); - buf->setPosition(0, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + buf->setPosition(0, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); } } } @@ -560,12 +562,12 @@ namespace SceneUtil LightManager* mLightManager; size_t mLastFrameNumber; }; - + class LightManagerStateAttribute : public osg::StateAttribute { public: LightManagerStateAttribute() : mLightManager(nullptr) {} - LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} + LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} LightManagerStateAttribute(const LightManagerStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop),mLightManager(copy.mLightManager) {} @@ -595,7 +597,7 @@ namespace SceneUtil : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) - , mPointLightRadiusMultiplier(std::max(0.f, Settings::Manager::getFloat("light bounds multiplier", "Shaders"))) + , mPointLightRadiusMultiplier(std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f)) , mPointLightFadeStart(0.f) { mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); @@ -608,7 +610,7 @@ namespace SceneUtil auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); bool validLightingModel = isValidLightingModelString(lightingModelString); if (!validLightingModel) - Log(Debug::Error) << "Invalid option for 'lighting model': got '" << lightingModelString + Log(Debug::Error) << "Invalid option for 'lighting model': got '" << lightingModelString << "', expected legacy, default, or experimental."; if (ffp || !validLightingModel) @@ -625,10 +627,10 @@ namespace SceneUtil osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); bool supportsUBO = exts && exts->isUniformBufferObjectSupported; - bool supportsGPU4 = exts && exts->isGpuShader4Supported; + bool supportsGPU4 = exts && exts->isGpuShader4Supported; if (!supportsUBO) - Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; + Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; else if (!supportsGPU4) Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; @@ -648,7 +650,7 @@ namespace SceneUtil osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); - + mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); mLightUniforms[i].emplace(UniformKey::Ambient, uambient); mLightUniforms[i].emplace(UniformKey::Specular, uspecular); @@ -681,7 +683,7 @@ namespace SceneUtil stateset->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); } - + stateset->addUniform(new osg::Uniform("PointLightCount", 0)); setUpdateCallback(new LightManagerUpdateCallback); @@ -720,10 +722,10 @@ namespace SceneUtil { mMaxLights = value; } - + int LightManager::getMaxLightsInScene() const { - static constexpr int max = 16384 / LightBuffer::queryBlockSize(1); + static constexpr int max = 16384 / LightBuffer::queryBlockSize(1); return max; } @@ -732,7 +734,7 @@ namespace SceneUtil Shader::ShaderManager::DefineMap defines; bool ffp = usingFFP(); - + defines["ffpLighting"] = ffp ? "1" : "0"; defines["maxLights"] = std::to_string(getMaxLights()); defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene()); @@ -794,7 +796,7 @@ namespace SceneUtil } void LightManager::update(size_t frameNum) - { + { getLightIndexMap(frameNum).clear(); mLights.clear(); mLightsInViewSpace.clear(); @@ -802,7 +804,7 @@ namespace SceneUtil // Do an occasional cleanup for orphaned lights. for (int i=0; i<2; ++i) { - if (mStateSetCache[i].size() > 5000) + if (mStateSetCache[i].size() > 5000) mStateSetCache[i].clear(); } } @@ -839,9 +841,9 @@ namespace SceneUtil auto id = lightList[i]->mLightSource->getId(); hash_combine(hash, id); - if (getLightingMethod() != LightingMethod::SingleUBO) + if (getLightingMethod() != LightingMethod::SingleUBO) continue; - + if (getLightIndexMap(frameNum).find(id) != getLightIndexMap(frameNum).end()) continue; @@ -875,11 +877,11 @@ namespace SceneUtil if (it == mLightsInViewSpace.end()) { it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; - + for (const auto& transform : mLights) { osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix); - + float radius = transform.mLightSource->getRadius(); osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius * mPointLightRadiusMultiplier); @@ -901,7 +903,7 @@ namespace SceneUtil LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; - it->second.push_back(l); + it->second.push_back(l); } } @@ -985,7 +987,7 @@ namespace SceneUtil // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); - + // get the node bounds in view space // NB do not node->getBound() * modelView, that would apply the node's transformation twice osg::BoundingSphere nodeBound; @@ -1026,7 +1028,7 @@ namespace SceneUtil for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights; ) { osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); - + osg::BoundingSphere bs = (*it)->mViewBound; bs._radius = bs._radius * 2.0; osg::CullingSet& cullingSet = stack.front(); diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index acc8482991..e1acd9411b 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -148,6 +148,69 @@ By default, the fog becomes thicker proportionally to your distance from the cli 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 +--------------- + +:Type: string +:Range: legacy|default|experimental +:Default: default + +Sets the internal handling of light sources. + +'legacy' is restricted to a maximum of 8 lights per object and guarantees fixed function pipeline compatible lighting. + +'default' removes the light limit via :ref:`max lights` and follows a new attenuation formula which can drastically reduce light popping and seams. +It is recommended to use this mode with older hardware, as the technique ensures a range of compatibility equal to that of 'legacy'. + +'experimental' carries all of the benefits that 'legacy' has, but uses a modern approach that allows for a higher 'max lights' count with little to no performance penalties on modern hardware. + +light bounds multiplier +----------------------- + +:Type: float +:Range: 0.0-10.0 +:Default: 2.0 + +Controls the bounding sphere radius of point lights, which is used to determine if an object should receive lighting from a particular light source. +Note, this has no direct effect on the overall illumination of lights. +Larger multipliers will allow for smoother transitions of light sources, but may require an increase in :ref:`max lights` and thus carries a performance penalty. +This especially helps with abrupt light popping with handheld light sources such as torches and lanterns. + +It is recommended to keep this at 1.0 if :ref:`lighting method` is set to 'legacy', as the number of lights is fixed in that mode. + +maximum light distance +---------------------- + +:Type: float +:Range: The whole range of 32-bit floating point +:Default: 8192 + +The maximum distance from the camera that lights will be illuminated, applies to both interiors and exteriors. +A lower distance will improve performance. +Set this to a non-positive value to disable fading. + +light fade start +---------------- + +:Type: float +:Range: 0.0-1.0 +:Default: 0.85 + +The fraction of the maximum distance at which lights will begin to fade away. +Tweaking it will make the transition proportionally more or less smooth. +This setting has no effect if the maximum light distance is non-positive. + +max lights +---------- + +:Type: integer +:Range: >=2 +:Default: 16 + +Sets the maximum number of lights that each object can receive lighting from. +Has no effect if :ref:`force shaders` option is off or :ref:`lighting method` is 'legacy'. In this case the maximum number of lights is fixed at 8. +Increasing this too much can cause significant performance loss, especially if :ref:`lighting method` is not set to 'experimental' or :ref:`force per pixel lighting` is on. + antialias alpha test --------------------------------------- diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index e26d0f44cd..615b57a8a2 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -76,8 +76,8 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_fragment.glsl" -#include "parallax.glsl" #include "lighting.glsl" +#include "parallax.glsl" #include "alpha.glsl" void main() diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index a8bf735528..6a7ac0bcc4 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -34,8 +34,8 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_fragment.glsl" -#include "parallax.glsl" #include "lighting.glsl" +#include "parallax.glsl" void main() { From 08b568128416d7e16211993f555a1ac79ee557a9 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 28 Mar 2021 11:06:00 -0700 Subject: [PATCH 07/24] Missed redundant formatting changes --- files/shaders/objects_vertex.glsl | 1 + files/shaders/terrain_vertex.glsl | 1 + 2 files changed, 2 insertions(+) diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 104f42c09c..7baaa4176a 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -60,6 +60,7 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_vertex.glsl" + #include "lighting.glsl" void main(void) diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 2bf74a0e12..ad1201b932 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -23,6 +23,7 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_vertex.glsl" + #include "lighting.glsl" void main(void) From 142c6d2993141a65d3d1cb37b312beddac638590 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 28 Mar 2021 16:46:51 -0700 Subject: [PATCH 08/24] Enable groundcover lighting for non FFP --- apps/openmw/mwrender/groundcover.cpp | 3 +++ docs/source/reference/modding/settings/shaders.rst | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 0baa85c52a..fd22462539 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwbase/environment.hpp" @@ -271,6 +272,8 @@ namespace MWRender group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); group->getBound(); group->setNodeMask(Mask_Groundcover); + if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) + group->setCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", false, true); return group; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index e1acd9411b..6af5e9ba02 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -160,6 +160,7 @@ Sets the internal handling of light sources. 'legacy' is restricted to a maximum of 8 lights per object and guarantees fixed function pipeline compatible lighting. 'default' removes the light limit via :ref:`max lights` and follows a new attenuation formula which can drastically reduce light popping and seams. +This mode also enables vertex lighting on groundcover, which is otherwise completely disabled with 'legacy'. It is recommended to use this mode with older hardware, as the technique ensures a range of compatibility equal to that of 'legacy'. 'experimental' carries all of the benefits that 'legacy' has, but uses a modern approach that allows for a higher 'max lights' count with little to no performance penalties on modern hardware. From 690995988bd4f0251ebb2de0a71aa6813566139e Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 29 Mar 2021 00:13:35 -0700 Subject: [PATCH 09/24] More formatting, OpenCS cells are unbroken --- apps/opencs/model/world/data.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 10 +- components/sceneutil/lightmanager.cpp | 259 +++++++++++------- components/sceneutil/lightmanager.hpp | 26 +- components/shader/shadermanager.cpp | 1 - .../reference/modding/settings/shaders.rst | 68 +++-- files/settings-default.cfg | 27 +- files/shaders/lighting.glsl | 14 +- files/shaders/water_fragment.glsl | 2 + 9 files changed, 251 insertions(+), 158 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 25ac051f41..31778b9047 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -83,7 +83,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat defines["clamp"] = "1"; // Clamp lighting defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["radialFog"] = "0"; - defines["ffpLighting"] = "0"; + defines["ffpLighting"] = "1"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 79f34c42ed..229c34f452 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -199,8 +199,8 @@ namespace MWRender , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) { - auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); - bool usingFFPLighting = lightingModelString == "legacy" && SceneUtil::LightManager::isValidLightingModelString(lightingModelString); + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); + bool usingFFPLighting = lightingMethod == SceneUtil::LightingMethod::FFP; resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); @@ -217,9 +217,9 @@ namespace MWRender resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); - + osg::ref_ptr sceneRoot = new SceneUtil::LightManager(!forceShaders || usingFFPLighting); - // Let LightManager choose which backend to use based, mostly depends on support for UBOs + // Let LightManager choose which backend to use based on our hint, mostly depends on support for UBOs resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); @@ -262,7 +262,7 @@ namespace MWRender float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); - + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 3c1f31364f..72470b8c2c 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -14,8 +14,6 @@ #include -#include "apps/openmw/mwrender/vismask.hpp" - namespace { /* similar to the boost::hash_combine */ @@ -28,7 +26,8 @@ namespace bool sortLights(const SceneUtil::LightManager::LightSourceViewBound* left, const SceneUtil::LightManager::LightSourceViewBound* right) { - return left->mViewBound.center().length2() - left->mViewBound.radius2()*81 < right->mViewBound.center().length2() - right->mViewBound.radius2()*81; + static auto constexpr illuminationBias = 81.f; + return left->mViewBound.center().length2() - left->mViewBound.radius2()*illuminationBias < right->mViewBound.center().length2() - right->mViewBound.radius2()*illuminationBias; } float getLightRadius(const osg::Light* light) @@ -99,8 +98,15 @@ namespace SceneUtil return (*mData)[3*index+1]; } - auto& getData() { return mData; } - void dirty() { mData->dirty(); } + auto& getData() + { + return mData; + } + + void dirty() + { + mData->dirty(); + } static constexpr int queryBlockSize(int sz) { @@ -135,6 +141,7 @@ namespace SceneUtil { switch (method) { + case LightingMethod::Undefined: case LightingMethod::FFP: { break; @@ -232,7 +239,7 @@ namespace SceneUtil bool getModeUsage(ModeUsage & usage) const override { - for (size_t i=0; ilastAppliedLight[i+mIndex]; if (current != mLights[i].get()) @@ -269,29 +276,28 @@ namespace SceneUtil void applyLight(GLenum lightNum, const osg::Light* light) const { - glLightfv( lightNum, GL_AMBIENT, light->getAmbient().ptr() ); - glLightfv( lightNum, GL_DIFFUSE, light->getDiffuse().ptr() ); - glLightfv( lightNum, GL_SPECULAR, light->getSpecular().ptr() ); - glLightfv( lightNum, GL_POSITION, light->getPosition().ptr() ); + glLightfv(lightNum, GL_AMBIENT, light->getAmbient().ptr()); + glLightfv(lightNum, GL_DIFFUSE, light->getDiffuse().ptr()); + glLightfv(lightNum, GL_SPECULAR, light->getSpecular().ptr()); + glLightfv(lightNum, GL_POSITION, light->getPosition().ptr()); // TODO: enable this once spot lights are supported // need to transform SPOT_DIRECTION by the world matrix? - //glLightfv( lightNum, GL_SPOT_DIRECTION, light->getDirection().ptr() ); - //glLightf ( lightNum, GL_SPOT_EXPONENT, light->getSpotExponent() ); - //glLightf ( lightNum, GL_SPOT_CUTOFF, light->getSpotCutoff() ); - glLightf ( lightNum, GL_CONSTANT_ATTENUATION, light->getConstantAttenuation() ); - glLightf ( lightNum, GL_LINEAR_ATTENUATION, light->getLinearAttenuation() ); - glLightf ( lightNum, GL_QUADRATIC_ATTENUATION, light->getQuadraticAttenuation() ); + //glLightfv(lightNum, GL_SPOT_DIRECTION, light->getDirection().ptr()); + //glLightf(lightNum, GL_SPOT_EXPONENT, light->getSpotExponent()); + //glLightf(lightNum, GL_SPOT_CUTOFF, light->getSpotCutoff()); + glLightf(lightNum, GL_CONSTANT_ATTENUATION, light->getConstantAttenuation()); + glLightf(lightNum, GL_LINEAR_ATTENUATION, light->getLinearAttenuation()); + glLightf(lightNum, GL_QUADRATIC_ATTENUATION, light->getQuadraticAttenuation()); } private: size_t mIndex; - std::vector> mLights; }; LightManager* findLightManager(const osg::NodePath& path) { - for (size_t i=0;i(path[i])) return lightManager; @@ -406,8 +412,8 @@ namespace SceneUtil return stateset; } - // Cached statesets must be re-validated in case the light indicies change. There is no actual link between - // a lights ID and the buffer index it will eventually be assigned (or reassigned) to. + // Cached statesets must be revalidated in case the light indices change. There is no actual link between + // a light's ID and the buffer index it will eventually be assigned (or reassigned) to. void update(osg::StateSet* stateset, const LightManager::LightList& lightList, size_t frameNum) override { int newCount = 0; @@ -587,19 +593,56 @@ namespace SceneUtil LightManager* mLightManager; }; + const std::unordered_map LightManager::mLightingMethodSettingMap = { + {"legacy", LightingMethod::FFP} + ,{"shaders compatibility", LightingMethod::PerObjectUniform} + ,{"shaders", LightingMethod::SingleUBO} + }; + bool LightManager::isValidLightingModelString(const std::string& value) { - static const std::unordered_set validLightingModels = {"legacy", "default", "experimental"}; - return validLightingModels.count(value) != 0; + return LightManager::mLightingMethodSettingMap.find(value) != LightManager::mLightingMethodSettingMap.end(); + } + + LightingMethod LightManager::getLightingMethodFromString(const std::string& value) + { + auto it = LightManager::mLightingMethodSettingMap.find(value); + if (it != LightManager::mLightingMethodSettingMap.end()) + return it->second; + else + return LightingMethod::Undefined; } LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) - , mPointLightRadiusMultiplier(std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f)) + , mPointLightRadiusMultiplier(1.f) + , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { + if (ffp) + { + setLightingMethod(LightingMethod::FFP); + initFFP(LightManager::mFFPMaxLights); + return; + } + + std::string lightingMethodString = Settings::Manager::getString("lighting method", "Shaders"); + auto lightingMethod = LightManager::getLightingMethodFromString(lightingMethodString); + if (lightingMethod == LightingMethod::Undefined) + { + Log(Debug::Error) << "Invalid option for 'lighting method': got '" << lightingMethodString + << "', expected legacy, shaders compatible, or shaders. Falling back to 'shaders compatible'."; + setLightingMethod(LightingMethod::PerObjectUniform); + } + else + { + setLightingMethod(lightingMethod); + } + + mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f); + mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); if (mPointLightFadeEnd > 0) { @@ -607,84 +650,31 @@ namespace SceneUtil mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; } - auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); - bool validLightingModel = isValidLightingModelString(lightingModelString); - if (!validLightingModel) - Log(Debug::Error) << "Invalid option for 'lighting model': got '" << lightingModelString - << "', expected legacy, default, or experimental."; - - if (ffp || !validLightingModel) - { - setMaxLights(LightManager::mFFPMaxLights); - setLightingMethod(LightingMethod::FFP); - - for (int i=0; i >())); - - setUpdateCallback(new LightManagerUpdateCallback); - return; - } - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); bool supportsUBO = exts && exts->isUniformBufferObjectSupported; bool supportsGPU4 = exts && exts->isGpuShader4Supported; - if (!supportsUBO) - Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; - else if (!supportsGPU4) - Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; + if (getLightingMethod() == LightingMethod::SingleUBO) + { + if (!supportsUBO) + Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; + else if (!supportsGPU4) + Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; + } int targetLights = Settings::Manager::getInt("max lights", "Shaders"); - auto* stateset = getOrCreateStateSet(); - if (!supportsUBO || !supportsGPU4 || lightingModelString == "default") + if (!supportsUBO || !supportsGPU4 || getLightingMethod() == LightingMethod::PerObjectUniform) { setLightingMethod(LightingMethod::PerObjectUniform); - setMaxLights(std::max(2, targetLights)); - - mLightUniforms.resize(getMaxLights()+1); - for (size_t i = 0; i < mLightUniforms.size(); ++i) - { - osg::ref_ptr udiffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].diffuse").c_str()); - osg::ref_ptr uspecular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].specular").c_str()); - osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); - osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); - osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); - - mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); - mLightUniforms[i].emplace(UniformKey::Ambient, uambient); - mLightUniforms[i].emplace(UniformKey::Specular, uspecular); - mLightUniforms[i].emplace(UniformKey::Position, uposition); - mLightUniforms[i].emplace(UniformKey::Attenuation, uattenuation); - - stateset->addUniform(udiffuse); - stateset->addUniform(uambient); - stateset->addUniform(uposition); - stateset->addUniform(uattenuation); - - // specular isn't used besides sun, complete waste to upload it - if (i == 0) - stateset->addUniform(uspecular); - } + initPerObjectUniform(targetLights); } else { - setLightingMethod(LightingMethod::SingleUBO); - setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); - - for (int i = 0; i < 2; ++i) - { - mLightBuffers[i] = new LightBuffer(getMaxLightsInScene()); - osg::ref_ptr ubo = new osg::UniformBufferObject; - ubo->setUsage(GL_STREAM_DRAW); - - mLightBuffers[i]->getData()->setBufferObject(ubo); - } - - stateset->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); + initSingleUBO(targetLights); } - stateset->addUniform(new osg::Uniform("PointLightCount", 0)); + getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); setUpdateCallback(new LightManagerUpdateCallback); addCullCallback(new LightManagerCullCallback(this)); @@ -726,7 +716,7 @@ namespace SceneUtil int LightManager::getMaxLightsInScene() const { static constexpr int max = 16384 / LightBuffer::queryBlockSize(1); - return max; + return max; } Shader::ShaderManager::DefineMap LightManager::getLightDefines() const @@ -746,6 +736,66 @@ namespace SceneUtil return defines; } + void LightManager::initFFP(int targetLights) + { + setMaxLights(targetLights); + + for (int i = 0; i < getMaxLights(); ++i) + mDummies.push_back(new FFPLightStateAttribute(i, std::vector>())); + + setUpdateCallback(new LightManagerUpdateCallback); + } + + void LightManager::initPerObjectUniform(int targetLights) + { + auto* stateset = getOrCreateStateSet(); + + setMaxLights(std::max(2, targetLights)); + + mLightUniforms.resize(getMaxLights()+1); + for (size_t i = 0; i < mLightUniforms.size(); ++i) + { + osg::ref_ptr udiffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].diffuse").c_str()); + osg::ref_ptr uspecular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].specular").c_str()); + osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); + osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); + osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); + + mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); + mLightUniforms[i].emplace(UniformKey::Ambient, uambient); + mLightUniforms[i].emplace(UniformKey::Specular, uspecular); + mLightUniforms[i].emplace(UniformKey::Position, uposition); + mLightUniforms[i].emplace(UniformKey::Attenuation, uattenuation); + + stateset->addUniform(udiffuse); + stateset->addUniform(uambient); + stateset->addUniform(uposition); + stateset->addUniform(uattenuation); + + // specular isn't used besides sun, complete waste to upload it + if (i == 0) + stateset->addUniform(uspecular); + } + } + + void LightManager::initSingleUBO(int targetLights) + { + setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); + + for (int i = 0; i < 2; ++i) + { + mLightBuffers[i] = new LightBuffer(getMaxLightsInScene()); + + osg::ref_ptr ubo = new osg::UniformBufferObject; + ubo->setUsage(GL_STREAM_DRAW); + + mLightBuffers[i]->getData()->setBufferObject(ubo); + } + + getOrCreateStateSet()->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); + } + + void LightManager::setLightingMethod(LightingMethod method) { mLightingMethod = method; @@ -760,6 +810,9 @@ namespace SceneUtil case LightingMethod::PerObjectUniform: mStateSetGenerator = std::make_unique(); break; + case LightingMethod::Undefined: + mStateSetGenerator = nullptr; + break; } mStateSetGenerator->mLightManager = this; } @@ -783,7 +836,7 @@ namespace SceneUtil // Set default light state to zero // This is necessary because shaders don't respect glDisable(GL_LIGHTX) so in addition to disabling // we'll have to set a light state that has no visible effect - for (int i=start; i defaultLight (new DisableLight(i)); getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); @@ -802,7 +855,7 @@ namespace SceneUtil mLightsInViewSpace.clear(); // Do an occasional cleanup for orphaned lights. - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) { if (mStateSetCache[i].size() > 5000) mStateSetCache[i].clear(); @@ -836,7 +889,7 @@ namespace SceneUtil { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; - for (size_t i=0; imLightSource->getId(); hash_combine(hash, id); @@ -860,13 +913,10 @@ namespace SceneUtil mStateSetGenerator->update(found->second, lightList, frameNum); return found->second; } - else - { - auto stateset = mStateSetGenerator->generate(lightList, frameNum); - stateSetCache.emplace(hash, stateset); - return stateset; - } - return new osg::StateSet; + + auto stateset = mStateSetGenerator->generate(lightList, frameNum); + stateSetCache.emplace(hash, stateset); + return stateset; } const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) @@ -946,7 +996,7 @@ namespace SceneUtil { mId = sLightId++; - for (int i=0; i<2; ++i) + for (int i = 0; i < 2; ++i) mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } @@ -981,7 +1031,6 @@ namespace SceneUtil // makes sure we don't update it more than once per frame when rendering with multiple cameras if (mLastFrameNumber != cv->getTraversalNumber()) { - mLastFrameNumber = cv->getTraversalNumber(); // Don't use Camera::getViewMatrix, that one might be relative to another camera! @@ -994,7 +1043,7 @@ namespace SceneUtil osg::Transform* transform = node->asTransform(); if (transform) { - for (size_t i=0; igetNumChildren(); ++i) + for (size_t i = 0; i < transform->getNumChildren(); ++i) nodeBound.expandBy(transform->getChild(i)->getBound()); } else @@ -1003,7 +1052,7 @@ namespace SceneUtil transformBoundingSphere(mat, nodeBound); mLightList.clear(); - for (size_t i=0; igetMaxLights() - mLightManager->getStartLight(); @@ -1025,7 +1073,7 @@ namespace SceneUtil { // remove lights culled by this camera LightManager::LightList lightList = mLightList; - for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights; ) + for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) { osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); @@ -1053,6 +1101,7 @@ namespace SceneUtil else stateset = mLightManager->getLightListStateSet(mLightList, cv->getTraversalNumber(), cv->getCurrentRenderStage()->getInitialViewMatrix()); + cv->pushStateSet(stateset); return true; } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 214ffa1385..cd8f2d3127 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -2,12 +2,12 @@ #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H #include -#include -#include #include +#include #include #include + #include #include #include @@ -18,6 +18,7 @@ namespace osgUtil { class CullVisitor; } + namespace SceneUtil { class LightBuffer; @@ -27,7 +28,8 @@ namespace SceneUtil { FFP, SingleUBO, - PerObjectUniform + PerObjectUniform, + Undefined }; void configureStateSetSunOverride(LightingMethod method, const osg::Light* light, osg::StateSet* stateset, int mode = osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); @@ -98,8 +100,8 @@ namespace SceneUtil class LightManager : public osg::Group { public: - static bool isValidLightingModelString(const std::string& value); + static LightingMethod getLightingMethodFromString(const std::string& value); enum class UniformKey { @@ -136,7 +138,7 @@ namespace SceneUtil /// By default, it's ~0u i.e. always on. /// If you have some views that do not require lighting, then set the Camera's cull mask to not include /// the lightingMask for a much faster cull and rendering. - void setLightingMask (size_t mask); + void setLightingMask(size_t mask); size_t getLightingMask() const; /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. @@ -167,7 +169,7 @@ namespace SceneUtil auto& getDummies() { return mDummies; } auto& getLightIndexMap(size_t frameNum) { return mLightIndexMaps[frameNum%2]; } - + auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; } auto& getLightUniform(int index, UniformKey key) { return mLightUniforms[index][key]; } @@ -175,10 +177,13 @@ namespace SceneUtil std::map getLightDefines() const; private: - friend class LightManagerStateAttribute; friend class LightManagerCullCallback; + void initFFP(int targetLights); + void initPerObjectUniform(int targetLights); + void initSingleUBO(int targetLights); + void setLightingMethod(LightingMethod method); void setMaxLights(int value); @@ -188,7 +193,7 @@ namespace SceneUtil using LightSourceViewBoundCollection = std::vector; std::map, LightSourceViewBoundCollection> mLightsInViewSpace; - + // < Light list hash , StateSet > using LightStateSetMap = std::map>; LightStateSetMap mStateSetCache[2]; @@ -221,6 +226,8 @@ namespace SceneUtil int mMaxLights; static constexpr auto mFFPMaxLights = 8; + + static const std::unordered_map mLightingMethodSettingMap; }; /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via @@ -259,7 +266,8 @@ namespace SceneUtil size_t mLastFrameNumber; LightManager::LightList mLightList; std::set mIgnoredLightSources; - }; + }; + } #endif diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 12980dffcc..3a5b464405 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 6af5e9ba02..ddffbfefb2 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -40,7 +40,7 @@ Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. Leaving this option at its default makes the lighting compatible with Morrowind's fixed-function method, -but the lighting may appear dull and there might be colour shifts. +but the lighting may appear dull and there might be colour shifts. Setting this option to 'false' results in more dynamic lighting. auto use object normal maps @@ -152,32 +152,51 @@ lighting method --------------- :Type: string -:Range: legacy|default|experimental +:Range: legacy|shaders compatibility|shaders :Default: default -Sets the internal handling of light sources. +Sets the internal handling of light sources. -'legacy' is restricted to a maximum of 8 lights per object and guarantees fixed function pipeline compatible lighting. +'legacy' is restricted to 8 lights per object and emulates fixed function +pipeline compatible lighting. -'default' removes the light limit via :ref:`max lights` and follows a new attenuation formula which can drastically reduce light popping and seams. -This mode also enables vertex lighting on groundcover, which is otherwise completely disabled with 'legacy'. -It is recommended to use this mode with older hardware, as the technique ensures a range of compatibility equal to that of 'legacy'. +'shaders compatibility' removes the light limit via :ref:`max lights` and +follows a modifed attenuation formula which can drastically reduce light popping +and seams. This mode also enables lighting on groundcover and a configurable +light fade. It is recommended to use this with older hardware and a light limit +closer to 8. Because of its wide range of compatibility it is set as the +default. -'experimental' carries all of the benefits that 'legacy' has, but uses a modern approach that allows for a higher 'max lights' count with little to no performance penalties on modern hardware. +'shaders' carries all of the benefits that 'shaders compatibility' does, but +uses a modern approach that allows for a higher :ref:`max lights` count with +little to no performance penalties on modern hardware. It is recommended to use +this mode when supported and where the GPU is not a bottleneck. On some weaker +devices, using this mode along with :ref:`force per pixel lighting` can carry +performance penalties. + +Note that when enabled, groundcover lighting is forced to be vertex lighting, +unless normal maps are provided. This is due to some groundcover mods using the +Z-Up Normals technique to avoid some common issues with shading. As a +consequence, per pixel lighting would give undesirable results. + +This setting has no effect if :ref:`force shaders` is 'false'. light bounds multiplier ----------------------- :Type: float :Range: 0.0-10.0 -:Default: 2.0 +:Default: 1.75 -Controls the bounding sphere radius of point lights, which is used to determine if an object should receive lighting from a particular light source. -Note, this has no direct effect on the overall illumination of lights. -Larger multipliers will allow for smoother transitions of light sources, but may require an increase in :ref:`max lights` and thus carries a performance penalty. -This especially helps with abrupt light popping with handheld light sources such as torches and lanterns. +Controls the bounding sphere radius of point lights, which is used to determine +if an object should receive lighting from a particular light source. Note, this +has no direct effect on the overall illumination of lights. Larger multipliers +will allow for smoother transitions of light sources, but may require an +increase in :ref:`max lights` and thus carries a performance penalty. This +especially helps with abrupt light popping with handheld light sources such as +torches and lanterns. -It is recommended to keep this at 1.0 if :ref:`lighting method` is set to 'legacy', as the number of lights is fixed in that mode. +This setting has no effect if :ref:`lighting method` is 'legacy'. maximum light distance ---------------------- @@ -186,9 +205,11 @@ maximum light distance :Range: The whole range of 32-bit floating point :Default: 8192 -The maximum distance from the camera that lights will be illuminated, applies to both interiors and exteriors. -A lower distance will improve performance. -Set this to a non-positive value to disable fading. +The maximum distance from the camera that lights will be illuminated, applies to +both interiors and exteriors. A lower distance will improve performance. Set +this to a non-positive value to disable fading. + +This setting has no effect if :ref:`lighting method` is 'legacy'. light fade start ---------------- @@ -199,18 +220,23 @@ light fade start The fraction of the maximum distance at which lights will begin to fade away. Tweaking it will make the transition proportionally more or less smooth. -This setting has no effect if the maximum light distance is non-positive. + +This setting has no effect if the :ref:`maximum light distance` is non-positive +or :ref:`lighting method` is 'legacy'. max lights ---------- :Type: integer :Range: >=2 -:Default: 16 +:Default: 8 Sets the maximum number of lights that each object can receive lighting from. -Has no effect if :ref:`force shaders` option is off or :ref:`lighting method` is 'legacy'. In this case the maximum number of lights is fixed at 8. -Increasing this too much can cause significant performance loss, especially if :ref:`lighting method` is not set to 'experimental' or :ref:`force per pixel lighting` is on. +Increasing this too much can cause significant performance loss, especially if +:ref:`lighting method` is not set to 'shaders' or :ref:`force per pixel +lighting` is on. + +This setting has no effect if :ref:`lighting method` is 'legacy'. antialias alpha test --------------------------------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 102c1aa66c..9525595883 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -442,23 +442,32 @@ apply lighting to environment maps = false # This makes fogging independent from the viewing angle. Shaders will be used to render all objects. radial fog = false -# Internal handling of lights, values are 'legacy', 'default', 'experimental' -lighting method = experimental +# 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 +# attenuation formula to reduce popping and light seams. "shaders" comes with +# all these benefits and is meant for larger light limits, but may not be +# supported on older hardware and may be less performant on weaker hardware when +# 'force per pixel lighting' is enabled. +lighting method = shaders compatibility -# Sets the bounding sphere multiplier of light sources, which are used to determine if an object should -# receive lighting. Higher values will allow for smoother transitions of light sources, but may have a performance cost and -# requires a higher number of 'max lights' set. It is recommended to keep this at 1.0 with 'legacy' lighting enabled. -light bounds multiplier = 1.0 +# Sets the bounding sphere multiplier of light sources, which are used to +# determine if an object should receive lighting. Higher values will allow for +# smoother transitions of light sources, but may have a performance cost and +# requires a higher number of 'max lights' set. It is recommended to keep this +# at 1.0 with 'legacy' lighting enabled. +light bounds multiplier = 1.75 -# The distance from the camera at which lights fade away completely. Set to 0 to disable fading. +# The distance from the camera at which lights fade away completely. +# Set to 0 to disable fading. maximum light distance = 8192 # Fraction of the maximum distance at which lights begin to gradually fade away. light fade start = 0.85 # Set maximum number of lights per object. -# Only used when 'lighting method' is not set to 'legacy' -max lights = 16 +# When 'lighting method' is set to 'legacy', this setting will have no effect. +max lights = 8 # Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. # This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index c6c03f2232..3678a35704 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -69,7 +69,7 @@ uniform int PointLightCount; #endif void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal) -{ +{ vec3 lightDir = normalize(getLight[0].position.xyz); #if @lightingModel == LIGHTING_MODEL_SINGLE_UBO @@ -98,9 +98,9 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) { - vec3 lightDir = getLight[lightIndex].position.xyz - viewPos; + vec3 lightPos = getLight[lightIndex].position.xyz - viewPos; - float lightDistance = length(lightDir); + float lightDistance = length(lightPos); #if !@ffpLighting // This has a *considerable* performance uplift where GPU is a bottleneck @@ -112,7 +112,7 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec } #endif - lightDir = normalize(lightDir); + lightPos = normalize(lightPos); #if @ffpLighting float illumination = clamp(1.0 / (getLight[lightIndex].constantAttenuation + getLight[lightIndex].linearAttenuation * lightDistance + getLight[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); @@ -128,8 +128,8 @@ void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec ambientOut = getLight[lightIndex].ambient.xyz * illumination; #endif - float lambert = dot(viewNormal.xyz, lightDir) * illumination; - + float lambert = dot(viewNormal.xyz, lightPos) * illumination; + #ifndef GROUNDCOVER lambert = max(lambert, 0.0); #else @@ -179,7 +179,7 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a for (int i=1; i <= PointLightCount; ++i) { perLightPoint(ambientOut, diffuseOut, i, viewPos, viewNormal); -#else +#else for (int i=0; i < PointLightCount; ++i) { perLightPoint(ambientOut, diffuseOut, PointLightIndex[i], viewPos, viewNormal); diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 1397f65e7e..06a3213011 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -151,6 +151,8 @@ uniform vec3 nodePosition; uniform float rainIntensity; +#define PER_PIXEL_LIGHTING 0 + #include "shadows_fragment.glsl" #include "lighting.glsl" From cc31e1eea1c066ccca40031a3150847f98f9c75e Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Tue, 30 Mar 2021 18:12:57 -0700 Subject: [PATCH 10/24] Ambient luminance threshold setting --- apps/openmw/mwrender/renderingmanager.cpp | 22 +++++++++++++++++++ apps/openmw/mwrender/renderingmanager.hpp | 1 + .../reference/modding/settings/shaders.rst | 19 ++++++++++++++++ files/settings-default.cfg | 7 +++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 229c34f452..865bae2f10 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -195,6 +195,7 @@ namespace MWRender , mWorkQueue(workQueue) , mUnrefQueue(new SceneUtil::UnrefQueue) , mNavigator(navigator) + , mMinimumAmbientLuminance(0.f) , mNightEyeFactor(0.f) , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) @@ -223,6 +224,9 @@ namespace MWRender resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); + if (sceneRoot->getLightingMethod() != SceneUtil::LightingMethod::FFP) + mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); + sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); @@ -1072,7 +1076,25 @@ namespace MWRender osg::Vec4f color = mAmbientColor; if (mNightEyeFactor > 0.f) + { color += osg::Vec4f(0.7, 0.7, 0.7, 0.0) * mNightEyeFactor; + } + // optionally brighten up ambient interiors when using a non-FFP emulated lighting method + else if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) + { + static constexpr float pR = 0.2126; + static constexpr float pG = 0.7152; + static constexpr float pB = 0.0722; + + // we already work in linear RGB so no conversions are needed for the luminosity function + float relativeLuminance = pR*color.r() + pG*color.g() + pB*color.b(); + if (relativeLuminance < mMinimumAmbientLuminance) + { + // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can + float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; + color *= targetBrightnessIncreaseFactor; + } + } mStateUpdater->setAmbientColor(color); } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index a7afa2fa0d..a0a74bd5c4 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -296,6 +296,7 @@ namespace MWRender osg::ref_ptr mStateUpdater; osg::Vec4f mAmbientColor; + float mMinimumAmbientLuminance; float mNightEyeFactor; float mNearClip; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index ddffbfefb2..3b0f6df5a6 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -238,6 +238,25 @@ lighting` is on. This setting has no effect if :ref:`lighting method` is 'legacy'. +minimum interior brightness +------------------------ + +:Type: float +:Range: 0.0-1.0 +:Default: 0.1 + +Sets the minimum interior ambient brightness for interior cells when +:ref:`lighting method` is not 'legacy'. A consequence of the new lighting system +is that interiors will sometimes be darker since light sources now have sensible +fall-offs. A couple solutions are to either add more lights or increase their +radii to compensate, but these require content changes. For best results it is +recommended to set this to 0.0 to retain the colors that level designers +intended. If brighter interiors are wanted, however, this setting should be +increased. Note, it is advised to keep this number small (< 0.1) to avoid the +aforementioned changes in visuals. + +This setting has no effect if :ref:`lighting method` is 'legacy'. + antialias alpha test --------------------------------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9525595883..2d543303e9 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -465,10 +465,15 @@ maximum light distance = 8192 # Fraction of the maximum distance at which lights begin to gradually fade away. light fade start = 0.85 -# Set maximum number of lights per object. +# Set maximum number of lights per object. # When 'lighting method' is set to 'legacy', this setting will have no effect. max lights = 8 +# Sets minimum ambient brightness of interior cells. Levels below this threshold will have their +# ambient values adjusted to balance the darker interiors. +# When 'lighting method' is set to 'legacy', this setting will have no effect. +minimum interior brightness = 0.1 + # Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. # 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. From d195602a9dcdc199b48a85ad5be8fdd860dcb110 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 31 Mar 2021 21:22:16 -0700 Subject: [PATCH 11/24] Switch to shared layout, some rewording --- components/sceneutil/lightmanager.cpp | 201 ++++++++++++++++++++++---- files/settings-default.cfg | 2 +- files/shaders/lighting.glsl | 10 +- 3 files changed, 175 insertions(+), 38 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 72470b8c2c..c7a4834299 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -47,55 +47,81 @@ namespace SceneUtil { static int sLightId = 0; + // Handles a GLSL shared layout by using configured offsets and strides to fill a continuous buffer, making the data upload to GPU simpler. class LightBuffer : public osg::Referenced { public: - LightBuffer(int count) : mData(new osg::Vec4Array(3*count)), mEndian(osg::getCpuByteOrder()) {} + enum LayoutOffset + { + Diffuse, + DiffuseSign, + Ambient, + Specular, + Position, + AttenuationRadius + }; + + LightBuffer(int count) + : mData(new osg::FloatArray(3*4*count)) + , mEndian(osg::getCpuByteOrder()) + , mCount(count) + , mStride(12) + { + mOffsets[Diffuse] = 0; + mOffsets[Ambient] = 1; + mOffsets[Specular] = 2; + mOffsets[DiffuseSign] = 3; + mOffsets[Position] = 4; + mOffsets[AttenuationRadius] = 8; + } + + LightBuffer(const LightBuffer& copy) + : osg::Referenced() + , mData(copy.mData) + , mEndian(copy.mEndian) + , mCount(copy.mCount) + , mStride(copy.mStride) + , mOffsets(copy.mOffsets) + {} void setDiffuse(int index, const osg::Vec4& value) { - auto signedValue = value; + // Deal with negative lights (negative diffuse) by passing a sign bit in the unused alpha component + auto positiveColor = value; float signBit = 1.0; if (value[0] < 0) { - signedValue *= -1.0; + positiveColor *= -1.0; signBit = -1.0; } - *(unsigned int*)(&(*mData)[3*index][0]) = asRGBA(signedValue); - *(int*)(&(*mData)[3*index][3]) = signBit; + *(unsigned int*)(&(*mData)[getOffset(index, Diffuse)]) = asRGBA(positiveColor); + *(int*)(&(*mData)[getOffset(index, DiffuseSign)]) = signBit; } void setAmbient(int index, const osg::Vec4& value) { - *(unsigned int*)(&(*mData)[3*index][1]) = asRGBA(value); + *(unsigned int*)(&(*mData)[getOffset(index, Ambient)]) = asRGBA(value); } void setSpecular(int index, const osg::Vec4& value) { - *(unsigned int*)(&(*mData)[3*index][2]) = asRGBA(value); + *(unsigned int*)(&(*mData)[getOffset(index, Specular)]) = asRGBA(value); } void setPosition(int index, const osg::Vec4& value) { - (*mData)[3*index+1] = value; + *(osg::Vec4*)(&(*mData)[getOffset(index, Position)]) = value; } - void setAttenuation(int index, float c, float l, float q) + void setAttenuationRadius(int index, const osg::Vec4& value) { - (*mData)[3*index+2][0] = c; - (*mData)[3*index+2][1] = l; - (*mData)[3*index+2][2] = q; - } - - void setRadius(int index, float value) - { - (*mData)[3*index+2][3] = value; + *(osg::Vec4*)(&(*mData)[getOffset(index, AttenuationRadius)]) = value; } auto getPosition(int index) { - return (*mData)[3*index+1]; + return *(osg::Vec4*)(&(*mData)[getOffset(index, Position)]); } auto& getData() @@ -118,8 +144,40 @@ namespace SceneUtil return mEndian == osg::BigEndian ? value.asABGR() : value.asRGBA(); } - osg::ref_ptr mData; + int getOffset(int index, LayoutOffset slot) + { + return mStride * index + mOffsets[slot]; + } + + void configureLayout(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int size, int stride) + { + static constexpr auto sizeofVec4 = sizeof(GL_FLOAT) * osg::Vec4::num_components; + static constexpr auto sizeofFloat = sizeof(GL_FLOAT); + + mOffsets[Diffuse] = offsetColors / sizeofFloat; + mOffsets[Ambient] = mOffsets[Diffuse] + 1; + mOffsets[Specular] = mOffsets[Diffuse] + 2; + mOffsets[DiffuseSign] = mOffsets[Diffuse] + 3; + mOffsets[Position] = offsetPosition / sizeofFloat; + mOffsets[AttenuationRadius] = offsetAttenuationRadius / sizeofFloat; + mStride = (offsetAttenuationRadius + sizeofVec4 + stride) / 4; + + // Copy over previous buffers light data. Buffers populate before we know the layout. + LightBuffer oldBuffer = LightBuffer(*this); + for (int i = 0; i < oldBuffer.mCount; ++i) + { + *(osg::Vec4*)(&(*mData)[getOffset(i, Diffuse)]) = *(osg::Vec4*)(&(*mData)[oldBuffer.getOffset(i, Diffuse)]); + *(osg::Vec4*)(&(*mData)[getOffset(i, Position)]) = *(osg::Vec4*)(&(*mData)[oldBuffer.getOffset(i, Position)]); + *(osg::Vec4*)(&(*mData)[getOffset(i, AttenuationRadius)]) = *(osg::Vec4*)(&(*mData)[oldBuffer.getOffset(i, AttenuationRadius)]); + } + } + + private: + osg::ref_ptr mData; osg::Endian mEndian; + int mCount; + int mStride; + std::unordered_map mOffsets; }; class LightStateCache @@ -165,8 +223,8 @@ namespace SceneUtil buffer->setPosition(0, light->getPosition()); osg::ref_ptr ubo = new osg::UniformBufferObject; - buffer->mData->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->mData.get(), 0, buffer->mData->getTotalDataSize()); + buffer->getData()->setBufferObject(ubo); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->getData().get(), 0, buffer->getData()->getTotalDataSize()); stateset->setAttributeAndModes(ubb, mode); @@ -572,11 +630,24 @@ namespace SceneUtil class LightManagerStateAttribute : public osg::StateAttribute { public: - LightManagerStateAttribute() : mLightManager(nullptr) {} - LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} + LightManagerStateAttribute() + : mLightManager(nullptr) {} - LightManagerStateAttribute(const LightManagerStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy,copyop),mLightManager(copy.mLightManager) {} + LightManagerStateAttribute(LightManager* lightManager) + : mLightManager(lightManager) + , mDummyProgram(new osg::Program) + { + static const std::string dummyVertSource = generateDummyShader(mLightManager->getMaxLightsInScene()); + + mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); + mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); + // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably + // available, regardless of extensions, until GLSL 140. + mLightManager->getOrCreateStateSet()->setAttributeAndModes(mDummyProgram, osg::StateAttribute::ON|osg::StateAttribute::PROTECTED); + } + + LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mLightManager(copy.mLightManager) {} int compare(const StateAttribute &sa) const override { @@ -585,12 +656,76 @@ namespace SceneUtil META_StateAttribute(NifOsg, LightManagerStateAttribute, osg::StateAttribute::LIGHT) + void initSharedLayout(osg::GLExtensions* ext, int handle) const + { + std::vector index = { static_cast(Shader::UBOBinding::LightBuffer) }; + int totalBlockSize = -1; + int stride = -1; + + ext->glGetActiveUniformBlockiv(handle, 0, GL_UNIFORM_BLOCK_DATA_SIZE, &totalBlockSize); + ext->glGetActiveUniformsiv(handle, index.size(), index.data(), GL_UNIFORM_ARRAY_STRIDE, &stride); + + std::vector names = { + "LightBuffer[0].packedColors" + ,"LightBuffer[0].position" + ,"LightBuffer[0].attenuation" + }; + std::vector indices(names.size()); + std::vector offsets(names.size()); + + ext->glGetUniformIndices(handle, names.size(), names.data(), indices.data()); + ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data()); + + for (int i = 0; i < 2; ++i) + { + auto& buf = mLightManager->getLightBuffer(i); + buf->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride); + } + } + void apply(osg::State& state) const override { - mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); + static bool init = false; + if (!init) + { + auto handle = mDummyProgram->getPCP(state)->getHandle(); + auto* ext = state.get(); + + int activeUniformBlocks = 0; + ext->glGetProgramiv(handle, GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks); + + // wait until the UBO binding is created + if (activeUniformBlocks > 0) + { + initSharedLayout(ext, handle); + init = true; + } + } + else + { + mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); + } + } + + private: + + std::string generateDummyShader(int maxLightsInScene) + { + return "#version 120\n" + "#extension GL_ARB_uniform_buffer_object : require\n" + "struct LightData {\n" + " ivec4 packedColors;\n" + " vec4 position;\n" + " vec4 attenuation;\n" + "};\n" + "uniform LightBufferBinding {\n" + " LightData LightBuffer[" + std::to_string(mLightManager->getMaxLightsInScene()) + "];\n" + "};\n" + "void main() { gl_Position = vec4(0.0); }\n"; } LightManager* mLightManager; + osg::ref_ptr mDummyProgram; }; const std::unordered_map LightManager::mLightingMethodSettingMap = { @@ -654,12 +789,15 @@ namespace SceneUtil bool supportsUBO = exts && exts->isUniformBufferObjectSupported; bool supportsGPU4 = exts && exts->isGpuShader4Supported; - if (getLightingMethod() == LightingMethod::SingleUBO) + static bool hasLoggedWarnings = false; + + if (getLightingMethod() == LightingMethod::SingleUBO && !hasLoggedWarnings) { if (!supportsUBO) - Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; - else if (!supportsGPU4) - Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; + Log(Debug::Warning) << "GL_ARB_uniform_buffer_object not supported: switching to shader compatibility lighting mode"; + if (!supportsGPU4) + Log(Debug::Warning) << "GL_EXT_gpu_shader4 not supported: switching to shader compatibility lighting mode"; + hasLoggedWarnings = true; } int targetLights = Settings::Manager::getInt("max lights", "Shaders"); @@ -978,8 +1116,7 @@ namespace SceneUtil auto& buf = getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); buf->setAmbient(index, light->getAmbient()); - buf->setAttenuation(index, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation()); - buf->setRadius(index, lightSource->getRadius()); + buf->setAttenuationRadius(index, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); buf->setPosition(index, light->getPosition() * (*viewMatrix)); } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 2d543303e9..c931789068 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -453,7 +453,7 @@ lighting method = shaders compatibility # Sets the bounding sphere multiplier of light sources, which are used to # determine if an object should receive lighting. Higher values will allow for -# smoother transitions of light sources, but may have a performance cost and +# smoother transitions of light sources, but may carry a performance cost and # requires a higher number of 'max lights' set. It is recommended to keep this # at 1.0 with 'legacy' lighting enabled. light bounds multiplier = 1.75 diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 3678a35704..a3df6666fa 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -35,15 +35,16 @@ vec4 unpackRGBA(int data) struct LightData { - ivec4 packedColors; // diffuse, ambient, specular + ivec4 packedColors; // diffuse, ambient, specular vec4 position; - vec4 attenuation; // constant, linear, quadratic, radius + vec4 attenuation; // constant, linear, quadratic, radius }; uniform int PointLightIndex[@maxLights]; uniform int PointLightCount; -layout(std140) uniform LightBufferBinding +// Defaults to shared layout. If we ever move to GLSL 140, std140 layout should be considered +uniform LightBufferBinding { LightData LightBuffer[@maxLightsInScene]; }; @@ -56,7 +57,7 @@ struct LightData vec4 diffuse; vec4 ambient; vec4 specular; - vec4 attenuation; // constant, linear, quadratic, radius + vec4 attenuation; // constant, linear, quadratic, radius }; uniform LightData LightBuffer[@maxLights]; @@ -92,7 +93,6 @@ void perLightSun(out vec3 ambientOut, out vec3 diffuseOut, vec3 viewPos, vec3 vi } lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif - diffuseOut = sunDiffuse * lambert; } From 157717693a0ab59b041c1998425b24b33edd807b Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 31 Mar 2021 22:03:06 -0700 Subject: [PATCH 12/24] Changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1fea773e9..8ced951e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -147,6 +147,7 @@ Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5813: Instanced groundcover support + Feature #5828: Support more than 8 lights Feature #5910: Fall back to delta time when physics can't keep up Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation From 71f7f30c0b3018b9451ea8ad39ebccffca9c7ef1 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 31 Mar 2021 22:05:47 -0700 Subject: [PATCH 13/24] Don't break NV shaders --- files/shaders/nv_default_fragment.glsl | 4 ++++ files/shaders/nv_default_vertex.glsl | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 03fa378a6d..1245069f60 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -1,5 +1,9 @@ #version 120 +#if @useUBO + #extension GL_ARB_uniform_buffer_object : require +#endif + #if @useGPUShader4 #extension GL_EXT_gpu_shader4: require #endif diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index 7c9d434f18..50f5daf25e 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -1,5 +1,13 @@ #version 120 +#if @useUBO + #extension GL_ARB_uniform_buffer_object : require +#endif + +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + #if @diffuseMap varying vec2 diffuseMapUV; #endif From 3d713e8602b71711db609ddc6746e3f3331dd74a Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 31 Mar 2021 22:35:02 -0700 Subject: [PATCH 14/24] Fix incorrect minimum ambient --- AUTHORS.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 45 +++++++------ components/sceneutil/lightmanager.cpp | 67 +++++++------------ .../reference/modding/settings/shaders.rst | 16 ++--- files/settings-default.cfg | 12 ++-- 5 files changed, 65 insertions(+), 76 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index d2de857477..07aef455c0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -49,6 +49,7 @@ Programmers Cédric Mocquillon Chris Boyce (slothlife) Chris Robinson (KittyCat) + Cody Glassman (Wazabear) Coleman Smith (olcoal) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 865bae2f10..c14431518d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -537,7 +537,32 @@ namespace MWRender void RenderingManager::configureAmbient(const ESM::Cell *cell) { - setAmbientColour(SceneUtil::colourFromRGB(cell->mAmbi.mAmbient)); + bool needsAdjusting = false; + if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) + needsAdjusting = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx); + + auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient); + + if (needsAdjusting) + { + static constexpr float pR = 0.2126; + static constexpr float pG = 0.7152; + static constexpr float pB = 0.0722; + + // we already work in linear RGB so no conversions are needed for the luminosity function + float relativeLuminance = pR*ambient.r() + pG*ambient.g() + pB*ambient.b(); + if (relativeLuminance < mMinimumAmbientLuminance) + { + // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can + float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; + if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f) + ambient = osg::Vec4(targetBrightnessIncreaseFactor, targetBrightnessIncreaseFactor, targetBrightnessIncreaseFactor, ambient.a()); + else + ambient *= targetBrightnessIncreaseFactor; + } + } + + setAmbientColour(ambient); osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); mSunLight->setDiffuse(diffuse); @@ -1076,25 +1101,7 @@ namespace MWRender osg::Vec4f color = mAmbientColor; if (mNightEyeFactor > 0.f) - { color += osg::Vec4f(0.7, 0.7, 0.7, 0.0) * mNightEyeFactor; - } - // optionally brighten up ambient interiors when using a non-FFP emulated lighting method - else if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP) - { - static constexpr float pR = 0.2126; - static constexpr float pG = 0.7152; - static constexpr float pB = 0.0722; - - // we already work in linear RGB so no conversions are needed for the luminosity function - float relativeLuminance = pR*color.r() + pG*color.g() + pB*color.b(); - if (relativeLuminance < mMinimumAmbientLuminance) - { - // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can - float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance; - color *= targetBrightnessIncreaseFactor; - } - } mStateUpdater->setAmbientColor(color); } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index c7a4834299..f20e28a7e3 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -381,27 +381,14 @@ namespace SceneUtil void apply(osg::State &state) const override { - osg::Matrix modelViewMatrix = state.getModelViewMatrix(); - - state.applyModelViewMatrix(state.getInitialViewMatrix()); - - LightStateCache* cache = getLightStateCache(state.getContextID(), mLightManager->getMaxLights()); for (size_t i = 0; i < mLights.size(); ++i) { - osg::Light* current = cache->lastAppliedLight[i]; auto light = mLights[i]; - if (current != light.get()) - { - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Diffuse)->set(light->getDiffuse()); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Ambient)->set(light->getAmbient()); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Attenuation)->set(osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light))); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Position)->set(light->getPosition() * state.getModelViewMatrix()); - - cache->lastAppliedLight[i] = mLights[i]; - } + mLightManager->getLightUniform(i+1, LightManager::UniformKey::Diffuse)->set(light->getDiffuse()); + mLightManager->getLightUniform(i+1, LightManager::UniformKey::Ambient)->set(light->getAmbient()); + mLightManager->getLightUniform(i+1, LightManager::UniformKey::Attenuation)->set(osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light))); + mLightManager->getLightUniform(i+1, LightManager::UniformKey::Position)->set(light->getPosition() * state.getInitialViewMatrix()); } - - state.applyModelViewMatrix(modelViewMatrix); } private: @@ -643,7 +630,7 @@ namespace SceneUtil mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably // available, regardless of extensions, until GLSL 140. - mLightManager->getOrCreateStateSet()->setAttributeAndModes(mDummyProgram, osg::StateAttribute::ON|osg::StateAttribute::PROTECTED); + mLightManager->getOrCreateStateSet()->setAttributeAndModes(mDummyProgram, osg::StateAttribute::ON); } LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) @@ -756,9 +743,10 @@ namespace SceneUtil , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { + setUpdateCallback(new LightManagerUpdateCallback); + if (ffp) { - setLightingMethod(LightingMethod::FFP); initFFP(LightManager::mFFPMaxLights); return; } @@ -769,11 +757,7 @@ namespace SceneUtil { Log(Debug::Error) << "Invalid option for 'lighting method': got '" << lightingMethodString << "', expected legacy, shaders compatible, or shaders. Falling back to 'shaders compatible'."; - setLightingMethod(LightingMethod::PerObjectUniform); - } - else - { - setLightingMethod(lightingMethod); + lightingMethod = LightingMethod::PerObjectUniform; } mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f); @@ -791,7 +775,7 @@ namespace SceneUtil static bool hasLoggedWarnings = false; - if (getLightingMethod() == LightingMethod::SingleUBO && !hasLoggedWarnings) + if (lightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings) { if (!supportsUBO) Log(Debug::Warning) << "GL_ARB_uniform_buffer_object not supported: switching to shader compatibility lighting mode"; @@ -802,19 +786,13 @@ namespace SceneUtil int targetLights = Settings::Manager::getInt("max lights", "Shaders"); - if (!supportsUBO || !supportsGPU4 || getLightingMethod() == LightingMethod::PerObjectUniform) - { - setLightingMethod(LightingMethod::PerObjectUniform); + if (!supportsUBO || !supportsGPU4 || lightingMethod == LightingMethod::PerObjectUniform) initPerObjectUniform(targetLights); - } else - { initSingleUBO(targetLights); - } getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); - setUpdateCallback(new LightManagerUpdateCallback); addCullCallback(new LightManagerCullCallback(this)); } @@ -876,18 +854,18 @@ namespace SceneUtil void LightManager::initFFP(int targetLights) { + setLightingMethod(LightingMethod::FFP); setMaxLights(targetLights); for (int i = 0; i < getMaxLights(); ++i) mDummies.push_back(new FFPLightStateAttribute(i, std::vector>())); - - setUpdateCallback(new LightManagerUpdateCallback); } void LightManager::initPerObjectUniform(int targetLights) { auto* stateset = getOrCreateStateSet(); + setLightingMethod(LightingMethod::PerObjectUniform); setMaxLights(std::max(2, targetLights)); mLightUniforms.resize(getMaxLights()+1); @@ -918,6 +896,7 @@ namespace SceneUtil void LightManager::initSingleUBO(int targetLights) { + setLightingMethod(LightingMethod::SingleUBO); setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); for (int i = 0; i < 2; ++i) @@ -1059,6 +1038,7 @@ namespace SceneUtil const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { + bool isReflectionCamera = camera->getName() == "ReflectionCamera"; osg::observer_ptr camPtr (camera); auto it = mLightsInViewSpace.find(camPtr); @@ -1075,17 +1055,18 @@ namespace SceneUtil osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius * mPointLightRadiusMultiplier); transformBoundingSphere(worldViewMat, viewBound); - static const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; - - if (mPointLightFadeEnd != 0.f) + if (!isReflectionCamera) { - float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); + static const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart; + if (mPointLightFadeEnd != 0.f) + { + float fade = 1 - std::clamp((viewBound.center().length() - mPointLightFadeStart) / fadeDelta, 0.f, 1.f); + if (fade == 0.f) + continue; - if (fade == 0.f) - continue; - - auto* light = transform.mLightSource->getLight(frameNum); - light->setDiffuse(light->getDiffuse() * fade); + auto* light = transform.mLightSource->getLight(frameNum); + light->setDiffuse(light->getDiffuse() * fade); + } } LightSourceViewBound l; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 3b0f6df5a6..be38bd3ec4 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -160,12 +160,12 @@ Sets the internal handling of light sources. 'legacy' is restricted to 8 lights per object and emulates fixed function pipeline compatible lighting. -'shaders compatibility' removes the light limit via :ref:`max lights` and -follows a modifed attenuation formula which can drastically reduce light popping -and seams. This mode also enables lighting on groundcover and a configurable -light fade. It is recommended to use this with older hardware and a light limit -closer to 8. Because of its wide range of compatibility it is set as the -default. +'shaders compatibility' removes the light limit controllable through :ref:`max +lights` and follows a modifed attenuation formula which can drastically reduce +light popping and seams. This mode also enables lighting on groundcover and a +configurable light fade. It is recommended to use this with older hardware and a +light limit closer to 8. Because of its wide range of compatibility it is set as +the default. 'shaders' carries all of the benefits that 'shaders compatibility' does, but uses a modern approach that allows for a higher :ref:`max lights` count with @@ -174,9 +174,9 @@ this mode when supported and where the GPU is not a bottleneck. On some weaker devices, using this mode along with :ref:`force per pixel lighting` can carry performance penalties. -Note that when enabled, groundcover lighting is forced to be vertex lighting, +Note that when enabled groundcover lighting is forced to be vertex lighting, unless normal maps are provided. This is due to some groundcover mods using the -Z-Up Normals technique to avoid some common issues with shading. As a +Z-Up normals technique to avoid some common issues with shading. As a consequence, per pixel lighting would give undesirable results. This setting has no effect if :ref:`force shaders` is 'false'. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c931789068..de2c237e8e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -451,11 +451,11 @@ radial fog = false # 'force per pixel lighting' is enabled. lighting method = shaders compatibility -# Sets the bounding sphere multiplier of light sources, which are used to -# determine if an object should receive lighting. Higher values will allow for -# smoother transitions of light sources, but may carry a performance cost and -# requires a higher number of 'max lights' set. It is recommended to keep this -# at 1.0 with 'legacy' lighting enabled. +# Sets the bounding sphere multiplier of light sources if 'lighting method' is +# not 'legacy'. These are used to determine if an object should receive +# lighting. Higher values will allow for smoother transitions of light sources, +# but may carry a performance cost and requires a higher number of 'max lights' +# set. light bounds multiplier = 1.75 # The distance from the camera at which lights fade away completely. @@ -470,7 +470,7 @@ light fade start = 0.85 max lights = 8 # Sets minimum ambient brightness of interior cells. Levels below this threshold will have their -# ambient values adjusted to balance the darker interiors. +# ambient values adjusted to balance the darker interiors. # When 'lighting method' is set to 'legacy', this setting will have no effect. minimum interior brightness = 0.1 From 71c30a31df20242a1554c3c632172a96336a081d Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 3 Apr 2021 22:25:13 -0700 Subject: [PATCH 15/24] in-game settings, some require restart --- apps/openmw/mwgui/settingswindow.cpp | 90 +++++++++++- apps/openmw/mwgui/settingswindow.hpp | 9 +- apps/openmw/mwrender/renderingmanager.cpp | 20 ++- components/sceneutil/lightmanager.cpp | 136 +++++++++++------- components/sceneutil/lightmanager.hpp | 11 +- .../reference/modding/settings/shaders.rst | 4 +- files/mygui/openmw_settings_window.layout | 89 ++++++++++++ files/shaders/lighting.glsl | 81 +++++++---- files/shaders/water_fragment.glsl | 7 +- 9 files changed, 352 insertions(+), 95 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 538b3db5ed..a93400490c 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -107,7 +109,7 @@ namespace namespace MWGui { - void SettingsWindow::configureWidgets(MyGUI::Widget* widget) + void SettingsWindow::configureWidgets(MyGUI::Widget* widget, bool init) { MyGUI::EnumeratorWidgetPtr widgets = widget->getEnumerator(); while (widgets.next()) @@ -121,7 +123,8 @@ namespace MWGui getSettingCategory(current)) ? "#{sOn}" : "#{sOff}"; current->castType()->setCaptionWithReplacing(initialValue); - current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + if (init) + current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); } if (type == sliderType) { @@ -141,6 +144,12 @@ namespace MWGui ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; valueStr = ss.str(); } + else if (valueType == "Float") + { + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << value; + valueStr = ss.str(); + } else valueStr = MyGUI::utility::toString(int(value)); @@ -155,12 +164,13 @@ namespace MWGui valueStr = MyGUI::utility::toString(value); scroll->setScrollPosition(value); } - scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + if (init) + scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); if (scroll->getVisible()) updateSliderLabel(scroll, valueStr); } - configureWidgets(current); + configureWidgets(current, init); } } @@ -187,7 +197,7 @@ namespace MWGui getWidget(unusedSlider, widgetName); unusedSlider->setVisible(false); - configureWidgets(mMainWidget); + configureWidgets(mMainWidget, true); setTitle("#{sOptions}"); @@ -204,6 +214,9 @@ namespace MWGui getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); + getWidget(mLightingMethodButton, "LightingMethodButton"); + getWidget(mMaxLightsSlider, "MaxLightsSlider"); + getWidget(mLightsResetButton, "LightsResetButton"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -229,6 +242,9 @@ namespace MWGui mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); + mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodChanged); + mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); + mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); @@ -272,6 +288,21 @@ namespace MWGui waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); + switch (lightingMethod) + { + case SceneUtil::LightingMethod::Undefined: + case SceneUtil::LightingMethod::FFP: + mLightingMethodButton->setIndexSelected(0); + break; + case SceneUtil::LightingMethod::PerObjectUniform: + mLightingMethodButton->setIndexSelected(1); + break; + case SceneUtil::LightingMethod::SingleUBO: + mLightingMethodButton->setIndexSelected(2); + break; + } + mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); @@ -358,6 +389,49 @@ namespace MWGui apply(); } + void SettingsWindow::onLightsResetButtonClicked(MyGUI::Widget* _sender) + { + std::vector buttons = {"#{sYes}", "#{sNo}"}; + std::string message = "This will reset all lighting settings to default, some changes will require a restart. Would you like to continue?"; + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true); + int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); + if (selectedButton == 1 || selectedButton == -1) + return; + + Settings::Manager::setString("lighting method", "Shaders", "shaders compatibility"); + Settings::Manager::setFloat("light bounds multiplier", "Shaders", 1.75); + Settings::Manager::setInt("maximum light distance", "Shaders", 8192); + Settings::Manager::setFloat("light fade start", "Shaders", 0.85); + Settings::Manager::setFloat("minimum interior brightness", "Shaders", 0.1); + Settings::Manager::setInt("max lights", "Shaders", 8); + + mLightingMethodButton->setIndexSelected(1); + + apply(); + configureWidgets(mMainWidget, false); + } + + void SettingsWindow::onLightingMethodChanged(MyGUI::ComboBox* _sender, size_t pos) + { + std::string setting; + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(_sender->getItemNameAt(pos)); + switch (lightingMethod) + { + case SceneUtil::LightingMethod::FFP: + setting = "legacy"; + break; + case SceneUtil::LightingMethod::Undefined: + case SceneUtil::LightingMethod::PerObjectUniform: + setting = "shaders compatibility"; + break; + case SceneUtil::LightingMethod::SingleUBO: + setting = "shaders"; + break; + } + Settings::Manager::setString("lighting method", "Shaders", setting); + apply(); + } + void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); @@ -460,6 +534,12 @@ namespace MWGui ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; valueStr = ss.str(); } + else if (valueType == "Float") + { + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << value; + valueStr = ss.str(); + } else valueStr = MyGUI::utility::toString(int(value)); } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 6f25dd1143..3ec142731e 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -35,6 +35,10 @@ namespace MWGui MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; + MyGUI::ComboBox* mLightingMethodButton; + MyGUI::ScrollBar* mMaxLightsSlider; + MyGUI::Button* mLightsResetButton; + // controls MyGUI::ScrollView* mControlsBox; MyGUI::Button* mResetControlsButton; @@ -55,6 +59,9 @@ namespace MWGui void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); + void onLightsResetButtonClicked(MyGUI::Widget* _sender); + void onLightingMethodChanged(MyGUI::ComboBox* _sender, size_t pos); + void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); void onResetDefaultBindings(MyGUI::Widget* _sender); @@ -66,7 +73,7 @@ namespace MWGui void apply(); - void configureWidgets(MyGUI::Widget* widget); + void configureWidgets(MyGUI::Widget* widget, bool init); void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value); void layoutControlsBox(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c14431518d..e5c5ace9d5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -52,6 +52,7 @@ #include "../mwgui/loadingscreen.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" #include "sky.hpp" #include "effectmanager.hpp" @@ -224,8 +225,7 @@ namespace MWRender resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); - if (sceneRoot->getLightingMethod() != SceneUtil::LightingMethod::FFP) - mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); + mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; @@ -1144,9 +1144,25 @@ namespace MWRender else if (it->first == "General" && (it->second == "texture filter" || it->second == "texture mipmap" || it->second == "anisotropy")) + { updateTextureFiltering(); + } else if (it->first == "Water") + { mWater->processChangedSettings(changed); + } + else if (it->first == "Shaders" && it->second == "minimum interior brightness") + { + mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); + if (MWMechanics::getPlayer().getCell()) + configureAmbient(MWMechanics::getPlayer().getCell()->getCell()); + } + else if (it->first == "Shaders" && (it->second == "light bounds multiplier" || + it->second == "maximum light distance" || + it->second == "light fade start")) + { + static_cast(getLightRoot())->processChangedSettings(changed); + } } } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index f20e28a7e3..8e4c9910f8 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -10,8 +10,6 @@ #include -#include - #include namespace @@ -41,6 +39,43 @@ namespace { light->setUserValue("radius", value); } + + void configurePosition(osg::Matrixf& mat, const osg::Vec4& pos) + { + mat(0, 0) = pos.x(); + mat(0, 1) = pos.y(); + mat(0, 2) = pos.z(); + } + + void configureAmbient(osg::Matrixf& mat, const osg::Vec4& color) + { + mat(1, 0) = color.r(); + mat(1, 1) = color.g(); + mat(1, 2) = color.b(); + } + + void configureDiffuse(osg::Matrixf& mat, const osg::Vec4& color) + { + mat(2, 0) = color.r(); + mat(2, 1) = color.g(); + mat(2, 2) = color.b(); + } + + void configureSpecular(osg::Matrixf& mat, const osg::Vec4& color) + { + mat(3, 0) = color.r(); + mat(3, 1) = color.g(); + mat(3, 2) = color.b(); + mat(3, 3) = color.a(); + } + + void configureAttenuation(osg::Matrixf& mat, float c, float l, float q, float r) + { + mat(0, 3) = c; + mat(1, 3) = l; + mat(2, 3) = q; + mat(3, 3) = r; + } } namespace SceneUtil @@ -206,11 +241,12 @@ namespace SceneUtil } case LightingMethod::PerObjectUniform: { - stateset->addUniform(new osg::Uniform("LightBuffer[0].diffuse", light->getDiffuse()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].ambient", light->getAmbient()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].specular", light->getSpecular()), mode); - stateset->addUniform(new osg::Uniform("LightBuffer[0].position", light->getPosition()), mode); - + osg::Matrixf lightMat; + configurePosition(lightMat, light->getPosition()); + configureAmbient(lightMat, light->getAmbient()); + configureDiffuse(lightMat, light->getDiffuse()); + configureSpecular(lightMat, light->getSpecular()); + stateset->addUniform(new osg::Uniform("LightBuffer", lightMat), mode); break; } case LightingMethod::SingleUBO: @@ -381,14 +417,20 @@ namespace SceneUtil void apply(osg::State &state) const override { + auto* lightUniform = mLightManager->getStateSet()->getUniform("LightBuffer"); for (size_t i = 0; i < mLights.size(); ++i) { auto light = mLights[i]; - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Diffuse)->set(light->getDiffuse()); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Ambient)->set(light->getAmbient()); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Attenuation)->set(osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light))); - mLightManager->getLightUniform(i+1, LightManager::UniformKey::Position)->set(light->getPosition() * state.getInitialViewMatrix()); + osg::Matrixf lightMat; + + configurePosition(lightMat, light->getPosition() * state.getInitialViewMatrix()); + configureAmbient(lightMat, light->getAmbient()); + configureDiffuse(lightMat, light->getDiffuse()); + configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light)); + + lightUniform->setElement(i+1, lightMat); } + lightUniform->dirty(); } private: @@ -589,10 +631,12 @@ namespace SceneUtil { if (mLightManager->getLightingMethod() == LightingMethod::PerObjectUniform) { - mLightManager->getLightUniform(0, LightManager::UniformKey::Diffuse)->set(sun->getDiffuse()); - mLightManager->getLightUniform(0, LightManager::UniformKey::Ambient)->set(sun->getAmbient()); - mLightManager->getLightUniform(0, LightManager::UniformKey::Specular)->set(sun->getSpecular()); - mLightManager->getLightUniform(0, LightManager::UniformKey::Position)->set(sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + osg::Matrixf lightMat; + configurePosition(lightMat, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + configureAmbient(lightMat, sun->getAmbient()); + configureDiffuse(lightMat, sun->getDiffuse()); + configureSpecular(lightMat, sun->getSpecular()); + mLightManager->getStateSet()->getUniform("LightBuffer")->setElement(0, lightMat); } else { @@ -760,14 +804,7 @@ namespace SceneUtil lightingMethod = LightingMethod::PerObjectUniform; } - mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f); - - mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); - if (mPointLightFadeEnd > 0) - { - mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); - mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; - } + updateSettings(); osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); bool supportsUBO = exts && exts->isUniformBufferObjectSupported; @@ -839,9 +876,6 @@ namespace SceneUtil { Shader::ShaderManager::DefineMap defines; - bool ffp = usingFFP(); - - defines["ffpLighting"] = ffp ? "1" : "0"; defines["maxLights"] = std::to_string(getMaxLights()); defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene()); defines["lightingModel"] = std::to_string(static_cast(mLightingMethod)); @@ -852,6 +886,26 @@ namespace SceneUtil return defines; } + void LightManager::processChangedSettings(const Settings::CategorySettingVector& changed) + { + updateSettings(); + } + + void LightManager::updateSettings() + { + if (getLightingMethod() == LightingMethod::FFP) + return; + + mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 5.f); + + mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); + if (mPointLightFadeEnd > 0) + { + mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); + mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; + } + } + void LightManager::initFFP(int targetLights) { setLightingMethod(LightingMethod::FFP); @@ -866,38 +920,15 @@ namespace SceneUtil auto* stateset = getOrCreateStateSet(); setLightingMethod(LightingMethod::PerObjectUniform); - setMaxLights(std::max(2, targetLights)); + setMaxLights(std::clamp(targetLights, 2, 64)); - mLightUniforms.resize(getMaxLights()+1); - for (size_t i = 0; i < mLightUniforms.size(); ++i) - { - osg::ref_ptr udiffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].diffuse").c_str()); - osg::ref_ptr uspecular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].specular").c_str()); - osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); - osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); - osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); - - mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); - mLightUniforms[i].emplace(UniformKey::Ambient, uambient); - mLightUniforms[i].emplace(UniformKey::Specular, uspecular); - mLightUniforms[i].emplace(UniformKey::Position, uposition); - mLightUniforms[i].emplace(UniformKey::Attenuation, uattenuation); - - stateset->addUniform(udiffuse); - stateset->addUniform(uambient); - stateset->addUniform(uposition); - stateset->addUniform(uattenuation); - - // specular isn't used besides sun, complete waste to upload it - if (i == 0) - stateset->addUniform(uspecular); - } + stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights() + 1)); } void LightManager::initSingleUBO(int targetLights) { setLightingMethod(LightingMethod::SingleUBO); - setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); + setMaxLights(std::clamp(targetLights, 2, 64)); for (int i = 0; i < 2; ++i) { @@ -912,7 +943,6 @@ namespace SceneUtil getOrCreateStateSet()->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); } - void LightManager::setLightingMethod(LightingMethod method) { mLightingMethod = method; diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index cd8f2d3127..2fdc14a8da 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -14,6 +14,8 @@ #include +#include + namespace osgUtil { class CullVisitor; @@ -172,10 +174,10 @@ namespace SceneUtil auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; } - auto& getLightUniform(int index, UniformKey key) { return mLightUniforms[index][key]; } - std::map getLightDefines() const; + void processChangedSettings(const Settings::CategorySettingVector& changed); + private: friend class LightManagerStateAttribute; friend class LightManagerCullCallback; @@ -184,6 +186,8 @@ namespace SceneUtil void initPerObjectUniform(int targetLights); void initSingleUBO(int targetLights); + void updateSettings(); + void setLightingMethod(LightingMethod method); void setMaxLights(int value); @@ -212,9 +216,6 @@ namespace SceneUtil using LightIndexMap = std::unordered_map; LightIndexMap mLightIndexMaps[2]; - using UniformMap = std::vector>>; - UniformMap mLightUniforms; - std::unique_ptr mStateSetGenerator; LightingMethod mLightingMethod; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index be38bd3ec4..0537332a81 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -185,7 +185,7 @@ light bounds multiplier ----------------------- :Type: float -:Range: 0.0-10.0 +:Range: 0.0-5.0 :Default: 1.75 Controls the bounding sphere radius of point lights, which is used to determine @@ -228,7 +228,7 @@ max lights ---------- :Type: integer -:Range: >=2 +:Range: 2-64 :Default: 8 Sets the maximum number of lights that each object can receive lighting from. diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index babb5c28f9..ef5d4a6cb4 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -456,6 +456,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -