diff --git a/simgear/scene/material/Effect.cxx b/simgear/scene/material/Effect.cxx index 994adeab..c2240c05 100644 --- a/simgear/scene/material/Effect.cxx +++ b/simgear/scene/material/Effect.cxx @@ -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); diff --git a/simgear/scene/material/EffectCullVisitor.cxx b/simgear/scene/material/EffectCullVisitor.cxx index 36c31653..b25012a0 100644 --- a/simgear/scene/material/EffectCullVisitor.cxx +++ b/simgear/scene/material/EffectCullVisitor.cxx @@ -21,6 +21,8 @@ #include #include +#include + #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(&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) { diff --git a/simgear/scene/material/EffectCullVisitor.hxx b/simgear/scene/material/EffectCullVisitor.hxx index 340ce7c7..3669126b 100644 --- a/simgear/scene/material/EffectCullVisitor.hxx +++ b/simgear/scene/material/EffectCullVisitor.hxx @@ -21,6 +21,8 @@ #include +#include + 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 > _bufferList; - std::vector > _lightList; + SGLightList _lightList; bool _collectLights; std::string _effScheme; }; diff --git a/simgear/scene/model/CMakeLists.txt b/simgear/scene/model/CMakeLists.txt index 69bb545f..50227941 100644 --- a/simgear/scene/model/CMakeLists.txt +++ b/simgear/scene/model/CMakeLists.txt @@ -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 @@ -62,4 +64,4 @@ if(ENABLE_TESTS) target_link_libraries(test_animations ${TEST_LIBS} ${OPENSCENEGRAPH_LIBRARIES}) add_test(animations ${EXECUTABLE_OUTPUT_PATH}/test_animations) -endif(ENABLE_TESTS) \ No newline at end of file +endif(ENABLE_TESTS) diff --git a/simgear/scene/model/SGLight.cxx b/simgear/scene/model/SGLight.cxx new file mode 100644 index 00000000..ab332750 --- /dev/null +++ b/simgear/scene/model/SGLight.cxx @@ -0,0 +1,157 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// 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 +#include +#include +#include +#include + +#include +#include + +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 _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() +{ + +} diff --git a/simgear/scene/model/SGLight.hxx b/simgear/scene/model/SGLight.hxx new file mode 100644 index 00000000..2a25413b --- /dev/null +++ b/simgear/scene/model/SGLight.hxx @@ -0,0 +1,105 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// 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 +#include + +#include + +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> SGLightList; + +#endif /* SG_LIGHT_HXX */ diff --git a/simgear/scene/model/SGReaderWriterXML.cxx b/simgear/scene/model/SGReaderWriterXML.cxx index c09a9579..b6fc37e9 100644 --- a/simgear/scene/model/SGReaderWriterXML.cxx +++ b/simgear/scene/model/SGReaderWriterXML.cxx @@ -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 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"); diff --git a/simgear/scene/viewer/CMakeLists.txt b/simgear/scene/viewer/CMakeLists.txt index d26297fc..ef223b56 100644 --- a/simgear/scene/viewer/CMakeLists.txt +++ b/simgear/scene/viewer/CMakeLists.txt @@ -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 diff --git a/simgear/scene/viewer/ClusteredForward.cxx b/simgear/scene/viewer/ClusteredForward.cxx deleted file mode 100644 index e2c9d593..00000000 --- a/simgear/scene/viewer/ClusteredForward.cxx +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (C) 2018 Fernando García Liñán -// -// 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 -#include -#include -#include -#include -#include - -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 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 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 light_data_ubo = - new osg::UniformBufferObject; - _light_data->setBufferObject(light_data_ubo.get()); - -#if OSG_VERSION_LESS_THAN(3,6,0) - osg::ref_ptr light_data_ubb = - new osg::UniformBufferBinding(0, light_data_ubo.get(), - 0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat)); -#else - osg::ref_ptr 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 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 - (_light_grid->data()); - GLushort *index_data = reinterpret_cast - (_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 diff --git a/simgear/scene/viewer/ClusteredForward.hxx b/simgear/scene/viewer/ClusteredForward.hxx deleted file mode 100644 index 63da3af6..00000000 --- a/simgear/scene/viewer/ClusteredForward.hxx +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2018 Fernando García Liñán -// -// 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 - -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 _light_grid; - osg::ref_ptr _light_indices; - osg::ref_ptr _light_data; -}; - -} // namespace compositor -} // namespace simgear - -#endif /* SG_CLUSTERED_FORWARD_HXX */ diff --git a/simgear/scene/viewer/ClusteredShading.cxx b/simgear/scene/viewer/ClusteredShading.cxx new file mode 100644 index 00000000..dcd13720 --- /dev/null +++ b/simgear/scene/viewer/ClusteredShading.cxx @@ -0,0 +1,395 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include + +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 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 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 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 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 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 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 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 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 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 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(_light_grid->data()); + GLushort *indices = reinterpret_cast(_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(&(*_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 diff --git a/simgear/scene/viewer/ClusteredShading.hxx b/simgear/scene/viewer/ClusteredShading.hxx new file mode 100644 index 00000000..4843a102 --- /dev/null +++ b/simgear/scene/viewer/ClusteredShading.hxx @@ -0,0 +1,96 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// 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 + +#include +#include + +#include + +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 _camera; + + osg::ref_ptr _slice_scale; + osg::ref_ptr _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 _light_grid; + osg::ref_ptr _light_indices; + osg::ref_ptr _pointlight_data; + osg::ref_ptr _spotlight_data; + + std::unique_ptr _subfrusta; + + std::vector _point_bounds; + std::vector _spot_bounds; + + std::atomic _global_light_count; +}; + +} // namespace compositor +} // namespace simgear + +#endif /* SG_CLUSTERED_SHADING_HXX */ diff --git a/simgear/scene/viewer/CompositorPass.cxx b/simgear/scene/viewer/CompositorPass.cxx index 939cc7b6..7e82765d 100644 --- a/simgear/scene/viewer/CompositorPass.cxx +++ b/simgear/scene/viewer/CompositorPass.cxx @@ -25,13 +25,14 @@ #include #include +#include #include #include #include #include #include -#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(node); + EffectCullVisitor *cv = dynamic_cast(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 _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);