diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 6fcbb4d51f..3762e8e280 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -94,6 +94,8 @@ namespace MWRender , mNormals(false) , mPrevNormals(false) , mNormalsSupported(false) + , mPassLights(false) + , mPrevPassLights(false) , mMainTemplate(new osg::Texture2D) { mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders") && !Stereo::getStereo() && !Stereo::getMultiview(); @@ -393,9 +395,10 @@ namespace MWRender mDirty = false; } - if (mNormalsSupported && mNormals != mPrevNormals) + if ((mNormalsSupported && mNormals != mPrevNormals) || (mPassLights != mPrevPassLights)) { mPrevNormals = mNormals; + mPrevPassLights = mPassLights; mViewer->stopThreading(); @@ -404,10 +407,15 @@ namespace MWRender defines["disableNormals"] = mNormals ? "0" : "1"; shaderManager.setGlobalDefines(defines); + mRendering.getLightRoot()->setCollectPPLights(mPassLights); + mStateUpdater->bindPointLights(mPassLights ? mRendering.getLightRoot()->getPPLightsBuffer() : nullptr); + mStateUpdater->reset(); + mViewer->startThreading(); createTexturesAndCamera(frameId); createObjectsForFrame(frameId); + mDirty = true; mDirtyFrameId = !frameId; } @@ -491,6 +499,7 @@ namespace MWRender bool sunglare = true; mHDR = false; mNormals = false; + mPassLights = false; for (const auto& technique : mTechniques) { @@ -513,6 +522,9 @@ namespace MWRender if (technique->getNormals()) mNormals = true; + if (technique->getLights()) + mPassLights = true; + if (node.mFlags & fx::Technique::Flag_Disable_SunGlare) sunglare = false; diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 8560b76eaa..f81615c90b 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -212,6 +212,8 @@ namespace MWRender bool mNormals; bool mPrevNormals; bool mNormalsSupported; + bool mPassLights; + bool mPrevPassLights; bool mUBO; int mGLSLVersion; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 359b2e0f9d..3c032e2fd6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -42,6 +42,8 @@ #include #include +#include + #include #include @@ -558,10 +560,9 @@ namespace MWRender cullingMode |= osg::CullStack::SMALL_FEATURE_CULLING; } - mViewer->getCamera()->setCullingMode( cullingMode ); - mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mViewer->getCamera()->setCullingMode(cullingMode); + mViewer->getCamera()->setName(Constants::SceneCamera); auto mask = ~(Mask_UpdateVisitor | Mask_SimpleWater); MWBase::Environment::get().getWindowManager()->setCullMask(mask); @@ -650,7 +651,7 @@ namespace MWRender return mViewer->getFrameStamp()->getReferenceTime(); } - osg::Group* RenderingManager::getLightRoot() + SceneUtil::LightManager* RenderingManager::getLightRoot() { return mSceneRoot.get(); } @@ -1360,7 +1361,7 @@ namespace MWRender it->second == "light fade start" || it->second == "max lights")) { - auto* lightManager = static_cast(getLightRoot()); + auto* lightManager = getLightRoot(); lightManager->processChangedSettings(changed); if (it->second == "max lights" && !lightManager->usingFFP()) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 850b1c5a39..cd4b6edca4 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -59,6 +59,7 @@ namespace SceneUtil { class ShadowManager; class WorkQueue; + class LightManager; } namespace DetourNavigator @@ -116,7 +117,7 @@ namespace MWRender double getReferenceTime() const; - osg::Group* getLightRoot(); + SceneUtil::LightManager* getLightRoot(); void setNightEyeFactor(float factor); @@ -271,7 +272,7 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; - osg::ref_ptr mSceneRoot; + osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 78eeab79ff..cda1940ac4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -197,7 +198,7 @@ namespace MWWorld } mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator, mGroundcoverStore)); - mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); + mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot()->asGroup(), resourceSystem, mRendering.get(), mPhysics.get())); mRendering->preloadCommonAssets(); mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mStore)); diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 0d86bdcf50..e0489fe0c8 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -77,6 +78,34 @@ uniform @builtinSampler omw_SamplerLastPass; uniform @builtinSampler omw_SamplerDepth; uniform @builtinSampler omw_SamplerNormals; +uniform vec4 omw_PointLights[@pointLightCount]; +uniform int omw_PointLightsCount; + +int omw_GetPointLightCount() +{ + return omw_PointLightsCount; +} + +vec3 omw_GetPointLightViewPos(int index) +{ + return omw_PointLights[(index * 3)].xyz; +} + +vec3 omw_GetPointLightDiffuse(int index) +{ + return omw_PointLights[(index * 3) + 1].xyz; +} + +vec3 omw_GetPointLightAttenuation(int index) +{ + return omw_PointLights[(index * 3) + 2].xyz; +} + +float omw_GetPointLightRadius(int index) +{ + return omw_PointLights[(index * 3) + 2].w; +} + #if @ubo layout(std140) uniform _data { _omw_data omw; }; #else @@ -143,6 +172,7 @@ uniform @builtinSampler omw_SamplerNormals; extBlock << "#ifdef " << extension << '\n' << "\t#extension " << extension << ": enable" << '\n' << "#endif" << '\n'; const std::vector> defines = { + {"@pointLightCount", std::to_string(SceneUtil::PPLightBuffer::sMaxPPLightsArraySize)}, {"@version", std::to_string(technique.getGLSLVersion())}, {"@multiview", Stereo::getMultiview() ? "1" : "0"}, {"@builtinSampler", Stereo::getMultiview() ? "sampler2DArray" : "sampler2D"}, diff --git a/components/fx/stateupdater.cpp b/components/fx/stateupdater.cpp index c362c16f54..36362e07a4 100644 --- a/components/fx/stateupdater.cpp +++ b/components/fx/stateupdater.cpp @@ -56,5 +56,8 @@ namespace fx std::apply([&] (const auto& ... v) { (setUniform(v) , ...); }, mData.getData()); } + + if (mPointLightBuffer) + mPointLightBuffer->applyUniforms(nv->getTraversalNumber(), stateset); } } \ No newline at end of file diff --git a/components/fx/stateupdater.hpp b/components/fx/stateupdater.hpp index 25bb17fffa..8b181729d1 100644 --- a/components/fx/stateupdater.hpp +++ b/components/fx/stateupdater.hpp @@ -3,6 +3,7 @@ #include +#include #include #include @@ -86,12 +87,21 @@ namespace fx void setWeatherTransition(float transition) { mData.get() = transition; } + void bindPointLights(std::shared_ptr buffer) + { + mPointLightBuffer = buffer; + } + static std::string getStructDefinition() { static std::string definition = UniformData::getDefinition("_omw_data"); return definition; } + void setDefaults(osg::StateSet* stateset) override; + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; + private: struct ProjectionMatrix : std140::Mat4 { static constexpr std::string_view sName = "projectionMatrix"; }; @@ -180,12 +190,10 @@ namespace fx IsInterior >; - private: - void setDefaults(osg::StateSet* stateset) override; - void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override; - UniformData mData; bool mUseUBO; + + std::shared_ptr mPointLightBuffer; }; } diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 86a6189fd7..7ef3fb61a4 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -59,6 +59,7 @@ namespace fx mValid = false; mHDR = false; mNormals = false; + mLights = false; mEnabled = true; mPassMap.clear(); mPasses.clear(); @@ -238,6 +239,8 @@ namespace fx mHDR = parseBool(); else if (key == "pass_normals") mNormals = parseBool() && mSupportsNormals; + else if (key == "pass_lights") + mLights = parseBool(); else if (key == "glsl_profile") { expect(); diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index ca8ef96a52..85a28d9944 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -129,6 +129,8 @@ namespace fx bool getNormals() const { return mNormals && mSupportsNormals; } + bool getLights() const { return mLights; } + const PassList& getPasses() { return mPasses; } const TexList& getTextures() const { return mTextures; } @@ -253,6 +255,7 @@ namespace fx bool mValid; bool mHDR; bool mNormals; + bool mLights; int mWidth; int mHeight; diff --git a/components/misc/constants.hpp b/components/misc/constants.hpp index 01d783a4fc..2f4d4f8a18 100644 --- a/components/misc/constants.hpp +++ b/components/misc/constants.hpp @@ -39,6 +39,9 @@ const float TorsoHeight = 0.75f; static constexpr float sStepSizeUp = 34.0f; static constexpr float sMaxSlope = 46.0f; +// Identifier for main scene camera +const std::string SceneCamera = "SceneCam"; + } #endif diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 45738bcad8..3092fc6f40 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -18,6 +18,7 @@ #include #include +#include #include @@ -839,6 +840,7 @@ namespace SceneUtil , mPointLightFadeEnd(copy.mPointLightFadeEnd) , mPointLightFadeStart(copy.mPointLightFadeStart) , mMaxLights(copy.mMaxLights) + , mPPLightBuffer(copy.mPPLightBuffer) { } @@ -1001,6 +1003,9 @@ namespace SceneUtil void LightManager::update(size_t frameNum) { + if (mPPLightBuffer) + mPPLightBuffer->clear(frameNum); + getLightIndexMap(frameNum).clear(); mLights.clear(); mLightsInViewSpace.clear(); @@ -1132,6 +1137,17 @@ namespace SceneUtil l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; it->second.push_back(l); + + if (mPPLightBuffer && it->first->getName() == Constants::SceneCamera) + { + const auto* light = l.mLightSource->getLight(frameNum); + mPPLightBuffer->setLight(frameNum, light->getPosition() * (*viewMatrix), + light->getDiffuse(), + light->getConstantAttenuation(), + light->getLinearAttenuation(), + light->getQuadraticAttenuation(), + l.mLightSource->getRadius()); + } } } @@ -1168,6 +1184,14 @@ namespace SceneUtil return uniform; } + void LightManager::setCollectPPLights(bool enabled) + { + if (enabled) + mPPLightBuffer = std::make_shared(); + else + mPPLightBuffer = nullptr; + } + LightSource::LightSource() : mRadius(0.f) , mActorFade(1.f) diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 64b7e325ad..ab48e9a23a 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -26,6 +26,64 @@ namespace SceneUtil class LightBuffer; struct StateSetGenerator; + class PPLightBuffer + { + public: + inline static constexpr auto sMaxPPLights = 30; + inline static constexpr auto sMaxPPLightsArraySize = sMaxPPLights * 3; + + PPLightBuffer() + { + for (size_t i = 0; i < 2; ++i) + { + mIndex[i] = 0; + mUniformBuffers[i] = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "omw_PointLights", sMaxPPLightsArraySize); + mUniformCount[i] = new osg::Uniform("omw_PointLightsCount", static_cast(0)); + } + } + + void applyUniforms(size_t frame, osg::StateSet* stateset) + { + size_t frameId = frame % 2; + + if (!stateset->getUniform("omw_PointLights")) + stateset->addUniform(mUniformBuffers[frameId]); + if (!stateset->getUniform("omw_PointLightsCount")) + stateset->addUniform(mUniformCount[frameId]); + + mUniformBuffers[frameId]->dirty(); + mUniformCount[frameId]->dirty(); + } + + void clear(size_t frame) + { + mIndex[frame % 2] = 0; + } + + void setLight(size_t frame, const osg::Vec4f& position, osg::Vec4f diffuse, float ac, float al, float aq, float radius) + { + size_t frameId = frame % 2; + size_t i = mIndex[frameId]; + + if (i >= (sMaxPPLights - 1)) + return; + + i *= 3; + + mUniformBuffers[frameId]->setElement(i + 0, position); + mUniformBuffers[frameId]->setElement(i + 1, diffuse); + mUniformBuffers[frameId]->setElement(i + 2, osg::Vec4f(ac, al, aq, radius)); + + mIndex[frameId]++; + mUniformCount[frameId]->set(static_cast(mIndex[frameId])); + } + + private: + std::array mIndex; + std::array, 2> mUniformBuffers; + std::array, 2> mUniformCount; + }; + enum class LightingMethod { FFP, @@ -227,6 +285,11 @@ namespace SceneUtil osg::ref_ptr generateLightBufferUniform(const osg::Matrixf& sun); + // Whether to collect main scene camera points lights into a buffer to be later sent to postprocessing shaders + void setCollectPPLights(bool enabled); + + std::shared_ptr getPPLightsBuffer() { return mPPLightBuffer; } + private: void initFFP(int targetLights); void initPerObjectUniform(int targetLights); @@ -285,6 +348,8 @@ namespace SceneUtil static constexpr auto mFFPMaxLights = 8; static const std::unordered_map mLightingMethodSettingMap; + + std::shared_ptr mPPLightBuffer; }; /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via