Merge branch 'why_are_the_christmas_lights_still_up' into 'master'

Remove 8 light limit and add configurable lighting enhancements

See merge request OpenMW/openmw!618
pull/593/head
psi29a 4 years ago
commit f1cfdafd4d

@ -49,6 +49,7 @@ Programmers
Cédric Mocquillon
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Cody Glassman (Wazabear)
Coleman Smith (olcoal)
Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam)

@ -152,6 +152,7 @@
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
Feature #5813: Instanced groundcover support
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
Feature #5828: Support more than 8 lights
Feature #5910: Fall back to delta time when physics can't keep up
Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation

@ -1,5 +1,7 @@
#include "advancedpage.hpp"
#include <array>
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include <QFileDialog>
@ -138,6 +140,13 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera")));
int lightingMethod = 1;
if (mEngineSettings.getString("lighting method", "Shaders") == "legacy")
lightingMethod = 0;
else if (mEngineSettings.getString("lighting method", "Shaders") == "shaders")
lightingMethod = 2;
lightingMethodComboBox->setCurrentIndex(lightingMethod);
}
// Audio
@ -288,6 +297,9 @@ void Launcher::AdvancedPage::saveSettings()
{
mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance));
}
static std::array<std::string, 3> lightingMethodMap = {"legacy", "shaders compatibility", "shaders"};
mEngineSettings.setString("lighting method", "Shaders", lightingMethodMap[lightingMethodComboBox->currentIndex()]);
}
// Audio

@ -83,6 +83,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
defines["clamp"] = "1"; // Clamp lighting
defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind
defines["radialFog"] = "0";
defines["lightingModel"] = "0";
for (const auto& define : shadowDefines)
defines[define.first] = define.second;
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);

@ -11,12 +11,16 @@
#include <iomanip>
#include <numeric>
#include <array>
#include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/constants.hpp>
#include <components/widgets/sharedstatebutton.hpp>
#include <components/settings/settings.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -106,11 +110,27 @@ namespace
if (!widget->getUserString(settingMax).empty())
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 "";
}
}
namespace MWGui
{
void SettingsWindow::configureWidgets(MyGUI::Widget* widget)
void SettingsWindow::configureWidgets(MyGUI::Widget* widget, bool init)
{
MyGUI::EnumeratorWidgetPtr widgets = widget->getEnumerator();
while (widgets.next())
@ -124,7 +144,8 @@ namespace MWGui
getSettingCategory(current))
? "#{sOn}" : "#{sOff}";
current->castType<MyGUI::Button>()->setCaptionWithReplacing(initialValue);
current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
if (init)
current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
}
if (type == sliderType)
{
@ -144,6 +165,12 @@ namespace MWGui
ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits;
valueStr = ss.str();
}
else if (valueType == "Float")
{
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << value;
valueStr = ss.str();
}
else
valueStr = MyGUI::utility::toString(int(value));
@ -158,12 +185,13 @@ namespace MWGui
valueStr = MyGUI::utility::toString(value);
scroll->setScrollPosition(value);
}
scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
if (init)
scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
if (scroll->getVisible())
updateSliderLabel(scroll, valueStr);
}
configureWidgets(current);
configureWidgets(current, init);
}
}
@ -190,7 +218,7 @@ namespace MWGui
getWidget(unusedSlider, widgetName);
unusedSlider->setVisible(false);
configureWidgets(mMainWidget);
configureWidgets(mMainWidget, true);
setTitle("#{sOptions}");
@ -207,6 +235,9 @@ namespace MWGui
getWidget(mControllerSwitch, "ControllerButton");
getWidget(mWaterTextureSize, "WaterTextureSize");
getWidget(mWaterReflectionDetail, "WaterReflectionDetail");
getWidget(mLightingMethodText, "LightingMethodText");
getWidget(mLightsResetButton, "LightsResetButton");
getWidget(mLightSettingOverlay, "LightSettingOverlay");
#ifndef WIN32
// hide gamma controls since it currently does not work under Linux
@ -232,6 +263,8 @@ namespace MWGui
mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged);
mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged);
mLightsResetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onLightsResetButtonClicked);
mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked);
mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked);
@ -363,6 +396,23 @@ namespace MWGui
apply();
}
void SettingsWindow::onLightsResetButtonClicked(MyGUI::Widget* _sender)
{
std::vector<std::string> buttons = {"#{sYes}", "#{sNo}"};
std::string message = "Resets to default values, would you like to continue?";
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true);
int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton();
if (selectedButton == 1 || selectedButton == -1)
return;
constexpr std::array<const char*, 5> settings = {"light bounds multiplier", "maximum light distance", "light fade start", "minimum interior brightness", "max lights"};
for (const auto& setting : settings)
Settings::Manager::setString(setting, "Shaders", Settings::Manager::mDefaultSettings[{"Shaders", setting}]);
apply();
configureWidgets(mMainWidget, false);
}
void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender)
{
std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On");
@ -465,6 +515,12 @@ namespace MWGui
ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits;
valueStr = ss.str();
}
else if (valueType == "Float")
{
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << value;
valueStr = ss.str();
}
else
valueStr = MyGUI::utility::toString(int(value));
}
@ -555,6 +611,25 @@ namespace MWGui
layoutControlsBox();
}
void SettingsWindow::updateLightSettings()
{
auto lightingMethod = MWBase::Environment::get().getResourceSystem()->getSceneManager()->getLightingMethod();
mLightingMethodText->setCaption(SceneUtil::LightManager::getLightingMethodString(lightingMethod));
if (lightingMethod == SceneUtil::LightingMethod::FFP || !Settings::Manager::getBool("force shaders", "Shaders"))
{
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);
}
mLightingMethodText->setUserString("Caption_Text", getLightingMethodCaptionText(lightingMethod));
}
void SettingsWindow::layoutControlsBox()
{
const int h = 18;
@ -617,6 +692,7 @@ namespace MWGui
{
highlightCurrentResolution();
updateControlsBox();
updateLightSettings();
resetScrollbars();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
}

@ -14,6 +14,8 @@ namespace MWGui
void updateControlsBox();
void updateLightSettings();
void onResChange(int, int) override { center(); }
protected:
@ -30,6 +32,10 @@ namespace MWGui
MyGUI::ComboBox* mWaterTextureSize;
MyGUI::ComboBox* mWaterReflectionDetail;
MyGUI::Widget* mLightSettingOverlay;
MyGUI::TextBox* mLightingMethodText;
MyGUI::Button* mLightsResetButton;
// controls
MyGUI::ScrollView* mControlsBox;
MyGUI::Button* mResetControlsButton;
@ -50,6 +56,8 @@ namespace MWGui
void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos);
void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos);
void onLightsResetButtonClicked(MyGUI::Widget* _sender);
void onRebindAction(MyGUI::Widget* _sender);
void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel);
void onResetDefaultBindings(MyGUI::Widget* _sender);
@ -61,7 +69,7 @@ namespace MWGui
void apply();
void configureWidgets(MyGUI::Widget* widget);
void configureWidgets(MyGUI::Widget* widget, bool init);
void updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value);
void layoutControlsBox();

