Shader-based scenery lights.

This commit implements shader-based scenery lights with support for
efficient rendering of a large number of lights, animations and
directionality. Detailed commit log can be found at [1]. Detailed
discussions can be found on the mailing list [2][3][4].

[1] https://sourceforge.net/u/fahimdalvi/fgdata/ci/feat/scenery-shader-lights/~/tree/
[2] (Timed animation stops working in certain configurations) https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/4A7AC359-63B3-4D8A-815B-09823BCEFF27%40gmail.com/#msg37095923
[3] (Shader-based scenery lights) https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/63E3C131-7662-4F59-B2E6-03208C78EF96%40gmail.com/#msg37190517
[4] (Shader-based scenery lights Part 2) https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/74F18179-CA34-4901-A44E-15498ECC230A%40gmail.com/#msg37331695
This commit is contained in:
Fahim Imaduddin Dalvi 2021-08-07 21:47:56 +03:00
parent 67806a59b0
commit 65483a373f
4 changed files with 469 additions and 2 deletions

View File

@ -5,6 +5,7 @@ set(HEADERS
CoastlineBin.hxx
GroundLightManager.hxx
LineFeatureBin.hxx
LightBin.hxx
ReaderWriterSPT.hxx
ReaderWriterSTG.hxx
SGBuildingBin.hxx
@ -36,6 +37,7 @@ set(SOURCES
CoastlineBin.cxx
GroundLightManager.cxx
LineFeatureBin.cxx
LightBin.cxx
ReaderWriterSPT.cxx
ReaderWriterSTG.cxx
SGBuildingBin.cxx

View File

@ -0,0 +1,271 @@
/* -*-c++-*-
*
* Copyright (C) 2020 Fahim Dalvi
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#ifdef HAVE_CONFIG_H
# include <simgear_config.h>
#endif
#include "LightBin.hxx"
#include <osg/AlphaFunc>
#include <osg/BlendFunc>
#include <osg/Geometry>
#include <osg/StateSet>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/scene/util/RenderConstants.hxx>
#include <simgear/scene/util/SGEnlargeBoundingBox.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/scene/util/StateAttributeFactory.hxx>
using namespace osg;
namespace simgear {
void LightBin::insert(const Light& light) {
_aggregated_size += light.size;
_aggregated_intensity += light.intensity;
_lights.push_back(light);
}
void LightBin::insert(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c) {
insert(Light(p, s, i, o, c));
}
void LightBin::insert(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec4f& a) {
insert(Light(p, s, i, o, c, a));
}
void LightBin::insert(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec3f& d, const double& ha, const double& va) {
insert(Light(p, s, i, o, c, d, ha, va));
}
void LightBin::insert(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec3f& d, const double& ha, const double& va, const SGVec4f& a) {
insert(Light(p, s, i, o, c, d, ha, va, a));
}
unsigned LightBin::getNumLights() const
{
return _lights.size();
}
const LightBin::Light& LightBin::getLight(unsigned i) const
{
return _lights[i];
}
double LightBin::getAverageSize() const
{
return _aggregated_size / getNumLights();
}
double LightBin::getAverageIntensity() const
{
return _aggregated_intensity / getNumLights();
}
LightBin::LightBin(const SGPath& absoluteFileName) {
if (!absoluteFileName.exists()) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Light list file " << absoluteFileName << " does not exist.");
return;
}
sg_gzifstream stream(absoluteFileName);
if (!stream.is_open()) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open " << absoluteFileName);
return;
}
// Every light is defined by at most 19 properties denoting position,
// size, intensity, color, directionality and animations
float props[19];
while (!stream.eof()) {
// read a line. Each line defines a single light position and its properties,
// and may have a comment, starting with #
std::string line;
std::getline(stream, line);
// strip comments
std::string::size_type hash_pos = line.find('#');
if (hash_pos != std::string::npos)
line.resize(hash_pos);
if (line.empty()) {
continue; // skip blank lines
}
// and process further
std::stringstream in(line);
int number_of_props = 0;
while(!in.eof()) {
in >> props[number_of_props++];
}
if (number_of_props == 10) {
// Omnidirectional
insert(
SGVec3f(props[0], props[1], props[2]), // position
props[3], // size
props[4], // intensity
props[5], // on_period
SGVec4f(props[6], props[7], props[8], props[9]) //color
);
} else if (number_of_props == 14) {
// Omnidirectional Animated
insert(
SGVec3f(props[0], props[1], props[2]), // position
props[3], // size
props[4], // intensity
props[5], // on_period
SGVec4f(props[6], props[7], props[8], props[9]), // color
SGVec4f(props[10], props[11], props[12], props[13]) // animation_params
);
} else if (number_of_props == 15) {
// Directional
insert(
SGVec3f(props[0], props[1], props[2]), // position
props[3], // size
props[4], // intensity
props[5], // on_period
SGVec4f(props[6], props[7], props[8], props[9]), // color
SGVec3f(props[10], props[11], props[12]), // direction
props[13], // horizontal_angle
props[14] // vertical_angle
);
} else if (number_of_props == 19) {
// Directional Animated
insert(
SGVec3f(props[0], props[1], props[2]), // position
props[3], // size
props[4], // intensity
props[5], // on_period
SGVec4f(props[6], props[7], props[8], props[9]), // color
SGVec3f(props[10], props[11], props[12]), // direction
props[13], // horizontal_angle
props[14], // vertical_angle
SGVec4f(props[15], props[16], props[17], props[18]) // animation_params
);
} else {
SG_LOG(SG_TERRAIN, SG_WARN, "Error parsing light entry in: " << absoluteFileName << " line: \"" << line << "\"");
continue;
}
}
stream.close();
}
Effect* getLightEffect(double average_size, double average_intensity, const SGReaderWriterOptions* options)
{
SGPropertyNode_ptr effectProp = new SGPropertyNode;
makeChild(effectProp, "inherits-from")->setStringValue("Effects/scenery-lights");
/** Add average size + intensity as uniforms for non-shader or
* non-point-sprite based lights. We cannot have per-light control using
* OpenGL's default lights GL_ARB_point_sprite/GL_ARB_point_parameters
* extensions, so we make a best-effort by making all lights in this bin
* the average size/intensity.
*/
// Clamp intensity between 0 and 50000 candelas
average_intensity = min(max(0.0, average_intensity), 50000.0);
// Scale size from cm to what OpenGL extensions expects
average_size = 1 + 50 * min(max(0.0, average_size), 500.0)/500.0;
SGPropertyNode* params = makeChild(effectProp, "parameters");
params->getNode("size",true)->setValue(average_size);
params->getNode("attenuation",true)->getNode("x", true)->setValue(1.0);
params->getNode("attenuation",true)->getNode("y", true)->setValue(0.00001);
params->getNode("attenuation",true)->getNode("z", true)->setValue(0.00000001);
params->getNode("min-size",true)->setValue(1.0f);
params->getNode("max-size",true)->setValue(average_size*(1+2*average_intensity/50000));
params->getNode("cull-face",true)->setValue("off");
return makeEffect(effectProp, true, options);
}
osg::Drawable* createDrawable(LightBin& lightList, const osg::Matrix& transform) {
if (lightList.getNumLights() <= 0)
return 0;
osg::Vec3Array* vertices = new osg::Vec3Array;
osg::Vec4Array* colors = new osg::Vec4Array;
osg::Vec3Array* light_params = new osg::Vec3Array;
osg::Vec4Array* animation_params = new osg::Vec4Array;
osg::Vec3Array* direction_params_1 = new osg::Vec3Array;
osg::Vec2Array* direction_params_2 = new osg::Vec2Array;
for (int lightIdx = 0; lightIdx < lightList.getNumLights(); lightIdx++) {
const auto l = lightList.getLight(lightIdx);
vertices->push_back(toOsg(l.position) * transform);
light_params->push_back(toOsg(SGVec3f(l.size, l.intensity, l.on_period)));
direction_params_1->push_back(toOsg(l.direction));
direction_params_2->push_back(toOsg(SGVec2f(l.horizontal_angle, l.vertical_angle)));
animation_params->push_back(toOsg(l.animation_params));
colors->push_back(toOsg(l.color));
}
osg::Geometry* geometry = new osg::Geometry;
geometry->setDataVariance(osg::Object::STATIC);
geometry->setVertexArray(vertices);
geometry->setVertexAttribArray(11, light_params, osg::Array::BIND_PER_VERTEX);
geometry->setVertexAttribArray(12, animation_params, osg::Array::BIND_PER_VERTEX);
geometry->setVertexAttribArray(13, direction_params_1, osg::Array::BIND_PER_VERTEX);
geometry->setVertexAttribArray(14, direction_params_2, osg::Array::BIND_PER_VERTEX);
geometry->setNormalBinding(osg::Geometry::BIND_OFF);
geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
geometry->setComputeBoundingBoxCallback(new SGEnlargeBoundingBox(1));
osg::DrawArrays* drawArrays;
drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
0, vertices->size());
geometry->addPrimitiveSet(drawArrays);
osg::StateSet* stateSet = geometry->getOrCreateStateSet();
stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
osg::BlendFunc* blendFunc = new osg::BlendFunc;
stateSet->setAttribute(blendFunc);
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
osg::AlphaFunc* alphaFunc;
alphaFunc = new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01);
stateSet->setAttribute(alphaFunc);
stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
return geometry;
}
osg::ref_ptr<simgear::EffectGeode> createLights(LightBin& lightList, const osg::Matrix& transform, const SGReaderWriterOptions* options) {
osg::ref_ptr<EffectGeode> geode = new EffectGeode;
osg::ref_ptr<Effect> lightEffect = getLightEffect(lightList.getAverageSize(), lightList.getAverageIntensity(), options);
geode->setEffect(lightEffect);
geode->addDrawable(createDrawable(lightList, transform));
// Set all light bits so that the lightbin is always on. Per light on/off
// control is provided by the shader.
geode->setNodeMask(LIGHTS_BITS | MODEL_BIT | PERMANENTLIGHT_BIT);
return geode;
}
};

