#include "lightmanager.hpp" #include #include #include #include #include #include #include #include #include "apps/openmw/mwrender/vismask.hpp" namespace { /* similar to the boost::hash_combine */ template inline void hash_combine(std::size_t& seed, const T& v) { std::hash hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); } bool sortLights(const SceneUtil::LightManager::LightSourceViewBound* left, const SceneUtil::LightManager::LightSourceViewBound* right) { return left->mViewBound.center().length2() - left->mViewBound.radius2()*81 < right->mViewBound.center().length2() - right->mViewBound.radius2()*81; } float getLightRadius(const osg::Light* light) { float value = 0.0; light->getUserValue("radius", value); return value; } void setLightRadius(osg::Light* light, float value) { light->setUserValue("radius", value); } } namespace SceneUtil { static int sLightId = 0; class LightBuffer : public osg::Referenced { public: LightBuffer(int count) : mData(new osg::Vec4Array(3*count)), mEndian(osg::getCpuByteOrder()) {} void setDiffuse(int index, const osg::Vec4& value) { *(unsigned int*)(&(*mData)[3*index][0]) = asRGBA(value); } void setAmbient(int index, const osg::Vec4& value) { *(unsigned int*)(&(*mData)[3*index][1]) = asRGBA(value); } void setSpecular(int index, const osg::Vec4& value) { *(unsigned int*)(&(*mData)[3*index][2]) = asRGBA(value); } void setPosition(int index, const osg::Vec4& value) { (*mData)[3*index+1] = value; } void setAttenuation(int index, float c, float l, float q) { (*mData)[3*index+2][0] = c; (*mData)[3*index+2][1] = l; (*mData)[3*index+2][2] = q; } void setRadius(int index, float value) { (*mData)[3*index+2][3] = value; } auto getPosition(int index) { return (*mData)[3*index+1]; } auto& getData() { return mData; } void dirty() { mData->dirty(); } static constexpr int queryBlockSize(int sz) { return 3 * osg::Vec4::num_components * sizeof(GL_FLOAT) * sz; } unsigned int asRGBA(const osg::Vec4& value) const { return mEndian == osg::BigEndian ? value.asABGR() : value.asRGBA(); } osg::ref_ptr mData; osg::Endian mEndian; }; class LightStateCache { public: std::vector lastAppliedLight; }; LightStateCache* getLightStateCache(size_t contextid, size_t size = 8) { static std::vector cacheVector; if (cacheVector.size() < contextid+1) cacheVector.resize(contextid+1); cacheVector[contextid].lastAppliedLight.resize(size); return &cacheVector[contextid]; } void configureStateSetSunOverride(LightingMethod method, const osg::Light* light, osg::StateSet* stateset, int mode) { switch (method) { case LightingMethod::FFP: break; case LightingMethod::PerObjectUniform: { stateset->addUniform(new osg::Uniform("LightBuffer[0].diffuse", light->getDiffuse()), mode); stateset->addUniform(new osg::Uniform("LightBuffer[0].ambient", light->getAmbient()), mode); stateset->addUniform(new osg::Uniform("LightBuffer[0].specular", light->getSpecular()), mode); stateset->addUniform(new osg::Uniform("LightBuffer[0].position", light->getPosition()), mode); break; } case LightingMethod::SingleUBO: { osg::ref_ptr buffer = new LightBuffer(1); buffer->setDiffuse(0, light->getDiffuse()); buffer->setAmbient(0, light->getAmbient()); buffer->setSpecular(0, light->getSpecular()); buffer->setPosition(0, light->getPosition()); osg::ref_ptr ubo = new osg::UniformBufferObject; buffer->mData->setBufferObject(ubo); osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->mData.get(), 0, buffer->mData->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) {} osg::Object* cloneType() const override { return new DisableLight(mIndex); } osg::Object* clone(const osg::CopyOp& copyop) const override { return new DisableLight(*this,copyop); } bool isSameKindAs(const osg::Object* obj) const override { return dynamic_cast(obj)!=nullptr; } const char* libraryName() const override { return "SceneUtil"; } const char* className() const override { return "DisableLight"; } Type getType() const override { return 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 >& 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; ilastAppliedLight[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> mLights; }; LightManager* findLightManager(const osg::NodePath& path) { for (size_t i=0;i(path[i])) return lightManager; } return nullptr; } class LightStateAttributePerObjectUniform : public osg::StateAttribute { public: LightStateAttributePerObjectUniform() {} LightStateAttributePerObjectUniform(const std::vector>& lights, LightManager* lightManager) : mLights(lights), mLightManager(lightManager) {} LightStateAttributePerObjectUniform(const LightStateAttributePerObjectUniform& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop), mLights(copy.mLights), mLightManager(copy.mLightManager) {} int compare(const StateAttribute &sa) const override { throw std::runtime_error("LightStateAttributePerObjectUniform::compare: unimplemented"); } META_StateAttribute(NifOsg, LightStateAttributePerObjectUniform, osg::StateAttribute::LIGHT) void apply(osg::State &state) const override { osg::Matrix modelViewMatrix = state.getModelViewMatrix(); state.applyModelViewMatrix(state.getInitialViewMatrix()); LightStateCache* cache = getLightStateCache(state.getContextID(), mLightManager->getMaxLights()); for (size_t i = 0; i < mLights.size(); ++i) { osg::Light* current = cache->lastAppliedLight[i]; auto light = mLights[i]; if (current != light.get()) { mLightManager->getLightUniform(i+1, LightManager::UniformKey::Diffuse)->set(light->getDiffuse()); mLightManager->getLightUniform(i+1, LightManager::UniformKey::Ambient)->set(light->getAmbient()); mLightManager->getLightUniform(i+1, LightManager::UniformKey::Attenuation)->set(osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), getLightRadius(light))); mLightManager->getLightUniform(i+1, LightManager::UniformKey::Position)->set(light->getPosition() * state.getModelViewMatrix()); cache->lastAppliedLight[i] = mLights[i]; } } state.applyModelViewMatrix(modelViewMatrix); } private: std::vector> mLights; LightManager* mLightManager; }; struct StateSetGenerator { LightManager* mLightManager; virtual ~StateSetGenerator() {} virtual osg::ref_ptr generate(const LightManager::LightList& lightList, size_t frameNum) = 0; virtual void update(osg::StateSet* stateset, const LightManager::LightList& lightList, size_t frameNum) {} }; struct StateSetGeneratorFFP : StateSetGenerator { osg::ref_ptr generate(const LightManager::LightList& lightList, size_t frameNum) override { osg::ref_ptr stateset = new osg::StateSet; std::vector> 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 generate(const LightManager::LightList& lightList, size_t frameNum) override { osg::ref_ptr stateset = new osg::StateSet; osg::ref_ptr indices = new osg::IntArray(mLightManager->getMaxLights()); osg::ref_ptr indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", indices->size()); int pointCount = 0; for (size_t i = 0; i < lightList.size(); ++i) { int bufIndex = mLightManager->getLightIndexMap(frameNum)[lightList[i]->mLightSource->getId()]; indices->at(pointCount++) = bufIndex; } indicesUni->setArray(indices); stateset->addUniform(indicesUni); stateset->addUniform(new osg::Uniform("PointLightCount", pointCount)); return stateset; } // Cached statesets must be re-validated in case the light indicies change. There is no actual link between // a lights 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); 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 generate(const LightManager::LightList& lightList, size_t frameNum) override { osg::ref_ptr stateset = new osg::StateSet; std::vector> lights(lightList.size()); for (size_t i = 0; i < lightList.size(); ++i) { auto* light = lightList[i]->mLightSource->getLight(frameNum); lights[i] = light; setLightRadius(light, lightList[i]->mLightSource->getRadius()); } stateset->setAttributeAndModes(new LightStateAttributePerObjectUniform(std::move(lights), mLightManager), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("PointLightCount", static_cast(lightList.size()))); return stateset; } }; // 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 osg::NodeCallback { public: CollectLightCallback() : mLightManager(nullptr) { } CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) , mLightManager(nullptr) { } META_Object(SceneUtil, SceneUtil::CollectLightCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv) override { if (!mLightManager) { mLightManager = findLightManager(nv->getNodePath()); if (!mLightManager) throw std::runtime_error("can't find parent LightManager"); } mLightManager->addLight(static_cast(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 osg::NodeCallback { public: LightManagerUpdateCallback() { } LightManagerUpdateCallback(const LightManagerUpdateCallback& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) { } META_Object(SceneUtil, LightManagerUpdateCallback) void operator()(osg::Node* node, osg::NodeVisitor* nv) override { LightManager* lightManager = static_cast(node); lightManager->update(nv->getTraversalNumber()); traverse(node, nv); } }; class LightManagerCullCallback : public osg::NodeCallback { public: LightManagerCullCallback(LightManager* lightManager) : mLightManager(lightManager) {} void operator()(osg::Node* node, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); if (mLastFrameNumber != cv->getTraversalNumber()) { mLastFrameNumber = cv->getTraversalNumber(); if (mLightManager->getLightingMethod() == LightingMethod::SingleUBO) { auto stateset = mLightManager->getStateSet(); auto bo = mLightManager->getLightBuffer(mLastFrameNumber); osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData().get(), 0, bo->getData()->getTotalDataSize()); stateset->setAttributeAndModes(ubb.get(), osg::StateAttribute::ON); } auto sun = mLightManager->getSunlight(); if (sun) { if (mLightManager->getLightingMethod() == LightingMethod::PerObjectUniform) { mLightManager->getLightUniform(0, LightManager::UniformKey::Diffuse)->set(sun->getDiffuse()); mLightManager->getLightUniform(0, LightManager::UniformKey::Ambient)->set(sun->getAmbient()); mLightManager->getLightUniform(0, LightManager::UniformKey::Specular)->set(sun->getSpecular()); mLightManager->getLightUniform(0, LightManager::UniformKey::Position)->set(sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); } else { auto buf = mLightManager->getLightBuffer(mLastFrameNumber); buf->setDiffuse(0, sun->getDiffuse()); buf->setAmbient(0, sun->getAmbient()); buf->setSpecular(0, sun->getSpecular()); buf->setPosition(0, sun->getPosition() * (*cv->getCurrentRenderStage()->getInitialViewMatrix())); } } } traverse(node, nv); } private: LightManager* mLightManager; size_t mLastFrameNumber; }; class LightManagerStateAttribute : public osg::StateAttribute { public: LightManagerStateAttribute() : mLightManager(nullptr) {} LightManagerStateAttribute(LightManager* lightManager) : mLightManager(lightManager) {} LightManagerStateAttribute(const LightManagerStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : osg::StateAttribute(copy,copyop),mLightManager(copy.mLightManager) {} int compare(const StateAttribute &sa) const override { throw std::runtime_error("LightManagerStateAttribute::compare: unimplemented"); } META_StateAttribute(NifOsg, LightManagerStateAttribute, osg::StateAttribute::LIGHT) void apply(osg::State& state) const override { mLightManager->getLightBuffer(state.getFrameStamp()->getFrameNumber())->dirty(); } LightManager* mLightManager; }; bool LightManager::isValidLightingModelString(const std::string& value) { static const std::unordered_set validLightingModels = {"legacy", "default", "experimental"}; return validLightingModels.count(value) != 0; } LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) , mPointLightRadiusMultiplier(std::max(0.0f, Settings::Manager::getFloat("light bounds multiplier", "Shaders"))) { auto lightingModelString = Settings::Manager::getString("lighting method", "Shaders"); bool validLightingModel = isValidLightingModelString(lightingModelString); if (!validLightingModel) Log(Debug::Error) << "Invalid option for 'lighting model': got '" << lightingModelString << "', expected legacy, default, or experimental."; if (ffp || !validLightingModel) { setMaxLights(LightManager::mFFPMaxLights); setLightingMethod(LightingMethod::FFP); for (int i=0; i >())); setUpdateCallback(new LightManagerUpdateCallback); return; } osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); bool supportsUBO = exts && exts->isUniformBufferObjectSupported; bool supportsGPU4 = exts && exts->isGpuShader4Supported; if (!supportsUBO) Log(Debug::Info) << "GL_ARB_uniform_buffer_object not supported: using fallback uniforms"; else if (!supportsGPU4) Log(Debug::Info) << "GL_EXT_gpu_shader4 not supported: using fallback uniforms"; int targetLights = Settings::Manager::getInt("max lights", "Shaders"); auto* stateset = getOrCreateStateSet(); if (!supportsUBO || !supportsGPU4 || lightingModelString == "default") { setLightingMethod(LightingMethod::PerObjectUniform); setMaxLights(std::max(2, targetLights)); mLightUniforms.resize(getMaxLights()+1); for (size_t i = 0; i < mLightUniforms.size(); ++i) { osg::ref_ptr udiffuse = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].diffuse").c_str()); osg::ref_ptr uspecular = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].specular").c_str()); osg::ref_ptr uambient = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].ambient").c_str()); osg::ref_ptr uposition = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].position").c_str()); osg::ref_ptr uattenuation = new osg::Uniform(osg::Uniform::FLOAT_VEC4, ("LightBuffer[" + std::to_string(i) + "].attenuation").c_str()); mLightUniforms[i].emplace(UniformKey::Diffuse, udiffuse); mLightUniforms[i].emplace(UniformKey::Ambient, uambient); mLightUniforms[i].emplace(UniformKey::Specular, uspecular); mLightUniforms[i].emplace(UniformKey::Position, uposition); mLightUniforms[i].emplace(UniformKey::Attenuation, uattenuation); stateset->addUniform(udiffuse); stateset->addUniform(uambient); stateset->addUniform(uposition); stateset->addUniform(uattenuation); // specular isn't used besides sun, complete waste to upload it if (i == 0) stateset->addUniform(uspecular); } } else { setLightingMethod(LightingMethod::SingleUBO); setMaxLights(std::clamp(targetLights, 2, getMaxLightsInScene() / 2)); for (int i = 0; i < 2; ++i) { mLightBuffers[i] = new LightBuffer(getMaxLightsInScene()); osg::ref_ptr ubo = new osg::UniformBufferObject; ubo->setUsage(GL_STREAM_DRAW); mLightBuffers[i]->getData()->setBufferObject(ubo); } stateset->setAttribute(new LightManagerStateAttribute(this), osg::StateAttribute::ON); } stateset->addUniform(new osg::Uniform("PointLightCount", 0)); setUpdateCallback(new LightManagerUpdateCallback); addCullCallback(new LightManagerCullCallback(this)); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) , mStartLight(copy.mStartLight) , mLightingMask(copy.mLightingMask) , mSun(copy.mSun) , mLightingMethod(copy.mLightingMethod) { } LightingMethod LightManager::getLightingMethod() const { return mLightingMethod; } LightManager::~LightManager() { } 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; bool ffp = usingFFP(); defines["ffpLighting"] = ffp ? "1" : "0"; defines["maxLights"] = std::to_string(getMaxLights()); defines["maxLightsInScene"] = std::to_string(getMaxLightsInScene()); defines["lightingModel"] = std::to_string(static_cast(mLightingMethod)); defines["useUBO"] = std::to_string(mLightingMethod == LightingMethod::SingleUBO); // exposes bitwise operators defines["useGPUShader4"] = std::to_string(mLightingMethod == LightingMethod::SingleUBO); return defines; } void LightManager::setLightingMethod(LightingMethod method) { mLightingMethod = method; switch (method) { case LightingMethod::FFP: mStateSetGenerator = std::make_unique(); break; case LightingMethod::SingleUBO: mStateSetGenerator = std::make_unique(); break; case LightingMethod::PerObjectUniform: mStateSetGenerator = std::make_unique(); break; } mStateSetGenerator->mLightManager = this; } void LightManager::setLightingMask(size_t mask) { mLightingMask = mask; } size_t LightManager::getLightingMask() const { return mLightingMask; } void LightManager::setStartLight(int start) { if (!usingFFP()) return; mStartLight = start; // 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 defaultLight (new DisableLight(i)); getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); } } int LightManager::getStartLight() const { return mStartLight; } void LightManager::update(size_t 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 sun) { if (usingFFP()) return; mSun = sun; } osg::ref_ptr LightManager::getSunlight() { return mSun; } osg::ref_ptr LightManager::getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix) { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; for (size_t i=0; imLightSource->getId(); hash_combine(hash, id); if (getLightingMethod() != LightingMethod::SingleUBO) continue; 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(lightList[i]->mLightSource->getId(), index); } auto& stateSetCache = mStateSetCache[frameNum%2]; auto found = stateSetCache.find(hash); if (found != stateSetCache.end()) { mStateSetGenerator->update(found->second, lightList, frameNum); return found->second; } else { auto stateset = mStateSetGenerator->generate(lightList, frameNum); stateSetCache.emplace(hash, stateset); return stateset; } return new osg::StateSet; } const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { osg::observer_ptr 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 * mPointLightRadiusMultiplier); transformBoundingSphere(worldViewMat, viewBound); LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; it->second.push_back(l); } } if (getLightingMethod() == LightingMethod::SingleUBO) { if (it->second.size() > static_cast(getMaxLightsInScene() - 1)) { 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() + 1, it->second.end(), sorter); it->second.erase((it->second.begin() + 1) + (getMaxLightsInScene() - 2), it->second.end()); } } return it->second; } void LightManager::updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum,const osg::RefMatrix* viewMatrix) { auto* light = lightSource->getLight(frameNum); auto& buf = getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); buf->setAmbient(index, light->getSpecular()); buf->setAttenuation(index, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation()); buf->setRadius(index, lightSource->getRadius()); buf->setPosition(index, light->getPosition() * (*viewMatrix)); } LightSource::LightSource() : mRadius(0.f) { setUpdateCallback(new CollectLightCallback); mId = sLightId++; } LightSource::LightSource(const LightSource ©, const osg::CopyOp ©op) : osg::Node(copy, copyop) , mRadius(copy.mRadius) { mId = sLightId++; for (int i=0; i<2; ++i) mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) { osgUtil::CullVisitor* cv = static_cast(nv); bool pushedState = pushLightState(node, cv); traverse(node, nv); 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: // - cull list of lights by the camera frustum // - organize lights in a quad tree // update light list if necessary // makes sure we don't update it more than once per frame when rendering with multiple cameras if (mLastFrameNumber != cv->getTraversalNumber()) { 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->getCurrentCamera(), 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; igetNumChildren(); ++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; igetMaxLights() - mLightManager->getStartLight(); osg::StateSet* stateset = nullptr; if (mLightList.size() > maxLights) { // remove lights culled by this camera LightManager::LightList lightList = mLightList; for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights; ) { osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); osg::BoundingSphere bs = (*it)->mViewBound; bs._radius = bs._radius * 2.0; osg::CullingSet& cullingSet = stack.front(); if (cullingSet.isCulled(bs)) { it = lightList.erase(it); continue; } else ++it; } if (lightList.size() > maxLights) { // 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; } }