@ -499,6 +499,11 @@ namespace MWRender
mAlpha = alpha;
}
void setLightSource(const osg::ref_ptr<SceneUtil::LightSource>& lightSource)
{
mLightSource = lightSource;
}
protected:
void setDefaults(osg::StateSet* stateset) override
{
@ -521,10 +526,13 @@ namespace MWRender
{
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha);
if (mLightSource)
mLightSource->setActorFade(mAlpha);
}
private:
float mAlpha;
osg::ref_ptr<SceneUtil::LightSource> mLightSource;
};
struct Animation::AnimSource
@ -1613,7 +1621,7 @@ namespace MWRender
{
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior);
mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior);
}
void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture)
@ -1763,6 +1771,7 @@ namespace MWRender
if (mTransparencyUpdater == nullptr)
{
mTransparencyUpdater = new TransparencyUpdater(alpha);
mTransparencyUpdater->setLightSource(mExtraLightSource);
mObjectRoot->addCullCallback(mTransparencyUpdater);
}
else

@ -278,6 +278,7 @@ protected:
osg::ref_ptr<SceneUtil::LightSource> mGlowLight;
osg::ref_ptr<SceneUtil::GlowUpdater> mGlowUpdater;
osg::ref_ptr<TransparencyUpdater> mTransparencyUpdater;
osg::ref_ptr<SceneUtil::LightSource> mExtraLightSource;
float mAlpha;

@ -174,7 +174,9 @@ namespace MWRender
mCamera->setNodeMask(Mask_RenderToTexture);
osg::ref_ptr<SceneUtil::LightManager> lightManager = new SceneUtil::LightManager;
bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP;
osg::ref_ptr<SceneUtil::LightManager> lightManager = new SceneUtil::LightManager(ffp);
lightManager->setStartLight(1);
osg::ref_ptr<osg::StateSet> stateset = lightManager->getOrCreateStateSet();
stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
@ -237,6 +239,7 @@ namespace MWRender
light->setConstantAttenuation(1.f);
light->setLinearAttenuation(0.f);
light->setQuadraticAttenuation(0.f);
lightManager->setSunlight(light);
osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource;
lightSource->setLight(light);

@ -5,6 +5,7 @@
#include <osg/VertexAttribDivisor>
#include <components/esm/esmreader.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include "apps/openmw/mwworld/esmstore.hpp"
#include "apps/openmw/mwbase/environment.hpp"
@ -271,6 +272,8 @@ namespace MWRender
group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON);
group->getBound();
group->setNodeMask(Mask_Groundcover);
if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP)
group->setCullCallback(new SceneUtil::LightListCallback);
mSceneManager->recreateShaders(group, "groundcover", false, true);
return group;

@ -19,7 +19,10 @@
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/shadow.hpp>
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/files/memorystream.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -220,6 +223,10 @@ osg::ref_ptr<osg::Camera> 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);
camera->addChild(lightSource);
camera->setStateSet(stateset);
camera->setViewport(0, 0, mMapResolution, mMapResolution);

