From 16856d45c58ae75a363285ba79602002ecc72065 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Fri, 16 Apr 2021 11:55:40 -0700 Subject: [PATCH] Lighting Patch Fixes build errors with older OSG builds and some issues with 'shared' layout. Bring back ambient in inventory through lightmodel instead of sun ambient, mirrors scene ambient/sunlight relationship. Forces shaders when certain lighting methods are enabled and finalize settings. Correctly override sun for localmap. --- apps/openmw/mwgui/settingswindow.cpp | 93 +++++--- apps/openmw/mwgui/settingswindow.hpp | 6 +- apps/openmw/mwrender/characterpreview.cpp | 11 +- apps/openmw/mwrender/localmap.cpp | 3 +- apps/openmw/mwrender/renderingmanager.cpp | 15 +- components/resource/scenemanager.cpp | 10 + components/resource/scenemanager.hpp | 4 + components/sceneutil/lightmanager.cpp | 132 ++++++------ components/sceneutil/lightmanager.hpp | 12 +- .../reference/modding/settings/shaders.rst | 14 +- files/mygui/openmw_settings_window.layout | 203 +++++++++--------- 11 files changed, 282 insertions(+), 221 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 9a7c234e6..3b4afc852 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -111,20 +111,17 @@ namespace max = MyGUI::utility::parseFloat(widget->getUserString(settingMax)); } - const char* getLightingMethodCaptionText(SceneUtil::LightingMethod lightingMethod) - { - switch (lightingMethod) - { - case SceneUtil::LightingMethod::FFP: - return "Emulates fixed function pipeline lighting, advanced light settings are disabled when this mode is active"; - case SceneUtil::LightingMethod::PerObjectUniform: - return "Removes limit of 8 lights per object, fixes lighting attenuation, and enables groundcover lighting and light fade." - "\n\nDesigned for compatibility across hardware, and is not meant for large max light counts."; - case SceneUtil::LightingMethod::SingleUBO: - return "Removes limit of 8 lights per object, fixes lighting attenuation, and enables groundcover lighting and light fade." - "\n\nDesigned for more modern hardware and large max light counts."; - } - return ""; + void updateMaxLightsComboBox(MyGUI::ComboBox* box) + { + constexpr int min = 8; + constexpr int max = 32; + constexpr int increment = 8; + int maxLights = Settings::Manager::getInt("max lights", "Shaders"); + // show increments of 8 in dropdown + if (maxLights >= min && maxLights <= max && !(maxLights % increment)) + box->setIndexSelected((maxLights / increment)-1); + else + box->setIndexSelected(MyGUI::ITEM_NONE); } } @@ -235,9 +232,9 @@ namespace MWGui getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); - getWidget(mLightingMethodText, "LightingMethodText"); + getWidget(mLightingMethodButton, "LightingMethodButton"); getWidget(mLightsResetButton, "LightsResetButton"); - getWidget(mLightSettingOverlay, "LightSettingOverlay"); + getWidget(mMaxLights, "MaxLights"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -263,7 +260,9 @@ namespace MWGui mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); + mLightingMethodButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onLightingMethodButtonChanged); mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked); + mMaxLights->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onMaxLightsChanged); mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); @@ -310,6 +309,8 @@ namespace MWGui waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); + updateMaxLightsComboBox(mMaxLights); + mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); @@ -396,19 +397,50 @@ namespace MWGui apply(); } + void SettingsWindow::onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos) + { + if (pos == MyGUI::ITEM_NONE) + return; + + std::string message = "This change requires a restart to take effect."; + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, {"#{sOK}"}, true); + + Settings::Manager::setString("lighting method", "Shaders", _sender->getItemNameAt(pos)); + apply(); + } + + void SettingsWindow::onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos) + { + int count = 8 * (pos + 1); + + Settings::Manager::setInt("max lights", "Shaders", count); + apply(); + configureWidgets(mMainWidget, false); + } + void SettingsWindow::onLightsResetButtonClicked(MyGUI::Widget* _sender) { std::vector buttons = {"#{sYes}", "#{sNo}"}; - std::string message = "Resets to default values, would you like to continue?"; + std::string message = "Resets to default values, would you like to continue? Changes to lighting method will require a restart."; MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true); int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); if (selectedButton == 1 || selectedButton == -1) return; - constexpr std::array settings = {"light bounds multiplier", "maximum light distance", "light fade start", "minimum interior brightness", "max lights"}; + constexpr std::array settings = { + "light bounds multiplier", + "maximum light distance", + "light fade start", + "minimum interior brightness", + "max lights", + "lighting method", + }; for (const auto& setting : settings) Settings::Manager::setString(setting, "Shaders", Settings::Manager::mDefaultSettings[{"Shaders", setting}]); + mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(Settings::Manager::mDefaultSettings[{"Shaders", "lighting method"}])); + updateMaxLightsComboBox(mMaxLights); + apply(); configureWidgets(mMainWidget, false); } @@ -614,20 +646,25 @@ namespace MWGui void SettingsWindow::updateLightSettings() { auto lightingMethod = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getLightingMethod(); - mLightingMethodText->setCaption(SceneUtil::LightManager::getLightingMethodString(lightingMethod)); + std::string lightingMethodStr = SceneUtil::LightManager::getLightingMethodString(lightingMethod); + + mLightingMethodButton->removeAllItems(); + + std::array methods = { + SceneUtil::LightingMethod::FFP, + SceneUtil::LightingMethod::PerObjectUniform, + SceneUtil::LightingMethod::SingleUBO, + }; - if (lightingMethod == SceneUtil::LightingMethod::FFP || !Settings::Manager::getBool("force shaders", "Shaders")) + for (const auto& method : methods) { - MyGUI::Widget* parent = mLightSettingOverlay->getParent(); - mLightSettingOverlay->setEnabled(false); - mLightSettingOverlay->setAlpha(0.8); - parent->setUserString("ToolTipType", "Layout"); - parent->setUserString("ToolTipLayout", "TextToolTip"); - parent->setUserString("Caption_Text", "Unavailable with current settings."); - parent->setEnabled(true); + if (!MWBase::Environment::get().getResourceSystem()->getSceneManager()->isSupportedLightingMethod(method)) + continue; + + mLightingMethodButton->addItem(SceneUtil::LightManager::getLightingMethodString(method)); } - mLightingMethodText->setUserString("Caption_Text", getLightingMethodCaptionText(lightingMethod)); + mLightingMethodButton->setIndexSelected(mLightingMethodButton->findItemIndexWith(lightingMethodStr)); } void SettingsWindow::layoutControlsBox() diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 0baf52c40..9c28733f9 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -32,8 +32,8 @@ namespace MWGui MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; - MyGUI::Widget* mLightSettingOverlay; - MyGUI::TextBox* mLightingMethodText; + MyGUI::ComboBox* mMaxLights; + MyGUI::ComboBox* mLightingMethodButton; MyGUI::Button* mLightsResetButton; // controls @@ -56,7 +56,9 @@ namespace MWGui void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); + void onLightingMethodButtonChanged(MyGUI::ComboBox* _sender, size_t pos); void onLightsResetButtonClicked(MyGUI::Widget* _sender); + void onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos); void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 63dd5ff70..62011e8bd 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -233,7 +233,16 @@ namespace MWRender float positionZ = std::cos(altitude); light->setPosition(osg::Vec4(positionX,positionY,positionZ, 0.0)); light->setDiffuse(osg::Vec4(diffuseR,diffuseG,diffuseB,1)); - light->setAmbient(osg::Vec4(ambientR,ambientG,ambientB,1)); + osg::Vec4 ambientRGBA = osg::Vec4(ambientR,ambientG,ambientB,1); + if (mResourceSystem->getSceneManager()->getForceShaders()) + { + // When using shaders, we now skip the ambient sun calculation as this is the only place it's used. + // Using the scene ambient will give identical results. + lightmodel->setAmbientIntensity(ambientRGBA); + light->setAmbient(osg::Vec4(0,0,0,1)); + } + else + light->setAmbient(ambientRGBA); light->setSpecular(osg::Vec4(0,0,0,0)); light->setLightNum(0); light->setConstantAttenuation(1.f); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 9a1623246..c2f0c9d2b 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -224,8 +224,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); // override sun for local map - auto lightingMethod = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getLightingMethod(); - SceneUtil::configureStateSetSunOverride(lightingMethod, light, stateset); + SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot.get()), light, stateset); camera->addChild(lightSource); camera->setStateSet(stateset); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2e342ad3a..16e0a351f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -201,11 +201,15 @@ namespace MWRender , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) { + auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); + resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); - bool explicitlyForceShaders = Settings::Manager::getBool("force shaders", "Shaders"); // Shadows and radial fog have problems with fixed-function mode - bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || explicitlyForceShaders || Settings::Manager::getBool("enable shadows", "Shadows"); + bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") + || Settings::Manager::getBool("force shaders", "Shaders") + || Settings::Manager::getBool("enable shadows", "Shadows") + || lightingMethod != SceneUtil::LightingMethod::FFP; 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")); @@ -217,12 +221,11 @@ namespace MWRender 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); - auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this depends on support for various OpenGL extensions. - osg::ref_ptr sceneRoot = new SceneUtil::LightManager(!explicitlyForceShaders || lightingMethod == SceneUtil::LightingMethod::FFP); + osg::ref_ptr sceneRoot = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); - + resourceSystem->getSceneManager()->setSupportedLightingMethods(sceneRoot->getSupportedLightingMethods()); mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); sceneRoot->setLightingMask(Mask_Lighting); @@ -554,7 +557,7 @@ namespace MWRender // 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()); + ambient = osg::Vec4(mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a()); else ambient *= targetBrightnessIncreaseFactor; } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 18fe3dae4..a3c751f7a 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -305,6 +305,16 @@ namespace Resource mApplyLightingToEnvMaps = apply; } + void SceneManager::setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported) + { + mSupportedLightingMethods = supported; + } + + bool SceneManager::isSupportedLightingMethod(SceneUtil::LightingMethod method) const + { + return mSupportedLightingMethods[static_cast(method)]; + } + void SceneManager::setLightingMethod(SceneUtil::LightingMethod method) { mLightingMethod = method; diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index fdc7d334f..7635cd20f 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -107,6 +107,9 @@ namespace Resource void setApplyLightingToEnvMaps(bool apply); + void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); + bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; + void setLightingMethod(SceneUtil::LightingMethod method); SceneUtil::LightingMethod getLightingMethod() const; @@ -197,6 +200,7 @@ namespace Resource std::string mSpecularMapPattern; bool mApplyLightingToEnvMaps; SceneUtil::LightingMethod mLightingMethod; + SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods; bool mConvertAlphaTestToAlphaToCoverage; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index c03bbca59..4b80b6f98 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include @@ -102,6 +104,7 @@ namespace SceneUtil , mEndian(osg::getCpuByteOrder()) , mCount(count) , mStride(12) + , mCachedSunPos(osg::Vec4()) { mOffsets[Diffuse] = 0; mOffsets[Ambient] = 1; @@ -118,6 +121,7 @@ namespace SceneUtil , mCount(copy.mCount) , mStride(copy.mStride) , mOffsets(copy.mOffsets) + , mCachedSunPos(copy.mCachedSunPos) {} void setDiffuse(int index, const osg::Vec4& value) @@ -172,6 +176,17 @@ namespace SceneUtil return 3 * osg::Vec4::num_components * sizeof(GL_FLOAT) * sz; } + void setCachedSunPos(const osg::Vec4& pos) + { + mCachedSunPos = pos; + } + + void uploadCachedSunPos(const osg::Matrix& viewMat) + { + osg::Vec4 viewPos = mCachedSunPos * viewMat; + std::memcpy(&(*mData)[getOffset(0, Position)], viewPos.ptr(), sizeof(osg::Vec4f)); + } + unsigned int asRGBA(const osg::Vec4& value) const { return mEndian == osg::BigEndian ? value.asABGR() : value.asRGBA(); @@ -187,6 +202,8 @@ namespace SceneUtil constexpr auto sizeofFloat = sizeof(GL_FLOAT); constexpr auto sizeofVec4 = sizeofFloat * osg::Vec4::num_components; + LightBuffer oldBuffer = LightBuffer(*this); + mOffsets[Diffuse] = offsetColors / sizeofFloat; mOffsets[Ambient] = mOffsets[Diffuse] + 1; mOffsets[Specular] = mOffsets[Diffuse] + 2; @@ -196,7 +213,6 @@ namespace SceneUtil mStride = (offsetAttenuationRadius + sizeofVec4 + stride) / 4; // Copy over previous buffers light data. Buffers populate before we know the layout. - LightBuffer oldBuffer = LightBuffer(*this); mData->resize(size / sizeofFloat); for (int i = 0; i < oldBuffer.mCount; ++i) { @@ -212,6 +228,7 @@ namespace SceneUtil int mCount; int mStride; std::array mOffsets; + osg::Vec4 mCachedSunPos; }; class LightStateCache @@ -229,8 +246,9 @@ namespace SceneUtil return &cacheVector[contextid]; } - void configureStateSetSunOverride(LightingMethod method, const osg::Light* light, osg::StateSet* stateset, int mode) + void configureStateSetSunOverride(LightManager* lightManager, const osg::Light* light, osg::StateSet* stateset, int mode) { + auto method = lightManager->getLightingMethod(); switch (method) { case LightingMethod::FFP: @@ -244,12 +262,15 @@ namespace SceneUtil configureAmbient(lightMat, light->getAmbient()); configureDiffuse(lightMat, light->getDiffuse()); configureSpecular(lightMat, light->getSpecular()); - stateset->addUniform(new osg::Uniform("LightBuffer", lightMat), mode); + + osg::ref_ptr uni = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", lightManager->getMaxLights()); + uni->setElement(0, lightMat); + stateset->addUniform(uni, mode); break; } case LightingMethod::SingleUBO: { - osg::ref_ptr buffer = new LightBuffer(1); + osg::ref_ptr buffer = new LightBuffer(lightManager->getMaxLightsInScene()); buffer->setDiffuse(0, light->getDiffuse()); buffer->setAmbient(0, light->getAmbient()); @@ -258,8 +279,11 @@ namespace SceneUtil osg::ref_ptr ubo = new osg::UniformBufferObject; buffer->getData()->setBufferObject(ubo); - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->getData().get(), 0, buffer->getData()->getTotalDataSize()); - +#if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); +#else + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), ubo, 0, buffer->getData()->getTotalDataSize()); +#endif stateset->setAttributeAndModes(ubb, mode); break; @@ -433,6 +457,11 @@ namespace SceneUtil lightUniform->setElement(i+1, lightMat); } + + auto sun = mLightManager->getSunlightBuffer(state.getFrameStamp()->getFrameNumber()); + configurePosition(sun, osg::Vec4(sun(0,0), sun(0,1), sun(0,2), 0.0) * state.getInitialViewMatrix()); + lightUniform->setElement(0, sun); + lightUniform->dirty(); } @@ -618,7 +647,6 @@ namespace SceneUtil void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); - bool pop = false; if (mLastFrameNumber != cv->getTraversalNumber()) { @@ -628,18 +656,24 @@ namespace SceneUtil { 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()); - stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); + +#if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); +#else + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData()->getBufferObject(), 0, bo->getData()->getTotalDataSize()); +#endif + stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); } auto sun = mLightManager->getSunlight(); if (sun) { + // we must defer uploading the transformation to view-space position to deal with different cameras (e.g. reflection RTT). if (mLightManager->getLightingMethod() == LightingMethod::PerObjectUniform) { osg::Matrixf lightMat; - configurePosition(lightMat, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + configurePosition(lightMat, sun->getPosition()); configureAmbient(lightMat, sun->getAmbient()); configureDiffuse(lightMat, sun->getDiffuse()); configureSpecular(lightMat, sun->getSpecular()); @@ -649,33 +683,15 @@ namespace SceneUtil { auto buf = mLightManager->getLightBuffer(mLastFrameNumber); - buf->setPosition(0, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); + buf->setCachedSunPos(sun->getPosition()); buf->setAmbient(0, sun->getAmbient()); buf->setDiffuse(0, sun->getDiffuse()); buf->setSpecular(0, sun->getSpecular()); } } } - else if (isReflectionCamera(cv->getCurrentCamera())) - { - auto sun = mLightManager->getSunlight(); - if (sun) - { - osg::Vec4 originalPos = sun->getPosition(); - sun->setPosition(originalPos * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); - - osg::ref_ptr stateset = new osg::StateSet; - configureStateSetSunOverride(mLightManager->getLightingMethod(), sun, stateset); - - sun->setPosition(originalPos); - cv->pushStateSet(stateset); - pop = true; - } - } traverse(node, nv); - if (pop) - cv->popStateSet(); } private: @@ -692,6 +708,7 @@ namespace SceneUtil LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) , mDummyProgram(new osg::Program) + , mInitLayout(false) { static const std::string dummyVertSource = generateDummyShader(mLightManager->getMaxLightsInScene()); @@ -741,8 +758,7 @@ namespace SceneUtil void apply(osg::State& state) const override { - static bool init = false; - if (!init) + if (!mInitLayout) { auto handle = mDummyProgram->getPCP(state)->getHandle(); auto* ext = state.get(); @@ -754,13 +770,11 @@ namespace SceneUtil if (activeUniformBlocks > 0) { initSharedLayout(ext, handle); - init = true; + mInitLayout = true; } } - else - { - mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); - } + mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->uploadCachedSunPos(state.getInitialViewMatrix()); + mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); } private: @@ -792,36 +806,7 @@ namespace SceneUtil LightManager* mLightManager; osg::ref_ptr mDummyProgram; - }; - - class LightManagerStateAttributePerObjectUniform : public osg::StateAttribute - { - public: - LightManagerStateAttributePerObjectUniform() - : mLightManager(nullptr) {} - - LightManagerStateAttributePerObjectUniform(LightManager* lightManager) - : mLightManager(lightManager) - { - } - - LightManagerStateAttributePerObjectUniform(const LightManagerStateAttributePerObjectUniform& 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("LightManagerStateAttributePerObjectUniform::compare: unimplemented"); - } - - META_StateAttribute(NifOsg, LightManagerStateAttributePerObjectUniform, osg::StateAttribute::LIGHT) - - void apply(osg::State& state) const override - { - mLightManager->getStateSet()->getUniform("LightBuffer")->setElement(0, mLightManager->getSunlightBuffer(state.getFrameStamp()->getFrameNumber())); - } - - private: - LightManager* mLightManager; + mutable bool mInitLayout; }; const std::unordered_map LightManager::mLightingMethodSettingMap = { @@ -857,6 +842,14 @@ namespace SceneUtil , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { + osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); + bool supportsUBO = exts && exts->isUniformBufferObjectSupported; + bool supportsGPU4 = exts && exts->isGpuShader4Supported; + + mSupported[static_cast(LightingMethod::FFP)] = true; + mSupported[static_cast(LightingMethod::PerObjectUniform)] = true; + mSupported[static_cast(LightingMethod::SingleUBO)] = supportsUBO && supportsGPU4; + setUpdateCallback(new LightManagerUpdateCallback); if (ffp) @@ -870,10 +863,6 @@ namespace SceneUtil updateSettings(); - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - bool supportsUBO = exts && exts->isUniformBufferObjectSupported; - bool supportsGPU4 = exts && exts->isGpuShader4Supported; - static bool hasLoggedWarnings = false; if (lightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings) @@ -1042,7 +1031,8 @@ namespace SceneUtil setLightingMethod(LightingMethod::PerObjectUniform); setMaxLights(std::clamp(targetLights, mMaxLightsLowerLimit, LightManager::mMaxLightsUpperLimit)); - stateset->setAttributeAndModes(new LightManagerStateAttributePerObjectUniform(this), osg::StateAttribute::ON); + // ensures sunlight element in our uniform array is updated when there are no point lights in scene + stateset->setAttributeAndModes(new LightStateAttributePerObjectUniform({}, this), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights())); } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 9a1fd5470..dc7f36e96 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -24,7 +25,7 @@ namespace osgUtil namespace SceneUtil { class LightBuffer; - class StateSetGenerator; + struct StateSetGenerator; enum class LightingMethod { @@ -33,8 +34,6 @@ namespace SceneUtil SingleUBO, }; - void configureStateSetSunOverride(LightingMethod method, const osg::Light* light, osg::StateSet* stateset, int mode = osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - /// LightSource managed by a LightManager. /// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole scene /// so do not need to be managed by a LightManager - so for directional lights use a plain osg::LightSource instead. @@ -130,6 +129,7 @@ namespace SceneUtil }; using LightList = std::vector; + using SupportedMethods = std::array; META_Node(SceneUtil, LightManager) @@ -178,6 +178,8 @@ namespace SceneUtil osg::Matrixf getSunlightBuffer(size_t frameNum) const { return mSunlightBuffers[frameNum%2]; } void setSunlightBuffer(const osg::Matrixf& buffer, size_t frameNum) { mSunlightBuffers[frameNum%2] = buffer; } + SupportedMethods getSupportedLightingMethods() { return mSupported; } + std::map getLightDefines() const; void processChangedSettings(const Settings::CategorySettingVector& changed); @@ -232,6 +234,8 @@ namespace SceneUtil int mMaxLights; + SupportedMethods mSupported; + static constexpr auto mMaxLightsLowerLimit = 2; static constexpr auto mMaxLightsUpperLimit = 64; static constexpr auto mFFPMaxLights = 8; @@ -277,6 +281,8 @@ namespace SceneUtil std::set mIgnoredLightSources; }; + void configureStateSetSunOverride(LightManager* lightManager, const osg::Light* light, osg::StateSet* stateset, int mode = osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } #endif diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 4814b206b..03b7805de 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -174,12 +174,14 @@ 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'. +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. + +Note that the rendering will act as if you have 'force shaders' option enabled +when not set to 'legacy'. This means that shaders will be used to render all objects and +the terrain. light bounds multiplier ----------------------- diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 11ad9fd39..92c185413 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -460,109 +460,108 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +