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.
416 lines
15 KiB
C++
416 lines
15 KiB
C++
#include "lightmanager.hpp"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <osg/NodeVisitor>
|
|
|
|
#include <osgUtil/CullVisitor>
|
|
|
|
#include <components/sceneutil/util.hpp>
|
|
|
|
#include <boost/functional/hash.hpp>
|
|
|
|
namespace SceneUtil
|
|
{
|
|
|
|
// Resets the modelview matrix to just the view matrix before applying lights.
|
|
class LightStateAttribute : public osg::StateAttribute
|
|
{
|
|
public:
|
|
LightStateAttribute() : mIndex(0) {}
|
|
LightStateAttribute(unsigned int index, const std::vector<osg::ref_ptr<osg::Light> >& lights) : mIndex(index), mLights(lights) {}
|
|
|
|
LightStateAttribute(const LightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY)
|
|
: osg::StateAttribute(copy,copyop), mIndex(copy.mIndex), mLights(copy.mLights) {}
|
|
|
|
unsigned int getMember() const
|
|
{
|
|
return mIndex;
|
|
}
|
|
|
|
virtual bool getModeUsage(ModeUsage & usage) const
|
|
{
|
|
for (unsigned int i=0; i<mLights.size(); ++i)
|
|
usage.usesMode(GL_LIGHT0 + mIndex + i);
|
|
return true;
|
|
}
|
|
|
|
virtual int compare(const StateAttribute &sa) const
|
|
{
|
|
throw std::runtime_error("LightStateAttribute::compare: unimplemented");
|
|
}
|
|
|
|
META_StateAttribute(NifOsg, LightStateAttribute, osg::StateAttribute::LIGHT)
|
|
|
|
virtual void apply(osg::State& state) const
|
|
{
|
|
if (mLights.empty())
|
|
return;
|
|
osg::Matrix modelViewMatrix = state.getModelViewMatrix();
|
|
|
|
state.applyModelViewMatrix(state.getInitialViewMatrix());
|
|
|
|
for (unsigned int i=0; i<mLights.size(); ++i)
|
|
applyLight((GLenum)((int)GL_LIGHT0 + 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:
|
|
unsigned int mIndex;
|
|
|
|
std::vector<osg::ref_ptr<osg::Light> > mLights;
|
|
};
|
|
|
|
LightManager* findLightManager(const osg::NodePath& path)
|
|
{
|
|
for (unsigned int i=0;i<path.size(); ++i)
|
|
{
|
|
if (LightManager* lightManager = dynamic_cast<LightManager*>(path[i]))
|
|
return lightManager;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// 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(0) { }
|
|
|
|
CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop)
|
|
: osg::NodeCallback(copy, copyop)
|
|
, mLightManager(0) { }
|
|
|
|
META_Object(SceneUtil, SceneUtil::CollectLightCallback)
|
|
|
|
virtual 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 osg::NodeCallback
|
|
{
|
|
public:
|
|
LightManagerUpdateCallback()
|
|
{ }
|
|
|
|
LightManagerUpdateCallback(const LightManagerUpdateCallback& copy, const osg::CopyOp& copyop)
|
|
: osg::NodeCallback(copy, copyop)
|
|
{ }
|
|
|
|
META_Object(SceneUtil, SceneUtil::LightManagerUpdateCallback)
|
|
|
|
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
|
{
|
|
LightManager* lightManager = static_cast<LightManager*>(node);
|
|
lightManager->update();
|
|
|
|
traverse(node, nv);
|
|
}
|
|
};
|
|
|
|
LightManager::LightManager()
|
|
: mStartLight(0)
|
|
, mLightingMask(~0u)
|
|
{
|
|
setUpdateCallback(new LightManagerUpdateCallback);
|
|
}
|
|
|
|
LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op)
|
|
: osg::Group(copy, copyop)
|
|
, mStartLight(copy.mStartLight)
|
|
, mLightingMask(copy.mLightingMask)
|
|
{
|
|
|
|
}
|
|
|
|
void LightManager::setLightingMask(unsigned int mask)
|
|
{
|
|
mLightingMask = mask;
|
|
}
|
|
|
|
unsigned int LightManager::getLightingMask() const
|
|
{
|
|
return mLightingMask;
|
|
}
|
|
|
|
void LightManager::update()
|
|
{
|
|
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, unsigned int frameNum)
|
|
{
|
|
LightSourceTransform l;
|
|
l.mLightSource = lightSource;
|
|
l.mWorldMatrix = worldMat;
|
|
lightSource->getLight(frameNum)->setPosition(osg::Vec4f(worldMat.getTrans().x(),
|
|
worldMat.getTrans().y(),
|
|
worldMat.getTrans().z(), 1.f));
|
|
mLights.push_back(l);
|
|
}
|
|
|
|
osg::ref_ptr<osg::StateSet> LightManager::getLightListStateSet(const LightList &lightList, unsigned int frameNum)
|
|
{
|
|
// possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists)
|
|
size_t hash = 0;
|
|
for (unsigned int i=0; i<lightList.size();++i)
|
|
boost::hash_combine(hash, lightList[i]->mLightSource->getId());
|
|
|
|
LightStateSetMap& stateSetCache = mStateSetCache[frameNum%2];
|
|
|
|
LightStateSetMap::iterator found = stateSetCache.find(hash);
|
|
if (found != stateSetCache.end())
|
|
return found->second;
|
|
else
|
|
{
|
|
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
|
|
std::vector<osg::ref_ptr<osg::Light> > lights;
|
|
for (unsigned int i=0; i<lightList.size();++i)
|
|
{
|
|
lights.push_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
|
|
osg::ref_ptr<LightStateAttribute> attr = new LightStateAttribute(mStartLight, lights);
|
|
// don't use setAttributeAndModes, that does not support light indices!
|
|
stateset->setAttribute(attr, osg::StateAttribute::ON);
|
|
stateset->setAssociatedModes(attr, 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 (unsigned int i=1; i<lightList.size(); ++i)
|
|
{
|
|
osg::ref_ptr<LightStateAttribute> dummy = new LightStateAttribute(mStartLight+i, std::vector<osg::ref_ptr<osg::Light> >());
|
|
stateset->setAttribute(dummy, osg::StateAttribute::ON);
|
|
}
|
|
|
|
stateSetCache.insert(std::make_pair(hash, stateset));
|
|
return stateset;
|
|
}
|
|
}
|
|
|
|
const std::vector<LightManager::LightSourceTransform>& LightManager::getLights() const
|
|
{
|
|
return mLights;
|
|
}
|
|
|
|
const std::vector<LightManager::LightSourceViewBound>& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix)
|
|
{
|
|
osg::observer_ptr<osg::Camera> camPtr (camera);
|
|
std::map<osg::observer_ptr<osg::Camera>, LightSourceViewBoundCollection>::iterator it = mLightsInViewSpace.find(camPtr);
|
|
|
|
if (it == mLightsInViewSpace.end())
|
|
{
|
|
it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first;
|
|
|
|
for (std::vector<LightSourceTransform>::iterator lightIt = mLights.begin(); lightIt != mLights.end(); ++lightIt)
|
|
{
|
|
osg::Matrixf worldViewMat = lightIt->mWorldMatrix * (*viewMatrix);
|
|
osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), lightIt->mLightSource->getRadius());
|
|
transformBoundingSphere(worldViewMat, viewBound);
|
|
|
|
LightSourceViewBound l;
|
|
l.mLightSource = lightIt->mLightSource;
|
|
l.mViewBound = viewBound;
|
|
it->second.push_back(l);
|
|
}
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
void LightManager::setStartLight(int start)
|
|
{
|
|
mStartLight = start;
|
|
|
|
// Set default light state to zero
|
|
for (int i=start; i<8; ++i)
|
|
{
|
|
osg::ref_ptr<osg::Light> defaultLight (new osg::Light(i));
|
|
defaultLight->setAmbient(osg::Vec4());
|
|
defaultLight->setDiffuse(osg::Vec4());
|
|
defaultLight->setSpecular(osg::Vec4());
|
|
defaultLight->setConstantAttenuation(0.f);
|
|
getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF);
|
|
}
|
|
}
|
|
|
|
int LightManager::getStartLight() const
|
|
{
|
|
return mStartLight;
|
|
}
|
|
|
|
static int sLightId = 0;
|
|
|
|
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] = osg::clone(copy.mLight[i].get(), copyop);
|
|
}
|
|
|
|
|
|
bool sortLights (const LightManager::LightSourceViewBound* left, const LightManager::LightSourceViewBound* right)
|
|
{
|
|
return left->mViewBound.center().length2() - left->mViewBound.radius2()/4.f < right->mViewBound.center().length2() - right->mViewBound.radius2()/4.f;
|
|
}
|
|
|
|
void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv)
|
|
{
|
|
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
|
|
|
|
if (!mLightManager)
|
|
{
|
|
mLightManager = findLightManager(nv->getNodePath());
|
|
if (!mLightManager)
|
|
{
|
|
traverse(node, nv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask()))
|
|
{
|
|
traverse(node, nv);
|
|
return;
|
|
}
|
|
|
|
// 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 != nv->getTraversalNumber())
|
|
{
|
|
mLastFrameNumber = nv->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->getCurrentCamera(), viewMatrix);
|
|
|
|
// 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::Group* group = node->asGroup();
|
|
if (group)
|
|
{
|
|
for (unsigned int i=0; i<group->getNumChildren(); ++i)
|
|
nodeBound.expandBy(group->getChild(i)->getBound());
|
|
}
|
|
osg::Matrixf mat = *cv->getModelViewMatrix();
|
|
transformBoundingSphere(mat, nodeBound);
|
|
|
|
mLightList.clear();
|
|
for (unsigned int i=0; i<lights.size(); ++i)
|
|
{
|
|
const LightManager::LightSourceViewBound& l = lights[i];
|
|
if (l.mViewBound.intersects(nodeBound))
|
|
mLightList.push_back(&l);
|
|
}
|
|
}
|
|
if (!mLightList.empty())
|
|
{
|
|
unsigned int maxLights = static_cast<unsigned int> (8 - mLightManager->getStartLight());
|
|
|
|
osg::StateSet* stateset = NULL;
|
|
|
|
if (mLightList.size() > maxLights)
|
|
{
|
|
// remove lights culled by this camera
|
|
LightManager::LightList lightList = mLightList;
|
|
for (LightManager::LightList::iterator 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;
|
|
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, nv->getTraversalNumber());
|
|
}
|
|
else
|
|
stateset = mLightManager->getLightListStateSet(mLightList, nv->getTraversalNumber());
|
|
|
|
|
|
cv->pushStateSet(stateset);
|
|
|
|
traverse(node, nv);
|
|
|
|
cv->popStateSet();
|
|
}
|
|
else
|
|
traverse(node, nv);
|
|
}
|
|
|
|
}
|