@ -52,6 +52,7 @@
#include "../mwgui/loadingscreen.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "sky.hpp"
#include "effectmanager.hpp"
@ -195,14 +196,16 @@ namespace MWRender
, mWorkQueue(workQueue)
, mUnrefQueue(new SceneUtil::UnrefQueue)
, mNavigator(navigator)
, mMinimumAmbientLuminance(0.f)
, mNightEyeFactor(0.f)
, mFieldOfViewOverridden(false)
, mFieldOfViewOverride(0.f)
{
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") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows");
bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || explicitlyForceShaders || Settings::Manager::getBool("enable shadows", "Shadows");
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"));
@ -214,7 +217,14 @@ 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);
osg::ref_ptr<SceneUtil::LightManager> sceneRoot = new SceneUtil::LightManager;
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<SceneUtil::LightManager> sceneRoot = new SceneUtil::LightManager(!explicitlyForceShaders || lightingMethod == SceneUtil::LightingMethod::FFP);
resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod());
resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod());
mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f);
sceneRoot->setLightingMask(Mask_Lighting);
mSceneRoot = sceneRoot;
sceneRoot->setStartLight(1);
@ -236,6 +246,7 @@ 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 = shadowDefines.begin(); itr != shadowDefines.end(); itr++)
@ -247,6 +258,9 @@ namespace MWRender
globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0";
globalDefines["useGPUShader4"] = "0";
for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++)
globalDefines[itr->first] = itr->second;
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);
@ -352,6 +366,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);
@ -520,7 +535,32 @@ namespace MWRender
void RenderingManager::configureAmbient(const ESM::Cell *cell)
{
setAmbientColour(SceneUtil::colourFromRGB(cell->mAmbi.mAmbient));
bool needsAdjusting = false;
if (mResourceSystem->getSceneManager()->getLightingMethod() != SceneUtil::LightingMethod::FFP)
needsAdjusting = !cell->isExterior() && !(cell->mData.mFlags & ESM::Cell::QuasiEx);
auto ambient = SceneUtil::colourFromRGB(cell->mAmbi.mAmbient);
if (needsAdjusting)
{
constexpr float pR = 0.2126;
constexpr float pG = 0.7152;
constexpr float pB = 0.0722;
// we already work in linear RGB so no conversions are needed for the luminosity function
float relativeLuminance = pR*ambient.r() + pG*ambient.g() + pB*ambient.b();
if (relativeLuminance < mMinimumAmbientLuminance)
{
// brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data as least we can
float targetBrightnessIncreaseFactor = mMinimumAmbientLuminance / relativeLuminance;
if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f)
ambient = osg::Vec4(targetBrightnessIncreaseFactor, targetBrightnessIncreaseFactor, targetBrightnessIncreaseFactor, ambient.a());
else
ambient *= targetBrightnessIncreaseFactor;
}
}
setAmbientColour(ambient);
osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight);
mSunLight->setDiffuse(diffuse);
@ -1103,9 +1143,47 @@ namespace MWRender
else if (it->first == "General" && (it->second == "texture filter" ||
it->second == "texture mipmap" ||
it->second == "anisotropy"))
{
updateTextureFiltering();
}
else if (it->first == "Water")
{
mWater->processChangedSettings(changed);
}
else if (it->first == "Shaders" && it->second == "minimum interior brightness")
{
mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f);
if (MWMechanics::getPlayer().isInCell())
configureAmbient(MWMechanics::getPlayer().getCell()->getCell());
}
else if (it->first == "Shaders" && (it->second == "light bounds multiplier" ||
it->second == "maximum light distance" ||
it->second == "light fade start" ||
it->second == "max lights"))
{
auto* lightManager = static_cast<SceneUtil::LightManager*>(getLightRoot());
lightManager->processChangedSettings(changed);
if (it->second == "max lights" && !lightManager->usingFFP())
{
mViewer->stopThreading();
lightManager->updateMaxLights();
auto defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines();
for (const auto& [name, key] : lightManager->getLightDefines())
defines[name] = key;
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);
mSceneRoot->removeUpdateCallback(mStateUpdater);
mStateUpdater = new StateUpdater;
mSceneRoot->addUpdateCallback(mStateUpdater);
mStateUpdater->setFogEnd(mViewDistance);
updateAmbient();
mViewer->startThreading();
}
}
}
}

@ -296,6 +296,7 @@ namespace MWRender
osg::ref_ptr<StateUpdater> mStateUpdater;
osg::Vec4f mAmbientColor;
float mMinimumAmbientLuminance;
float mNightEyeFactor;
float mNearClip;

@ -28,6 +28,7 @@
#include <components/sceneutil/shadow.hpp>
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/waterutil.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/misc/constants.hpp>
@ -670,6 +671,9 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
osg::ref_ptr<osg::Program> program (new osg::Program);
program->addShader(vertexShader);
program->addShader(fragmentShader);
auto method = mResourceSystem->getSceneManager()->getLightingMethod();
if (method == SceneUtil::LightingMethod::SingleUBO)
program->addBindUniformBlock("LightBufferBinding", static_cast<int>(Shader::UBOBinding::LightBuffer));
shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON);
node->setStateSet(shaderStateset);

@ -0,0 +1,15 @@
#ifndef MISC_HASH_H
#define MISC_HASH_H
namespace Misc
{
/// Implemented similar to the boost::hash_combine
template <class T>
inline void hashCombine(std::size_t& seed, const T& v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
}
#endif

@ -226,6 +226,7 @@ namespace Resource
, mAutoUseNormalMaps(false)
, mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false)
, mLightingMethod(SceneUtil::LightingMethod::FFP)
, mConvertAlphaTestToAlphaToCoverage(false)
, mInstanceCache(new MultiObjectCache)
, mSharedStateManager(new SharedStateManager)
@ -304,6 +305,16 @@ namespace Resource
mApplyLightingToEnvMaps = apply;
}
void SceneManager::setLightingMethod(SceneUtil::LightingMethod method)
{
mLightingMethod = method;
}
SceneUtil::LightingMethod SceneManager::getLightingMethod() const
{
return mLightingMethod;
}
void SceneManager::setConvertAlphaTestToAlphaToCoverage(bool convert)
{
mConvertAlphaTestToAlphaToCoverage = convert;

@ -12,6 +12,8 @@
#include "resourcemanager.hpp"
#include <components/sceneutil/lightmanager.hpp>
namespace Resource
{
class ImageManager;
@ -105,6 +107,9 @@ namespace Resource
void setApplyLightingToEnvMaps(bool apply);
void setLightingMethod(SceneUtil::LightingMethod method);
SceneUtil::LightingMethod getLightingMethod() const;
void setConvertAlphaTestToAlphaToCoverage(bool convert);
void setShaderPath(const std::string& path);
@ -191,6 +196,7 @@ namespace Resource
bool mAutoUseSpecularMaps;
std::string mSpecularMapPattern;
bool mApplyLightingToEnvMaps;
SceneUtil::LightingMethod mLightingMethod;
bool mConvertAlphaTestToAlphaToCoverage;
osg::ref_ptr<MultiObjectCache> mInstanceCache;

@ -62,7 +62,8 @@ namespace SceneUtil
mPhase = mPhase <= 0.5f ? 1.f : 0.25f;
}
static_cast<SceneUtil::LightSource*>(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness);
auto* lightSource = static_cast<SceneUtil::LightSource*>(node);
lightSource->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * lightSource->getActorFade());
traverse(node, nv);
}

File diff suppressed because it is too large Load Diff

@ -2,6 +2,9 @@
#define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <memory>
#include <osg/Light>
@ -9,6 +12,10 @@
#include <osg/NodeVisitor>
#include <osg/observer_ptr>
#include <components/shader/shadermanager.hpp>
#include <components/settings/settings.hpp>
namespace osgUtil
{
class CullVisitor;
@ -16,6 +23,17 @@ namespace osgUtil
namespace SceneUtil
{
class LightBuffer;
class StateSetGenerator;
enum class LightingMethod
{
FFP,
PerObjectUniform,
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
@ -35,6 +53,8 @@ namespace SceneUtil
int mId;
float mActorFade;
public:
META_Node(SceneUtil, LightSource)
@ -54,10 +74,20 @@ namespace SceneUtil
mRadius = radius;
}
void setActorFade(float alpha)
{
mActorFade = alpha;
}
float getActorFade() const
{
return mActorFade;
}
/// 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 +113,27 @@ namespace SceneUtil
class LightManager : public osg::Group
{
public:
static LightingMethod getLightingMethodFromString(const std::string& value);
/// Returns string as used in settings file, or the empty string if the method is undefined
static std::string getLightingMethodString(LightingMethod method);
struct LightSourceTransform
{
LightSource* mLightSource;
osg::Matrixf mWorldMatrix;
};
struct LightSourceViewBound
{
LightSource* mLightSource;
osg::BoundingSphere mViewBound;
};
using LightList = std::vector<const LightSourceViewBound*>;
META_Node(SceneUtil, LightManager)
LightManager();
LightManager(bool ffp = true);
LightManager(const LightManager& copy, const osg::CopyOp& copyop);
@ -94,57 +141,102 @@ 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();
void update(size_t frameNum);
/// Internal use only, called automatically by the LightSource's UpdateCallback
void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum);
void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum);
struct LightSourceTransform
{
LightSource* mLightSource;
osg::Matrixf mWorldMatrix;
};
const std::vector<LightSourceViewBound>& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum);
const std::vector<LightSourceTransform>& getLights() const;
osg::ref_ptr<osg::StateSet> getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix);
struct LightSourceViewBound
{
LightSource* mLightSource;
osg::BoundingSphere mViewBound;
};
void setSunlight(osg::ref_ptr<osg::Light> sun);
osg::ref_ptr<osg::Light> getSunlight();
bool usingFFP() const;
LightingMethod getLightingMethod() const;
int getMaxLights() const;
int getMaxLightsInScene() const;
auto& getDummies() { return mDummies; }
auto& getLightIndexMap(size_t frameNum) { return mLightIndexMaps[frameNum%2]; }
auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; }
const std::vector<LightSourceViewBound>& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix);
osg::Matrixf getSunlightBuffer(size_t frameNum) const { return mSunlightBuffers[frameNum%2]; }
void setSunlightBuffer(const osg::Matrixf& buffer, size_t frameNum) { mSunlightBuffers[frameNum%2] = buffer; }
typedef std::vector<const LightSourceViewBound*> LightList;
std::map<std::string, std::string> getLightDefines() const;
osg::ref_ptr<osg::StateSet> getLightListStateSet(const LightList& lightList, unsigned int frameNum);
void processChangedSettings(const Settings::CategorySettingVector& changed);
/// Not thread safe, it is the responsibility of the caller to stop/start threading on the viewer
void updateMaxLights();
private:
// Lights collected from the scene graph. Only valid during the cull traversal.
void initFFP(int targetLights);
void initPerObjectUniform(int targetLights);
void initSingleUBO(int targetLights);
void updateSettings();
void setLightingMethod(LightingMethod method);
void setMaxLights(int value);
void updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum, const osg::RefMatrix* viewMatrix);
std::vector<LightSourceTransform> mLights;
typedef std::vector<LightSourceViewBound> LightSourceViewBoundCollection;
using LightSourceViewBoundCollection = std::vector<LightSourceViewBound>;
std::map<osg::observer_ptr<osg::Camera>, LightSourceViewBoundCollection> mLightsInViewSpace;
// < Light list hash , StateSet >
typedef std::map<size_t, osg::ref_ptr<osg::StateSet> > LightStateSetMap;
using LightStateSetMap = std::map<size_t, osg::ref_ptr<osg::StateSet>>;
LightStateSetMap mStateSetCache[2];
std::vector<osg::ref_ptr<osg::StateAttribute>> mDummies;
int mStartLight;
unsigned int mLightingMask;
size_t mLightingMask;
osg::ref_ptr<osg::Light> mSun;
osg::ref_ptr<LightBuffer> mLightBuffers[2];
osg::Matrixf mSunlightBuffers[2];
// < Light ID , Buffer Index >
using LightIndexMap = std::unordered_map<int, int>;
LightIndexMap mLightIndexMaps[2];
std::unique_ptr<StateSetGenerator> mStateSetGenerator;
LightingMethod mLightingMethod;
float mPointLightRadiusMultiplier;
float mPointLightFadeEnd;
float mPointLightFadeStart;
int mMaxLights;
static constexpr auto mMaxLightsLowerLimit = 2;
static constexpr auto mMaxLightsUpperLimit = 64;
static constexpr auto mFFPMaxLights = 8;
static const std::unordered_map<std::string, LightingMethod> mLightingMethodSettingMap;
};
/// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via
@ -180,7 +272,7 @@ namespace SceneUtil
private:
LightManager* mLightManager;
unsigned int mLastFrameNumber;
size_t mLastFrameNumber;
LightManager::LightList mLightList;
std::set<SceneUtil::LightSource*> mIgnoredLightSources;
};

@ -58,7 +58,7 @@ namespace SceneUtil
light->setQuadraticAttenuation(quadraticAttenuation);
}
void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior)
osg::ref_ptr<LightSource> addLight(osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior)
{
SceneUtil::FindByNameVisitor visitor("AttachLight");
node->accept(visitor);
@ -85,8 +85,9 @@ namespace SceneUtil
attachTo = trans;
}
osg::ref_ptr<LightSource> lightSource = createLightSource(esmLight, lightMask, isExterior);
osg::ref_ptr<LightSource> lightSource = createLightSource(esmLight, lightMask, isExterior, osg::Vec4f(0,0,0,1));
attachTo->addChild(lightSource);
return lightSource;
}
osg::ref_ptr<LightSource> createLightSource(const ESM::Light* esmLight, unsigned int lightMask, bool isExterior, const osg::Vec4f& ambient)

@ -32,7 +32,7 @@ namespace SceneUtil
/// @param partsysMask Node mask to ignore when computing the sub graph's bounding box.
/// @param lightMask Mask to assign to the newly created LightSource.
/// @param isExterior Is the light outside? May be used for deciding which attenuation settings to use.
void addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior);
osg::ref_ptr<LightSource> addLight (osg::Group* node, const ESM::Light* esmLight, unsigned int partsysMask, unsigned int lightMask, bool isExterior);
/// @brief Convert an ESM::Light to a SceneUtil::LightSource, and return it.
/// @param esmLight The light definition coming from the game files containing radius, color, flicker, etc.

@ -9,17 +9,28 @@
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp>
namespace Shader
{
ShaderManager::ShaderManager()
: mLightingMethod(SceneUtil::LightingMethod::FFP)
{
}
void ShaderManager::setShaderPath(const std::string &path)
{
mPath = path;
}
void ShaderManager::setLightingMethod(SceneUtil::LightingMethod method)
{
mLightingMethod = method;
}
bool addLineDirectivesAfterConditionalBlocks(std::string& source)
{
for (size_t position = 0; position < source.length(); )
@ -344,6 +355,8 @@ namespace Shader
program->addShader(fragmentShader);
program->addBindAttribLocation("aOffset", 6);
program->addBindAttribLocation("aRotation", 7);
if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO)
program->addBindUniformBlock("LightBufferBinding", static_cast<int>(UBOBinding::LightBuffer));
found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first;
}
return found->second;

@ -11,16 +11,38 @@
#include <osgViewer/Viewer>
#include <components/sceneutil/lightmanager.hpp>
namespace Resource
{
class SceneManager;
}
namespace SceneUtil
{
enum class LightingMethod;
}
namespace Shader
{
enum class UBOBinding
{
LightBuffer
};
/// @brief Reads shader template files and turns them into a concrete shader, based on a list of define's.
/// @par Shader templates can get the value of a define with the syntax @define.
class ShaderManager
{
public:
ShaderManager();
void setShaderPath(const std::string& path);
void setLightingMethod(SceneUtil::LightingMethod method);
typedef std::map<std::string, std::string> DefineMap;
/// Create or retrieve a shader instance.
@ -59,6 +81,8 @@ namespace Shader
typedef std::map<std::pair<osg::ref_ptr<osg::Shader>, osg::ref_ptr<osg::Shader> >, osg::ref_ptr<osg::Program> > ProgramMap;
ProgramMap mPrograms;
SceneUtil::LightingMethod mLightingMethod;
std::mutex mMutex;
};

@ -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
@ -148,6 +148,115 @@ By default, the fog becomes thicker proportionally to your distance from the cli
This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.
Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain.
lighting method
---------------
:Type: string
:Range: legacy|shaders compatibility|shaders
:Default: default
Sets the internal handling of light sources.
'legacy' is restricted to 8 lights per object and emulates fixed function
pipeline compatible lighting.
'shaders compatibility' removes the light limit controllable through :ref:`max
lights` and follows a modifed attenuation formula which can drastically reduce
light popping and seams. This mode also enables lighting on groundcover and a
configurable light fade. It is recommended to use this with older hardware and a
light limit closer to 8. Because of its wide range of compatibility it is set as
the default.
'shaders' carries all of the benefits that 'shaders compatibility' does, but
uses a modern approach that allows for a higher :ref:`max lights` count with
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-5.0
:Default: 1.65
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.
This setting has no effect if :ref:`lighting method` is 'legacy'.
maximum light distance
----------------------
:Type: float
:Range: The whole range of 32-bit floating point
:Default: 8192
The maximum distance from the camera that lights will be illuminated, applies to
both interiors and exteriors. A lower distance will improve performance. Set
this to a non-positive value to disable fading.
This setting has no effect if :ref:`lighting method` is 'legacy'.
light fade start
----------------
:Type: float
:Range: 0.0-1.0
:Default: 0.85
The fraction of the maximum distance at which lights will begin to fade away.
Tweaking it will make the transition proportionally more or less smooth.
This setting has no effect if the :ref:`maximum light distance` is non-positive
or :ref:`lighting method` is 'legacy'.
max lights
----------
:Type: integer
:Range: 2-64
:Default: 8
Sets the maximum number of lights that each object can receive lighting from.
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'.
minimum interior brightness
------------------------
:Type: float
:Range: 0.0-1.0
:Default: 0.08
Sets the minimum interior ambient brightness for interior cells when
:ref:`lighting method` is not 'legacy'. A consequence of the new lighting system
is that interiors will sometimes be darker since light sources now have sensible
fall-offs. A couple solutions are to either add more lights or increase their
radii to compensate, but these require content changes. For best results it is
recommended to set this to 0.0 to retain the colors that level designers
intended. If brighter interiors are wanted, however, this setting should be
increased. Note, it is advised to keep this number small (< 0.1) to avoid the
aforementioned changes in visuals.
This setting has no effect if :ref:`lighting method` is 'legacy'.
antialias alpha test
---------------------------------------

@ -457,6 +457,114 @@
</Widget>
</Widget>
<Widget type="TabItem" skin="" position="4 32 360 308">
<Property key="Caption" value=" Lights "/>
<Widget type="Widget" skin="" position="0 0 360 315" align="Stretch">
<Widget type="Widget" skin="" position="0 0 360 315" align="Stretch" name="LightSettingOverlay">
<!-- Lighting Method -->
<Widget type="TextBox" skin="NormalText" position="0 4 170 18" align="Left Top">
<Property key="Caption" value="Lighting Method:"/>
</Widget>
<Widget type="TextBox" skin="SandText" position="0 28 170 18" align="Left Top" name="LightingMethodText">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="TextToolTip"/>
</Widget>
<!-- Max Lights -->
<Widget type="TextBox" skin="NormalText" position="178 4 160 18" align="Left Top" name="MaxLightsText">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="TextToolTip"/>
<UserString key="Caption_Text" value="Default: 8\nMaximum number of lights per object.\n\nA low number near default will cause light popping similar to what you would see with legacy lighting."/>
</Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="178 30 160 18" align="Right Top HStretch" name="MaxLightsSlider">
<Property key="Range" value="24"/>
<Property key="Page" value="1"/>
<UserString key="SettingType" value="Slider"/>
<UserString key="SettingMin" value="4"/>
<UserString key="SettingMax" value="32"/>
<UserString key="SettingCategory" value="Shaders"/>
<UserString key="SettingName" value="max lights"/>
<UserString key="SettingValueType" value="Integer"/>
<UserString key="SettingLabelWidget" value="MaxLightsText"/>
<UserString key="SettingLabelCaption" value="Max Lights (%s)"/>
</Widget>
<Widget type="ImageBox" skin="MW_HLine" position="0 54 360 18" align="Top HStretch"/>
<!-- Light Fade Start -->
<Widget type="TextBox" skin="NormalText" position="0 78 352 18" align="Left Top" name="MaxLightDistanceText">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="TextToolTip"/>
<UserString key="Caption_Text" value="Default: 8192 (units)\nMaximum distance at which lights will appear.\n\nSet this to 0.0 to disable it."/>
</Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="0 104 352 18" align="HStretch Top">
<Property key="Range" value="8192"/>
<Property key="Page" value="1"/>
<UserString key="SettingType" value="Slider"/>
<UserString key="SettingMin" value="0"/>
<UserString key="SettingMax" value="8192"/>
<UserString key="SettingCategory" value="Shaders"/>
<UserString key="SettingName" value="maximum light distance"/>
<UserString key="SettingValueType" value="Integer"/>
<UserString key="SettingLabelWidget" value="MaxLightDistanceText"/>
<UserString key="SettingLabelCaption" value="Maximum Light Distance (%s)"/>
</Widget>
<!-- Light Fade Multiplier -->
<Widget type="TextBox" skin="NormalText" position="0 128 352 18" align="Left Top" name="LightFadeMultiplierText">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="TextToolTip"/>
<UserString key="Caption_Text" value="Default: 0.85\nFraction of maximum distance at which lights will start to fade.\n\nSet this to a low value for slower transitions or a high value of quicker transitions."/>
</Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="0 152 352 18" align="HStretch Top">
<Property key="Range" value="10000"/>
<Property key="Page" value="100"/>
<UserString key="SettingType" value="Slider"/>
<UserString key="SettingCategory" value="Shaders"/>
<UserString key="SettingName" value="light fade start"/>
<UserString key="SettingValueType" value="Float"/>
<UserString key="SettingMin" value="0.0"/>
<UserString key="SettingMax" value="1.0"/>
<UserString key="SettingLabelWidget" value="LightFadeMultiplierText"/>
<UserString key="SettingLabelCaption" value="Fade Start Multiplier (%s)"/>
</Widget>
<!-- Bounding Sphere Multiplier -->
<Widget type="TextBox" skin="NormalText" position="0 176 352 18" align="Left Top" name="BoundingSphereMultText">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="TextToolTip"/>
<UserString key="Caption_Text" value="Default: 1.65\nMultipler for bounding sphere of lights.\nHigher numbers allows for smooth falloff but require an increase in number of max lights.\n\nDoes not effect the illumination or strength of lights."/>
</Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="0 200 352 18" align="HStretch Top">
<Property key="Range" value="500000"/>
<Property key="Page" value="1000"/>
<UserString key="SettingType" value="Slider"/>
<UserString key="SettingMin" value="0.0"/>
<UserString key="SettingMax" value="5.0"/>
<UserString key="SettingCategory" value="Shaders"/>
<UserString key="SettingName" value="light bounds multiplier"/>
<UserString key="SettingValueType" value="Float"/>
<UserString key="SettingLabelWidget" value="BoundingSphereMultText"/>
<UserString key="SettingLabelCaption" value="Bounding Sphere Multiplier (%s)"/>
</Widget>
<!-- Minimum Ambient Brightness -->
<Widget type="TextBox" skin="NormalText" position="0 224 352 18" align="Left Top" name="MinimumBrightnessText">
<UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="TextToolTip"/>
<UserString key="Caption_Text" value="Default: 0.08\nMinimum ambient interior brightness.\n\nIncrease this if you feel interiors are too dark."/>
</Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="0 248 352 18" align="HStretch Top">
<Property key="Range" value="10000"/>
<Property key="Page" value="100"/>
<UserString key="SettingType" value="Slider"/>
<UserString key="SettingCategory" value="Shaders"/>
<UserString key="SettingName" value="minimum interior brightness"/>
<UserString key="SettingValueType" value="Float"/>
<UserString key="SettingLabelWidget" value="MinimumBrightnessText"/>
<UserString key="SettingLabelCaption" value="Minimum Interior Brightness (%s)"/>
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" position="0 290 0 0" align="Top Left" name="LightsResetButton">
<Property key="Caption" value="Reset To Defaults"/>
</Widget>
</Widget>
</Widget>
</Widget>
<!--
<Widget type="TabItem" skin="" position="0 28 352 268">
<Widget type="Widget" skin="" position="0 28 352 268">

@ -446,6 +446,38 @@ 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, 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 slower on weaker hardware when
# 'force per pixel lighting' is enabled.
lighting method = shaders compatibility
# Sets the bounding sphere multiplier of light sources if 'lighting method' is
# not 'legacy'. These are used to determine if an object should receive
# lighting. Higher values will allow for smoother transitions of light sources,
# but may carry a performance cost and requires a higher number of 'max lights'
# set.
light bounds multiplier = 1.65
# 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.
# When 'lighting method' is set to 'legacy', this setting will have no effect.
max lights = 8
# Sets minimum ambient brightness of interior cells. Levels below this threshold will have their
# ambient values adjusted to balance the darker interiors.
# When 'lighting method' is set to 'legacy', this setting will have no effect.
minimum interior brightness = 0.08
# Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage.
# This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation.
# When MSAA is off, this setting will have no visible effect, but might have a performance cost.

@ -18,6 +18,7 @@ set(SHADER_FILES
terrain_vertex.glsl
terrain_fragment.glsl
lighting.glsl
lighting_util.glsl
parallax.glsl
s360_fragment.glsl
s360_vertex.glsl

@ -1,5 +1,9 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif

@ -1,5 +1,13 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif
#define GROUNDCOVER
attribute vec4 aOffset;

@ -1,15 +1,48 @@
#define MAX_LIGHTS 8
#include "lighting_util.glsl"
void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal)
void perLightSun(out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal)
{
vec3 lightDir = gl_LightSource[lightIndex].position.xyz - viewPos * gl_LightSource[lightIndex].position.w;
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);
vec3 lightDir = normalize(lcalcPosition(0));
float lambert = dot(viewNormal.xyz, lightDir);
ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination;
#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 = lcalcDiffuse(0).xyz * lambert;
}
void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal)
{
vec3 lightPos = lcalcPosition(lightIndex) - viewPos;
float lightDistance = length(lightPos);
// cull non-FFP point lighting by radius, light is guaranteed to not fall outside this bound with our cutoff
#if !@lightingMethodFFP
float radius = lcalcRadius(lightIndex);
if (lightDistance > radius * 2.0)
{
ambientOut = vec3(0.0);
diffuseOut = vec3(0.0);
return;
}
#endif
lightPos = normalize(lightPos);
float illumination = lcalcIllumination(lightIndex, lightDistance);
ambientOut = lcalcAmbient(lightIndex) * illumination;
float lambert = dot(viewNormal.xyz, lightPos) * illumination;
float lambert = dot(viewNormal.xyz, lightDir) * illumination;
#ifndef GROUNDCOVER
lambert = max(lambert, 0.0);
#else
@ -21,7 +54,8 @@ 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 = lcalcDiffuse(lightIndex) * lambert;
}
#if PER_PIXEL_LIGHTING
@ -31,31 +65,35 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a
#endif
{
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(diffuseOut, viewPos, viewNormal);
ambientLight = gl_LightModel.ambient.xyz;
#if PER_PIXEL_LIGHTING
diffuseLight = diffuseOut * shadowing - diffuseOut;
diffuseLight = diffuseOut * shadowing;
#else
shadowDiffuse = diffuseOut;
diffuseLight = -diffuseOut;
diffuseLight = vec3(0.0);
#endif
ambientLight = gl_LightModel.ambient.xyz;
for (int i=0; i<MAX_LIGHTS; ++i)
for (int i = @startLight; i < @endLight; ++i)
{
perLight(ambientOut, diffuseOut, i, viewPos, viewNormal);
#if @lightingMethodUBO
perLightPoint(ambientOut, diffuseOut, PointLightIndex[i], viewPos, viewNormal);
#else
perLightPoint(ambientOut, diffuseOut, i, viewPos, viewNormal);
#endif
ambientLight += ambientOut;
diffuseLight += diffuseOut;
}
}
vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec)
{
vec3 lightDir = normalize(gl_LightSource[0].position.xyz);
vec3 lightDir = normalize(lcalcPosition(0));
float NdotL = dot(viewNormal, lightDir);
if (NdotL <= 0.0)
return vec3(0.0);
vec3 halfVec = normalize(lightDir - viewDirection);
float NdotH = dot(viewNormal, halfVec);
return pow(max(NdotH, 0.0), max(1e-4, shininess)) * gl_LightSource[0].specular.xyz * matSpec;
return pow(max(NdotH, 0.0), max(1e-4, shininess)) * lcalcSpecular(0).xyz * matSpec;
}

