From 916706c6c1c1470d4102ffc8ad30658f155cf679 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 24 May 2024 02:59:16 +0300 Subject: [PATCH 1/2] Make object light lists (mostly) view-independent Remove frustum culling Sort lights by object proximity Restore multiple camera rendering optimization --- components/sceneutil/lightmanager.cpp | 126 ++++++++++---------------- components/sceneutil/lightmanager.hpp | 2 +- 2 files changed, 49 insertions(+), 79 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index aa94495519..221f419c74 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -26,14 +26,6 @@ namespace { constexpr int ffpMaxLights = 8; - bool sortLights(const SceneUtil::LightManager::LightSourceViewBound* left, - const SceneUtil::LightManager::LightSourceViewBound* right) - { - static auto constexpr illuminationBias = 81.f; - return left->mViewBound.center().length2() - left->mViewBound.radius2() * illuminationBias - < right->mViewBound.center().length2() - right->mViewBound.radius2() * illuminationBias; - } - void configurePosition(osg::Matrixf& mat, const osg::Vec4& pos) { mat(0, 0) = pos.x(); @@ -1130,10 +1122,8 @@ namespace SceneUtil } const std::vector& LightManager::getLightsInViewSpace( - osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum) + osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { - osg::Camera* camera = cv->getCurrentCamera(); - osg::observer_ptr camPtr(camera); auto it = mLightsInViewSpace.find(camPtr); @@ -1147,7 +1137,7 @@ namespace SceneUtil float radius = transform.mLightSource->getRadius(); - osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0, 0, 0), radius); + osg::BoundingSphere viewBound(osg::Vec3f(), radius * mPointLightRadiusMultiplier); transformBoundingSphere(worldViewMat, viewBound); if (transform.mLightSource->getLastAppliedFrame() != frameNum && mPointLightFadeEnd != 0.f) @@ -1164,15 +1154,6 @@ namespace SceneUtil transform.mLightSource->setLastAppliedFrame(frameNum); } - // remove lights culled by this camera - if (!usingFFP()) - { - viewBound._radius *= 2.f; - if (cv->getModelViewCullingStack().front().isCulled(viewBound)) - continue; - viewBound._radius /= 2.f; - } - viewBound._radius *= mPointLightRadiusMultiplier; LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; @@ -1284,74 +1265,63 @@ namespace SceneUtil // Possible optimizations: // - organize lights in a quad tree - mLastFrameNumber = cv->getTraversalNumber(); - // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); - const std::vector& lights - = mLightManager->getLightsInViewSpace(cv, viewMatrix, mLastFrameNumber); - // get the node bounds in view space - // NB do not node->getBound() * modelView, that would apply the node's transformation twice - osg::BoundingSphere nodeBound; - osg::Transform* transform = node->asTransform(); - if (transform) + // Update light list if necessary + // This makes sure we don't update it more than once per frame when rendering with multiple cameras + if (mLastFrameNumber != cv->getTraversalNumber()) { - for (size_t i = 0; i < transform->getNumChildren(); ++i) - nodeBound.expandBy(transform->getChild(i)->getBound()); - } - else - nodeBound = node->getBound(); - osg::Matrixf mat = *cv->getModelViewMatrix(); - transformBoundingSphere(mat, nodeBound); + mLastFrameNumber = cv->getTraversalNumber(); - mLightList.clear(); - for (size_t i = 0; i < lights.size(); ++i) - { - const LightManager::LightSourceViewBound& l = lights[i]; + // Get the node bounds in view space + // NB: do not node->getBound() * modelView, that would apply the node's transformation twice + osg::BoundingSphere nodeBound; + const osg::Transform* transform = node->asTransform(); + if (transform) + { + for (size_t i = 0; i < transform->getNumChildren(); ++i) + nodeBound.expandBy(transform->getChild(i)->getBound()); + } + else + nodeBound = node->getBound(); - if (mIgnoredLightSources.count(l.mLightSource)) - continue; + transformBoundingSphere(*cv->getModelViewMatrix(), nodeBound); - if (l.mViewBound.intersects(nodeBound)) - mLightList.push_back(&l); + const std::vector& lights + = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); + + mLightList.clear(); + for (const LightManager::LightSourceViewBound& light : lights) + { + if (mIgnoredLightSources.contains(light.mLightSource)) + continue; + + if (light.mViewBound.intersects(nodeBound)) + mLightList.push_back(&light); + } + + const size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight(); + + if (mLightList.size() > maxLights) + { + // Sort by proximity to object: prefer closer lights with larger radius + std::sort(mLightList.begin(), mLightList.end(), + [&](const SceneUtil::LightManager::LightSourceViewBound* left, + const SceneUtil::LightManager::LightSourceViewBound* right) { + const float leftDist = (nodeBound.center() - left->mViewBound.center()).length2(); + const float rightDist = (nodeBound.center() - right->mViewBound.center()).length2(); + // A tricky way to compare normalized distance. This avoids division by near zero + return left->mViewBound.radius() * rightDist > right->mViewBound.radius() * leftDist; + }); + + mLightList.resize(maxLights); + } } if (!mLightList.empty()) { - size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight(); - - osg::ref_ptr stateset = nullptr; - - if (mLightList.size() > maxLights) - { - LightManager::LightList lightList = mLightList; - - if (mLightManager->usingFFP()) - { - for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) - { - osg::BoundingSphere bs = (*it)->mViewBound; - bs._radius = bs._radius * 2.0; - if (cv->getModelViewCullingStack().front().isCulled(bs)) - it = lightList.erase(it); - else - ++it; - } - } - - // sort by proximity to camera, then get rid of furthest away lights - std::sort(lightList.begin(), lightList.end(), sortLights); - while (lightList.size() > maxLights) - lightList.pop_back(); - stateset = mLightManager->getLightListStateSet( - lightList, cv->getTraversalNumber(), cv->getCurrentRenderStage()->getInitialViewMatrix()); - } - else - stateset = mLightManager->getLightListStateSet( - mLightList, cv->getTraversalNumber(), cv->getCurrentRenderStage()->getInitialViewMatrix()); - - cv->pushStateSet(stateset); + cv->pushStateSet(mLightManager->getLightListStateSet(mLightList, mLastFrameNumber, viewMatrix)); return true; } return false; diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 0b30a77e5c..ee7dc5b0af 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -238,7 +238,7 @@ namespace SceneUtil void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); const std::vector& getLightsInViewSpace( - osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum); + osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum); osg::ref_ptr getLightListStateSet( const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix); From c92d016e43cf4b989993bd486e4b5be4234063f8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 26 May 2024 00:00:24 +0300 Subject: [PATCH 2/2] Don't sort scene light list by camera distance if the limit isn't reached and PP doesn't need it Also don't clip it to the single UBO scene light limit if the lighting method isn't actually single UBO --- components/sceneutil/lightmanager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 221f419c74..0377905c9d 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1161,8 +1161,10 @@ namespace SceneUtil } const bool fillPPLights = mPPLightBuffer && it->first->getName() == Constants::SceneCamera; + const bool sceneLimitReached = getLightingMethod() == LightingMethod::SingleUBO + && it->second.size() > static_cast(getMaxLightsInScene() - 1); - if (fillPPLights || getLightingMethod() == LightingMethod::SingleUBO) + if (fillPPLights || sceneLimitReached) { auto sorter = [](const LightSourceViewBound& left, const LightSourceViewBound& right) { return left.mViewBound.center().length2() - left.mViewBound.radius2() @@ -1183,7 +1185,7 @@ namespace SceneUtil } } - if (it->second.size() > static_cast(getMaxLightsInScene() - 1)) + if (sceneLimitReached) it->second.resize(getMaxLightsInScene() - 1); } }