View File

@ -0,0 +1,109 @@
/* -*-c++-*-
*
* Copyright (C) 2020 Fahim Dalvi
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#ifndef LIGHT_BIN_HXX
#define LIGHT_BIN_HXX
#include <osg/Drawable>
#include <simgear/math/SGMath.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
namespace simgear
{
class LightBin {
public:
struct Light {
// Omni-directional non-animated lights constructor
Light(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c) :
position(p), color(c), size(s), intensity(i), on_period(o),
direction(SGVec3f(0.0f, 0.0f, 0.0f)), horizontal_angle(360.0f), vertical_angle(360.0f),
animation_params(SGVec4f(-1.0f, 0.0f, 0.0f, 0.0f))
{ }
// Omni-directional animated lights constructor
Light(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec4f& a) :
position(p), color(c), size(s), intensity(i), on_period(o),
direction(SGVec3f(0.0f, 0.0f, 0.0f)), horizontal_angle(360.0f), vertical_angle(360.0f),
animation_params(a)
{ }
// Directional non-animated lights constructor
Light(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec3f& d, const double& ha, const double& va) :
position(p), color(c), size(s), intensity(i), on_period(o),
direction(d), horizontal_angle(ha), vertical_angle(va),
animation_params(SGVec4f(-1.0f, 0.0f, 0.0f, 0.0f))
{ }
// Directional animated lights constructor
Light(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec3f& d, const double& ha, const double& va, const SGVec4f& a) :
position(p), color(c), size(s), intensity(i), on_period(o),
direction(d), horizontal_angle(ha), vertical_angle(va),
animation_params(a)
{ }
// Basic Light parameters
SGVec3f position;
SGVec4f color;
double size, intensity;
int on_period; // 0 - all day, 1 - turn on exactly at sunset for night, 2 - turn on randomly around sunset, 3 - night + low visibility
// Directionality parameters
SGVec3f direction;
double horizontal_angle, vertical_angle; // If both angles are 360, the light is treated as omni-directional
/*
* animation_params defines the animation (if any) that the light has to be
* rendered with. It consists of 4 parameters:
* 1. animation_params[0] - interval: Length of the animation (in seconds) - i.e. the animation will loop every `interval` seconds. Animation is disabled if `interval` is less than equal to 0.
* 2. animation_params[1] - on_portion: Portion of the interval the light should functional, i.e. a value of 0.5 implies that the light will only function in the first half of the interval.
* 3. animation_params[2] - strobe_rate: Number of light strobes in the entire `interval`. A value of 1 implies the light will turn on and off once within an `interval`, 2 implies the light will turn on, turn off, turn on, turn off in one `interval`. A value less than or equal to 0 implies an always on light.
* 4. animation_params[3] - offset: Offset the interval start to allow for variety within multiple lights in the same scene, or specific multi-light animations like the RABBIT near the runway. A value less than 0 implies randomized offset.
*/
SGVec4f animation_params;
};
typedef std::vector<Light> LightList;
LightBin() = default;
LightBin(const SGPath& absoluteFileName);
~LightBin() = default;
void insert(const Light& light);
void insert(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c);
void insert(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec4f& a);
void insert(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec3f& d, const double& ha, const double& va);
void insert(const SGVec3f& p, const double& s, const double& i, const int& o, const SGVec4f& c, const SGVec3f& d, const double& ha, const double& va, const SGVec4f& a);
unsigned getNumLights() const;
const Light& getLight(unsigned i) const;
double getAverageSize() const;
double getAverageIntensity() const;
private:
LightList _lights;
double _aggregated_size, _aggregated_intensity;
};
osg::ref_ptr<simgear::EffectGeode> createLights(LightBin& lightList, const osg::Matrix& transform, const SGReaderWriterOptions* options);
};
#endif