@ -0,0 +1,131 @@
#if !@lightingMethodFFP
float quickstep(float x)
{
x = clamp(x, 0.0, 1.0);
x = 1.0 - x*x;
x = 1.0 - x*x;
return x;
}
#endif
#if @lightingMethodUBO
const int mask = int(0xff);
const ivec4 shift = ivec4(int(0), int(8), int(16), int(24));
vec3 unpackRGB(int data)
{
return vec3( (float(((data >> shift.x) & mask)) / 255.0)
,(float(((data >> shift.y) & mask)) / 255.0)
,(float(((data >> shift.z) & mask)) / 255.0));
}
vec4 unpackRGBA(int data)
{
return vec4( (float(((data >> shift.x) & mask)) / 255.0)
,(float(((data >> shift.y) & mask)) / 255.0)
,(float(((data >> shift.z) & mask)) / 255.0)
,(float(((data >> shift.w) & mask)) / 255.0));
}
/* Layout:
packedColors: 8-bit unsigned RGB packed as (diffuse, ambient, specular).
sign bit is stored in unused alpha component
attenuation: constant, linear, quadratic, light radius (as defined in content)
*/
struct LightData
{
ivec4 packedColors;
vec4 position;
vec4 attenuation;
};
uniform int PointLightIndex[@maxLights];
uniform int PointLightCount;
// Defaults to shared layout. If we ever move to GLSL 140, std140 layout should be considered
uniform LightBufferBinding
{
LightData LightBuffer[@maxLightsInScene];
};
#elif @lightingMethodPerObjectUniform
/* Layout:
--------------------------------------- -----------
| pos_x | ambi_r | diff_r | spec_r |
| pos_y | ambi_g | diff_g | spec_g |
| pos_z | ambi_b | diff_b | spec_b |
| att_c | att_l | att_q | radius/spec_a |
--------------------------------------------------
*/
uniform mat4 LightBuffer[@maxLights];
uniform int PointLightCount;
#endif
#if !@lightingMethodFFP
float lcalcRadius(int lightIndex)
{
#if @lightingMethodPerObjectUniform
return @getLight[lightIndex][3].w;
#else
return @getLight[lightIndex].attenuation.w;
#endif
}
#endif
float lcalcIllumination(int lightIndex, float lightDistance)
{
#if @lightingMethodPerObjectUniform
float illumination = clamp(1.0 / (@getLight[lightIndex][0].w + @getLight[lightIndex][1].w * lightDistance + @getLight[lightIndex][2].w * lightDistance * lightDistance), 0.0, 1.0);
return (illumination * (1.0 - quickstep((lightDistance / lcalcRadius(lightIndex)) - 1.0)));
#elif @lightingMethodUBO
float illumination = clamp(1.0 / (@getLight[lightIndex].attenuation.x + @getLight[lightIndex].attenuation.y * lightDistance + @getLight[lightIndex].attenuation.z * lightDistance * lightDistance), 0.0, 1.0);
return (illumination * (1.0 - quickstep((lightDistance / lcalcRadius(lightIndex)) - 1.0)));
#else
return clamp(1.0 / (@getLight[lightIndex].constantAttenuation + @getLight[lightIndex].linearAttenuation * lightDistance + @getLight[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0);
#endif
}
vec3 lcalcPosition(int lightIndex)
{
#if @lightingMethodPerObjectUniform
return @getLight[lightIndex][0].xyz;
#else
return @getLight[lightIndex].position.xyz;
#endif
}
vec3 lcalcDiffuse(int lightIndex)
{
#if @lightingMethodPerObjectUniform
return @getLight[lightIndex][2].xyz;
#elif @lightingMethodUBO
return unpackRGB(@getLight[lightIndex].packedColors.x) * float(@getLight[lightIndex].packedColors.w);
#else
return @getLight[lightIndex].diffuse.xyz;
#endif
}
vec3 lcalcAmbient(int lightIndex)
{
#if @lightingMethodPerObjectUniform
return @getLight[lightIndex][1].xyz;
#elif @lightingMethodUBO
return unpackRGB(@getLight[lightIndex].packedColors.y);
#else
return @getLight[lightIndex].ambient.xyz;
#endif
}
vec4 lcalcSpecular(int lightIndex)
{
#if @lightingMethodPerObjectUniform
return @getLight[lightIndex][3];
#elif @lightingMethodUBO
return unpackRGBA(@getLight[lightIndex].packedColors.z);
#else
return @getLight[lightIndex].specular;
#endif
}

@ -1,5 +1,9 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif

@ -1,5 +1,13 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif
#if @diffuseMap
varying vec2 diffuseMapUV;
#endif

@ -1,5 +1,9 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif

@ -1,5 +1,13 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif
#if @diffuseMap
varying vec2 diffuseMapUV;
#endif

@ -1,5 +1,13 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif
varying vec2 uv;
uniform sampler2D diffuseMap;

@ -1,5 +1,13 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif
varying vec2 uv;
varying float euclideanDepth;
varying float linearDepth;

@ -1,5 +1,13 @@
#version 120
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
#endif
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif
#define REFRACTION @refraction_enabled
// Inspired by Blender GLSL Water by martinsh ( https://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html )
@ -142,7 +150,10 @@ uniform vec3 nodePosition;
uniform float rainIntensity;
#define PER_PIXEL_LIGHTING 0
#include "shadows_fragment.glsl"
#include "lighting.glsl"
float frustumDepth;
@ -192,8 +203,7 @@ void main(void)
normal3 * midWaves.y + normal4 * smallWaves.x + normal5 * smallWaves.y + rippleAdd);
normal = normalize(vec3(-normal.x * bump, -normal.y * bump, normal.z));
vec3 lVec = normalize((gl_ModelViewMatrixInverse * vec4(gl_LightSource[0].position.xyz, 0.0)).xyz);
vec3 lVec = normalize((gl_ModelViewMatrixInverse * vec4(lcalcPosition(0).xyz, 0.0)).xyz);
vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz;
vec3 vVec = normalize(position.xyz - cameraPos.xyz);
@ -228,6 +238,8 @@ void main(void)
vec3 waterColor = WATER_COLOR * sunFade;
vec4 sunSpec = lcalcSpecular(0);
#if REFRACTION
// refraction
vec3 refraction = texture2D(refractionMap, screenCoords - screenCoordsOffset).rgb;
@ -247,11 +259,11 @@ void main(void)
vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0));
vec3 lR = reflect(lVec, lNormal);
float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0);
gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * gl_LightSource[0].specular.xyz + vec3(rainRipple.w) * 0.2;
gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + vec3(rainRipple.w) * 0.2;
gl_FragData[0].w = 1.0;
#else
gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * gl_LightSource[0].specular.xyz + vec3(rainRipple.w) * 0.7;
gl_FragData[0].w = clamp(fresnel*6.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0);
gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + vec3(rainRipple.w) * 0.7;
gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0);
#endif
// fog

@ -283,6 +283,36 @@
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="lightingMethodLayout">
<item>
<widget class="QLabel" name="lightingMethodLabel">
<property name="text">
<string>Lighting Method:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="lightingMethodComboBox">
<item>
<property name="text">
<string>legacy</string>
</property>
</item>
<item>
<property name="text">
<string>shaders compatibility</string>
</property>
</item>
<item>
<property name="text">
<string>shaders</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="smoothMovementCheckBox">
<property name="toolTip">

Loading…
Cancel
Save