From dda735c54a0e4676c4d0c99b4b0fe14ebeab79d8 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 21 Feb 2021 10:38:15 -0800 Subject: [PATCH] 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 | 573 +++++++++++++++++----- components/sceneutil/lightmanager.hpp | 88 +++- 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, 698 insertions(+), 166 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,14 +155,143 @@ namespace SceneUtil return &cacheVector[contextid]; } - // Resets the modelview matrix to just the view matrix before applying lights. + 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: + 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: + size_t mIndex; + osg::Vec4f mnullptr; + }; + class LightStateAttribute : public osg::StateAttribute { public: - LightStateAttribute() : mIndex(0) {} - LightStateAttribute(unsigned int index, const std::vector >& lights) : mIndex(index), mLights(lights) {} + 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: + 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 @@ -38,17 +301,17 @@ namespace SceneUtil bool getModeUsage(ModeUsage & usage) const override { - for (unsigned int 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) + { + } + + 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(unsigned int mask) + 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::getSunlight() + { + return mSun; + } + + osg::ref_ptr LightManager::getSunBuffer() + { + return mSunBuffer; } - osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, unsigned int frameNum) + 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); - - for (unsigned int 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); + 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 (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 (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,10 +107,25 @@ namespace SceneUtil class LightManager : public osg::Group { public: + struct LightSourceTransform + { + LightSource* mLightSource; + osg::Matrixf mWorldMatrix; + }; + + struct LightSourceViewBound + { + LightSource* mLightSource; + osg::BoundingSphere mViewBound; + }; + + typedef std::vector LightList; + + static bool queryNonFFPLightingSupport(); META_Node(SceneUtil, LightManager) - LightManager(); + LightManager(bool ffp = true); LightManager(const LightManager& copy, const osg::CopyOp& copyop); @@ -94,40 +133,36 @@ 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 (unsigned int mask); - - unsigned int getLightingMask() const; + 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, unsigned int frameNum); - - struct LightSourceTransform - { - LightSource* mLightSource; - osg::Matrixf mWorldMatrix; - }; + void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); const std::vector& getLights() const; - struct LightSourceViewBound - { - LightSource* mLightSource; - osg::BoundingSphere mViewBound; - }; + const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum); - const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix); + osg::ref_ptr getLightListStateSet(const LightList& lightList, size_t frameNum); - typedef std::vector LightList; + void setSunlight(osg::ref_ptr sun); + osg::ref_ptr getSunlight(); + + osg::ref_ptr getSunBuffer(); - osg::ref_ptr getLightListStateSet(const LightList& lightList, unsigned int frameNum); + 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 +{ + 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 = gl_LightSource[lightIndex].position.xyz - viewPos * gl_LightSource[lightIndex].position.w; + 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