mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-27 03:26:38 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1358 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1358 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "lightmanager.hpp"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <array>
 | |
| #include <cstring>
 | |
| #include <iterator>
 | |
| 
 | |
| #include <osg/BufferIndexBinding>
 | |
| #include <osg/BufferObject>
 | |
| #include <osg/Endian>
 | |
| #include <osg/ValueObject>
 | |
| 
 | |
| #include <osgUtil/CullVisitor>
 | |
| 
 | |
| #include <components/resource/scenemanager.hpp>
 | |
| #include <components/sceneutil/util.hpp>
 | |
| #include <components/shader/shadermanager.hpp>
 | |
| 
 | |
| #include <components/misc/constants.hpp>
 | |
| #include <components/misc/hash.hpp>
 | |
| 
 | |
| #include <components/debug/debuglog.hpp>
 | |
| 
 | |
| 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();
 | |
|         mat(0, 1) = pos.y();
 | |
|         mat(0, 2) = pos.z();
 | |
|     }
 | |
| 
 | |
|     void configureAmbient(osg::Matrixf& mat, const osg::Vec4& color)
 | |
|     {
 | |
|         mat(1, 0) = color.r();
 | |
|         mat(1, 1) = color.g();
 | |
|         mat(1, 2) = color.b();
 | |
|     }
 | |
| 
 | |
|     void configureDiffuse(osg::Matrixf& mat, const osg::Vec4& color)
 | |
|     {
 | |
|         mat(2, 0) = color.r();
 | |
|         mat(2, 1) = color.g();
 | |
|         mat(2, 2) = color.b();
 | |
|     }
 | |
| 
 | |
|     void configureSpecular(osg::Matrixf& mat, const osg::Vec4& color)
 | |
|     {
 | |
|         mat(3, 0) = color.r();
 | |
|         mat(3, 1) = color.g();
 | |
|         mat(3, 2) = color.b();
 | |
|         mat(3, 3) = color.a();
 | |
|     }
 | |
| 
 | |
|     void configureAttenuation(osg::Matrixf& mat, float c, float l, float q, float r)
 | |
|     {
 | |
|         mat(0, 3) = c;
 | |
|         mat(1, 3) = l;
 | |
|         mat(2, 3) = q;
 | |
|         mat(3, 3) = r;
 | |
|     }
 | |
| }
 | |
| 
 | |
| namespace SceneUtil
 | |
| {
 | |
|     namespace
 | |
|     {
 | |
|         const std::unordered_map<std::string, LightingMethod> lightingMethodSettingMap = {
 | |
|             { "legacy", LightingMethod::FFP },
 | |
|             { "shaders compatibility", LightingMethod::PerObjectUniform },
 | |
|             { "shaders", LightingMethod::SingleUBO },
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     static int sLightId = 0;
 | |
| 
 | |
|     // Handles a GLSL shared layout by using configured offsets and strides to fill a continuous buffer, making the data
 | |
|     // upload to GPU simpler.
 | |
|     class LightBuffer : public osg::Referenced
 | |
|     {
 | |
|     public:
 | |
|         enum LayoutOffset
 | |
|         {
 | |
|             Diffuse,
 | |
|             DiffuseSign,
 | |
|             Ambient,
 | |
|             Specular,
 | |
|             Position,
 | |
|             AttenuationRadius
 | |
|         };
 | |
| 
 | |
|         LightBuffer(int count)
 | |
|             : mData(new osg::FloatArray(3 * 4 * count))
 | |
|             , mEndian(osg::getCpuByteOrder())
 | |
|             , mCount(count)
 | |
|             , mCachedSunPos(osg::Vec4())
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         LightBuffer(const LightBuffer&) = delete;
 | |
| 
 | |
|         void setDiffuse(int index, const osg::Vec4& value)
 | |
|         {
 | |
|             // Deal with negative lights (negative diffuse) by passing a sign bit in the unused alpha component
 | |
|             auto positiveColor = value;
 | |
|             unsigned int signBit = 1;
 | |
|             if (value[0] < 0)
 | |
|             {
 | |
|                 positiveColor *= -1.0;
 | |
|                 signBit = ~0u;
 | |
|             }
 | |
|             unsigned int packedColor = asRGBA(positiveColor);
 | |
|             std::memcpy(&(*mData)[getOffset(index, Diffuse)], &packedColor, sizeof(unsigned int));
 | |
|             std::memcpy(&(*mData)[getOffset(index, DiffuseSign)], &signBit, sizeof(unsigned int));
 | |
|         }
 | |
| 
 | |
|         void setAmbient(int index, const osg::Vec4& value)
 | |
|         {
 | |
|             unsigned int packed = asRGBA(value);
 | |
|             std::memcpy(&(*mData)[getOffset(index, Ambient)], &packed, sizeof(unsigned int));
 | |
|         }
 | |
| 
 | |
|         void setSpecular(int index, const osg::Vec4& value)
 | |
|         {
 | |
|             unsigned int packed = asRGBA(value);
 | |
|             std::memcpy(&(*mData)[getOffset(index, Specular)], &packed, sizeof(unsigned int));
 | |
|         }
 | |
| 
 | |
|         void setPosition(int index, const osg::Vec4& value)
 | |
|         {
 | |
|             std::memcpy(&(*mData)[getOffset(index, Position)], value.ptr(), sizeof(osg::Vec4f));
 | |
|         }
 | |
| 
 | |
|         void setAttenuationRadius(int index, const osg::Vec4& value)
 | |
|         {
 | |
|             std::memcpy(&(*mData)[getOffset(index, AttenuationRadius)], value.ptr(), sizeof(osg::Vec4f));
 | |
|         }
 | |
| 
 | |
|         auto& getData() { return mData; }
 | |
| 
 | |
|         void dirty() { mData->dirty(); }
 | |
| 
 | |
|         static constexpr int queryBlockSize(int sz) { return 3 * osg::Vec4::num_components * sizeof(GLfloat) * 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();
 | |
|         }
 | |
| 
 | |
|         int getOffset(int index, LayoutOffset slot) const { return mOffsets.get(index, slot); }
 | |
| 
 | |
|         void configureLayout(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int size, int stride)
 | |
|         {
 | |
|             configureLayout(Offsets(offsetColors, offsetPosition, offsetAttenuationRadius, stride), size);
 | |
|         }
 | |
| 
 | |
|         void configureLayout(const LightBuffer* other)
 | |
|         {
 | |
|             mOffsets = other->mOffsets;
 | |
|             int size = other->mData->size();
 | |
| 
 | |
|             configureLayout(mOffsets, size);
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         class Offsets
 | |
|         {
 | |
|         public:
 | |
|             Offsets()
 | |
|                 : mStride(12)
 | |
|             {
 | |
|                 mValues[Diffuse] = 0;
 | |
|                 mValues[Ambient] = 1;
 | |
|                 mValues[Specular] = 2;
 | |
|                 mValues[DiffuseSign] = 3;
 | |
|                 mValues[Position] = 4;
 | |
|                 mValues[AttenuationRadius] = 8;
 | |
|             }
 | |
| 
 | |
|             Offsets(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int stride)
 | |
|                 : mStride((offsetAttenuationRadius + sizeof(GLfloat) * osg::Vec4::num_components + stride) / 4)
 | |
|             {
 | |
|                 constexpr auto sizeofFloat = sizeof(GLfloat);
 | |
|                 const auto diffuseOffset = offsetColors / sizeofFloat;
 | |
| 
 | |
|                 mValues[Diffuse] = diffuseOffset;
 | |
|                 mValues[Ambient] = diffuseOffset + 1;
 | |
|                 mValues[Specular] = diffuseOffset + 2;
 | |
|                 mValues[DiffuseSign] = diffuseOffset + 3;
 | |
|                 mValues[Position] = offsetPosition / sizeofFloat;
 | |
|                 mValues[AttenuationRadius] = offsetAttenuationRadius / sizeofFloat;
 | |
|             }
 | |
| 
 | |
|             int get(int index, LayoutOffset slot) const { return mStride * index + mValues[slot]; }
 | |
| 
 | |
|         private:
 | |
|             int mStride;
 | |
|             std::array<int, 6> mValues;
 | |
|         };
 | |
| 
 | |
|         void configureLayout(const Offsets& offsets, int size)
 | |
|         {
 | |
|             // Copy cloned data using current layout into current data using new layout.
 | |
|             // This allows to preserve osg::FloatArray buffer object in mData.
 | |
|             const auto data = mData->asVector();
 | |
|             mData->resizeArray(static_cast<unsigned>(size));
 | |
|             for (int i = 0; i < mCount; ++i)
 | |
|             {
 | |
|                 std::memcpy(
 | |
|                     &(*mData)[offsets.get(i, Diffuse)], data.data() + getOffset(i, Diffuse), sizeof(osg::Vec4f));
 | |
|                 std::memcpy(
 | |
|                     &(*mData)[offsets.get(i, Position)], data.data() + getOffset(i, Position), sizeof(osg::Vec4f));
 | |
|                 std::memcpy(&(*mData)[offsets.get(i, AttenuationRadius)], data.data() + getOffset(i, AttenuationRadius),
 | |
|                     sizeof(osg::Vec4f));
 | |
|             }
 | |
|             mOffsets = offsets;
 | |
|         }
 | |
| 
 | |
|         osg::ref_ptr<osg::FloatArray> mData;
 | |
|         osg::Endian mEndian;
 | |
|         int mCount;
 | |
|         Offsets mOffsets;
 | |
|         osg::Vec4 mCachedSunPos;
 | |
|     };
 | |
| 
 | |
|     struct LightStateCache
 | |
|     {
 | |
|         std::vector<osg::Light*> lastAppliedLight;
 | |
|     };
 | |
| 
 | |
|     LightStateCache* getLightStateCache(size_t contextid, size_t size = 8)
 | |
|     {
 | |
|         static std::vector<LightStateCache> cacheVector;
 | |
|         if (cacheVector.size() < contextid + 1)
 | |
|             cacheVector.resize(contextid + 1);
 | |
|         cacheVector[contextid].lastAppliedLight.resize(size);
 | |
|         return &cacheVector[contextid];
 | |
|     }
 | |
| 
 | |
|     void configureStateSetSunOverride(
 | |
|         LightManager* lightManager, const osg::Light* light, osg::StateSet* stateset, int mode)
 | |
|     {
 | |
|         auto method = lightManager->getLightingMethod();
 | |
|         switch (method)
 | |
|         {
 | |
|             case LightingMethod::FFP:
 | |
|             {
 | |
|                 break;
 | |
|             }
 | |
|             case LightingMethod::PerObjectUniform:
 | |
|             {
 | |
|                 osg::Matrixf lightMat;
 | |
|                 configurePosition(lightMat, light->getPosition());
 | |
|                 configureAmbient(lightMat, light->getAmbient());
 | |
|                 configureDiffuse(lightMat, light->getDiffuse());
 | |
|                 configureSpecular(lightMat, light->getSpecular());
 | |
| 
 | |
|                 stateset->addUniform(lightManager->generateLightBufferUniform(lightMat), mode);
 | |
|                 break;
 | |
|             }
 | |
|             case LightingMethod::SingleUBO:
 | |
|             {
 | |
|                 osg::ref_ptr<LightBuffer> buffer = new LightBuffer(lightManager->getMaxLightsInScene());
 | |
| 
 | |
|                 buffer->setDiffuse(0, light->getDiffuse());
 | |
|                 buffer->setAmbient(0, light->getAmbient());
 | |
|                 buffer->setSpecular(0, light->getSpecular());
 | |
|                 buffer->setPosition(0, light->getPosition());
 | |
| 
 | |
|                 osg::ref_ptr<osg::UniformBufferObject> ubo = new osg::UniformBufferObject;
 | |
|                 buffer->getData()->setBufferObject(ubo);
 | |
|                 osg::ref_ptr<osg::UniformBufferBinding> ubb
 | |
|                     = new osg::UniformBufferBinding(static_cast<int>(Resource::SceneManager::UBOBinding::LightBuffer),
 | |
|                         buffer->getData(), 0, buffer->getData()->getTotalDataSize());
 | |
|                 stateset->setAttributeAndModes(ubb, mode);
 | |
| 
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     class DisableLight : public osg::StateAttribute
 | |
|     {
 | |
|     public:
 | |
|         DisableLight()
 | |
|             : mIndex(0)
 | |
|         {
 | |
|         }
 | |
|         DisableLight(int index)
 | |
|             : mIndex(index)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         DisableLight(const DisableLight& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY)
 | |
|             : osg::StateAttribute(copy, copyop)
 | |
|             , mIndex(copy.mIndex)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         META_StateAttribute(SceneUtil, DisableLight, osg::StateAttribute::LIGHT)
 | |
| 
 | |
|         unsigned int getMember() const override { return mIndex; }
 | |
| 
 | |
|         bool getModeUsage(ModeUsage& usage) const override
 | |
|         {
 | |
|             usage.usesMode(GL_LIGHT0 + mIndex);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         int compare(const StateAttribute& sa) const override
 | |
|         {
 | |
|             throw std::runtime_error("DisableLight::compare: unimplemented");
 | |
|         }
 | |
| 
 | |
|         void apply(osg::State& state) const override
 | |
|         {
 | |
|             int lightNum = GL_LIGHT0 + mIndex;
 | |
|             glLightfv(lightNum, GL_AMBIENT, mNullptr.ptr());
 | |
|             glLightfv(lightNum, GL_DIFFUSE, mNullptr.ptr());
 | |
|             glLightfv(lightNum, GL_SPECULAR, mNullptr.ptr());
 | |
| 
 | |
|             LightStateCache* cache = getLightStateCache(state.getContextID());
 | |
|             cache->lastAppliedLight[mIndex] = nullptr;
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         size_t mIndex;
 | |
|         osg::Vec4f mNullptr;
 | |
|     };
 | |
| 
 | |
|     class FFPLightStateAttribute : public osg::StateAttribute
 | |
|     {
 | |
|     public:
 | |
|         FFPLightStateAttribute()
 | |
|             : mIndex(0)
 | |
|         {
 | |
|         }
 | |
|         FFPLightStateAttribute(size_t index, const std::vector<osg::ref_ptr<osg::Light>>& lights)
 | |
|             : mIndex(index)
 | |
|             , mLights(lights)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         FFPLightStateAttribute(
 | |
|             const FFPLightStateAttribute& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY)
 | |
|             : osg::StateAttribute(copy, copyop)
 | |
|             , mIndex(copy.mIndex)
 | |
|             , mLights(copy.mLights)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         unsigned int getMember() const override { return mIndex; }
 | |
| 
 | |
|         bool getModeUsage(ModeUsage& usage) const override
 | |
|         {
 | |
|             for (size_t i = 0; i < mLights.size(); ++i)
 | |
|                 usage.usesMode(GL_LIGHT0 + mIndex + i);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         int compare(const StateAttribute& sa) const override
 | |
|         {
 | |
|             throw std::runtime_error("FFPLightStateAttribute::compare: unimplemented");
 | |
|         }
 | |
| 
 | |
|         META_StateAttribute(SceneUtil, FFPLightStateAttribute, osg::StateAttribute::LIGHT)
 | |
| 
 | |
|         void apply(osg::State& state) const override
 | |
|         {
 | |
|             if (mLights.empty())
 | |
|                 return;
 | |
|             osg::Matrix modelViewMatrix = state.getModelViewMatrix();
 | |
| 
 | |
|             state.applyModelViewMatrix(state.getInitialViewMatrix());
 | |
| 
 | |
|             LightStateCache* cache = getLightStateCache(state.getContextID());
 | |
| 
 | |
|             for (size_t i = 0; i < mLights.size(); ++i)
 | |
|             {
 | |
|                 osg::Light* current = cache->lastAppliedLight[i + mIndex];
 | |
|                 if (current != mLights[i].get())
 | |
|                 {
 | |
|                     applyLight((GLenum)((int)GL_LIGHT0 + i + mIndex), mLights[i].get());
 | |
|                     cache->lastAppliedLight[i + mIndex] = mLights[i].get();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             state.applyModelViewMatrix(modelViewMatrix);
 | |
|         }
 | |
| 
 | |
|         void applyLight(GLenum lightNum, const osg::Light* light) const
 | |
|         {
 | |
|             glLightfv(lightNum, GL_AMBIENT, light->getAmbient().ptr());
 | |
|             glLightfv(lightNum, GL_DIFFUSE, light->getDiffuse().ptr());
 | |
|             glLightfv(lightNum, GL_SPECULAR, light->getSpecular().ptr());
 | |
|             glLightfv(lightNum, GL_POSITION, light->getPosition().ptr());
 | |
|             // TODO: enable this once spot lights are supported
 | |
|             // need to transform SPOT_DIRECTION by the world matrix?
 | |
|             // glLightfv(lightNum, GL_SPOT_DIRECTION, light->getDirection().ptr());
 | |
|             // glLightf(lightNum, GL_SPOT_EXPONENT, light->getSpotExponent());
 | |
|             // glLightf(lightNum, GL_SPOT_CUTOFF, light->getSpotCutoff());
 | |
|             glLightf(lightNum, GL_CONSTANT_ATTENUATION, light->getConstantAttenuation());
 | |
|             glLightf(lightNum, GL_LINEAR_ATTENUATION, light->getLinearAttenuation());
 | |
|             glLightf(lightNum, GL_QUADRATIC_ATTENUATION, light->getQuadraticAttenuation());
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         size_t mIndex;
 | |
|         std::vector<osg::ref_ptr<osg::Light>> mLights;
 | |
|     };
 | |
| 
 | |
|     struct StateSetGenerator
 | |
|     {
 | |
|         LightManager* mLightManager;
 | |
| 
 | |
|         virtual ~StateSetGenerator() {}
 | |
| 
 | |
|         virtual osg::ref_ptr<osg::StateSet> generate(const LightManager::LightList& lightList, size_t frameNum) = 0;
 | |
| 
 | |
|         virtual void update(osg::StateSet* stateset, const LightManager::LightList& lightList, size_t frameNum) {}
 | |
| 
 | |
|         osg::Matrix mViewMatrix;
 | |
|     };
 | |
| 
 | |
|     struct StateSetGeneratorFFP : StateSetGenerator
 | |
|     {
 | |
|         osg::ref_ptr<osg::StateSet> generate(const LightManager::LightList& lightList, size_t frameNum) override
 | |
|         {
 | |
|             osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
 | |
| 
 | |
|             std::vector<osg::ref_ptr<osg::Light>> lights;
 | |
|             lights.reserve(lightList.size());
 | |
|             for (size_t i = 0; i < lightList.size(); ++i)
 | |
|                 lights.emplace_back(lightList[i]->mLightSource->getLight(frameNum));
 | |
| 
 | |
|             // the first light state attribute handles the actual state setting for all lights
 | |
|             // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary
 | |
|             // don't use setAttributeAndModes, that does not support light indices!
 | |
|             stateset->setAttribute(
 | |
|                 new FFPLightStateAttribute(mLightManager->getStartLight(), std::move(lights)), osg::StateAttribute::ON);
 | |
| 
 | |
|             for (size_t i = 0; i < lightList.size(); ++i)
 | |
|                 stateset->setMode(GL_LIGHT0 + mLightManager->getStartLight() + i, osg::StateAttribute::ON);
 | |
| 
 | |
|             // need to push some dummy attributes to ensure proper state tracking
 | |
|             // lights need to reset to their default when the StateSet is popped
 | |
|             for (size_t i = 1; i < lightList.size(); ++i)
 | |
|                 stateset->setAttribute(
 | |
|                     mLightManager->getDummies()[i + mLightManager->getStartLight()].get(), osg::StateAttribute::ON);
 | |
| 
 | |
|             return stateset;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     struct StateSetGeneratorSingleUBO : StateSetGenerator
 | |
|     {
 | |
|         osg::ref_ptr<osg::StateSet> generate(const LightManager::LightList& lightList, size_t frameNum) override
 | |
|         {
 | |
|             osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
 | |
| 
 | |
|             osg::ref_ptr<osg::Uniform> indicesUni
 | |
|                 = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", mLightManager->getMaxLights());
 | |
|             int pointCount = 0;
 | |
| 
 | |
|             for (size_t i = 0; i < lightList.size(); ++i)
 | |
|             {
 | |
|                 int bufIndex = mLightManager->getLightIndexMap(frameNum)[lightList[i]->mLightSource->getId()];
 | |
|                 indicesUni->setElement(pointCount++, bufIndex);
 | |
|             }
 | |
|             stateset->addUniform(indicesUni);
 | |
|             stateset->addUniform(new osg::Uniform("PointLightCount", pointCount));
 | |
| 
 | |
|             return stateset;
 | |
|         }
 | |
| 
 | |
|         // Cached statesets must be revalidated in case the light indices change. There is no actual link between
 | |
|         // a light's ID and the buffer index it will eventually be assigned (or reassigned) to.
 | |
|         void update(osg::StateSet* stateset, const LightManager::LightList& lightList, size_t frameNum) override
 | |
|         {
 | |
|             int newCount = 0;
 | |
|             int oldCount;
 | |
| 
 | |
|             auto uOldArray = stateset->getUniform("PointLightIndex");
 | |
|             auto uOldCount = stateset->getUniform("PointLightCount");
 | |
| 
 | |
|             uOldCount->get(oldCount);
 | |
| 
 | |
|             // max lights count can change during runtime
 | |
|             oldCount = std::min(mLightManager->getMaxLights(), oldCount);
 | |
| 
 | |
|             auto& lightData = mLightManager->getLightIndexMap(frameNum);
 | |
| 
 | |
|             for (int i = 0; i < oldCount; ++i)
 | |
|             {
 | |
|                 auto* lightSource = lightList[i]->mLightSource;
 | |
|                 auto it = lightData.find(lightSource->getId());
 | |
|                 if (it != lightData.end())
 | |
|                     uOldArray->setElement(newCount++, it->second);
 | |
|             }
 | |
| 
 | |
|             uOldArray->dirty();
 | |
|             uOldCount->set(newCount);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     struct StateSetGeneratorPerObjectUniform : StateSetGenerator
 | |
|     {
 | |
|         osg::ref_ptr<osg::StateSet> generate(const LightManager::LightList& lightList, size_t frameNum) override
 | |
|         {
 | |
|             osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
 | |
|             osg::ref_ptr<osg::Uniform> data
 | |
|                 = mLightManager->generateLightBufferUniform(mLightManager->getSunlightBuffer(frameNum));
 | |
| 
 | |
|             for (size_t i = 0; i < lightList.size(); ++i)
 | |
|             {
 | |
|                 auto* light = lightList[i]->mLightSource->getLight(frameNum);
 | |
|                 osg::Matrixf lightMat;
 | |
|                 configurePosition(lightMat, light->getPosition() * mViewMatrix);
 | |
|                 configureAmbient(lightMat, light->getAmbient());
 | |
|                 configureDiffuse(lightMat, light->getDiffuse());
 | |
|                 configureSpecular(lightMat, light->getSpecular());
 | |
|                 configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(),
 | |
|                     light->getQuadraticAttenuation(), lightList[i]->mLightSource->getRadius());
 | |
| 
 | |
|                 data->setElement(i + 1, lightMat);
 | |
|             }
 | |
| 
 | |
|             stateset->addUniform(data);
 | |
|             stateset->addUniform(new osg::Uniform("PointLightCount", static_cast<int>(lightList.size() + 1)));
 | |
| 
 | |
|             return stateset;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     LightManager* findLightManager(const osg::NodePath& path)
 | |
|     {
 | |
|         for (size_t i = 0; i < path.size(); ++i)
 | |
|         {
 | |
|             if (LightManager* lightManager = dynamic_cast<LightManager*>(path[i]))
 | |
|                 return lightManager;
 | |
|         }
 | |
|         return nullptr;
 | |
|     }
 | |
| 
 | |
|     // Set on a LightSource. Adds the light source to its light manager for the current frame.
 | |
|     // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the
 | |
|     // manager.
 | |
|     class CollectLightCallback : public NodeCallback<CollectLightCallback>
 | |
|     {
 | |
|     public:
 | |
|         CollectLightCallback()
 | |
|             : mLightManager(nullptr)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop)
 | |
|             : NodeCallback<CollectLightCallback>(copy, copyop)
 | |
|             , mLightManager(nullptr)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         META_Object(SceneUtil, CollectLightCallback)
 | |
| 
 | |
|         void operator()(osg::Node* node, osg::NodeVisitor* nv)
 | |
|         {
 | |
|             if (!mLightManager)
 | |
|             {
 | |
|                 mLightManager = findLightManager(nv->getNodePath());
 | |
| 
 | |
|                 if (!mLightManager)
 | |
|                     throw std::runtime_error("can't find parent LightManager");
 | |
|             }
 | |
| 
 | |
|             mLightManager->addLight(
 | |
|                 static_cast<LightSource*>(node), osg::computeLocalToWorld(nv->getNodePath()), nv->getTraversalNumber());
 | |
| 
 | |
|             traverse(node, nv);
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         LightManager* mLightManager;
 | |
|     };
 | |
| 
 | |
|     // Set on a LightManager. Clears the data from the previous frame.
 | |
|     class LightManagerUpdateCallback : public SceneUtil::NodeCallback<LightManagerUpdateCallback>
 | |
|     {
 | |
|     public:
 | |
|         void operator()(osg::Node* node, osg::NodeVisitor* nv)
 | |
|         {
 | |
|             LightManager* lightManager = static_cast<LightManager*>(node);
 | |
|             lightManager->update(nv->getTraversalNumber());
 | |
| 
 | |
|             traverse(node, nv);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     class LightManagerCullCallback
 | |
|         : public SceneUtil::NodeCallback<LightManagerCullCallback, LightManager*, osgUtil::CullVisitor*>
 | |
|     {
 | |
|     public:
 | |
|         LightManagerCullCallback(LightManager* lightManager)
 | |
|         {
 | |
|             if (!lightManager->getUBOManager())
 | |
|                 return;
 | |
| 
 | |
|             for (size_t i = 0; i < mUBBs.size(); ++i)
 | |
|             {
 | |
|                 auto& buffer = lightManager->getUBOManager()->getLightBuffer(i);
 | |
|                 mUBBs[i]
 | |
|                     = new osg::UniformBufferBinding(static_cast<int>(Resource::SceneManager::UBOBinding::LightBuffer),
 | |
|                         buffer->getData(), 0, buffer->getData()->getTotalDataSize());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void operator()(LightManager* node, osgUtil::CullVisitor* cv)
 | |
|         {
 | |
|             osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
 | |
| 
 | |
|             if (node->getLightingMethod() == LightingMethod::SingleUBO)
 | |
|             {
 | |
|                 const size_t frameId = cv->getTraversalNumber() % 2;
 | |
|                 stateset->setAttributeAndModes(mUBBs[frameId], osg::StateAttribute::ON);
 | |
| 
 | |
|                 auto& buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber());
 | |
| 
 | |
|                 if (auto sun = node->getSunlight())
 | |
|                 {
 | |
|                     buffer->setCachedSunPos(sun->getPosition());
 | |
|                     buffer->setAmbient(0, sun->getAmbient());
 | |
|                     buffer->setDiffuse(0, sun->getDiffuse());
 | |
|                     buffer->setSpecular(0, sun->getSpecular());
 | |
|                 }
 | |
|             }
 | |
|             else if (node->getLightingMethod() == LightingMethod::PerObjectUniform)
 | |
|             {
 | |
|                 if (auto sun = node->getSunlight())
 | |
|                 {
 | |
|                     osg::Matrixf lightMat;
 | |
|                     configurePosition(
 | |
|                         lightMat, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix()));
 | |
|                     configureAmbient(lightMat, sun->getAmbient());
 | |
|                     configureDiffuse(lightMat, sun->getDiffuse());
 | |
|                     configureSpecular(lightMat, sun->getSpecular());
 | |
|                     node->setSunlightBuffer(lightMat, cv->getTraversalNumber());
 | |
|                     stateset->addUniform(node->generateLightBufferUniform(lightMat));
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             cv->pushStateSet(stateset);
 | |
|             traverse(node, cv);
 | |
|             cv->popStateSet();
 | |
| 
 | |
|             if (node->getPPLightsBuffer() && cv->getCurrentCamera()->getName() == Constants::SceneCamera)
 | |
|                 node->getPPLightsBuffer()->updateCount(cv->getTraversalNumber());
 | |
|         }
 | |
| 
 | |
|         std::array<osg::ref_ptr<osg::UniformBufferBinding>, 2> mUBBs;
 | |
|     };
 | |
| 
 | |
|     UBOManager::UBOManager(int lightCount)
 | |
|         : mDummyProgram(new osg::Program)
 | |
|         , mInitLayout(false)
 | |
|         , mDirty({ true, true })
 | |
|         , mTemplate(new LightBuffer(lightCount))
 | |
|     {
 | |
|         static const std::string dummyVertSource = generateDummyShader(lightCount);
 | |
| 
 | |
|         // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not
 | |
|         // reliably available, regardless of extensions, until GLSL 140.
 | |
|         mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource));
 | |
|         mDummyProgram->addBindUniformBlock(
 | |
|             "LightBufferBinding", static_cast<int>(Resource::SceneManager::UBOBinding::LightBuffer));
 | |
| 
 | |
|         for (size_t i = 0; i < mLightBuffers.size(); ++i)
 | |
|         {
 | |
|             mLightBuffers[i] = new LightBuffer(lightCount);
 | |
| 
 | |
|             osg::ref_ptr<osg::UniformBufferObject> ubo = new osg::UniformBufferObject;
 | |
|             ubo->setUsage(GL_STREAM_DRAW);
 | |
| 
 | |
|             mLightBuffers[i]->getData()->setBufferObject(ubo);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     UBOManager::UBOManager(const UBOManager& copy, const osg::CopyOp& copyop)
 | |
|         : osg::StateAttribute(copy, copyop)
 | |
|         , mDummyProgram(copy.mDummyProgram)
 | |
|         , mInitLayout(copy.mInitLayout)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     void UBOManager::releaseGLObjects(osg::State* state) const
 | |
|     {
 | |
|         mDummyProgram->releaseGLObjects(state);
 | |
|     }
 | |
| 
 | |
|     int UBOManager::compare(const StateAttribute& sa) const
 | |
|     {
 | |
|         throw std::runtime_error("LightManagerStateAttribute::compare: unimplemented");
 | |
|     }
 | |
| 
 | |
|     void UBOManager::apply(osg::State& state) const
 | |
|     {
 | |
|         unsigned int frame = state.getFrameStamp()->getFrameNumber();
 | |
|         unsigned int index = frame % 2;
 | |
| 
 | |
|         if (!mInitLayout)
 | |
|         {
 | |
|             mDummyProgram->apply(state);
 | |
|             auto handle = mDummyProgram->getPCP(state)->getHandle();
 | |
|             auto* ext = state.get<osg::GLExtensions>();
 | |
| 
 | |
|             int activeUniformBlocks = 0;
 | |
|             ext->glGetProgramiv(handle, GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks);
 | |
| 
 | |
|             // wait until the UBO binding is created
 | |
|             if (activeUniformBlocks > 0)
 | |
|             {
 | |
|                 initSharedLayout(ext, handle, frame);
 | |
|                 mInitLayout = true;
 | |
|             }
 | |
|         }
 | |
|         else if (mDirty[index])
 | |
|         {
 | |
|             mDirty[index] = false;
 | |
|             mLightBuffers[index]->configureLayout(mTemplate);
 | |
|         }
 | |
| 
 | |
|         mLightBuffers[index]->uploadCachedSunPos(state.getInitialViewMatrix());
 | |
|         mLightBuffers[index]->dirty();
 | |
|     }
 | |
| 
 | |
|     std::string UBOManager::generateDummyShader(int maxLightsInScene)
 | |
|     {
 | |
|         const std::string define = "@maxLightsInScene";
 | |
| 
 | |
|         std::string shader = R"GLSL(
 | |
|             #version 120
 | |
|             #extension GL_ARB_uniform_buffer_object : require
 | |
|             struct LightData {
 | |
|                 ivec4 packedColors;
 | |
|                 vec4 position;
 | |
|                 vec4 attenuation;
 | |
|             };
 | |
|             uniform LightBufferBinding {
 | |
|                 LightData LightBuffer[@maxLightsInScene];
 | |
|             };
 | |
|             void main()
 | |
|             {
 | |
|                 gl_Position = vec4(0.0);
 | |
|             }
 | |
|         )GLSL";
 | |
| 
 | |
|         shader.replace(shader.find(define), define.length(), std::to_string(maxLightsInScene));
 | |
|         return shader;
 | |
|     }
 | |
| 
 | |
|     void UBOManager::initSharedLayout(osg::GLExtensions* ext, int handle, unsigned int frame) const
 | |
|     {
 | |
|         constexpr std::array<unsigned int, 1> index
 | |
|             = { static_cast<unsigned int>(Resource::SceneManager::UBOBinding::LightBuffer) };
 | |
|         int totalBlockSize = -1;
 | |
|         int stride = -1;
 | |
| 
 | |
|         ext->glGetActiveUniformBlockiv(handle, 0, GL_UNIFORM_BLOCK_DATA_SIZE, &totalBlockSize);
 | |
|         ext->glGetActiveUniformsiv(handle, index.size(), index.data(), GL_UNIFORM_ARRAY_STRIDE, &stride);
 | |
| 
 | |
|         std::array<const char*, 3> names = {
 | |
|             "LightBuffer[0].packedColors",
 | |
|             "LightBuffer[0].position",
 | |
|             "LightBuffer[0].attenuation",
 | |
|         };
 | |
|         std::vector<unsigned int> indices(names.size());
 | |
|         std::vector<int> offsets(names.size());
 | |
| 
 | |
|         ext->glGetUniformIndices(handle, names.size(), names.data(), indices.data());
 | |
|         ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data());
 | |
| 
 | |
|         mTemplate->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride);
 | |
|     }
 | |
| 
 | |
|     LightingMethod LightManager::getLightingMethodFromString(const std::string& value)
 | |
|     {
 | |
|         auto it = lightingMethodSettingMap.find(value);
 | |
|         if (it != lightingMethodSettingMap.end())
 | |
|             return it->second;
 | |
| 
 | |
|         constexpr const char* fallback = "shaders compatibility";
 | |
|         Log(Debug::Warning) << "Unknown lighting method '" << value << "', returning fallback '" << fallback << "'";
 | |
|         return LightingMethod::PerObjectUniform;
 | |
|     }
 | |
| 
 | |
|     std::string LightManager::getLightingMethodString(LightingMethod method)
 | |
|     {
 | |
|         for (const auto& p : lightingMethodSettingMap)
 | |
|             if (p.second == method)
 | |
|                 return p.first;
 | |
|         return "";
 | |
|     }
 | |
| 
 | |
|     LightManager::LightManager(const LightSettings& settings)
 | |
|         : mStartLight(0)
 | |
|         , mLightingMask(~0u)
 | |
|         , mSun(nullptr)
 | |
|         , mPointLightRadiusMultiplier(1.f)
 | |
|         , 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<int>(LightingMethod::FFP)] = true;
 | |
|         mSupported[static_cast<int>(LightingMethod::PerObjectUniform)] = true;
 | |
|         mSupported[static_cast<int>(LightingMethod::SingleUBO)] = supportsUBO && supportsGPU4;
 | |
| 
 | |
|         setUpdateCallback(new LightManagerUpdateCallback);
 | |
| 
 | |
|         if (settings.mLightingMethod == LightingMethod::FFP)
 | |
|         {
 | |
|             initFFP(ffpMaxLights);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         static bool hasLoggedWarnings = false;
 | |
| 
 | |
|         if (settings.mLightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings)
 | |
|         {
 | |
|             if (!supportsUBO)
 | |
|                 Log(Debug::Warning)
 | |
|                     << "GL_ARB_uniform_buffer_object not supported: switching to shader compatibility lighting mode";
 | |
|             if (!supportsGPU4)
 | |
|                 Log(Debug::Warning)
 | |
|                     << "GL_EXT_gpu_shader4 not supported: switching to shader compatibility lighting mode";
 | |
|             hasLoggedWarnings = true;
 | |
|         }
 | |
| 
 | |
|         if (!supportsUBO || !supportsGPU4 || settings.mLightingMethod == LightingMethod::PerObjectUniform)
 | |
|             initPerObjectUniform(settings.mMaxLights);
 | |
|         else
 | |
|             initSingleUBO(settings.mMaxLights);
 | |
| 
 | |
|         updateSettings(settings.mLightBoundsMultiplier, settings.mMaximumLightDistance, settings.mLightFadeStart);
 | |
| 
 | |
|         getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0));
 | |
| 
 | |
|         addCullCallback(new LightManagerCullCallback(this));
 | |
|     }
 | |
| 
 | |
|     LightManager::LightManager(const LightManager& copy, const osg::CopyOp& copyop)
 | |
|         : osg::Group(copy, copyop)
 | |
|         , mStartLight(copy.mStartLight)
 | |
|         , mLightingMask(copy.mLightingMask)
 | |
|         , mSun(copy.mSun)
 | |
|         , mLightingMethod(copy.mLightingMethod)
 | |
|         , mPointLightRadiusMultiplier(copy.mPointLightRadiusMultiplier)
 | |
|         , mPointLightFadeEnd(copy.mPointLightFadeEnd)
 | |
|         , mPointLightFadeStart(copy.mPointLightFadeStart)
 | |
|         , mMaxLights(copy.mMaxLights)
 | |
|         , mPPLightBuffer(copy.mPPLightBuffer)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     LightingMethod LightManager::getLightingMethod() const
 | |
|     {
 | |
|         return mLightingMethod;
 | |
|     }
 | |
| 
 | |
|     bool LightManager::usingFFP() const
 | |
|     {
 | |
|         return mLightingMethod == LightingMethod::FFP;
 | |
|     }
 | |
| 
 | |
|     int LightManager::getMaxLights() const
 | |
|     {
 | |
|         return mMaxLights;
 | |
|     }
 | |
| 
 | |
|     void LightManager::setMaxLights(int value)
 | |
|     {
 | |
|         mMaxLights = value;
 | |
|     }
 | |
| 
 | |
|     int LightManager::getMaxLightsInScene() const
 | |
|     {
 | |
|         static constexpr int max = 16384 / LightBuffer::queryBlockSize(1);
 | |
|         return max;
 | |
|     }
 | |
| 
 | |
|     Shader::ShaderManager::DefineMap LightManager::getLightDefines() const
 | |
|     {
 | |
|         Shader::ShaderManager::DefineMap defines;
 | |
| 
 | |
|         defines["maxLights"] = std::to_string(getMaxLights());
 | |
|         defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene());
 | |
|         defines["lightingMethodFFP"] = getLightingMethod() == LightingMethod::FFP ? "1" : "0";
 | |
|         defines["lightingMethodPerObjectUniform"] = getLightingMethod() == LightingMethod::PerObjectUniform ? "1" : "0";
 | |
|         defines["lightingMethodUBO"] = getLightingMethod() == LightingMethod::SingleUBO ? "1" : "0";
 | |
|         defines["useUBO"] = std::to_string(getLightingMethod() == LightingMethod::SingleUBO);
 | |
|         // exposes bitwise operators
 | |
|         defines["useGPUShader4"] = std::to_string(getLightingMethod() == LightingMethod::SingleUBO);
 | |
|         defines["getLight"] = getLightingMethod() == LightingMethod::FFP ? "gl_LightSource" : "LightBuffer";
 | |
|         defines["startLight"] = getLightingMethod() == LightingMethod::SingleUBO ? "0" : "1";
 | |
|         defines["endLight"] = getLightingMethod() == LightingMethod::FFP ? defines["maxLights"] : "PointLightCount";
 | |
| 
 | |
|         return defines;
 | |
|     }
 | |
| 
 | |
|     void LightManager::processChangedSettings(
 | |
|         float lightBoundsMultiplier, float maximumLightDistance, float lightFadeStart)
 | |
|     {
 | |
|         updateSettings(lightBoundsMultiplier, maximumLightDistance, lightFadeStart);
 | |
|     }
 | |
| 
 | |
|     void LightManager::updateMaxLights(int maxLights)
 | |
|     {
 | |
|         if (usingFFP())
 | |
|             return;
 | |
| 
 | |
|         setMaxLights(maxLights);
 | |
| 
 | |
|         if (getLightingMethod() == LightingMethod::PerObjectUniform)
 | |
|         {
 | |
|             getStateSet()->removeUniform("LightBuffer");
 | |
|             getStateSet()->addUniform(generateLightBufferUniform(osg::Matrixf()));
 | |
|         }
 | |
| 
 | |
|         for (auto& cache : mStateSetCache)
 | |
|             cache.clear();
 | |
|     }
 | |
| 
 | |
|     void LightManager::updateSettings(float lightBoundsMultiplier, float maximumLightDistance, float lightFadeStart)
 | |
|     {
 | |
|         if (getLightingMethod() == LightingMethod::FFP)
 | |
|             return;
 | |
| 
 | |
|         mPointLightRadiusMultiplier = lightBoundsMultiplier;
 | |
|         mPointLightFadeEnd = maximumLightDistance;
 | |
|         if (mPointLightFadeEnd > 0)
 | |
|             mPointLightFadeStart = mPointLightFadeEnd * lightFadeStart;
 | |
|     }
 | |
| 
 | |
|     void LightManager::initFFP(int targetLights)
 | |
|     {
 | |
|         setLightingMethod(LightingMethod::FFP);
 | |
|         setMaxLights(targetLights);
 | |
| 
 | |
|         for (int i = 0; i < getMaxLights(); ++i)
 | |
|             mDummies.push_back(new FFPLightStateAttribute(i, std::vector<osg::ref_ptr<osg::Light>>()));
 | |
|     }
 | |
| 
 | |
|     void LightManager::initPerObjectUniform(int targetLights)
 | |
|     {
 | |
|         setLightingMethod(LightingMethod::PerObjectUniform);
 | |
|         setMaxLights(targetLights);
 | |
| 
 | |
|         getOrCreateStateSet()->addUniform(generateLightBufferUniform(osg::Matrixf()));
 | |
|     }
 | |
| 
 | |
|     void LightManager::initSingleUBO(int targetLights)
 | |
|     {
 | |
|         setLightingMethod(LightingMethod::SingleUBO);
 | |
|         setMaxLights(targetLights);
 | |
| 
 | |
|         mUBOManager = new UBOManager(getMaxLightsInScene());
 | |
|         getOrCreateStateSet()->setAttributeAndModes(mUBOManager);
 | |
|     }
 | |
| 
 | |
|     void LightManager::setLightingMethod(LightingMethod method)
 | |
|     {
 | |
|         mLightingMethod = method;
 | |
|         switch (method)
 | |
|         {
 | |
|             case LightingMethod::FFP:
 | |
|                 mStateSetGenerator = std::make_unique<StateSetGeneratorFFP>();
 | |
|                 break;
 | |
|             case LightingMethod::SingleUBO:
 | |
|                 mStateSetGenerator = std::make_unique<StateSetGeneratorSingleUBO>();
 | |
|                 break;
 | |
|             case LightingMethod::PerObjectUniform:
 | |
|                 mStateSetGenerator = std::make_unique<StateSetGeneratorPerObjectUniform>();
 | |
|                 break;
 | |
|         }
 | |
|         mStateSetGenerator->mLightManager = this;
 | |
|     }
 | |
| 
 | |
|     void LightManager::setLightingMask(size_t mask)
 | |
|     {
 | |
|         mLightingMask = mask;
 | |
|     }
 | |
| 
 | |
|     size_t LightManager::getLightingMask() const
 | |
|     {
 | |
|         return mLightingMask;
 | |
|     }
 | |
| 
 | |
|     void LightManager::setStartLight(int start)
 | |
|     {
 | |
|         mStartLight = start;
 | |
| 
 | |
|         if (!usingFFP())
 | |
|             return;
 | |
| 
 | |
|         // Set default light state to zero
 | |
|         // This is necessary because shaders don't respect glDisable(GL_LIGHTX) so in addition to disabling
 | |
|         // we'll have to set a light state that has no visible effect
 | |
|         for (int i = start; i < getMaxLights(); ++i)
 | |
|         {
 | |
|             osg::ref_ptr<DisableLight> defaultLight(new DisableLight(i));
 | |
|             getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     int LightManager::getStartLight() const
 | |
|     {
 | |
|         return mStartLight;
 | |
|     }
 | |
| 
 | |
|     void LightManager::update(size_t frameNum)
 | |
|     {
 | |
|         if (mPPLightBuffer)
 | |
|             mPPLightBuffer->clear(frameNum);
 | |
| 
 | |
|         getLightIndexMap(frameNum).clear();
 | |
|         mLights.clear();
 | |
|         mLightsInViewSpace.clear();
 | |
| 
 | |
|         // Do an occasional cleanup for orphaned lights.
 | |
|         for (int i = 0; i < 2; ++i)
 | |
|         {
 | |
|             if (mStateSetCache[i].size() > 5000)
 | |
|                 mStateSetCache[i].clear();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum)
 | |
|     {
 | |
|         LightSourceTransform l;
 | |
|         l.mLightSource = lightSource;
 | |
|         l.mWorldMatrix = worldMat;
 | |
|         osg::Vec3f pos = osg::Vec3f(worldMat.getTrans().x(), worldMat.getTrans().y(), worldMat.getTrans().z());
 | |
|         lightSource->getLight(frameNum)->setPosition(osg::Vec4f(pos, 1.f));
 | |
| 
 | |
|         mLights.push_back(l);
 | |
|     }
 | |
| 
 | |
|     void LightManager::setSunlight(osg::ref_ptr<osg::Light> sun)
 | |
|     {
 | |
|         if (usingFFP())
 | |
|             return;
 | |
| 
 | |
|         mSun = sun;
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Light> LightManager::getSunlight()
 | |
|     {
 | |
|         return mSun;
 | |
|     }
 | |
| 
 | |
|     size_t LightManager::HashLightIdList::operator()(const LightIdList& lightIdList) const
 | |
|     {
 | |
|         size_t hash = 0;
 | |
|         for (size_t i = 0; i < lightIdList.size(); ++i)
 | |
|             Misc::hashCombine(hash, lightIdList[i]);
 | |
|         return hash;
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::StateSet> LightManager::getLightListStateSet(
 | |
|         const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix)
 | |
|     {
 | |
|         if (getLightingMethod() == LightingMethod::PerObjectUniform)
 | |
|         {
 | |
|             mStateSetGenerator->mViewMatrix = *viewMatrix;
 | |
|             return mStateSetGenerator->generate(lightList, frameNum);
 | |
|         }
 | |
| 
 | |
|         // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a
 | |
|         // suitable one exists)
 | |
| 
 | |
|         if (getLightingMethod() == LightingMethod::SingleUBO)
 | |
|         {
 | |
|             for (size_t i = 0; i < lightList.size(); ++i)
 | |
|             {
 | |
|                 auto id = lightList[i]->mLightSource->getId();
 | |
|                 if (getLightIndexMap(frameNum).find(id) != getLightIndexMap(frameNum).end())
 | |
|                     continue;
 | |
| 
 | |
|                 int index = getLightIndexMap(frameNum).size() + 1;
 | |
|                 updateGPUPointLight(index, lightList[i]->mLightSource, frameNum, viewMatrix);
 | |
|                 getLightIndexMap(frameNum).emplace(id, index);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         auto& stateSetCache = mStateSetCache[frameNum % 2];
 | |
| 
 | |
|         LightIdList lightIdList;
 | |
|         lightIdList.reserve(lightList.size());
 | |
|         std::transform(lightList.begin(), lightList.end(), std::back_inserter(lightIdList),
 | |
|             [](const LightSourceViewBound* l) { return l->mLightSource->getId(); });
 | |
| 
 | |
|         auto found = stateSetCache.find(lightIdList);
 | |
|         if (found != stateSetCache.end())
 | |
|         {
 | |
|             mStateSetGenerator->update(found->second, lightList, frameNum);
 | |
|             return found->second;
 | |
|         }
 | |
| 
 | |
|         auto stateset = mStateSetGenerator->generate(lightList, frameNum);
 | |
|         stateSetCache.emplace(lightIdList, stateset);
 | |
|         return stateset;
 | |
|     }
 | |
| 
 | |
|     const std::vector<LightManager::LightSourceViewBound>& LightManager::getLightsInViewSpace(
 | |
|         osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum)
 | |
|     {
 | |
|         osg::Camera* camera = cv->getCurrentCamera();
 | |
| 
 | |
|         osg::observer_ptr<osg::Camera> camPtr(camera);
 | |
|         auto it = mLightsInViewSpace.find(camPtr);
 | |
| 
 | |
|         if (it == mLightsInViewSpace.end())
 | |
|         {
 | |
|             it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first;
 | |
| 
 | |
|             for (const auto& transform : mLights)
 | |
|             {
 | |
|                 osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix);
 | |
| 
 | |
|                 float radius = transform.mLightSource->getRadius();
 | |
| 
 | |
|                 osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0, 0, 0), radius);
 | |
|                 transformBoundingSphere(worldViewMat, viewBound);
 | |
| 
 | |
|                 if (transform.mLightSource->getLastAppliedFrame() != frameNum && mPointLightFadeEnd != 0.f)
 | |
|                 {
 | |
|                     const float fadeDelta = mPointLightFadeEnd - mPointLightFadeStart;
 | |
|                     const float viewDelta = viewBound.center().length() - mPointLightFadeStart;
 | |
|                     float fade = 1 - std::clamp(viewDelta / fadeDelta, 0.f, 1.f);
 | |
|                     if (fade == 0.f)
 | |
|                         continue;
 | |
| 
 | |
|                     auto* light = transform.mLightSource->getLight(frameNum);
 | |
|                     light->setDiffuse(light->getDiffuse() * fade);
 | |
|                     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;
 | |
|                 it->second.push_back(l);
 | |
|             }
 | |
| 
 | |
|             const bool fillPPLights = mPPLightBuffer && it->first->getName() == Constants::SceneCamera;
 | |
| 
 | |
|             if (fillPPLights || getLightingMethod() == LightingMethod::SingleUBO)
 | |
|             {
 | |
|                 auto sorter = [](const LightSourceViewBound& left, const LightSourceViewBound& right) {
 | |
|                     return left.mViewBound.center().length2() - left.mViewBound.radius2()
 | |
|                         < right.mViewBound.center().length2() - right.mViewBound.radius2();
 | |
|                 };
 | |
| 
 | |
|                 std::sort(it->second.begin(), it->second.end(), sorter);
 | |
| 
 | |
|                 if (fillPPLights)
 | |
|                 {
 | |
|                     for (const auto& bound : it->second)
 | |
|                     {
 | |
|                         if (bound.mLightSource->getEmpty())
 | |
|                             continue;
 | |
|                         const auto* light = bound.mLightSource->getLight(frameNum);
 | |
|                         if (light->getDiffuse().x() >= 0.f)
 | |
|                             mPPLightBuffer->setLight(frameNum, light, bound.mLightSource->getRadius());
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (it->second.size() > static_cast<size_t>(getMaxLightsInScene() - 1))
 | |
|                     it->second.resize(getMaxLightsInScene() - 1);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return it->second;
 | |
|     }
 | |
| 
 | |
|     void LightManager::updateGPUPointLight(
 | |
|         int index, LightSource* lightSource, size_t frameNum, const osg::RefMatrix* viewMatrix)
 | |
|     {
 | |
|         auto* light = lightSource->getLight(frameNum);
 | |
|         auto& buf = getUBOManager()->getLightBuffer(frameNum);
 | |
|         buf->setDiffuse(index, light->getDiffuse());
 | |
|         buf->setAmbient(index, light->getAmbient());
 | |
|         buf->setSpecular(index, light->getSpecular());
 | |
|         buf->setAttenuationRadius(index,
 | |
|             osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(),
 | |
|                 lightSource->getRadius()));
 | |
|         buf->setPosition(index, light->getPosition() * (*viewMatrix));
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Uniform> LightManager::generateLightBufferUniform(const osg::Matrixf& sun)
 | |
|     {
 | |
|         osg::ref_ptr<osg::Uniform> uniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "LightBuffer", getMaxLights());
 | |
|         uniform->setElement(0, sun);
 | |
| 
 | |
|         return uniform;
 | |
|     }
 | |
| 
 | |
|     void LightManager::setCollectPPLights(bool enabled)
 | |
|     {
 | |
|         if (enabled)
 | |
|             mPPLightBuffer = std::make_shared<PPLightBuffer>();
 | |
|         else
 | |
|             mPPLightBuffer = nullptr;
 | |
|     }
 | |
| 
 | |
|     LightSource::LightSource()
 | |
|         : mRadius(0.f)
 | |
|         , mActorFade(1.f)
 | |
|         , mLastAppliedFrame(0)
 | |
|     {
 | |
|         setUpdateCallback(new CollectLightCallback);
 | |
|         mId = sLightId++;
 | |
|     }
 | |
| 
 | |
|     LightSource::LightSource(const LightSource& copy, const osg::CopyOp& copyop)
 | |
|         : osg::Node(copy, copyop)
 | |
|         , mRadius(copy.mRadius)
 | |
|         , mActorFade(copy.mActorFade)
 | |
|         , mLastAppliedFrame(copy.mLastAppliedFrame)
 | |
|     {
 | |
|         mId = sLightId++;
 | |
| 
 | |
|         for (size_t i = 0; i < mLight.size(); ++i)
 | |
|             mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop);
 | |
|     }
 | |
| 
 | |
|     void LightListCallback::operator()(osg::Node* node, osgUtil::CullVisitor* cv)
 | |
|     {
 | |
|         bool pushedState = pushLightState(node, cv);
 | |
|         traverse(node, cv);
 | |
|         if (pushedState)
 | |
|             cv->popStateSet();
 | |
|     }
 | |
| 
 | |
|     bool LightListCallback::pushLightState(osg::Node* node, osgUtil::CullVisitor* cv)
 | |
|     {
 | |
|         if (!mLightManager)
 | |
|         {
 | |
|             mLightManager = findLightManager(cv->getNodePath());
 | |
|             if (!mLightManager)
 | |
|                 return false;
 | |
|         }
 | |
| 
 | |
|         if (!(cv->getTraversalMask() & mLightManager->getLightingMask()))
 | |
|             return false;
 | |
| 
 | |
|         // 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<LightManager::LightSourceViewBound>& 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)
 | |
|         {
 | |
|             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);
 | |
| 
 | |
|         mLightList.clear();
 | |
|         for (size_t i = 0; i < lights.size(); ++i)
 | |
|         {
 | |
|             const LightManager::LightSourceViewBound& l = lights[i];
 | |
| 
 | |
|             if (mIgnoredLightSources.count(l.mLightSource))
 | |
|                 continue;
 | |
| 
 | |
|             if (l.mViewBound.intersects(nodeBound))
 | |
|                 mLightList.push_back(&l);
 | |
|         }
 | |
| 
 | |
|         if (!mLightList.empty())
 | |
|         {
 | |
|             size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight();
 | |
| 
 | |
|             osg::ref_ptr<osg::StateSet> 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);
 | |
|             return true;
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
| }
 |