You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/components/sceneutil/lightmanager.hpp

291 lines
10 KiB
C++

#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H
#define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H
#include <set>
4 years ago
#include <unordered_set>
#include <unordered_map>
#include <memory>
#include <array>
#include <osg/Light>
#include <osg/Group>
#include <osg/NodeVisitor>
#include <osg/observer_ptr>
4 years ago
#include <components/shader/shadermanager.hpp>
#include <components/settings/settings.hpp>
namespace osgUtil
{
class CullVisitor;
}
namespace SceneUtil
{
class LightBuffer;
struct StateSetGenerator;
4 years ago
enum class LightingMethod
4 years ago
{
FFP,
PerObjectUniform,
SingleUBO,
4 years ago
};
/// LightSource managed by a LightManager.
/// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole scene
/// so do not need to be managed by a LightManager - so for directional lights use a plain osg::LightSource instead.
/// @note LightSources must be decorated by a LightManager node in order to have an effect. Typical use would
/// be one LightManager as the root of the scene graph.
/// @note One needs to attach LightListCallback's to the scene to have objects receive lighting from LightSources.
/// See the documentation of LightListCallback for more information.
/// @note The position of the contained osg::Light is automatically updated based on the LightSource's world position.
class LightSource : public osg::Node
{
// double buffered osg::Light's, since one of them may be in use by the draw thread at any given time
osg::ref_ptr<osg::Light> mLight[2];
// LightSource will affect objects within this radius
float mRadius;
int mId;
float mActorFade;
public:
META_Node(SceneUtil, LightSource)
LightSource();
LightSource(const LightSource& copy, const osg::CopyOp& copyop);
float getRadius() const
{
return mRadius;
}
/// The LightSource will affect objects within this radius.
void setRadius(float radius)
{
mRadius = radius;
}
void setActorFade(float alpha)
{
mActorFade = alpha;
}
float getActorFade() const
{
return mActorFade;
}
/// Get the osg::Light safe for modification in the given frame.
/// @par May be used externally to animate the light's color/attenuation properties,
/// and is used internally to synchronize the light's position with the position of the LightSource.
4 years ago
osg::Light* getLight(size_t frame)
{
return mLight[frame % 2];
}
/// @warning It is recommended not to replace an existing osg::Light, because there might still be
/// references to it in the light StateSet cache that are associated with this LightSource's ID.
/// These references will stay valid due to ref_ptr but will point to the old object.
/// @warning Do not modify the \a light after you've called this function.
void setLight(osg::Light* light)
{
mLight[0] = light;
mLight[1] = new osg::Light(*light);
}
/// Get the unique ID for this light source.
int getId() const
{
return mId;
}
};
/// @brief Decorator node implementing the rendering of any number of LightSources that can be anywhere in the subgraph.
class LightManager : public osg::Group
{
public:
static LightingMethod getLightingMethodFromString(const std::string& value);
/// Returns string as used in settings file, or the empty string if the method is undefined
static std::string getLightingMethodString(LightingMethod method);
4 years ago
struct LightSourceTransform
{
LightSource* mLightSource;
osg::Matrixf mWorldMatrix;
};
struct LightSourceViewBound
{
LightSource* mLightSource;
osg::BoundingSphere mViewBound;
};
4 years ago
using LightList = std::vector<const LightSourceViewBound*>;
using SupportedMethods = std::array<bool, 3>;
4 years ago
META_Node(SceneUtil, LightManager)
4 years ago
LightManager(bool ffp = true);
LightManager(const LightManager& copy, const osg::CopyOp& copyop);
~LightManager();
/// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired.
/// By default, it's ~0u i.e. always on.
/// If you have some views that do not require lighting, then set the Camera's cull mask to not include
/// the lightingMask for a much faster cull and rendering.
void setLightingMask(size_t mask);
4 years ago
size_t getLightingMask() const;
/// Set the first light index that should be used by this manager, typically the number of directional lights in the scene.
void setStartLight(int start);
int getStartLight() const;
/// Internal use only, called automatically by the LightManager's UpdateCallback
void update(size_t frameNum);
/// Internal use only, called automatically by the LightSource's UpdateCallback
4 years ago
void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum);
4 years ago
const std::vector<LightSourceViewBound>& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum);
osg::ref_ptr<osg::StateSet> getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix);
4 years ago
void setSunlight(osg::ref_ptr<osg::Light> sun);
osg::ref_ptr<osg::Light> getSunlight();
bool usingFFP() const;
LightingMethod getLightingMethod() const;
4 years ago
int getMaxLights() const;
4 years ago
int getMaxLightsInScene() const;
auto& getDummies() { return mDummies; }
auto& getLightIndexMap(size_t frameNum) { return mLightIndexMaps[frameNum%2]; }
auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum%2]; }
osg::Matrixf getSunlightBuffer(size_t frameNum) const { return mSunlightBuffers[frameNum%2]; }
void setSunlightBuffer(const osg::Matrixf& buffer, size_t frameNum) { mSunlightBuffers[frameNum%2] = buffer; }
SupportedMethods getSupportedLightingMethods() { return mSupported; }
std::map<std::string, std::string> getLightDefines() const;
void processChangedSettings(const Settings::CategorySettingVector& changed);
/// Not thread safe, it is the responsibility of the caller to stop/start threading on the viewer
void updateMaxLights();
private:
void initFFP(int targetLights);
void initPerObjectUniform(int targetLights);
void initSingleUBO(int targetLights);
void updateSettings();
void setLightingMethod(LightingMethod method);
void setMaxLights(int value);
4 years ago
void updateGPUPointLight(int index, LightSource* lightSource, size_t frameNum, const osg::RefMatrix* viewMatrix);
4 years ago
std::vector<LightSourceTransform> mLights;
using LightSourceViewBoundCollection = std::vector<LightSourceViewBound>;
std::map<osg::observer_ptr<osg::Camera>, LightSourceViewBoundCollection> mLightsInViewSpace;
// < Light list hash , StateSet >
using LightStateSetMap = std::map<size_t, osg::ref_ptr<osg::StateSet>>;
LightStateSetMap mStateSetCache[2];
std::vector<osg::ref_ptr<osg::StateAttribute>> mDummies;
int mStartLight;
4 years ago
size_t mLightingMask;
osg::ref_ptr<osg::Light> mSun;
4 years ago
osg::ref_ptr<LightBuffer> mLightBuffers[2];
4 years ago
osg::Matrixf mSunlightBuffers[2];
4 years ago
// < Light ID , Buffer Index >
using LightIndexMap = std::unordered_map<int, int>;
LightIndexMap mLightIndexMaps[2];
4 years ago
std::unique_ptr<StateSetGenerator> mStateSetGenerator;
4 years ago
LightingMethod mLightingMethod;
float mPointLightRadiusMultiplier;
float mPointLightFadeEnd;
float mPointLightFadeStart;
int mMaxLights;
SupportedMethods mSupported;
static constexpr auto mMaxLightsLowerLimit = 2;
static constexpr auto mMaxLightsUpperLimit = 64;
static constexpr auto mFFPMaxLights = 8;
static const std::unordered_map<std::string, LightingMethod> mLightingMethodSettingMap;
};
/// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via
/// node->addCullCallback(new LightListCallback). Once a light list callback is added to a node, that node and all
/// its child nodes can receive lighting.
/// @par The placement of these LightListCallbacks affects the granularity of light lists. Having too fine grained
/// light lists can result in degraded performance. Too coarse grained light lists can result in lights no longer
/// rendering when the size of a light list exceeds the OpenGL limit on the number of concurrent lights (8). A good
/// starting point is to attach a LightListCallback to each game object's base node.
/// @note Not thread safe for CullThreadPerCamera threading mode.
/// @note Due to lack of OSG support, the callback does not work on Drawables.
class LightListCallback : public osg::NodeCallback
{
public:
LightListCallback()
: mLightManager(nullptr)
, mLastFrameNumber(0)
{}
LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop)
: osg::Object(copy, copyop), osg::NodeCallback(copy, copyop)
, mLightManager(copy.mLightManager)
, mLastFrameNumber(0)
, mIgnoredLightSources(copy.mIgnoredLightSources)
{}
META_Object(SceneUtil, LightListCallback)
void operator()(osg::Node* node, osg::NodeVisitor* nv) override;
bool pushLightState(osg::Node* node, osgUtil::CullVisitor* nv);
std::set<SceneUtil::LightSource*>& getIgnoredLightSources() { return mIgnoredLightSources; }
private:
LightManager* mLightManager;
4 years ago
size_t mLastFrameNumber;
LightManager::LightList mLightList;
std::set<SceneUtil::LightSource*> mIgnoredLightSources;
};
void configureStateSetSunOverride(LightManager* lightManager, const osg::Light* light, osg::StateSet* stateset, int mode = osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
}
#endif