diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 25ac051f4..31778b904 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 79f34c42e..229c34f45 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 3c1f31364..72470b8c2 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,104 +593,88 @@ 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) { - mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); - if (mPointLightFadeEnd > 0) + if (ffp) { - mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); - mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; + setLightingMethod(LightingMethod::FFP); + initFFP(LightManager::mFFPMaxLights); + return; } - 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) + std::string lightingMethodString = Settings::Manager::getString("lighting method", "Shaders"); + auto lightingMethod = LightManager::getLightingMethodFromString(lightingMethodString); + if (lightingMethod == LightingMethod::Undefined) { - setMaxLights(LightManager::mFFPMaxLights); - setLightingMethod(LightingMethod::FFP); + 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); + } - for (int i=0; i >())); + mPointLightRadiusMultiplier = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 10.f); - setUpdateCallback(new LightManagerUpdateCallback); - return; + 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; } 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 214ffa138..cd8f2d312 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 12980dffc..3a5b46440 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 6af5e9ba0..ddffbfefb 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 102c1aa66..952559588 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 - -# 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 - -# The distance from the camera at which lights fade away completely. Set to 0 to disable fading. +# 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.75 + +# 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 c6c03f223..3678a3570 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 1397f65e7..06a321301 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"