Compositor: Significant rework of the clustered shading feature
- Added light definitions to the model XML files. This is not compatible with Rembrandt. - Added depth slicing. - Added threading support. For now it's faster to run everything on the main thread since there aren't many lights.
This commit is contained in:
parent
9ac3c1a394
commit
1ca3f60f80
@ -978,6 +978,13 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
|
||||
type);
|
||||
program->setParameter(GL_GEOMETRY_OUTPUT_TYPE_EXT, type);
|
||||
}
|
||||
PropertyList pUniformBlockBindings
|
||||
= prop->getChildren("uniform-block-binding");
|
||||
for (const auto &pUniformBlockBinding : pUniformBlockBindings) {
|
||||
program->addBindUniformBlock(
|
||||
pUniformBlockBinding->getStringValue("name"),
|
||||
pUniformBlockBinding->getIntValue("index"));
|
||||
}
|
||||
programMap.insert(ProgramMap::value_type(prgKey, program));
|
||||
resolvedProgramMap.insert(ProgramMap::value_type(resolvedKey, program));
|
||||
pass->setAttributeAndModes(program);
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include <osg/StateSet>
|
||||
#include <osg/Texture2D>
|
||||
|
||||
#include <osg/io_utils>
|
||||
|
||||
#include "EffectCullVisitor.hxx"
|
||||
|
||||
#include "EffectGeode.hxx"
|
||||
@ -50,6 +52,19 @@ CullVisitor* EffectCullVisitor::clone() const
|
||||
return new EffectCullVisitor(*this);
|
||||
}
|
||||
|
||||
void EffectCullVisitor::apply(osg::Node &node)
|
||||
{
|
||||
// TODO: Properly cull lights outside the viewport (override computeBounds())
|
||||
// if (isCulled(node))
|
||||
// return;
|
||||
SGLight *light = dynamic_cast<SGLight *>(&node);
|
||||
if (!light) {
|
||||
CullVisitor::apply(node);
|
||||
return;
|
||||
}
|
||||
_lightList.push_back(light);
|
||||
}
|
||||
|
||||
void EffectCullVisitor::apply(osg::Geode& node)
|
||||
{
|
||||
if (isCulled(node))
|
||||
@ -59,9 +74,6 @@ void EffectCullVisitor::apply(osg::Geode& node)
|
||||
CullVisitor::apply(node);
|
||||
return;
|
||||
}
|
||||
if (_collectLights && ( eg->getNodeMask() & MODELLIGHT_BIT ) ) {
|
||||
_lightList.push_back( eg );
|
||||
}
|
||||
Effect* effect = eg->getEffect();
|
||||
Technique* technique = 0;
|
||||
if (!effect) {
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <simgear/scene/model/SGLight.hxx>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Geode;
|
||||
@ -38,6 +40,7 @@ public:
|
||||
EffectCullVisitor(const EffectCullVisitor&);
|
||||
virtual osgUtil::CullVisitor* clone() const;
|
||||
using osgUtil::CullVisitor::apply;
|
||||
virtual void apply(osg::Node& node);
|
||||
virtual void apply(osg::Geode& node);
|
||||
virtual void reset();
|
||||
|
||||
@ -45,9 +48,11 @@ public:
|
||||
void addBuffer(std::string b, osg::Texture2D* tex);
|
||||
osg::Texture2D* getBuffer(std::string b);
|
||||
|
||||
SGLightList getLightList() const { return _lightList; }
|
||||
|
||||
private:
|
||||
std::map<std::string,osg::ref_ptr<osg::Texture2D> > _bufferList;
|
||||
std::vector<osg::ref_ptr<EffectGeode> > _lightList;
|
||||
SGLightList _lightList;
|
||||
bool _collectLights;
|
||||
std::string _effScheme;
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ set(HEADERS
|
||||
PrimitiveCollector.hxx
|
||||
SGClipGroup.hxx
|
||||
SGInteractionAnimation.hxx
|
||||
SGLight.hxx
|
||||
SGMaterialAnimation.hxx
|
||||
SGPickAnimation.hxx
|
||||
SGOffsetTransform.hxx
|
||||
@ -35,6 +36,7 @@ set(SOURCES
|
||||
PrimitiveCollector.cxx
|
||||
SGClipGroup.cxx
|
||||
SGInteractionAnimation.cxx
|
||||
SGLight.cxx
|
||||
SGLightAnimation.cxx
|
||||
SGPickAnimation.cxx
|
||||
SGMaterialAnimation.cxx
|
||||
|
157
simgear/scene/model/SGLight.cxx
Normal file
157
simgear/scene/model/SGLight.cxx
Normal file
@ -0,0 +1,157 @@
|
||||
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#include "SGLight.hxx"
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/PolygonMode>
|
||||
#include <osg/ShapeDrawable>
|
||||
#include <osg/Switch>
|
||||
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
#include <simgear/scene/tgdb/userdata.hxx>
|
||||
|
||||
class SGLightDebugListener : public SGPropertyChangeListener {
|
||||
public:
|
||||
SGLightDebugListener(osg::Switch *sw) : _sw(sw) {}
|
||||
virtual void valueChanged(SGPropertyNode *node) {
|
||||
_sw->setValue(0, node->getBoolValue());
|
||||
}
|
||||
private:
|
||||
osg::ref_ptr<osg::Switch> _sw;
|
||||
};
|
||||
|
||||
osg::Node *
|
||||
SGLight::appendLight(const SGPropertyNode *configNode,
|
||||
SGPropertyNode *modelRoot,
|
||||
const osgDB::Options *options)
|
||||
{
|
||||
SGConstPropertyNode_ptr p;
|
||||
|
||||
SGLight *light = new SGLight;
|
||||
|
||||
if((p = configNode->getNode("type")) != NULL) {
|
||||
std::string type = p->getStringValue();
|
||||
if (type == "point")
|
||||
light->setType(SGLight::Type::POINT);
|
||||
else if (type == "spot")
|
||||
light->setType(SGLight::Type::SPOT);
|
||||
else
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "ignoring unknown light type '" << type << "'");
|
||||
}
|
||||
|
||||
light->setRange(configNode->getFloatValue("range-m"));
|
||||
|
||||
#define SGLIGHT_GET_COLOR_VALUE(n) \
|
||||
osg::Vec4(configNode->getFloatValue(n "/r"), \
|
||||
configNode->getFloatValue(n "/g"), \
|
||||
configNode->getFloatValue(n "/b"), \
|
||||
configNode->getFloatValue(n "/a"))
|
||||
light->setAmbient(SGLIGHT_GET_COLOR_VALUE("ambient"));
|
||||
light->setDiffuse(SGLIGHT_GET_COLOR_VALUE("diffuse"));
|
||||
light->setSpecular(SGLIGHT_GET_COLOR_VALUE("specular"));
|
||||
#undef SGLIGHT_GET_COLOR_VALUE
|
||||
|
||||
light->setConstantAttenuation(configNode->getFloatValue("attenuation/c"));
|
||||
light->setLinearAttenuation(configNode->getFloatValue("attenuation/l"));
|
||||
light->setQuadraticAttenuation(configNode->getFloatValue("attenuation/q"));
|
||||
|
||||
light->setSpotExponent(configNode->getFloatValue("spot-exponent"));
|
||||
light->setSpotCutoff(configNode->getFloatValue("spot-cutoff"));
|
||||
|
||||
osg::Group *group = 0;
|
||||
if ((p = configNode->getNode("offsets")) == NULL) {
|
||||
group = new osg::Group;
|
||||
} else {
|
||||
// Set up the alignment node ("stolen" from animation.cxx)
|
||||
// XXX Order of rotations is probably not correct.
|
||||
osg::MatrixTransform *align = new osg::MatrixTransform;
|
||||
osg::Matrix res_matrix;
|
||||
res_matrix.makeRotate(
|
||||
p->getFloatValue("pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
|
||||
osg::Vec3(0, 1, 0),
|
||||
p->getFloatValue("roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
|
||||
osg::Vec3(1, 0, 0),
|
||||
p->getFloatValue("heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
|
||||
osg::Vec3(0, 0, 1));
|
||||
|
||||
osg::Matrix tmat;
|
||||
tmat.makeTranslate(configNode->getFloatValue("offsets/x-m", 0.0),
|
||||
configNode->getFloatValue("offsets/y-m", 0.0),
|
||||
configNode->getFloatValue("offsets/z-m", 0.0));
|
||||
|
||||
align->setMatrix(res_matrix * tmat);
|
||||
group = align;
|
||||
}
|
||||
|
||||
group->addChild(light);
|
||||
|
||||
osg::Shape *debug_shape;
|
||||
if (light->getType() == SGLight::Type::POINT) {
|
||||
debug_shape = new osg::Sphere(osg::Vec3(0, 0, 0), light->getRange());
|
||||
} else if (light->getType() == SGLight::Type::SPOT) {
|
||||
debug_shape = new osg::Cone(
|
||||
// Origin of the cone is at its center of mass
|
||||
osg::Vec3(0, 0, -0.75 * light->getRange()),
|
||||
tan(light->getSpotCutoff() * SG_DEGREES_TO_RADIANS) * light->getRange(),
|
||||
light->getRange());
|
||||
}
|
||||
osg::ShapeDrawable *debug_drawable = new osg::ShapeDrawable(debug_shape);
|
||||
debug_drawable->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0));
|
||||
osg::Geode *debug_geode = new osg::Geode;
|
||||
debug_geode->addDrawable(debug_drawable);
|
||||
|
||||
osg::StateSet *debug_ss = debug_drawable->getOrCreateStateSet();
|
||||
debug_ss->setAttributeAndModes(
|
||||
new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE),
|
||||
osg::StateAttribute::ON);
|
||||
debug_ss->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
|
||||
|
||||
osg::Switch *debug_switch = new osg::Switch;
|
||||
debug_switch->addChild(debug_geode);
|
||||
simgear::getPropertyRoot()->getNode("/sim/debug/show-light-volumes", true)->
|
||||
addChangeListener(new SGLightDebugListener(debug_switch), true);
|
||||
group->addChild(debug_switch);
|
||||
|
||||
if ((p = configNode->getNode("name")) != NULL)
|
||||
group->setName(p->getStringValue());
|
||||
else
|
||||
group->setName("light");
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
SGLight::SGLight() :
|
||||
_type(Type::POINT),
|
||||
_range(0.0f)
|
||||
{
|
||||
// Default values taken from osg::Light
|
||||
// They don't matter anyway as they are overwritten by the XML config values
|
||||
_ambient.set(0.05f, 0.05f, 0.05f, 1.0f);
|
||||
_diffuse.set(0.8f, 0.8f, 0.8f, 1.0f);
|
||||
_specular.set(0.05f, 0.05f, 0.05f, 1.0f);
|
||||
_constant_attenuation = 1.0f;
|
||||
_linear_attenuation = 0.0f;
|
||||
_quadratic_attenuation = 0.0f;
|
||||
_spot_exponent = 0.0f;
|
||||
_spot_cutoff = 180.0f;
|
||||
}
|
||||
|
||||
SGLight::~SGLight()
|
||||
{
|
||||
|
||||
}
|
105
simgear/scene/model/SGLight.hxx
Normal file
105
simgear/scene/model/SGLight.hxx
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#ifndef SG_LIGHT_HXX
|
||||
#define SG_LIGHT_HXX
|
||||
|
||||
#include <osgDB/ReaderWriter>
|
||||
#include <osg/Group>
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
class SGLight : public osg::Node {
|
||||
public:
|
||||
enum Type {
|
||||
POINT,
|
||||
SPOT
|
||||
};
|
||||
|
||||
SGLight();
|
||||
|
||||
SGLight(const SGLight& l,
|
||||
const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) :
|
||||
osg::Node(l, copyop),
|
||||
_type(l._type),
|
||||
_range(l._range),
|
||||
_ambient(l._ambient),
|
||||
_diffuse(l._diffuse),
|
||||
_specular(l._specular),
|
||||
_constant_attenuation(l._constant_attenuation),
|
||||
_linear_attenuation(l._linear_attenuation),
|
||||
_quadratic_attenuation(l._quadratic_attenuation),
|
||||
_spot_exponent(l._spot_exponent),
|
||||
_spot_cutoff(l._spot_cutoff)
|
||||
{}
|
||||
|
||||
META_Node(simgear, SGLight);
|
||||
|
||||
static osg::Node *appendLight(const SGPropertyNode *configNode,
|
||||
SGPropertyNode *modelRoot,
|
||||
const osgDB::Options *options);
|
||||
|
||||
void setType(Type type) { _type = type; }
|
||||
Type getType() const { return _type; }
|
||||
|
||||
void setRange(float range) { _range = range; }
|
||||
float getRange() const { return _range; }
|
||||
|
||||
void setAmbient(const osg::Vec4 &ambient) { _ambient = ambient; }
|
||||
const osg::Vec4 &getAmbient() const { return _ambient; }
|
||||
|
||||
void setDiffuse(const osg::Vec4 &diffuse) { _diffuse = diffuse; }
|
||||
const osg::Vec4 &getDiffuse() const { return _diffuse; }
|
||||
|
||||
void setSpecular(const osg::Vec4 &specular) { _specular = specular; }
|
||||
const osg::Vec4 &getSpecular() const { return _specular; }
|
||||
|
||||
void setConstantAttenuation(float constant_attenuation) { _constant_attenuation = constant_attenuation; }
|
||||
float getConstantAttenuation() const { return _constant_attenuation; }
|
||||
|
||||
void setLinearAttenuation(float linear_attenuation) { _linear_attenuation = linear_attenuation; }
|
||||
float getLinearAttenuation() const { return _linear_attenuation; }
|
||||
|
||||
void setQuadraticAttenuation(float quadratic_attenuation) { _quadratic_attenuation = quadratic_attenuation; }
|
||||
float getQuadraticAttenuation() const { return _quadratic_attenuation; }
|
||||
|
||||
void setSpotExponent(float spot_exponent) { _spot_exponent = spot_exponent; }
|
||||
float getSpotExponent() const { return _spot_exponent; }
|
||||
|
||||
void setSpotCutoff(float spot_cutoff) { _spot_cutoff = spot_cutoff; }
|
||||
float getSpotCutoff() const { return _spot_cutoff; }
|
||||
|
||||
protected:
|
||||
virtual ~SGLight();
|
||||
|
||||
Type _type;
|
||||
|
||||
float _range;
|
||||
|
||||
osg::Vec4 _ambient;
|
||||
osg::Vec4 _diffuse;
|
||||
osg::Vec4 _specular;
|
||||
|
||||
float _constant_attenuation;
|
||||
float _linear_attenuation;
|
||||
float _quadratic_attenuation;
|
||||
float _spot_exponent;
|
||||
float _spot_cutoff;
|
||||
};
|
||||
|
||||
typedef std::vector<osg::ref_ptr<SGLight>> SGLightList;
|
||||
|
||||
#endif /* SG_LIGHT_HXX */
|
@ -50,6 +50,7 @@
|
||||
#include "animation.hxx"
|
||||
#include "particles.hxx"
|
||||
#include "model.hxx"
|
||||
#include "SGLight.hxx"
|
||||
#include "SGText.hxx"
|
||||
#include "SGMaterialAnimation.hxx"
|
||||
|
||||
@ -518,6 +519,14 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
options.get()));
|
||||
}
|
||||
|
||||
std::vector<SGPropertyNode_ptr> light_nodes;
|
||||
light_nodes = props->getChildren("light");
|
||||
for (unsigned i = 0; i < light_nodes.size(); ++i) {
|
||||
group->addChild(SGLight::appendLight(light_nodes[i],
|
||||
prop_root,
|
||||
options.get()));
|
||||
}
|
||||
|
||||
PropertyList effect_nodes = props->getChildren("effect");
|
||||
PropertyList animation_nodes = props->getChildren("animation");
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
set(HEADERS
|
||||
ClusteredForward.hxx
|
||||
ClusteredShading.hxx
|
||||
Compositor.hxx
|
||||
CompositorBuffer.hxx
|
||||
CompositorPass.hxx
|
||||
@ -7,7 +7,7 @@ set(HEADERS
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
ClusteredForward.cxx
|
||||
ClusteredShading.cxx
|
||||
Compositor.cxx
|
||||
CompositorBuffer.cxx
|
||||
CompositorPass.cxx
|
||||
|
@ -1,216 +0,0 @@
|
||||
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#include "ClusteredForward.hxx"
|
||||
|
||||
#include <osg/BufferIndexBinding>
|
||||
#include <osg/BufferObject>
|
||||
#include <osg/RenderInfo>
|
||||
#include <osg/Texture3D>
|
||||
#include <osg/TextureBuffer>
|
||||
#include <osg/Version>
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
///// BEGIN DEBUG
|
||||
#define DATA_SIZE 24
|
||||
const GLfloat LIGHT_DATA[DATA_SIZE] = {
|
||||
0.0, 0.0, -10.0, 1.0, 1.0, 0.0, 0.0, 1.0,
|
||||
0.0, 0.0, 10.0, 1.0, 0.0, 1.0, 0.0, 1.0,
|
||||
0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0
|
||||
};
|
||||
|
||||
#define MAX_LIGHT_INDICES 4096
|
||||
#define MAX_POINT_LIGHTS 256
|
||||
|
||||
struct Light {
|
||||
osg::Vec3 position;
|
||||
float range;
|
||||
};
|
||||
|
||||
#define NUM_LIGHTS 2
|
||||
Light LIGHT_LIST[NUM_LIGHTS] = {
|
||||
{osg::Vec3(0.0f, 0.0f, -10.0f), 10.0f},
|
||||
{osg::Vec3(0.0f, 0.0f, 5.0f), 1000.0f}
|
||||
};
|
||||
///// END DEBUG
|
||||
|
||||
ClusteredForwardDrawCallback::ClusteredForwardDrawCallback(int tile_size) :
|
||||
_initialized(false),
|
||||
_tile_size(tile_size),
|
||||
_light_grid(new osg::Image),
|
||||
_light_indices(new osg::Image),
|
||||
_light_data(new osg::FloatArray(MAX_POINT_LIGHTS))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ClusteredForwardDrawCallback::operator()(osg::RenderInfo &renderInfo) const
|
||||
{
|
||||
osg::Camera *camera = renderInfo.getCurrentCamera();
|
||||
const osg::Viewport *vp = camera->getViewport();
|
||||
const int width = vp->width();
|
||||
const int height = vp->height();
|
||||
|
||||
// Round up
|
||||
int n_htiles = (width + _tile_size - 1) / _tile_size;
|
||||
int n_vtiles = (height + _tile_size - 1) / _tile_size;
|
||||
|
||||
if (!_initialized) {
|
||||
// Create and associate the light grid 3D texture
|
||||
_light_grid->allocateImage(n_htiles, n_vtiles, 1,
|
||||
GL_RGB_INTEGER_EXT, GL_UNSIGNED_SHORT);
|
||||
_light_grid->setInternalTextureFormat(GL_RGB16UI_EXT);
|
||||
|
||||
osg::ref_ptr<osg::Texture3D> light_grid_tex = new osg::Texture3D;
|
||||
light_grid_tex->setResizeNonPowerOfTwoHint(false);
|
||||
light_grid_tex->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::CLAMP_TO_BORDER);
|
||||
light_grid_tex->setWrap(osg::Texture3D::WRAP_S, osg::Texture3D::CLAMP_TO_BORDER);
|
||||
light_grid_tex->setWrap(osg::Texture3D::WRAP_T, osg::Texture3D::CLAMP_TO_BORDER);
|
||||
light_grid_tex->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::NEAREST);
|
||||
light_grid_tex->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::NEAREST);
|
||||
light_grid_tex->setImage(0, _light_grid.get());
|
||||
|
||||
camera->getOrCreateStateSet()->setTextureAttributeAndModes(
|
||||
10, light_grid_tex.get(), osg::StateAttribute::ON);
|
||||
|
||||
// Create and associate the light indices TBO
|
||||
_light_indices->allocateImage(4096, 1, 1, GL_RED_INTEGER_EXT, GL_UNSIGNED_SHORT);
|
||||
|
||||
osg::ref_ptr<osg::TextureBuffer> light_indices_tbo =
|
||||
new osg::TextureBuffer;
|
||||
light_indices_tbo->setInternalFormat(GL_R16UI);
|
||||
light_indices_tbo->setImage(_light_indices.get());
|
||||
|
||||
camera->getOrCreateStateSet()->setTextureAttribute(
|
||||
11, light_indices_tbo.get());
|
||||
|
||||
// Create and associate the light data UBO
|
||||
osg::ref_ptr<osg::UniformBufferObject> light_data_ubo =
|
||||
new osg::UniformBufferObject;
|
||||
_light_data->setBufferObject(light_data_ubo.get());
|
||||
|
||||
#if OSG_VERSION_LESS_THAN(3,6,0)
|
||||
osg::ref_ptr<osg::UniformBufferBinding> light_data_ubb =
|
||||
new osg::UniformBufferBinding(0, light_data_ubo.get(),
|
||||
0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat));
|
||||
#else
|
||||
osg::ref_ptr<osg::UniformBufferBinding> light_data_ubb =
|
||||
new osg::UniformBufferBinding(0, _light_data.get(),
|
||||
0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat));
|
||||
#endif
|
||||
light_data_ubb->setDataVariance(osg::Object::DYNAMIC);
|
||||
|
||||
camera->getOrCreateStateSet()->setAttribute(
|
||||
light_data_ubb.get(), osg::StateAttribute::ON);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
std::vector<osg::Polytope> subfrustums;
|
||||
const osg::Matrix &view_matrix = camera->getViewMatrix();
|
||||
const osg::Matrix &proj_matrix = camera->getProjectionMatrix();
|
||||
osg::Matrix view_proj_inverse = osg::Matrix::inverse(view_matrix * proj_matrix);
|
||||
|
||||
double x_step = (_tile_size / width) * 2.0;
|
||||
double y_step = (_tile_size / height) * 2.0;
|
||||
for (int y = 0; y < n_vtiles; ++y) {
|
||||
for (int x = 0; x < n_htiles; ++x) {
|
||||
// Create the subfrustum in clip space
|
||||
double x_min = -1.0 + x_step * x; double x_max = x_min + x_step;
|
||||
double y_min = -1.0 + y_step * y; double y_max = y_min + y_step;
|
||||
double z_min = 1.0; double z_max = -1.0;
|
||||
osg::BoundingBox subfrustum_bb(
|
||||
x_min, y_min, z_min, x_max, y_max, z_max);
|
||||
osg::Polytope subfrustum;
|
||||
subfrustum.setToBoundingBox(subfrustum_bb);
|
||||
|
||||
// Transform it to world space
|
||||
subfrustum.transformProvidingInverse(view_proj_inverse);
|
||||
|
||||
subfrustums.push_back(subfrustum);
|
||||
}
|
||||
}
|
||||
|
||||
GLushort *grid_data = reinterpret_cast<GLushort *>
|
||||
(_light_grid->data());
|
||||
GLushort *index_data = reinterpret_cast<GLushort *>
|
||||
(_light_indices->data());
|
||||
|
||||
GLushort global_light_count = 0;
|
||||
for (size_t i = 0; i < subfrustums.size(); ++i) {
|
||||
GLushort start_offset = global_light_count;
|
||||
GLushort local_light_count = 0;
|
||||
|
||||
for (GLushort light_list_index = 0;
|
||||
light_list_index < NUM_LIGHTS;
|
||||
++light_list_index) {
|
||||
const Light &light = LIGHT_LIST[light_list_index];
|
||||
osg::BoundingSphere bs(light.position, light.range);
|
||||
|
||||
if (subfrustums[i].contains(bs)) {
|
||||
index_data[global_light_count] = light_list_index;
|
||||
++local_light_count;
|
||||
++global_light_count;
|
||||
}
|
||||
}
|
||||
grid_data[i * 3 + 0] = start_offset;
|
||||
grid_data[i * 3 + 1] = local_light_count;
|
||||
grid_data[i * 3 + 2] = 0;
|
||||
}
|
||||
|
||||
_light_grid->dirty();
|
||||
_light_indices->dirty();
|
||||
|
||||
// Upload light data
|
||||
for (int i = 0; i < DATA_SIZE; ++i) {
|
||||
(*_light_data)[i] = LIGHT_DATA[i];
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
/*
|
||||
if (!_debug) {
|
||||
for (int y = 0; y < num_vtiles; ++y) {
|
||||
for (int x = 0; x < num_htiles; ++x) {
|
||||
std::cout << grid_data[(y * num_htiles + x) * 3 + 0] << ","
|
||||
<< grid_data[(y * num_htiles + x) * 3 + 1] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << "\n\n";
|
||||
|
||||
for (int i = 0; i < num_vtiles * num_htiles; ++i) {
|
||||
std::cout << index_data[i] << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
_debug = true;
|
||||
}
|
||||
*/
|
||||
/*
|
||||
for (int y = 0; y < num_vtiles; ++y) {
|
||||
for (int x = 0; x < num_htiles; ++x) {
|
||||
data[(y * num_htiles + x) * 3 + 0] = (unsigned short)x;
|
||||
data[(y * num_htiles + x) * 3 + 1] = (unsigned short)y;
|
||||
data[(y * num_htiles + x) * 3 + 2] = 0;
|
||||
}
|
||||
}
|
||||
_light_grid->dirty();
|
||||
*/
|
||||
}
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
@ -1,40 +0,0 @@
|
||||
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#ifndef SG_CLUSTERED_FORWARD_HXX
|
||||
#define SG_CLUSTERED_FORWARD_HXX
|
||||
|
||||
#include <osg/Camera>
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
class ClusteredForwardDrawCallback : public osg::Camera::DrawCallback {
|
||||
public:
|
||||
ClusteredForwardDrawCallback(int tile_size);
|
||||
virtual void operator()(osg::RenderInfo &renderInfo) const;
|
||||
protected:
|
||||
mutable bool _initialized;
|
||||
int _tile_size;
|
||||
osg::ref_ptr<osg::Image> _light_grid;
|
||||
osg::ref_ptr<osg::Image> _light_indices;
|
||||
osg::ref_ptr<osg::FloatArray> _light_data;
|
||||
};
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_CLUSTERED_FORWARD_HXX */
|
395
simgear/scene/viewer/ClusteredShading.cxx
Normal file
395
simgear/scene/viewer/ClusteredShading.cxx
Normal file
@ -0,0 +1,395 @@
|
||||
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#include "ClusteredShading.hxx"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <osg/BufferIndexBinding>
|
||||
#include <osg/BufferObject>
|
||||
#include <osg/RenderInfo>
|
||||
#include <osg/Texture3D>
|
||||
#include <osg/TextureBuffer>
|
||||
#include <osg/Version>
|
||||
|
||||
#include <osg/io_utils>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
const int MAX_LIGHT_INDICES = 524288; // 1 MB (2 bytes per index)
|
||||
const int MAX_POINTLIGHTS = 256;
|
||||
const int MAX_SPOTLIGHTS = 256;
|
||||
|
||||
// Size in floats (4 bytes) of the light struct to be passed to the GLSL shader.
|
||||
// It must be a multiple of the size of a vec4 as per the std140 layout rules.
|
||||
// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_uniform_buffer_object.txt
|
||||
const int POINTLIGHT_BLOCK_SIZE = 20;
|
||||
const int SPOTLIGHT_BLOCK_SIZE = 8;
|
||||
|
||||
ClusteredShading::ClusteredShading(osg::Camera *camera,
|
||||
const SGPropertyNode *config) :
|
||||
_camera(camera)
|
||||
{
|
||||
_tile_size = config->getIntValue("tile-size", 128);
|
||||
_depth_slices = config->getIntValue("depth-slices", 1);
|
||||
_num_threads = config->getIntValue("num-threads", 1);
|
||||
_slices_per_thread = _depth_slices / _num_threads;
|
||||
if (_slices_per_thread == 0) {
|
||||
SG_LOG(SG_INPUT, SG_INFO, "ClusteredShading::ClusteredShading(): "
|
||||
"More threads than depth slices");
|
||||
_num_threads = _depth_slices;
|
||||
}
|
||||
_slices_remainder = _depth_slices % _num_threads;
|
||||
|
||||
osg::StateSet *ss = _camera->getOrCreateStateSet();
|
||||
|
||||
osg::Uniform *tile_size_uniform =
|
||||
new osg::Uniform("fg_ClusteredTileSize", _tile_size);
|
||||
ss->addUniform(tile_size_uniform);
|
||||
_slice_scale = new osg::Uniform("fg_ClusteredSliceScale", 0.0f);
|
||||
ss->addUniform(_slice_scale.get());
|
||||
_slice_bias = new osg::Uniform("fg_ClusteredSliceBias", 0.0f);
|
||||
ss->addUniform(_slice_bias.get());
|
||||
|
||||
// Create and associate the light grid 3D texture
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
_light_grid = new osg::Image;
|
||||
_light_grid->setInternalTextureFormat(GL_RGB32UI_EXT);
|
||||
// Image allocation happens in setupSubfrusta() because the light grid size
|
||||
// can change at runtime (viewport resize)
|
||||
|
||||
osg::ref_ptr<osg::Texture3D> light_grid_tex = new osg::Texture3D;
|
||||
light_grid_tex->setResizeNonPowerOfTwoHint(false);
|
||||
light_grid_tex->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::CLAMP_TO_BORDER);
|
||||
light_grid_tex->setWrap(osg::Texture3D::WRAP_S, osg::Texture3D::CLAMP_TO_BORDER);
|
||||
light_grid_tex->setWrap(osg::Texture3D::WRAP_T, osg::Texture3D::CLAMP_TO_BORDER);
|
||||
light_grid_tex->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::NEAREST);
|
||||
light_grid_tex->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::NEAREST);
|
||||
light_grid_tex->setImage(_light_grid.get());
|
||||
|
||||
int light_grid_bind_unit = config->getIntValue("grid-bind-unit", 11);
|
||||
ss->setTextureAttributeAndModes(
|
||||
light_grid_bind_unit, light_grid_tex.get(), osg::StateAttribute::ON);
|
||||
|
||||
osg::ref_ptr<osg::Uniform> light_grid_uniform =
|
||||
new osg::Uniform("fg_ClusteredLightGrid", light_grid_bind_unit);
|
||||
ss->addUniform(light_grid_uniform.get());
|
||||
|
||||
// Create and associate the light indices TBO
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
_light_indices = new osg::Image;
|
||||
_light_indices->allocateImage(
|
||||
MAX_LIGHT_INDICES, 1, 1, GL_RED_INTEGER_EXT, GL_UNSIGNED_SHORT);
|
||||
|
||||
osg::ref_ptr<osg::TextureBuffer> light_indices_tbo =
|
||||
new osg::TextureBuffer;
|
||||
light_indices_tbo->setInternalFormat(GL_R16UI);
|
||||
light_indices_tbo->setImage(_light_indices.get());
|
||||
|
||||
int light_indices_bind_unit = config->getIntValue("indices-bind-unit", 12);
|
||||
ss->setTextureAttribute(light_indices_bind_unit, light_indices_tbo.get());
|
||||
|
||||
osg::ref_ptr<osg::Uniform> light_indices_uniform =
|
||||
new osg::Uniform("fg_ClusteredLightIndices", light_indices_bind_unit);
|
||||
ss->addUniform(light_indices_uniform.get());
|
||||
|
||||
// Create and associate the pointlight data UBO
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
_pointlight_data = new osg::FloatArray(MAX_POINTLIGHTS * POINTLIGHT_BLOCK_SIZE);
|
||||
|
||||
osg::ref_ptr<osg::UniformBufferObject> pointlight_data_ubo =
|
||||
new osg::UniformBufferObject;
|
||||
_pointlight_data->setBufferObject(pointlight_data_ubo.get());
|
||||
|
||||
int pointlight_ubo_index = config->getIntValue("pointlight-ubo-index", 5);
|
||||
#if OSG_VERSION_LESS_THAN(3,6,0)
|
||||
osg::ref_ptr<osg::UniformBufferBinding> pointlight_data_ubb =
|
||||
new osg::UniformBufferBinding(
|
||||
pointlight_ubo_index,
|
||||
pointlight_data_ubo.get(),
|
||||
0,
|
||||
MAX_POINTLIGHTS * POINTLIGHT_BLOCK_SIZE * sizeof(GLfloat));
|
||||
#else
|
||||
osg::ref_ptr<osg::UniformBufferBinding> pointlight_data_ubb =
|
||||
new osg::UniformBufferBinding(
|
||||
pointlight_ubo_index,
|
||||
_pointlight_data.get(),
|
||||
0,
|
||||
MAX_POINTLIGHTS * POINTLIGHT_BLOCK_SIZE * sizeof(GLfloat));
|
||||
#endif
|
||||
pointlight_data_ubb->setDataVariance(osg::Object::DYNAMIC);
|
||||
ss->setAttribute(pointlight_data_ubb.get(), osg::StateAttribute::ON);
|
||||
|
||||
// Create and associate the spotlight data UBO
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
_spotlight_data = new osg::FloatArray(MAX_SPOTLIGHTS * SPOTLIGHT_BLOCK_SIZE);
|
||||
|
||||
osg::ref_ptr<osg::UniformBufferObject> spotlight_data_ubo =
|
||||
new osg::UniformBufferObject;
|
||||
_spotlight_data->setBufferObject(spotlight_data_ubo.get());
|
||||
|
||||
int spotlight_ubo_index = config->getIntValue("spotlight-ubo-index", 6);
|
||||
#if OSG_VERSION_LESS_THAN(3,6,0)
|
||||
osg::ref_ptr<osg::UniformBufferBinding> spotlight_data_ubb =
|
||||
new osg::UniformBufferBinding(
|
||||
spotlight_ubo_index,
|
||||
spotlight_data_ubo.get(),
|
||||
0,
|
||||
MAX_SPOTLIGHTS * SPOTLIGHT_BLOCK_SIZE * sizeof(GLfloat));
|
||||
#else
|
||||
osg::ref_ptr<osg::UniformBufferBinding> spotlight_data_ubb =
|
||||
new osg::UniformBufferBinding(
|
||||
spotlight_ubo_index,
|
||||
_spotlight_data.get(),
|
||||
0,
|
||||
MAX_SPOTLIGHTS * SPOTLIGHT_BLOCK_SIZE * sizeof(GLfloat));
|
||||
#endif
|
||||
spotlight_data_ubb->setDataVariance(osg::Object::DYNAMIC);
|
||||
ss->setAttribute(spotlight_data_ubb.get(), osg::StateAttribute::ON);
|
||||
}
|
||||
|
||||
ClusteredShading::~ClusteredShading()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ClusteredShading::update(const SGLightList &light_list)
|
||||
{
|
||||
// Transform every light to a more comfortable data structure for collision
|
||||
// testing, separating point and spot lights in the process
|
||||
_point_bounds.clear();
|
||||
_spot_bounds.clear();
|
||||
for (const auto &light : light_list) {
|
||||
if (light->getType() == SGLight::Type::POINT) {
|
||||
PointlightBound point;
|
||||
point.light = light;
|
||||
point.position = osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f) *
|
||||
osg::computeLocalToWorld(light->getParentalNodePaths()[0]) *
|
||||
_camera->getViewMatrix();
|
||||
point.range = light->getRange();
|
||||
_point_bounds.push_back(point);
|
||||
} else if (light->getType() == SGLight::Type::SPOT) {
|
||||
|
||||
}
|
||||
}
|
||||
if (_point_bounds.size() > MAX_POINTLIGHTS ||
|
||||
_spot_bounds.size() > MAX_SPOTLIGHTS) {
|
||||
throw sg_range_exception("Maximum amount of visible lights surpassed");
|
||||
}
|
||||
|
||||
float l, r, b, t;
|
||||
_camera->getProjectionMatrix().getFrustum(l, r, b, t, _zNear, _zFar);
|
||||
_slice_scale->set(_depth_slices / log2(_zFar / _zNear));
|
||||
_slice_bias->set(-_depth_slices * log2(_zNear) / log2(_zFar / _zNear));
|
||||
|
||||
const osg::Viewport *vp = _camera->getViewport();
|
||||
static int old_width = 0, old_height = 0;
|
||||
int width = vp->width(); int height = vp->height();
|
||||
if (width != old_width || height != old_height) {
|
||||
old_width = width; old_height = height;
|
||||
|
||||
_n_htiles = (width + _tile_size - 1) / _tile_size;
|
||||
_n_vtiles = (height + _tile_size - 1) / _tile_size;
|
||||
|
||||
_x_step = (_tile_size / float(width)) * 2.0;
|
||||
_y_step = (_tile_size / float(height)) * 2.0;
|
||||
|
||||
_light_grid->allocateImage(_n_htiles, _n_vtiles, _depth_slices,
|
||||
GL_RGB_INTEGER_EXT, GL_UNSIGNED_INT);
|
||||
_subfrusta.reset(new Subfrustum[_n_htiles * _n_vtiles]);
|
||||
}
|
||||
|
||||
for (int y = 0; y < _n_vtiles; ++y) {
|
||||
float ymin = -1.0 + _y_step * float(y);
|
||||
float ymax = ymin + _y_step;
|
||||
for (int x = 0; x < _n_htiles; ++x) {
|
||||
float xmin = -1.0 + _x_step * float(x);
|
||||
float xmax = xmin + _x_step;
|
||||
|
||||
// Create the subfrustum in clip space
|
||||
// The near and far planes will be filled later as they change from
|
||||
// slice to slice
|
||||
Subfrustum &subfrustum = _subfrusta[y*_n_htiles + x];
|
||||
subfrustum.plane[0].set(1.0f,0.0f,0.0f,-xmin); // left plane.
|
||||
subfrustum.plane[1].set(-1.0f,0.0f,0.0f,xmax); // right plane.
|
||||
subfrustum.plane[2].set(0.0f,1.0f,0.0f,-ymin); // bottom plane.
|
||||
subfrustum.plane[3].set(0.0f,-1.0f,0.0f,ymax); // top plane.
|
||||
|
||||
// Transform it to view space
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
osg::Vec4f &p = subfrustum.plane[i];
|
||||
p = _camera->getProjectionMatrix() * p;
|
||||
float inv_length = 1.0 / sqrt(p._v[0]*p._v[0] +
|
||||
p._v[1]*p._v[1] +
|
||||
p._v[2]*p._v[2]);
|
||||
p *= inv_length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_global_light_count = 0;
|
||||
|
||||
if (_depth_slices == 1) {
|
||||
// Just run the light assignment on the main thread to avoid the
|
||||
// unnecessary threading overhead
|
||||
assignLightsToSlice(0);
|
||||
} else if (_num_threads == 1) {
|
||||
// Again, avoid the unnecessary threading overhead
|
||||
threadFunc(0);
|
||||
} else {
|
||||
std::thread light_threads[_num_threads];
|
||||
for (int i = 0; i < _num_threads; ++i)
|
||||
light_threads[i] = std::thread(&ClusteredShading::threadFunc, this, i);
|
||||
|
||||
for (int i = 0; i < _num_threads; ++i)
|
||||
light_threads[i].join();
|
||||
}
|
||||
|
||||
// Force upload of the image data
|
||||
_light_grid->dirty();
|
||||
_light_indices->dirty();
|
||||
|
||||
// Upload pointlight data
|
||||
writePointlightData();
|
||||
}
|
||||
|
||||
void
|
||||
ClusteredShading::threadFunc(int thread_id)
|
||||
{
|
||||
for (int i = 0; i < _slices_per_thread; ++i)
|
||||
assignLightsToSlice(thread_id * _slices_per_thread + i);
|
||||
|
||||
if (_slices_remainder > thread_id)
|
||||
assignLightsToSlice(_slices_per_thread * _num_threads + thread_id);
|
||||
}
|
||||
|
||||
void
|
||||
ClusteredShading::assignLightsToSlice(int slice)
|
||||
{
|
||||
size_t z_offset = slice * _n_htiles * _n_vtiles;
|
||||
|
||||
float near = getDepthForSlice(slice);
|
||||
float far = getDepthForSlice(slice + 1);
|
||||
osg::Vec4f near_plane(0.0f, 0.0f, -1.0f, -near);
|
||||
osg::Vec4f far_plane (0.0f, 0.0f, 1.0f, far);
|
||||
|
||||
GLuint *grid = reinterpret_cast<GLuint *>(_light_grid->data());
|
||||
GLushort *indices = reinterpret_cast<GLushort *>(_light_indices->data());
|
||||
|
||||
for (int i = 0; i < (_n_htiles * _n_vtiles); ++i) {
|
||||
Subfrustum subfrustum = _subfrusta[i];
|
||||
subfrustum.plane[4] = near_plane;
|
||||
subfrustum.plane[5] = far_plane;
|
||||
|
||||
GLuint start_offset = _global_light_count;
|
||||
GLuint local_point_count = 0;
|
||||
GLuint local_spot_count = 0;
|
||||
|
||||
for (GLushort point_iterator = 0;
|
||||
point_iterator < _point_bounds.size();
|
||||
++point_iterator) {
|
||||
PointlightBound point = _point_bounds[point_iterator];
|
||||
|
||||
// Perform frustum-sphere collision tests
|
||||
float distance = 0.0f;
|
||||
for (int j = 0; j < 6; j++) {
|
||||
distance = subfrustum.plane[j] * point.position + point.range;
|
||||
if (distance <= 0.0f)
|
||||
break;
|
||||
}
|
||||
|
||||
if (distance > 0.0f) {
|
||||
// Update light index list
|
||||
indices[_global_light_count] = point_iterator;
|
||||
++local_point_count;
|
||||
++_global_light_count; // Atomic increment
|
||||
}
|
||||
|
||||
if (_global_light_count >= MAX_LIGHT_INDICES) {
|
||||
throw sg_range_exception(
|
||||
"Clustered shading light index count is over the hardcoded limit ("
|
||||
+ std::to_string(MAX_LIGHT_INDICES) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
// Update light grid
|
||||
grid[(z_offset + i) * 3 + 0] = start_offset;
|
||||
grid[(z_offset + i) * 3 + 1] = local_point_count;
|
||||
grid[(z_offset + i) * 3 + 2] = local_spot_count;
|
||||
}
|
||||
|
||||
// for (int y = 0; y < _n_vtiles; ++y) {
|
||||
// for (int x = 0; x < _n_htiles; ++x) {
|
||||
// std::cout << grid[(y * _n_htiles + x) * 3 + 0] << ","
|
||||
// << grid[(y * _n_htiles + x) * 3 + 1] << " ";
|
||||
// }
|
||||
// std::cout << std::endl;
|
||||
// }
|
||||
// std::cout << "\n\n";
|
||||
|
||||
// for (int i = 0; i < n_vtiles * n_htiles; ++i) {
|
||||
// std::cout << indices[i] << " ";
|
||||
// }
|
||||
// std::cout << "\n";
|
||||
}
|
||||
|
||||
void
|
||||
ClusteredShading::writePointlightData()
|
||||
{
|
||||
GLfloat *data = reinterpret_cast<GLfloat *>(&(*_pointlight_data)[0]);
|
||||
|
||||
for (const auto &point : _point_bounds) {
|
||||
// vec4 position
|
||||
*data++ = point.position.x();
|
||||
*data++ = point.position.y();
|
||||
*data++ = point.position.z();
|
||||
*data++ = 1.0f;
|
||||
// vec4 ambient
|
||||
*data++ = point.light->getAmbient().x();
|
||||
*data++ = point.light->getAmbient().y();
|
||||
*data++ = point.light->getAmbient().z();
|
||||
*data++ = point.light->getAmbient().w();
|
||||
// vec4 diffuse
|
||||
*data++ = point.light->getDiffuse().x();
|
||||
*data++ = point.light->getDiffuse().y();
|
||||
*data++ = point.light->getDiffuse().z();
|
||||
*data++ = point.light->getDiffuse().w();
|
||||
// vec4 specular
|
||||
*data++ = point.light->getSpecular().x();
|
||||
*data++ = point.light->getSpecular().y();
|
||||
*data++ = point.light->getSpecular().z();
|
||||
*data++ = point.light->getSpecular().w();
|
||||
// vec4 attenuation (x = constant, y = linear, z = quadratic, w = range)
|
||||
*data++ = point.light->getConstantAttenuation();
|
||||
*data++ = point.light->getLinearAttenuation();
|
||||
*data++ = point.light->getQuadraticAttenuation();
|
||||
*data++ = point.light->getRange();
|
||||
// No padding needed as the resulting size is a multiple of vec4
|
||||
}
|
||||
_pointlight_data->dirty();
|
||||
}
|
||||
|
||||
float
|
||||
ClusteredShading::getDepthForSlice(int slice) const
|
||||
{
|
||||
return _zNear * pow(_zFar / _zNear, float(slice) / _depth_slices);
|
||||
}
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
96
simgear/scene/viewer/ClusteredShading.hxx
Normal file
96
simgear/scene/viewer/ClusteredShading.hxx
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#ifndef SG_CLUSTERED_SHADING_HXX
|
||||
#define SG_CLUSTERED_SHADING_HXX
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <osg/Camera>
|
||||
#include <osg/Uniform>
|
||||
|
||||
#include <simgear/scene/model/SGLight.hxx>
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
class ClusteredShading : public osg::Referenced {
|
||||
public:
|
||||
ClusteredShading(osg::Camera *camera, const SGPropertyNode *config);
|
||||
~ClusteredShading();
|
||||
|
||||
void update(const SGLightList &light_list);
|
||||
protected:
|
||||
// We could make use of osg::Polytope, but it does a lot of std::vector
|
||||
// push_back() calls, so we make our own frustum structure for huge
|
||||
// performance gains.
|
||||
struct Subfrustum {
|
||||
osg::Vec4f plane[6];
|
||||
};
|
||||
|
||||
struct PointlightBound {
|
||||
SGLight *light;
|
||||
osg::Vec4f position;
|
||||
float range;
|
||||
};
|
||||
struct SpotlightBound {
|
||||
SGLight *light;
|
||||
osg::Vec4f position;
|
||||
float range;
|
||||
};
|
||||
|
||||
void threadFunc(int thread_id);
|
||||
void assignLightsToSlice(int slice);
|
||||
void writePointlightData();
|
||||
float getDepthForSlice(int slice) const;
|
||||
|
||||
osg::observer_ptr<osg::Camera> _camera;
|
||||
|
||||
osg::ref_ptr<osg::Uniform> _slice_scale;
|
||||
osg::ref_ptr<osg::Uniform> _slice_bias;
|
||||
|
||||
int _tile_size;
|
||||
int _depth_slices;
|
||||
int _num_threads;
|
||||
int _slices_per_thread;
|
||||
int _slices_remainder;
|
||||
|
||||
float _zNear;
|
||||
float _zFar;
|
||||
|
||||
int _n_htiles;
|
||||
int _n_vtiles;
|
||||
|
||||
float _x_step;
|
||||
float _y_step;
|
||||
|
||||
osg::ref_ptr<osg::Image> _light_grid;
|
||||
osg::ref_ptr<osg::Image> _light_indices;
|
||||
osg::ref_ptr<osg::FloatArray> _pointlight_data;
|
||||
osg::ref_ptr<osg::FloatArray> _spotlight_data;
|
||||
|
||||
std::unique_ptr<Subfrustum[]> _subfrusta;
|
||||
|
||||
std::vector<PointlightBound> _point_bounds;
|
||||
std::vector<SpotlightBound> _spot_bounds;
|
||||
|
||||
std::atomic<GLuint> _global_light_count;
|
||||
};
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_CLUSTERED_SHADING_HXX */
|
@ -25,13 +25,14 @@
|
||||
#include <osgUtil/CullVisitor>
|
||||
|
||||
#include <simgear/props/vectorPropTemplates.hxx>
|
||||
#include <simgear/scene/material/EffectCullVisitor.hxx>
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/scene/util/SGUpdateVisitor.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "ClusteredForward.hxx"
|
||||
#include "ClusteredShading.hxx"
|
||||
#include "Compositor.hxx"
|
||||
#include "CompositorUtil.hxx"
|
||||
|
||||
@ -656,6 +657,27 @@ protected:
|
||||
float _zFar;
|
||||
};
|
||||
|
||||
class SceneCullCallback : public osg::NodeCallback {
|
||||
public:
|
||||
SceneCullCallback(ClusteredShading *clustered) :
|
||||
_clustered(clustered) {}
|
||||
|
||||
virtual void operator()(osg::Node *node, osg::NodeVisitor *nv) {
|
||||
osg::Camera *camera = static_cast<osg::Camera *>(node);
|
||||
EffectCullVisitor *cv = dynamic_cast<EffectCullVisitor *>(nv);
|
||||
|
||||
cv->traverse(*camera);
|
||||
|
||||
if (_clustered) {
|
||||
// Retrieve the light list from the cull visitor
|
||||
SGLightList light_list = cv->getLightList();
|
||||
_clustered->update(light_list);
|
||||
}
|
||||
}
|
||||
protected:
|
||||
osg::ref_ptr<ClusteredShading> _clustered;
|
||||
};
|
||||
|
||||
struct ScenePassBuilder : public PassBuilder {
|
||||
public:
|
||||
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root,
|
||||
@ -666,11 +688,12 @@ public:
|
||||
osg::Camera *camera = pass->camera;
|
||||
camera->setAllowEventFocus(true);
|
||||
|
||||
const SGPropertyNode *clustered = root->getChild("clustered-forward");
|
||||
if (clustered) {
|
||||
int tile_size = clustered->getIntValue("tile-size", 64);
|
||||
camera->setInitialDrawCallback(new ClusteredForwardDrawCallback(tile_size));
|
||||
}
|
||||
const SGPropertyNode *p_clustered = root->getNode("clustered-shading");
|
||||
ClusteredShading *clustered = 0;
|
||||
if (p_clustered)
|
||||
clustered = new ClusteredShading(camera, p_clustered);
|
||||
|
||||
camera->setCullCallback(new SceneCullCallback(clustered));
|
||||
|
||||
int cubemap_face = root->getIntValue("cubemap-face", -1);
|
||||
float zNear = root->getFloatValue("z-near", 0.0f);
|
||||
|
Loading…
Reference in New Issue
Block a user