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:
Fernando García Liñán 2019-06-06 23:42:35 +02:00
parent 9ac3c1a394
commit 1ca3f60f80
13 changed files with 824 additions and 269 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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;
};

View File

@ -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

View 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()
{
}

View 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 */

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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 */

View 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

View 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 */

View File

@ -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);