View File

@ -56,7 +56,7 @@
#include <simgear/scene/tgdb/SGBuildingBin.hxx>
#include <simgear/scene/tgdb/TreeBin.hxx>
#include <simgear/scene/tgdb/VPBTechnique.hxx>
#include <simgear/scene/tgdb/LightBin.hxx>
#include <simgear/scene/util/SGSceneFeatures.hxx>
@ -73,6 +73,8 @@
#define LINE_FEATURE_LIST "LINE_FEATURE_LIST"
#define AREA_FEATURE_LIST "AREA_FEATURE_LIST"
#define COASTLINE_LIST "COASTLINE_LIST"
#define OBJECT_LIGHT "OBJECT_LIGHT"
#define LIGHT_LIST "LIGHT_LIST"
namespace simgear {
@ -154,6 +156,21 @@ struct ReaderWriterSTG::_ModelBin {
std::string _material_name;
double _lon, _lat, _elev;
};
struct _Light {
_Light() : _lon(0), _lat(0), _elev(0), _size(0), _intensity(0), _on_period(0), _horizontal_angle(0), _vertical_angle(0) { }
double _lon, _lat, _elev;
double _size, _intensity;
int _on_period;
SGVec4f _color;
SGVec3f _direction;
double _horizontal_angle, _vertical_angle;
SGVec4f _animation_params;
};
struct _LightList {
_LightList() : _lon(0), _lat(0), _elev(0) { }
std::string _filename;
double _lon, _lat, _elev;
};
struct _LineFeatureList {
_LineFeatureList() { }
@ -334,6 +351,49 @@ struct ReaderWriterSTG::_ModelBin {
}
}
// Add lights
if (!_lightList.empty()) {
// Transform lights frame of ref for better precision
osg::MatrixTransform* matrixTransform;
matrixTransform = new osg::MatrixTransform(makeZUpFrame(_bucket.get_center()));
matrixTransform->setName("rotateLights");
matrixTransform->setDataVariance(osg::Object::STATIC);
LightBin lightList;
for (const auto& light : _lightList) {
osg::Matrix _position_frame(makeZUpFrame(SGGeod::fromDegM(light._lon, light._lat, light._elev)));
SGVec3f _position(_position_frame(3,0), _position_frame(3,1), _position_frame(3,2));
lightList.insert(
_position,
light._size, light._intensity,
light._on_period,
light._color,
light._direction,
light._horizontal_angle, light._vertical_angle,
light._animation_params
);
}
matrixTransform->addChild(createLights(lightList, matrixTransform->getInverseMatrix(), _options));
group->addChild(matrixTransform);
}
if (!_lightListList.empty()) {
for (const auto& ll : _lightListList) {
osg::MatrixTransform* matrixTransform;
matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(ll._lon, ll._lat, ll._elev)));
matrixTransform->setName("rotateLights");
matrixTransform->setDataVariance(osg::Object::STATIC);
const auto path = SGPath(ll._filename);
LightBin lightList(path);
matrixTransform->addChild(createLights(lightList, osg::Matrix::identity(), _options));
group->addChild(matrixTransform);
}
}
return group.release();
}
@ -342,6 +402,8 @@ struct ReaderWriterSTG::_ModelBin {
std::list<_Sign> _signList;
std::list<_BuildingList> _buildingList;
std::list<_TreeList> _treeList;
std::list<_Light> _lightList;
std::list<_LightList> _lightListList;
/// The original options to use for this bunch of models
osg::ref_ptr<SGReaderWriterOptions> _options;
@ -653,6 +715,23 @@ struct ReaderWriterSTG::_ModelBin {
coastFeaturelist._filename = path.utf8Str();
coastFeaturelist._bucket = bucketIndexFromFileName(absoluteFileName.file_base().c_str());
_coastFeatureListList.push_back(coastFeaturelist);
} else if (token == OBJECT_LIGHT) {
_Light light;
in >> light._lon >> light._lat >> light._elev
>> light._size >> light._intensity
>> light._on_period
>> light._color[0] >> light._color[1] >> light._color[2] >> light._color[3]
>> light._direction[0] >> light._direction[1] >> light._direction[2]
>> light._horizontal_angle >> light._vertical_angle
>> light._animation_params[0] >> light._animation_params[1] >> light._animation_params[2] >> light._animation_params[3];
checkInsideBucket(absoluteFileName, light._lon, light._lat);
_lightList.push_back(light);
} else if (token == LIGHT_LIST) {
_LightList lightList;
lightList._filename = path.utf8Str();
in >> lightList._lon >> lightList._lat >> lightList._elev;
checkInsideBucket(absoluteFileName, lightList._lon, lightList._lat);
_lightListList.push_back(lightList);
} else {
// Check registered callback for token. Keep lock until callback completed to make sure it will not be
// executed after a thread successfully executed removeSTGObjectHandler()
@ -818,7 +897,9 @@ struct ReaderWriterSTG::_ModelBin {
_treeListList.empty() &&
_lineFeatureListList.empty() &&
_areaFeatureListList.empty() &&
_coastFeatureListList.empty())
_coastFeatureListList.empty() &&
_lightList.empty() &&
_lightListList.empty())
{
// The simple case, just return the terrain group
return terrainGroup.release();
@ -839,6 +920,8 @@ struct ReaderWriterSTG::_ModelBin {
readFileCallback->_objectStaticList = _objectStaticList;
readFileCallback->_buildingList = _buildingListList;
readFileCallback->_treeList = _treeListList;
readFileCallback->_lightList = _lightList;
readFileCallback->_lightListList = _lightListList;
readFileCallback->_signList = _signList;
readFileCallback->_options = options;
readFileCallback->_bucket = bucket;
@ -871,6 +954,8 @@ struct ReaderWriterSTG::_ModelBin {
std::list<_LineFeatureList> _lineFeatureListList;
std::list<_AreaFeatureList> _areaFeatureListList;
std::list<_CoastlineList> _coastFeatureListList;
std::list<_Light> _lightList;
std::list<_LightList> _lightListList;
};
ReaderWriterSTG::ReaderWriterSTG()