Compositor framework initial commit.
This commit is contained in:
@@ -9,6 +9,7 @@ foreach( mylibfolder
|
||||
tgdb
|
||||
util
|
||||
tsync
|
||||
viewer
|
||||
)
|
||||
|
||||
add_subdirectory(${mylibfolder})
|
||||
|
||||
@@ -34,8 +34,9 @@ namespace simgear
|
||||
|
||||
using osgUtil::CullVisitor;
|
||||
|
||||
EffectCullVisitor::EffectCullVisitor(bool collectLights) :
|
||||
_collectLights(collectLights)
|
||||
EffectCullVisitor::EffectCullVisitor(bool collectLights, Effect *effectOverride) :
|
||||
_collectLights(collectLights),
|
||||
_effectOverride(effectOverride)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -61,12 +62,18 @@ void EffectCullVisitor::apply(osg::Geode& node)
|
||||
if (_collectLights && ( eg->getNodeMask() & MODELLIGHT_BIT ) ) {
|
||||
_lightList.push_back( eg );
|
||||
}
|
||||
Effect* effect = eg->getEffect();
|
||||
Effect *effect;
|
||||
if (_effectOverride) {
|
||||
effect = _effectOverride;
|
||||
} else {
|
||||
effect = eg->getEffect();
|
||||
if (!effect) {
|
||||
CullVisitor::apply(node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Technique* technique = 0;
|
||||
if (!effect) {
|
||||
CullVisitor::apply(node);
|
||||
return;
|
||||
} else if (!(technique = effect->chooseTechnique(&getRenderInfo()))) {
|
||||
if (!(technique = effect->chooseTechnique(&getRenderInfo()))) {
|
||||
return;
|
||||
}
|
||||
// push the node's state.
|
||||
|
||||
@@ -29,11 +29,12 @@ class Texture2D;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
class Effect;
|
||||
class EffectGeode;
|
||||
class EffectCullVisitor : public osgUtil::CullVisitor
|
||||
{
|
||||
public:
|
||||
EffectCullVisitor(bool collectLights = false);
|
||||
EffectCullVisitor(bool collectLights = false, Effect *effectOverride = 0);
|
||||
EffectCullVisitor(const EffectCullVisitor&);
|
||||
virtual osgUtil::CullVisitor* clone() const;
|
||||
using osgUtil::CullVisitor::apply;
|
||||
@@ -48,6 +49,7 @@ private:
|
||||
std::map<std::string,osg::ref_ptr<osg::Texture2D> > _bufferList;
|
||||
std::vector<osg::ref_ptr<EffectGeode> > _lightList;
|
||||
bool _collectLights;
|
||||
osg::ref_ptr<Effect> _effectOverride;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
16
simgear/scene/viewer/CMakeLists.txt
Normal file
16
simgear/scene/viewer/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
set(HEADERS
|
||||
ClusteredForward.hxx
|
||||
Compositor.hxx
|
||||
CompositorCommon.hxx
|
||||
CompositorBuffer.hxx
|
||||
CompositorPass.hxx
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
ClusteredForward.cxx
|
||||
Compositor.cxx
|
||||
CompositorBuffer.cxx
|
||||
CompositorPass.cxx
|
||||
)
|
||||
|
||||
simgear_scene_component(viewer scene/viewer "${SOURCES}" "${HEADERS}")
|
||||
209
simgear/scene/viewer/ClusteredForward.cxx
Normal file
209
simgear/scene/viewer/ClusteredForward.cxx
Normal file
@@ -0,0 +1,209 @@
|
||||
// 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>
|
||||
|
||||
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() :
|
||||
_initialized(false),
|
||||
_tile_size(64),
|
||||
_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, GL_UNSIGNED_SHORT);
|
||||
_light_grid->setInternalTextureFormat(GL_RGB16UI);
|
||||
|
||||
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, 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());
|
||||
|
||||
osg::ref_ptr<osg::UniformBufferBinding> light_data_ubb =
|
||||
new osg::UniformBufferBinding(0, light_data_ubo.get(),
|
||||
0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat));
|
||||
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
|
||||
40
simgear/scene/viewer/ClusteredForward.hxx
Normal file
40
simgear/scene/viewer/ClusteredForward.hxx
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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();
|
||||
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 */
|
||||
313
simgear/scene/viewer/Compositor.cxx
Normal file
313
simgear/scene/viewer/Compositor.cxx
Normal file
@@ -0,0 +1,313 @@
|
||||
// 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 "Compositor.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <osgUtil/IntersectionVisitor>
|
||||
|
||||
#include <osgViewer/Renderer>
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include <simgear/math/SGRect.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/scene/material/EffectCullVisitor.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/scene/util/RenderConstants.hxx>
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
Compositor *
|
||||
Compositor::create(osg::View *view,
|
||||
osg::GraphicsContext *gc,
|
||||
osg::Viewport *viewport,
|
||||
const SGPropertyNode *property_list)
|
||||
{
|
||||
osg::ref_ptr<Compositor> compositor = new Compositor(view, gc, viewport);
|
||||
compositor->_name = property_list->getStringValue("name");
|
||||
|
||||
// Read all buffers first so passes can use them
|
||||
PropertyList p_buffers = property_list->getChildren("buffer");
|
||||
for (auto const &p_buffer : p_buffers) {
|
||||
const std::string &buffer_name = p_buffer->getStringValue("name");
|
||||
if (buffer_name.empty()) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Buffer requires "
|
||||
"a name to be available to passes. Skipping...");
|
||||
continue;
|
||||
}
|
||||
Buffer *buffer = buildBuffer(compositor.get(), p_buffer);
|
||||
if (buffer)
|
||||
compositor->addBuffer(buffer_name, buffer);
|
||||
}
|
||||
// Read passes
|
||||
PropertyList p_passes = property_list->getChildren("pass");
|
||||
for (auto const &p_pass : p_passes) {
|
||||
Pass *pass = buildPass(compositor.get(), p_pass);
|
||||
if (pass)
|
||||
compositor->addPass(pass);
|
||||
}
|
||||
|
||||
return compositor.release();
|
||||
}
|
||||
|
||||
Compositor *
|
||||
Compositor::create(osg::View *view,
|
||||
osg::GraphicsContext *gc,
|
||||
osg::Viewport *viewport,
|
||||
const std::string &name)
|
||||
{
|
||||
std::string filename(name);
|
||||
filename += ".xml";
|
||||
std::string abs_filename = SGModelLib::findDataFile(filename);
|
||||
if (abs_filename.empty()) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Could not find file '"
|
||||
<< filename << "'");
|
||||
return 0;
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr property_list = new SGPropertyNode;
|
||||
try {
|
||||
readProperties(abs_filename, property_list.ptr(), 0, true);
|
||||
} catch (sg_io_exception &e) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Failed to parse file '"
|
||||
<< abs_filename << "'. " << e.getFormattedMessage());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return create(view, gc, viewport, property_list);
|
||||
}
|
||||
|
||||
Compositor::Compositor(osg::View *view,
|
||||
osg::GraphicsContext *gc,
|
||||
osg::Viewport *viewport) :
|
||||
_view(view),
|
||||
_gc(gc),
|
||||
_viewport(viewport),
|
||||
_uniforms{
|
||||
new osg::Uniform("fg_ViewportSize", osg::Vec2f()),
|
||||
new osg::Uniform("fg_ViewMatrix", osg::Matrixf()),
|
||||
new osg::Uniform("fg_ViewMatrixInverse", osg::Matrixf()),
|
||||
new osg::Uniform("fg_ProjectionMatrix", osg::Matrixf()),
|
||||
new osg::Uniform("fg_ProjectionMatrixInverse", osg::Matrixf()),
|
||||
new osg::Uniform("fg_CameraPositionCart", osg::Vec3f()),
|
||||
new osg::Uniform("fg_CameraPositionGeod", osg::Vec3f())
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
Compositor::~Compositor()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Compositor::update(const osg::Matrix &view_matrix,
|
||||
const osg::Matrix &proj_matrix)
|
||||
{
|
||||
for (auto &pass : _passes) {
|
||||
if (pass->update_callback.valid())
|
||||
pass->update_callback->updatePass(*pass.get(), view_matrix, proj_matrix);
|
||||
}
|
||||
|
||||
// Update uniforms
|
||||
osg::Matrixd view_inverse = osg::Matrix::inverse(view_matrix);
|
||||
osg::Vec4d camera_pos = osg::Vec4(0.0, 0.0, 0.0, 1.0) * view_inverse;
|
||||
SGGeod camera_pos_geod = SGGeod::fromCart(
|
||||
SGVec3d(camera_pos.x(), camera_pos.y(), camera_pos.z()));
|
||||
|
||||
for (int i = 0; i < TOTAL_BUILTIN_UNIFORMS; ++i) {
|
||||
osg::ref_ptr<osg::Uniform> u = _uniforms[i];
|
||||
switch (i) {
|
||||
case VIEWPORT_SIZE:
|
||||
u->set(osg::Vec2f(_viewport->width(), _viewport->height()));
|
||||
break;
|
||||
case VIEW_MATRIX:
|
||||
u->set(view_matrix);
|
||||
break;
|
||||
case VIEW_MATRIX_INV:
|
||||
u->set(view_inverse);
|
||||
break;
|
||||
case PROJECTION_MATRIX:
|
||||
u->set(proj_matrix);
|
||||
break;
|
||||
case PROJECTION_MATRIX_INV:
|
||||
u->set(osg::Matrix::inverse(proj_matrix));
|
||||
break;
|
||||
case CAMERA_POSITION_CART:
|
||||
u->set(osg::Vec3f(camera_pos.x(), camera_pos.y(), camera_pos.z()));
|
||||
break;
|
||||
case CAMERA_POSITION_GEOD:
|
||||
u->set(osg::Vec3f(camera_pos_geod.getLongitudeRad(),
|
||||
camera_pos_geod.getLatitudeRad(),
|
||||
camera_pos_geod.getElevationM()));
|
||||
break;
|
||||
default:
|
||||
// Unknown uniform
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Compositor::resized()
|
||||
{
|
||||
// Cameras attached directly to the framebuffer were already resized by
|
||||
// osg::GraphicsContext::resizedImplementation(). However, RTT cameras were
|
||||
// ignored. Here we resize RTT cameras that need to match the physical
|
||||
// viewport size.
|
||||
for (const auto &pass : _passes) {
|
||||
osg::Camera *camera = pass->camera;
|
||||
if (!camera->isRenderToTextureCamera() ||
|
||||
pass->viewport_width_scale == 0.0f ||
|
||||
pass->viewport_height_scale == 0.0f)
|
||||
continue;
|
||||
|
||||
// Resize both the viewport and its texture attachments
|
||||
camera->resize(pass->viewport_width_scale * _viewport->width(),
|
||||
pass->viewport_height_scale * _viewport->height());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Compositor::setCameraCullMasks(osg::Node::NodeMask nm)
|
||||
{
|
||||
for (const auto &pass : _passes) {
|
||||
osg::Camera *camera = pass->camera;
|
||||
osg::Node::NodeMask pass_cm = nm;
|
||||
// Disable traversal of the scene graph if the pass isn't enabled
|
||||
if (!pass->isEnabled())
|
||||
pass_cm &= 0x0;
|
||||
else
|
||||
pass_cm &= pass->cull_mask;
|
||||
|
||||
camera->setCullMask(pass_cm);
|
||||
camera->setCullMaskLeft(pass_cm);
|
||||
camera->setCullMaskRight(pass_cm);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Compositor::computeIntersection(
|
||||
const osg::Vec2d& windowPos,
|
||||
osgUtil::LineSegmentIntersector::Intersections& intersections)
|
||||
{
|
||||
using osgUtil::Intersector;
|
||||
using osgUtil::LineSegmentIntersector;
|
||||
|
||||
osg::Camera *camera = getPass(0)->camera;
|
||||
const osg::Viewport* viewport = camera->getViewport();
|
||||
SGRect<double> viewportRect(viewport->x(), viewport->y(),
|
||||
viewport->x() + viewport->width() - 1.0,
|
||||
viewport->y() + viewport->height()- 1.0);
|
||||
|
||||
double epsilon = 0.5;
|
||||
if (!viewportRect.contains(windowPos.x(), windowPos.y(), epsilon))
|
||||
return false;
|
||||
|
||||
osg::Vec4d start(windowPos.x(), windowPos.y(), 0.0, 1.0);
|
||||
osg::Vec4d end(windowPos.x(), windowPos.y(), 1.0, 1.0);
|
||||
osg::Matrix windowMat = viewport->computeWindowMatrix();
|
||||
osg::Matrix startPtMat = osg::Matrix::inverse(camera->getProjectionMatrix()
|
||||
* windowMat);
|
||||
osg::Matrix endPtMat = startPtMat; // no far camera
|
||||
|
||||
start = start * startPtMat;
|
||||
start /= start.w();
|
||||
end = end * endPtMat;
|
||||
end /= end.w();
|
||||
osg::ref_ptr<LineSegmentIntersector> picker
|
||||
= new LineSegmentIntersector(Intersector::VIEW,
|
||||
osg::Vec3d(start.x(), start.y(), start.z()),
|
||||
osg::Vec3d(end.x(), end.y(), end.z()));
|
||||
osgUtil::IntersectionVisitor iv(picker.get());
|
||||
iv.setTraversalMask( simgear::PICK_BIT );
|
||||
|
||||
const_cast<osg::Camera*>(camera)->accept(iv);
|
||||
if (picker->containsIntersections()) {
|
||||
intersections = picker->getIntersections();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
Compositor::addBuffer(const std::string &name, Buffer *buffer)
|
||||
{
|
||||
_buffers[name] = buffer;
|
||||
}
|
||||
|
||||
void
|
||||
Compositor::addPass(Pass *pass)
|
||||
{
|
||||
if (!_view) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "Compositor::addPass: Couldn't add camera "
|
||||
"as a slave to the view. View doesn't exist!");
|
||||
return;
|
||||
}
|
||||
|
||||
_view->addSlave(pass->camera, pass->useMastersSceneData);
|
||||
|
||||
// Install the Effect cull visitor
|
||||
osgViewer::Renderer* renderer
|
||||
= static_cast<osgViewer::Renderer*>(pass->camera->getRenderer());
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
osgUtil::SceneView* sceneView = renderer->getSceneView(i);
|
||||
|
||||
osg::ref_ptr<osgUtil::CullVisitor::Identifier> identifier;
|
||||
identifier = sceneView->getCullVisitor()->getIdentifier();
|
||||
|
||||
sceneView->setCullVisitor(
|
||||
new EffectCullVisitor(false, pass->effect_override));
|
||||
sceneView->getCullVisitor()->setIdentifier(identifier.get());
|
||||
|
||||
identifier = sceneView->getCullVisitorLeft()->getIdentifier();
|
||||
sceneView->setCullVisitorLeft(sceneView->getCullVisitor()->clone());
|
||||
sceneView->getCullVisitorLeft()->setIdentifier(identifier.get());
|
||||
|
||||
identifier = sceneView->getCullVisitorRight()->getIdentifier();
|
||||
sceneView->setCullVisitorRight(sceneView->getCullVisitor()->clone());
|
||||
sceneView->getCullVisitorRight()->setIdentifier(identifier.get());
|
||||
}
|
||||
|
||||
_passes.push_back(pass);
|
||||
}
|
||||
|
||||
Buffer *
|
||||
Compositor::getBuffer(const std::string &name) const
|
||||
{
|
||||
auto it = _buffers.find(name);
|
||||
if (it == _buffers.end())
|
||||
return 0;
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
Pass *
|
||||
Compositor::getPass(const std::string &name) const
|
||||
{
|
||||
auto it = std::find_if(_passes.begin(), _passes.end(),
|
||||
[&name](const osg::ref_ptr<Pass> &p) {
|
||||
return p->name == name;
|
||||
});
|
||||
if (it == _passes.end())
|
||||
return 0;
|
||||
return (*it);
|
||||
}
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
140
simgear/scene/viewer/Compositor.hxx
Normal file
140
simgear/scene/viewer/Compositor.hxx
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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_COMPOSITOR_HXX
|
||||
#define SG_COMPOSITOR_HXX
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// For osgUtil::LineSegmentIntersector::Intersections, which is a typedef.
|
||||
#include <osgUtil/LineSegmentIntersector>
|
||||
|
||||
#include "CompositorBuffer.hxx"
|
||||
#include "CompositorPass.hxx"
|
||||
|
||||
class SGPropertyNode;
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
/**
|
||||
* A Compositor manages the rendering pipeline of a single physical camera,
|
||||
* usually via a property tree interface.
|
||||
*
|
||||
* The building blocks that define a Compositor are:
|
||||
* - Buffers. They represent a zone of GPU memory. This is implemented in the
|
||||
* form of an OpenGL texture, but any type of information can be stored
|
||||
* (which can be useful in compute shaders for example).
|
||||
* - Passes. They represent render operations. They can get buffers as input
|
||||
* and they can output to other buffers. They are also integrated with the
|
||||
* Effects framework, so the OpenGL internal state is configurable per pass.
|
||||
*/
|
||||
class Compositor : public osg::Referenced {
|
||||
public:
|
||||
enum BuiltinUniform {
|
||||
VIEWPORT_SIZE = 0,
|
||||
VIEW_MATRIX,
|
||||
VIEW_MATRIX_INV,
|
||||
PROJECTION_MATRIX,
|
||||
PROJECTION_MATRIX_INV,
|
||||
CAMERA_POSITION_CART,
|
||||
CAMERA_POSITION_GEOD,
|
||||
TOTAL_BUILTIN_UNIFORMS
|
||||
};
|
||||
|
||||
Compositor(osg::View *view,
|
||||
osg::GraphicsContext *gc,
|
||||
osg::Viewport *viewport);
|
||||
~Compositor();
|
||||
|
||||
/**
|
||||
* \brief Create a Compositor from a property tree.
|
||||
*
|
||||
* @param view The View where the passes will be added as slaves.
|
||||
* @param gc The context where the internal osg::Cameras will draw on.
|
||||
* @param viewport The viewport position and size inside the window.
|
||||
* @param property_list A valid property list that describes the Compositor.
|
||||
* @return A Compositor or a null pointer if there was an error.
|
||||
*/
|
||||
static Compositor *create(osg::View *view,
|
||||
osg::GraphicsContext *gc,
|
||||
osg::Viewport *viewport,
|
||||
const SGPropertyNode *property_list);
|
||||
/**
|
||||
* \overload
|
||||
* \brief Create a Compositor from a file.
|
||||
*
|
||||
* @param name Name of the compositor. The function will search for a file
|
||||
* named <name>.xml in $FG_ROOT.
|
||||
*/
|
||||
static Compositor *create(osg::View *view,
|
||||
osg::GraphicsContext *gc,
|
||||
osg::Viewport *viewport,
|
||||
const std::string &name);
|
||||
|
||||
void update(const osg::Matrix &view_matrix,
|
||||
const osg::Matrix &proj_matrix);
|
||||
|
||||
void resized();
|
||||
|
||||
void setCameraCullMasks(osg::Node::NodeMask nm);
|
||||
|
||||
bool computeIntersection(
|
||||
const osg::Vec2d& windowPos,
|
||||
osgUtil::LineSegmentIntersector::Intersections& intersections);
|
||||
|
||||
const osg::GraphicsContext *getGraphicsContext() const { return _gc; }
|
||||
|
||||
const osg::Viewport *getViewport() const { return _viewport; }
|
||||
|
||||
typedef std::array<
|
||||
osg::ref_ptr<osg::Uniform>,
|
||||
TOTAL_BUILTIN_UNIFORMS> BuiltinUniforms;
|
||||
const BuiltinUniforms &getUniforms() const { return _uniforms; }
|
||||
|
||||
void addBuffer(const std::string &name, Buffer *buffer);
|
||||
void addPass(Pass *pass);
|
||||
|
||||
void setName(const std::string &name) { _name = name; }
|
||||
const std::string &getName() const { return _name; }
|
||||
|
||||
typedef std::unordered_map<std::string, osg::ref_ptr<Buffer>> BufferMap;
|
||||
const BufferMap & getBufferMap() const { return _buffers; }
|
||||
Buffer * getBuffer(const std::string &name) const;
|
||||
|
||||
typedef std::vector<osg::ref_ptr<Pass>> PassList;
|
||||
const PassList & getPassList() const { return _passes; }
|
||||
unsigned int getNumPasses() const { return _passes.size(); }
|
||||
Pass * getPass(size_t index) const { return _passes[index]; }
|
||||
Pass * getPass(const std::string &name) const;
|
||||
|
||||
protected:
|
||||
friend class PassBuilder;
|
||||
|
||||
osg::View *_view;
|
||||
osg::GraphicsContext *_gc;
|
||||
osg::ref_ptr<osg::Viewport> _viewport;
|
||||
std::string _name;
|
||||
BufferMap _buffers;
|
||||
PassList _passes;
|
||||
BuiltinUniforms _uniforms;
|
||||
};
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_COMPOSITOR_HXX */
|
||||
213
simgear/scene/viewer/CompositorBuffer.cxx
Normal file
213
simgear/scene/viewer/CompositorBuffer.cxx
Normal file
@@ -0,0 +1,213 @@
|
||||
// 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 "CompositorBuffer.hxx"
|
||||
|
||||
#include <osg/Texture1D>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Texture2DArray>
|
||||
#include <osg/Texture2DMultisample>
|
||||
#include <osg/Texture3D>
|
||||
#include <osg/TextureRectangle>
|
||||
#include <osg/TextureCubeMap>
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/vectorPropTemplates.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
|
||||
#include "Compositor.hxx"
|
||||
#include "CompositorCommon.hxx"
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
struct BufferFormat {
|
||||
GLint internal_format;
|
||||
GLenum source_format;
|
||||
GLenum source_type;
|
||||
};
|
||||
|
||||
PropStringMap<BufferFormat> buffer_format_map {
|
||||
{"rgb8", {GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE}},
|
||||
{"rgba8", {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}},
|
||||
{"rgb16f", {GL_RGB16F, GL_RGBA, GL_FLOAT}},
|
||||
{"rgb32f", {GL_RGB32F, GL_RGBA, GL_FLOAT}},
|
||||
{"rgba16f", {GL_RGBA16F, GL_RGBA, GL_FLOAT}},
|
||||
{"rgba32f", {GL_RGBA32F, GL_RGBA, GL_FLOAT}},
|
||||
{"r32f", {GL_R32F, GL_RED, GL_FLOAT}},
|
||||
{"rg32f", {GL_RG32F, GL_RG, GL_FLOAT}},
|
||||
{"depth16", {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}},
|
||||
{"depth24", {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_FLOAT}},
|
||||
{"depth32", {GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_FLOAT}},
|
||||
{"depth32f", {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}},
|
||||
{"depth-stencil", {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT}}
|
||||
};
|
||||
|
||||
PropStringMap<osg::Texture::WrapMode> wrap_mode_map = {
|
||||
{"clamp", osg::Texture::CLAMP},
|
||||
{"clamp-to-edge", osg::Texture::CLAMP_TO_EDGE},
|
||||
{"clamp-to-border", osg::Texture::CLAMP_TO_BORDER},
|
||||
{"repeat", osg::Texture::REPEAT},
|
||||
{"mirror", osg::Texture::MIRROR}
|
||||
};
|
||||
|
||||
PropStringMap<osg::Texture::FilterMode> filter_mode_map = {
|
||||
{"linear", osg::Texture::LINEAR},
|
||||
{"linear-mipmap-linear", osg::Texture::LINEAR_MIPMAP_LINEAR},
|
||||
{"linear-mipmap-nearest", osg::Texture::LINEAR_MIPMAP_NEAREST},
|
||||
{"nearest", osg::Texture::NEAREST},
|
||||
{"nearest-mipmap-linear", osg::Texture::NEAREST_MIPMAP_LINEAR},
|
||||
{"nearest-mipmap-nearest", osg::Texture::NEAREST_MIPMAP_NEAREST}
|
||||
};
|
||||
|
||||
PropStringMap<osg::Texture::ShadowTextureMode> shadow_texture_mode_map = {
|
||||
{"luminance", osg::Texture::LUMINANCE},
|
||||
{"intensity", osg::Texture::INTENSITY},
|
||||
{"alpha", osg::Texture::ALPHA}
|
||||
};
|
||||
|
||||
PropStringMap<osg::Texture::ShadowCompareFunc> shadow_compare_func_map = {
|
||||
{"never", osg::Texture::NEVER},
|
||||
{"less", osg::Texture::LESS},
|
||||
{"equal", osg::Texture::EQUAL},
|
||||
{"lequal", osg::Texture::LEQUAL},
|
||||
{"greater", osg::Texture::GREATER},
|
||||
{"notequal", osg::Texture::NOTEQUAL},
|
||||
{"gequal", osg::Texture::GEQUAL},
|
||||
{"always", osg::Texture::ALWAYS}
|
||||
};
|
||||
|
||||
Buffer *
|
||||
buildBuffer(Compositor *compositor, const SGPropertyNode *node)
|
||||
{
|
||||
std::string type = node->getStringValue("type");
|
||||
if (type.empty()) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "buildBuffer: No type specified");
|
||||
return 0;
|
||||
}
|
||||
|
||||
osg::ref_ptr<Buffer> buffer = new Buffer;
|
||||
osg::Texture *texture;
|
||||
|
||||
int width;
|
||||
if (node->getStringValue("width") == std::string("screen")) {
|
||||
float w_scale = node->getFloatValue("screen-width-scale", 1.0f);
|
||||
buffer->width_scale = w_scale;
|
||||
width = w_scale * compositor->getViewport()->width();
|
||||
} else {
|
||||
width = node->getIntValue("width");
|
||||
}
|
||||
int height;
|
||||
if (node->getStringValue("height") == std::string("screen")) {
|
||||
float h_scale = node->getFloatValue("screen-height-scale", 1.0f);
|
||||
buffer->height_scale = h_scale;
|
||||
height = h_scale * compositor->getViewport()->height();
|
||||
} else {
|
||||
height = node->getIntValue("height");
|
||||
}
|
||||
int depth = node->getIntValue("depth");
|
||||
if (type == "1d") {
|
||||
osg::Texture1D *tex1D = new osg::Texture1D;
|
||||
tex1D->setTextureWidth(width);
|
||||
texture = tex1D;
|
||||
} else if (type == "2d") {
|
||||
osg::Texture2D *tex2D = new osg::Texture2D;
|
||||
tex2D->setTextureSize(width, height);
|
||||
texture = tex2D;
|
||||
} else if (type == "2d-array") {
|
||||
osg::Texture2DArray *tex2D_array = new osg::Texture2DArray;
|
||||
tex2D_array->setTextureSize(width, height, depth);
|
||||
texture = tex2D_array;
|
||||
} else if (type == "2d-multisample") {
|
||||
osg::Texture2DMultisample *tex2DMS = new osg::Texture2DMultisample;
|
||||
tex2DMS->setTextureSize(width, height);
|
||||
tex2DMS->setNumSamples(node->getIntValue("num-samples", 0));
|
||||
texture = tex2DMS;
|
||||
} else if (type == "3d") {
|
||||
osg::Texture3D *tex3D = new osg::Texture3D;
|
||||
tex3D->setTextureSize(width, height, depth);
|
||||
texture = tex3D;
|
||||
} else if (type == "rect") {
|
||||
osg::TextureRectangle *tex_rect = new osg::TextureRectangle;
|
||||
tex_rect->setTextureSize(width, height);
|
||||
texture = tex_rect;
|
||||
} else if (type == "cubemap") {
|
||||
osg::TextureCubeMap *tex_cubemap = new osg::TextureCubeMap;
|
||||
tex_cubemap->setTextureSize(width, height);
|
||||
texture = tex_cubemap;
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Unknown texture type '" << type << "'");
|
||||
return 0;
|
||||
}
|
||||
buffer->texture = texture;
|
||||
|
||||
bool resize_npot = node->getBoolValue("resize-npot", false);
|
||||
texture->setResizeNonPowerOfTwoHint(resize_npot);
|
||||
|
||||
BufferFormat format;
|
||||
if (findPropString(node, "format", format, buffer_format_map)) {
|
||||
texture->setInternalFormat(format.internal_format);
|
||||
texture->setSourceFormat(format.source_format);
|
||||
texture->setSourceType(format.source_type);
|
||||
} else {
|
||||
texture->setInternalFormat(GL_RGBA);
|
||||
SG_LOG(SG_INPUT, SG_WARN, "Unknown buffer format specified, using RGBA");
|
||||
}
|
||||
|
||||
osg::Texture::FilterMode filter_mode = osg::Texture::LINEAR;
|
||||
findPropString(node, "min-filter", filter_mode, filter_mode_map);
|
||||
texture->setFilter(osg::Texture::MIN_FILTER, filter_mode);
|
||||
findPropString(node, "mag-filter", filter_mode, filter_mode_map);
|
||||
texture->setFilter(osg::Texture::MAG_FILTER, filter_mode);
|
||||
|
||||
osg::Texture::WrapMode wrap_mode = osg::Texture::CLAMP_TO_BORDER;
|
||||
findPropString(node, "wrap-s", wrap_mode, wrap_mode_map);
|
||||
texture->setWrap(osg::Texture::WRAP_S, wrap_mode);
|
||||
findPropString(node, "wrap-t", wrap_mode, wrap_mode_map);
|
||||
texture->setWrap(osg::Texture::WRAP_T, wrap_mode);
|
||||
findPropString(node, "wrap-r", wrap_mode, wrap_mode_map);
|
||||
texture->setWrap(osg::Texture::WRAP_R, wrap_mode);
|
||||
|
||||
float anis = node->getFloatValue("anisotropy", 1.0f);
|
||||
texture->setMaxAnisotropy(anis);
|
||||
|
||||
osg::Vec4f border_color(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
const SGPropertyNode *p_border_color = node->getChild("border-color");
|
||||
if (p_border_color)
|
||||
border_color = toOsg(p_border_color->getValue<SGVec4d>());
|
||||
texture->setBorderColor(border_color);
|
||||
|
||||
bool shadow_comparison = node->getBoolValue("shadow-comparison", false);
|
||||
texture->setShadowComparison(shadow_comparison);
|
||||
if (shadow_comparison) {
|
||||
osg::Texture::ShadowTextureMode shadow_texture_mode =
|
||||
osg::Texture::LUMINANCE;
|
||||
findPropString(node, "shadow-texture-mode",
|
||||
shadow_texture_mode, shadow_texture_mode_map);
|
||||
texture->setShadowTextureMode(shadow_texture_mode);
|
||||
|
||||
osg::Texture::ShadowCompareFunc shadow_compare_func =
|
||||
osg::Texture::LEQUAL;
|
||||
findPropString(node, "shadow-compare-func",
|
||||
shadow_compare_func, shadow_compare_func_map);
|
||||
texture->setShadowCompareFunc(shadow_compare_func);
|
||||
}
|
||||
|
||||
return buffer.release();
|
||||
}
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
46
simgear/scene/viewer/CompositorBuffer.hxx
Normal file
46
simgear/scene/viewer/CompositorBuffer.hxx
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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_COMPOSITOR_BUFFER_HXX
|
||||
#define SG_COMPOSITOR_BUFFER_HXX
|
||||
|
||||
#include <osg/Texture>
|
||||
|
||||
class SGPropertyNode;
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
class Compositor;
|
||||
|
||||
struct Buffer : public osg::Referenced {
|
||||
Buffer() : width_scale(0.0f), height_scale(0.0f) {}
|
||||
|
||||
osg::ref_ptr<osg::Texture> texture;
|
||||
|
||||
/**
|
||||
* The amount to multiply the size of the default framebuffer.
|
||||
* A factor of 0.0 means that the buffer has a fixed size.
|
||||
*/
|
||||
float width_scale, height_scale;
|
||||
};
|
||||
|
||||
Buffer *buildBuffer(Compositor *compositor, const SGPropertyNode *node);
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_COMPOSITOR_BUFFER_HXX */
|
||||
62
simgear/scene/viewer/CompositorCommon.hxx
Normal file
62
simgear/scene/viewer/CompositorCommon.hxx
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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_COMPOSITOR_COMMON_HXX
|
||||
#define SG_COMPOSITOR_COMMON_HXX
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
/**
|
||||
* Lookup table that ties a string property value to a type that cannot be
|
||||
* represented in the property tree. Useful for OSG or OpenGL enums.
|
||||
*/
|
||||
template<class T>
|
||||
using PropStringMap = const std::unordered_map<std::string, T>;
|
||||
|
||||
template <class T>
|
||||
bool findPropString(const std::string &str,
|
||||
T &value,
|
||||
const PropStringMap<T> &map)
|
||||
{
|
||||
auto itr = map.find(str);
|
||||
if (itr == map.end())
|
||||
return false;
|
||||
|
||||
value = itr->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool findPropString(const SGPropertyNode *parent,
|
||||
const std::string &child_name,
|
||||
T &value,
|
||||
const PropStringMap<T> &map)
|
||||
{
|
||||
const SGPropertyNode *child = parent->getNode(child_name);
|
||||
if (child) {
|
||||
if (findPropString<T>(child->getStringValue(), value, map))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_COMPOSITOR_COMMON_HXX */
|
||||
741
simgear/scene/viewer/CompositorPass.cxx
Normal file
741
simgear/scene/viewer/CompositorPass.cxx
Normal file
@@ -0,0 +1,741 @@
|
||||
// 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 "CompositorPass.hxx"
|
||||
|
||||
#include <osg/Depth>
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/PolygonMode>
|
||||
#include <osg/io_utils>
|
||||
|
||||
#include <simgear/props/vectorPropTemplates.hxx>
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/tgdb/userdata.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/scene/util/SGUpdateVisitor.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "ClusteredForward.hxx"
|
||||
#include "Compositor.hxx"
|
||||
#include "CompositorCommon.hxx"
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
PropStringMap<osg::Camera::BufferComponent> buffer_component_map = {
|
||||
{"color", osg::Camera::COLOR_BUFFER},
|
||||
{"color0", osg::Camera::COLOR_BUFFER0},
|
||||
{"color1", osg::Camera::COLOR_BUFFER1},
|
||||
{"color2", osg::Camera::COLOR_BUFFER2},
|
||||
{"color3", osg::Camera::COLOR_BUFFER3},
|
||||
{"color4", osg::Camera::COLOR_BUFFER4},
|
||||
{"color5", osg::Camera::COLOR_BUFFER5},
|
||||
{"color6", osg::Camera::COLOR_BUFFER6},
|
||||
{"color7", osg::Camera::COLOR_BUFFER7},
|
||||
{"depth", osg::Camera::DEPTH_BUFFER},
|
||||
{"stencil", osg::Camera::STENCIL_BUFFER},
|
||||
{"packed-depth-stencil", osg::Camera::PACKED_DEPTH_STENCIL_BUFFER}
|
||||
};
|
||||
|
||||
Pass *
|
||||
PassBuilder::build(Compositor *compositor, const SGPropertyNode *root)
|
||||
{
|
||||
// The pass index matches its render order
|
||||
int render_order = root->getIndex();
|
||||
|
||||
osg::ref_ptr<Pass> pass = new Pass;
|
||||
pass->name = root->getStringValue("name");
|
||||
if (pass->name.empty()) {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Pass " << render_order
|
||||
<< " has no name. It won't be addressable by name!");
|
||||
}
|
||||
pass->type = root->getStringValue("type");
|
||||
|
||||
const SGPropertyNode *condition = root->getChild("condition");
|
||||
if (condition)
|
||||
pass->condition = sgReadCondition(getPropertyRoot(), condition);
|
||||
|
||||
std::string eff_override_file = root->getStringValue("effect-override");
|
||||
if (!eff_override_file.empty())
|
||||
pass->effect_override = makeEffect(eff_override_file, true, 0);
|
||||
|
||||
pass->cull_mask = std::stoul(root->getStringValue("cull-mask", "0xffffff"),
|
||||
nullptr, 0);
|
||||
|
||||
osg::Camera *camera = new Camera;
|
||||
pass->camera = camera;
|
||||
|
||||
camera->setName(pass->name);
|
||||
camera->setGraphicsContext(compositor->_gc);
|
||||
// Even though this camera will be added as a slave to the view, it will
|
||||
// always be updated manually in Compositor::update()
|
||||
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
|
||||
// Same with the projection matrix
|
||||
camera->setProjectionResizePolicy(osg::Camera::FIXED);
|
||||
camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
|
||||
|
||||
osg::Vec4f clear_color(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
const SGPropertyNode *p_clear_color = root->getChild("clear-color");
|
||||
if (p_clear_color)
|
||||
clear_color = toOsg(p_clear_color->getValue<SGVec4d>());
|
||||
camera->setClearColor(clear_color);
|
||||
osg::Vec4f clear_accum(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
const SGPropertyNode *p_clear_accum = root->getChild("clear-accum");
|
||||
if (p_clear_accum)
|
||||
clear_accum = toOsg(p_clear_accum->getValue<SGVec4d>());
|
||||
camera->setClearAccum(clear_accum);
|
||||
camera->setClearDepth(root->getFloatValue("clear-depth", 1.0f));
|
||||
camera->setClearStencil(root->getIntValue("clear-stencil", 0));
|
||||
|
||||
GLbitfield clear_mask = 0;
|
||||
if (root->getBoolValue("clear-color-bit", true))
|
||||
clear_mask |= GL_COLOR_BUFFER_BIT;
|
||||
if (root->getBoolValue("clear-accum-bit", false))
|
||||
clear_mask |= GL_ACCUM_BUFFER_BIT;
|
||||
if (root->getBoolValue("clear-depth-bit", true))
|
||||
clear_mask |= GL_DEPTH_BUFFER_BIT;
|
||||
if (root->getBoolValue("clear-stencil-bit", false))
|
||||
clear_mask |= GL_STENCIL_BUFFER_BIT;
|
||||
// Default clear mask is GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, as in OSG
|
||||
camera->setClearMask(clear_mask);
|
||||
|
||||
PropertyList p_bindings = root->getChildren("binding");
|
||||
for (auto const &p_binding : p_bindings) {
|
||||
try {
|
||||
std::string buffer_name = p_binding->getStringValue("buffer");
|
||||
if (buffer_name.empty())
|
||||
throw sg_exception("No buffer specified");
|
||||
|
||||
Buffer *buffer = compositor->getBuffer(buffer_name);
|
||||
if (!buffer)
|
||||
throw sg_exception(std::string("Unknown buffer '") +
|
||||
buffer_name + "'");
|
||||
|
||||
osg::Texture *texture = buffer->texture;
|
||||
|
||||
int unit = p_binding->getIntValue("unit", -1);
|
||||
if (unit < 0)
|
||||
throw sg_exception("No texture unit specified");
|
||||
|
||||
// Make the texture available to every child of the pass, overriding
|
||||
// existing units
|
||||
camera->getOrCreateStateSet()->setTextureAttributeAndModes(
|
||||
unit,
|
||||
texture,
|
||||
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
|
||||
} catch (sg_exception &e) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "PassBuilder::build: Skipping binding "
|
||||
<< p_binding->getIndex() << " in pass " << render_order
|
||||
<< ": " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
PropertyList p_attachments = root->getChildren("attachment");
|
||||
if (p_attachments.empty()) {
|
||||
// If there are no attachments, assume the pass is rendering
|
||||
// directly to the screen
|
||||
|
||||
camera->setRenderOrder(osg::Camera::NESTED_RENDER, render_order * 10);
|
||||
// OSG cameras use the framebuffer by default, but it is stated
|
||||
// explicitly anyway
|
||||
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER);
|
||||
|
||||
camera->setDrawBuffer(GL_BACK);
|
||||
camera->setReadBuffer(GL_BACK);
|
||||
|
||||
// Use the physical viewport. We can't let the user choose the viewport
|
||||
// size because some parts of the window might not be ours.
|
||||
camera->setViewport(compositor->_viewport);
|
||||
} else {
|
||||
// This is a RTT camera
|
||||
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER, render_order * 10);
|
||||
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
||||
|
||||
bool viewport_absolute = false;
|
||||
// The index of the attachment to be used as the size of the viewport.
|
||||
// The one with index 0 is used by default.
|
||||
int viewport_attachment = 0;
|
||||
const SGPropertyNode *p_viewport = root->getChild("viewport");
|
||||
if (p_viewport) {
|
||||
// The user has manually specified a viewport size
|
||||
viewport_absolute = p_viewport->getBoolValue("absolute", false);
|
||||
if (viewport_absolute) {
|
||||
camera->setViewport(p_viewport->getIntValue("x"),
|
||||
p_viewport->getIntValue("y"),
|
||||
p_viewport->getIntValue("width"),
|
||||
p_viewport->getIntValue("height"));
|
||||
}
|
||||
viewport_attachment = p_viewport->getIntValue("use-attachment", 0);
|
||||
if (!root->getChild("attachment", viewport_attachment)) {
|
||||
// Let OSG manage the viewport automatically
|
||||
camera->setViewport(new osg::Viewport);
|
||||
SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Can't use attachment "
|
||||
<< viewport_attachment << " to resize the viewport");
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const &p_attachment : p_attachments) {
|
||||
try {
|
||||
std::string buffer_name = p_attachment->getStringValue("buffer");
|
||||
if (buffer_name.empty())
|
||||
throw sg_exception("No buffer specified");
|
||||
|
||||
Buffer *buffer = compositor->getBuffer(buffer_name);
|
||||
if (!buffer)
|
||||
throw sg_exception(std::string("Unknown buffer '") +
|
||||
buffer_name + "'");
|
||||
|
||||
osg::Texture *texture = buffer->texture;
|
||||
|
||||
osg::Camera::BufferComponent component = osg::Camera::COLOR_BUFFER;
|
||||
findPropString(p_attachment, "component", component, buffer_component_map);
|
||||
|
||||
unsigned int level = p_attachment->getIntValue("level", 0);
|
||||
unsigned int face = p_attachment->getIntValue("face", 0);
|
||||
bool mipmap_generation =
|
||||
p_attachment->getBoolValue("mipmap-generation", false);
|
||||
unsigned int multisample_samples =
|
||||
p_attachment->getIntValue("multisample-samples", 0);
|
||||
unsigned int multisample_color_samples =
|
||||
p_attachment->getIntValue("multisample-color-samples", 0);
|
||||
|
||||
camera->attach(component,
|
||||
texture,
|
||||
level,
|
||||
face,
|
||||
mipmap_generation,
|
||||
multisample_samples,
|
||||
multisample_color_samples);
|
||||
|
||||
if (!viewport_absolute &&
|
||||
(p_attachment->getIndex() == viewport_attachment)) {
|
||||
if ((buffer->width_scale == 0.0f) &&
|
||||
(buffer->height_scale == 0.0f)) {
|
||||
// This is a fixed size pass. We allow the user to use
|
||||
// relative coordinates to shape the viewport.
|
||||
float x = p_viewport->getFloatValue("x", 0.0f);
|
||||
float y = p_viewport->getFloatValue("y", 0.0f);
|
||||
float width = p_viewport->getFloatValue("width", 1.0f);
|
||||
float height = p_viewport->getFloatValue("height", 1.0f);
|
||||
camera->setViewport(x * texture->getTextureWidth(),
|
||||
y * texture->getTextureHeight(),
|
||||
width * texture->getTextureWidth(),
|
||||
height * texture->getTextureHeight());
|
||||
} else {
|
||||
// This is a pass that should match the physical viewport
|
||||
// size. Store the scales so we can resize the pass later
|
||||
// if the physical viewport changes size.
|
||||
pass->viewport_width_scale = buffer->width_scale;
|
||||
pass->viewport_height_scale = buffer->height_scale;
|
||||
camera->setViewport(
|
||||
0,
|
||||
0,
|
||||
buffer->width_scale * compositor->_viewport->width(),
|
||||
buffer->height_scale * compositor->_viewport->height());
|
||||
}
|
||||
}
|
||||
} catch (sg_exception &e) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "PassBuilder::build: Skipping attachment "
|
||||
<< p_attachment->getIndex() << " in pass " << render_order
|
||||
<< ": " << e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pass.release();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
struct QuadPassBuilder : public PassBuilder {
|
||||
public:
|
||||
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) {
|
||||
osg::ref_ptr<Pass> pass = PassBuilder::build(compositor, root);
|
||||
pass->useMastersSceneData = false;
|
||||
|
||||
osg::Camera *camera = pass->camera;
|
||||
camera->setAllowEventFocus(false);
|
||||
camera->setViewMatrix(osg::Matrix::identity());
|
||||
camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1));
|
||||
|
||||
float left = 0.0f, bottom = 0.0f, width = 1.0f, height = 1.0f, scale = 1.0f;
|
||||
const SGPropertyNode *p_geometry = root->getNode("geometry");
|
||||
if (p_geometry) {
|
||||
left = p_geometry->getFloatValue("left", left);
|
||||
bottom = p_geometry->getFloatValue("bottom", bottom);
|
||||
width = p_geometry->getFloatValue("width", width);
|
||||
height = p_geometry->getFloatValue("height", height);
|
||||
scale = p_geometry->getFloatValue("scale", scale);
|
||||
}
|
||||
|
||||
const std::string eff_file = root->getStringValue("effect");
|
||||
|
||||
osg::ref_ptr<osg::Geode> quad = createFullscreenQuad(
|
||||
left, bottom, width, height, scale, eff_file);
|
||||
camera->addChild(quad);
|
||||
|
||||
osg::StateSet *ss = camera->getOrCreateStateSet();
|
||||
for (const auto &uniform : compositor->getUniforms())
|
||||
ss->addUniform(uniform);
|
||||
|
||||
return pass.release();
|
||||
}
|
||||
protected:
|
||||
osg::Geode *createFullscreenQuad(float left,
|
||||
float bottom,
|
||||
float width,
|
||||
float height,
|
||||
float scale,
|
||||
const std::string &eff_file) {
|
||||
osg::Geometry *geom;
|
||||
|
||||
// When the quad is fullscreen, it can be optimized by using a
|
||||
// a fullscreen triangle instead of a quad to avoid discarding pixels
|
||||
// in the diagonal. If the desired geometry does not occupy the entire
|
||||
// viewport, this optimization does not occur and a normal quad is drawn
|
||||
// instead.
|
||||
if (left != 0.0f || bottom != 0.0f || width != 1.0f || height != 1.0f
|
||||
|| scale != 1.0f) {
|
||||
geom = osg::createTexturedQuadGeometry(
|
||||
osg::Vec3(left, bottom, 0.0f),
|
||||
osg::Vec3(width, 0.0f, 0.0f),
|
||||
osg::Vec3(0.0f, height, 0.0f),
|
||||
0.0f, 0.0f, scale, scale);
|
||||
} else {
|
||||
geom = new osg::Geometry;
|
||||
|
||||
osg::Vec3Array *coords = new osg::Vec3Array(3);
|
||||
(*coords)[0].set(0.0f, 2.0f, 0.0f);
|
||||
(*coords)[1].set(0.0f, 0.0f, 0.0f);
|
||||
(*coords)[2].set(2.0f, 0.0f, 0.0f);
|
||||
geom->setVertexArray(coords);
|
||||
|
||||
osg::Vec2Array *tcoords = new osg::Vec2Array(3);
|
||||
(*tcoords)[0].set(0.0f, 2.0f);
|
||||
(*tcoords)[1].set(0.0f, 0.0f);
|
||||
(*tcoords)[2].set(2.0f, 0.0f);
|
||||
geom->setTexCoordArray(0, tcoords);
|
||||
|
||||
osg::Vec4Array *colours = new osg::Vec4Array(1);
|
||||
(*colours)[0].set(1.0f, 1.0f, 1.0, 1.0f);
|
||||
geom->setColorArray(colours, osg::Array::BIND_OVERALL);
|
||||
|
||||
osg::Vec3Array *normals = new osg::Vec3Array(1);
|
||||
(*normals)[0].set(0.0f, 0.0f, 1.0f);
|
||||
geom->setNormalArray(normals, osg::Array::BIND_OVERALL);
|
||||
|
||||
geom->addPrimitiveSet(new osg::DrawArrays(
|
||||
osg::PrimitiveSet::TRIANGLES, 0, 3));
|
||||
}
|
||||
|
||||
osg::ref_ptr<EffectGeode> quad = new EffectGeode;
|
||||
if (!eff_file.empty()) {
|
||||
Effect *eff = makeEffect(eff_file, true, 0);
|
||||
if (eff)
|
||||
quad->setEffect(eff);
|
||||
}
|
||||
quad->addDrawable(geom);
|
||||
quad->setCullingActive(false);
|
||||
|
||||
osg::ref_ptr<osg::StateSet> quad_state = quad->getOrCreateStateSet();
|
||||
int values = osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED;
|
||||
quad_state->setAttribute(new osg::PolygonMode(
|
||||
osg::PolygonMode::FRONT_AND_BACK,
|
||||
osg::PolygonMode::FILL),
|
||||
values);
|
||||
quad_state->setMode(GL_LIGHTING, values);
|
||||
quad_state->setMode(GL_DEPTH_TEST, values);
|
||||
|
||||
return quad.release();
|
||||
}
|
||||
};
|
||||
RegisterPassBuilder<QuadPassBuilder> registerQuadPass("quad");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if 0
|
||||
class ShadowMapUpdateCallback; // Forward declaration
|
||||
|
||||
class ShadowMapCullCallback : public osg::NodeCallback {
|
||||
public:
|
||||
ShadowMapCullCallback(int light_num, float near_m, float far_m) :
|
||||
_light_num(light_num),
|
||||
_near_m(near_m),
|
||||
_far_m(far_m) {}
|
||||
|
||||
virtual void operator()(osg::Node *node, osg::NodeVisitor *nv) {
|
||||
osg::Camera *light_camera = static_cast<osg::Camera *>(node);
|
||||
osgUtil::CullVisitor *cv = dynamic_cast<osgUtil::CullVisitor *>(nv);
|
||||
|
||||
osg::Vec4 light_pos = _light->getPosition();
|
||||
if (light_pos.w() != 0.0) {
|
||||
// We only support directional light sources for now
|
||||
traverse(node, nv);
|
||||
return;
|
||||
}
|
||||
|
||||
osg::Vec3 light_dir =
|
||||
osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z());
|
||||
|
||||
// Find the light
|
||||
// Mostly taken from osgShadow
|
||||
osgUtil::RenderStage *rs = cv->getRenderStage();
|
||||
osgUtil::PositionalStateContainer::AttrMatrixList &aml =
|
||||
rs->getPositionalStateContainer()->getAttrMatrixList();
|
||||
for (auto &itr : aml) {
|
||||
const osg::Light *light = dynamic_cast<const osg::Light *>(itr.first.get());
|
||||
if (light) {
|
||||
if (light->getLightNum() != _light_num)
|
||||
continue;
|
||||
|
||||
osg::Vec4 light_pos = light->getPosition();
|
||||
if (light_pos.w() == 0.0) {
|
||||
// We only want directional lights
|
||||
light_dir = osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z());
|
||||
}
|
||||
}
|
||||
}
|
||||
traverse(node, nv);
|
||||
}
|
||||
protected:
|
||||
friend ShadowMapUpdateCallback;
|
||||
int _light_num;
|
||||
float _near_m;
|
||||
float _far_m;
|
||||
osg::Matrix _camera_view;
|
||||
osg::Matrix _camera_proj;
|
||||
osg::observer_ptr<osg::Light> _light;
|
||||
osg::ref_ptr<osg::Uniform> _light_matrix_uniform;
|
||||
};
|
||||
#endif
|
||||
|
||||
class LightFinder : public osg::NodeVisitor {
|
||||
public:
|
||||
LightFinder(const std::string &name) :
|
||||
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
|
||||
_name(name) {}
|
||||
virtual void apply(osg::Node &node) {
|
||||
// Only traverse the scene graph if we haven't found a light yet (or if
|
||||
// the one we found earlier is no longer valid).
|
||||
if (getLight().valid())
|
||||
return;
|
||||
|
||||
if (node.getName() == _name) {
|
||||
osg::LightSource *light_source =
|
||||
dynamic_cast<osg::LightSource *>(&node);
|
||||
if (light_source)
|
||||
_light = light_source->getLight();
|
||||
}
|
||||
|
||||
traverse(node);
|
||||
}
|
||||
osg::ref_ptr<osg::Light> getLight() const {
|
||||
osg::ref_ptr<osg::Light> light_ref;
|
||||
_light.lock(light_ref);
|
||||
return light_ref;
|
||||
}
|
||||
protected:
|
||||
std::string _name;
|
||||
osg::observer_ptr<osg::Light> _light;
|
||||
};
|
||||
|
||||
struct ShadowMapUpdateCallback : public Pass::PassUpdateCallback {
|
||||
public:
|
||||
ShadowMapUpdateCallback(const std::string &light_name,
|
||||
float near_m, float far_m,
|
||||
const std::string &suffix,
|
||||
int sm_width, int sm_height) :
|
||||
_light_finder(new LightFinder(light_name)),
|
||||
_near_m(near_m),
|
||||
_far_m(far_m) {
|
||||
_light_matrix_uniform = new osg::Uniform(
|
||||
osg::Uniform::FLOAT_MAT4, std::string("fg_LightMatrix_") + suffix);
|
||||
_half_sm_size = osg::Vec2d((double)sm_width, (double)sm_height) / 2.0;
|
||||
}
|
||||
virtual void updatePass(Pass &pass,
|
||||
const osg::Matrix &view_matrix,
|
||||
const osg::Matrix &proj_matrix) {
|
||||
osg::Camera *camera = pass.camera;
|
||||
// Look for the light
|
||||
camera->accept(*_light_finder);
|
||||
osg::ref_ptr<osg::Light> light = _light_finder->getLight();
|
||||
if (!light) {
|
||||
// We could not find any light
|
||||
return;
|
||||
}
|
||||
osg::Vec4 light_pos = light->getPosition();
|
||||
if (light_pos.w() != 0.0) {
|
||||
// We only support directional light sources for now
|
||||
return;
|
||||
}
|
||||
osg::Vec3 light_dir =
|
||||
osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z());
|
||||
|
||||
// The light direction we've just queried is from the previous frame.
|
||||
// This is because the position of the osg::LightSource gets updated
|
||||
// during the update traversal, and this function happens before that
|
||||
// in the SubsystemMgr update.
|
||||
// This is not a problem though (for now).
|
||||
|
||||
osg::Matrix view_inverse = osg::Matrix::inverse(view_matrix);
|
||||
|
||||
// Calculate the light's point of view transformation matrices.
|
||||
// Taken from Project Rembrandt.
|
||||
double left, right, bottom, top, zNear, zFar;
|
||||
proj_matrix.getFrustum(left, right, bottom, top, zNear, zFar);
|
||||
|
||||
osg::BoundingSphere bs;
|
||||
bs.expandBy(osg::Vec3(left, bottom, -zNear) * (_near_m / zNear));
|
||||
bs.expandBy(osg::Vec3(right, top, -zNear) * (_far_m / zNear));
|
||||
bs.expandBy(osg::Vec3(left, bottom, -zNear) * (_far_m / zNear));
|
||||
bs.expandBy(osg::Vec3(right, top, -zNear) * (_near_m / zNear));
|
||||
|
||||
osg::Vec4 aim4 = osg::Vec4(bs.center(), 1.0) * view_inverse;
|
||||
osg::Vec3 aim(aim4.x(), aim4.y(), aim4.z());
|
||||
osg::Vec3 up(0.0f, 1.0f, 0.0f);
|
||||
|
||||
osg::Matrixd &light_view_matrix = camera->getViewMatrix();
|
||||
light_view_matrix.makeLookAt(
|
||||
aim + (light_dir * bs.radius() * 2.0f),
|
||||
aim,
|
||||
aim);
|
||||
|
||||
osg::Matrixd &light_proj_matrix = camera->getProjectionMatrix();
|
||||
light_proj_matrix.makeOrtho(
|
||||
-bs.radius(), bs.radius(),
|
||||
-bs.radius(), bs.radius(),
|
||||
-bs.radius() * 6.0f, bs.radius() * 6.0f);
|
||||
|
||||
// Do texel snapping to prevent flickering or shimmering.
|
||||
// We are using double precision vectors and matrices because in FG
|
||||
// world coordinates are relative to the center of the Earth, which can
|
||||
// (and will) cause precision issues due to their magnitude.
|
||||
osg::Vec4d shadow_origin4 = osg::Vec4d(0.0, 0.0, 0.0, 1.0) *
|
||||
light_view_matrix * light_proj_matrix;
|
||||
osg::Vec2d shadow_origin(shadow_origin4.x(), shadow_origin4.y());
|
||||
shadow_origin = osg::Vec2d(shadow_origin.x() * _half_sm_size.x(),
|
||||
shadow_origin.y() * _half_sm_size.y());
|
||||
osg::Vec2d rounded_origin(std::round(shadow_origin.x()),
|
||||
std::round(shadow_origin.y()));
|
||||
osg::Vec2d rounding = rounded_origin - shadow_origin;
|
||||
rounding = osg::Vec2d(rounding.x() / _half_sm_size.x(),
|
||||
rounding.y() / _half_sm_size.y());
|
||||
|
||||
osg::Matrixd round_matrix = osg::Matrixd::translate(
|
||||
rounding.x(), rounding.y(), 0.0);
|
||||
light_proj_matrix *= round_matrix;
|
||||
|
||||
osg::Matrixf light_matrix =
|
||||
// Include the real camera inverse view matrix because if the shader
|
||||
// used world coordinates, there would be precision issues.
|
||||
view_inverse *
|
||||
camera->getViewMatrix() *
|
||||
camera->getProjectionMatrix() *
|
||||
// Bias matrices
|
||||
osg::Matrix::translate(1.0, 1.0, 1.0) *
|
||||
osg::Matrix::scale(0.5, 0.5, 0.5);
|
||||
_light_matrix_uniform->set(light_matrix);
|
||||
}
|
||||
|
||||
osg::Uniform *getLightMatrixUniform() const {
|
||||
return _light_matrix_uniform.get();
|
||||
}
|
||||
protected:
|
||||
osg::ref_ptr<LightFinder> _light_finder;
|
||||
float _near_m;
|
||||
float _far_m;
|
||||
osg::ref_ptr<osg::Uniform> _light_matrix_uniform;
|
||||
osg::Vec2d _half_sm_size;
|
||||
};
|
||||
|
||||
struct ShadowMapPassBuilder : public PassBuilder {
|
||||
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) {
|
||||
osg::ref_ptr<Pass> pass = PassBuilder::build(compositor, root);
|
||||
|
||||
osg::Camera *camera = pass->camera;
|
||||
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
|
||||
|
||||
std::string light_name = root->getStringValue("light-name");
|
||||
float near_m = root->getFloatValue("near-m");
|
||||
float far_m = root->getFloatValue("far-m");
|
||||
int sm_width = camera->getViewport()->width();
|
||||
int sm_height = camera->getViewport()->height();
|
||||
pass->update_callback = new ShadowMapUpdateCallback(
|
||||
light_name,
|
||||
near_m, far_m,
|
||||
pass->name,
|
||||
sm_width, sm_height);
|
||||
|
||||
return pass.release();
|
||||
}
|
||||
};
|
||||
RegisterPassBuilder<ShadowMapPassBuilder> registerShadowMapPass("shadow-map");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class SceneUpdateCallback : public Pass::PassUpdateCallback {
|
||||
public:
|
||||
SceneUpdateCallback(int cubemap_face, float zNear, float zFar) :
|
||||
_cubemap_face(cubemap_face),
|
||||
_zNear(zNear),
|
||||
_zFar(zFar) {}
|
||||
|
||||
virtual void updatePass(Pass &pass,
|
||||
const osg::Matrix &view_matrix,
|
||||
const osg::Matrix &proj_matrix) {
|
||||
osg::Camera *camera = pass.camera;
|
||||
if (_cubemap_face < 0) {
|
||||
camera->setViewMatrix(view_matrix);
|
||||
camera->setProjectionMatrix(proj_matrix);
|
||||
} else {
|
||||
osg::Vec3 camera_pos = osg::Vec3(0.0, 0.0, 0.0) *
|
||||
osg::Matrix::inverse(view_matrix);
|
||||
|
||||
typedef std::pair<osg::Vec3, osg::Vec3> CubemapFace;
|
||||
const CubemapFace id[] = {
|
||||
CubemapFace(osg::Vec3( 1, 0, 0), osg::Vec3( 0, -1, 0)), // +X
|
||||
CubemapFace(osg::Vec3(-1, 0, 0), osg::Vec3( 0, -1, 0)), // -X
|
||||
CubemapFace(osg::Vec3( 0, 1, 0), osg::Vec3( 0, 0, 1)), // +Y
|
||||
CubemapFace(osg::Vec3( 0, -1, 0), osg::Vec3( 0, 0, -1)), // -Y
|
||||
CubemapFace(osg::Vec3( 0, 0, 1), osg::Vec3( 0, -1, 0)), // +Z
|
||||
CubemapFace(osg::Vec3( 0, 0, -1), osg::Vec3( 0, -1, 0)) // -Z
|
||||
};
|
||||
|
||||
osg::Matrix cubemap_view_matrix;
|
||||
cubemap_view_matrix.makeLookAt(camera_pos,
|
||||
camera_pos + id[_cubemap_face].first,
|
||||
camera_pos + id[_cubemap_face].second);
|
||||
camera->setViewMatrix(cubemap_view_matrix);
|
||||
camera->setProjectionMatrixAsFrustum(-1.0, 1.0, -1.0, 1.0,
|
||||
1.0, 10000.0);
|
||||
}
|
||||
|
||||
if (_zNear != 0.0f && _zFar != 0.0f) {
|
||||
osg::Matrix new_proj;
|
||||
makeNewProjMat(camera->getProjectionMatrix(),
|
||||
_zNear, _zFar, new_proj);
|
||||
camera->setProjectionMatrix(new_proj);
|
||||
}
|
||||
}
|
||||
protected:
|
||||
// Given a projection matrix, return a new one with the same frustum
|
||||
// sides and new near / far values.
|
||||
void makeNewProjMat(Matrixd& oldProj, double znear,
|
||||
double zfar, Matrixd& projection) {
|
||||
projection = oldProj;
|
||||
// Slightly inflate the near & far planes to avoid objects at the
|
||||
// extremes being clipped out.
|
||||
znear *= 0.999;
|
||||
zfar *= 1.001;
|
||||
|
||||
// Clamp the projection matrix z values to the range (near, far)
|
||||
double epsilon = 1.0e-6;
|
||||
if (fabs(projection(0,3)) < epsilon &&
|
||||
fabs(projection(1,3)) < epsilon &&
|
||||
fabs(projection(2,3)) < epsilon) {
|
||||
// Projection is Orthographic
|
||||
epsilon = -1.0/(zfar - znear); // Used as a temp variable
|
||||
projection(2,2) = 2.0*epsilon;
|
||||
projection(3,2) = (zfar + znear)*epsilon;
|
||||
} else {
|
||||
// Projection is Perspective
|
||||
double trans_near = (-znear*projection(2,2) + projection(3,2)) /
|
||||
(-znear*projection(2,3) + projection(3,3));
|
||||
double trans_far = (-zfar*projection(2,2) + projection(3,2)) /
|
||||
(-zfar*projection(2,3) + projection(3,3));
|
||||
double ratio = fabs(2.0/(trans_near - trans_far));
|
||||
double center = -0.5*(trans_near + trans_far);
|
||||
|
||||
projection.postMult(osg::Matrixd(1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, ratio, 0.0,
|
||||
0.0, 0.0, center*ratio, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
int _cubemap_face;
|
||||
float _zNear;
|
||||
float _zFar;
|
||||
};
|
||||
|
||||
struct ScenePassBuilder : public PassBuilder {
|
||||
public:
|
||||
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) {
|
||||
osg::ref_ptr<Pass> pass = PassBuilder::build(compositor, root);
|
||||
|
||||
osg::Camera *camera = pass->camera;
|
||||
camera->setAllowEventFocus(true);
|
||||
|
||||
const SGPropertyNode *clustered = root->getChild("clustered-forward");
|
||||
if (clustered) {
|
||||
camera->setInitialDrawCallback(new ClusteredForwardDrawCallback);
|
||||
}
|
||||
|
||||
int cubemap_face = root->getIntValue("cubemap-face", -1);
|
||||
float zNear = root->getFloatValue("z-near", 0.0f);
|
||||
float zFar = root->getFloatValue("z-far", 0.0f);
|
||||
pass->update_callback = new SceneUpdateCallback(cubemap_face, zNear, zFar);
|
||||
|
||||
std::string shadow_pass_name = root->getStringValue("use-shadow-pass");
|
||||
if (!shadow_pass_name.empty()) {
|
||||
Pass *shadow_pass = compositor->getPass(shadow_pass_name);
|
||||
if (shadow_pass) {
|
||||
ShadowMapUpdateCallback *updatecb =
|
||||
dynamic_cast<ShadowMapUpdateCallback *>(
|
||||
shadow_pass->update_callback.get());
|
||||
if (updatecb) {
|
||||
camera->getOrCreateStateSet()->addUniform(
|
||||
updatecb->getLightMatrixUniform());
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Pass '"
|
||||
<< shadow_pass_name << "is not a shadow pass");
|
||||
}
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Could not "
|
||||
"find shadow pass named '" << shadow_pass_name << "'");
|
||||
}
|
||||
}
|
||||
|
||||
return pass.release();
|
||||
}
|
||||
};
|
||||
|
||||
RegisterPassBuilder<ScenePassBuilder> registerScenePass("scene");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Pass *
|
||||
buildPass(Compositor *compositor, const SGPropertyNode *root)
|
||||
{
|
||||
std::string type = root->getStringValue("type");
|
||||
if (type.empty()) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "buildPass: Unspecified pass type");
|
||||
return 0;
|
||||
}
|
||||
PassBuilder *builder = PassBuilder::find(type);
|
||||
if (!builder) {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "buildPass: Unknown pass type '"
|
||||
<< type << "'");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return builder->build(compositor, root);
|
||||
}
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
134
simgear/scene/viewer/CompositorPass.hxx
Normal file
134
simgear/scene/viewer/CompositorPass.hxx
Normal file
@@ -0,0 +1,134 @@
|
||||
// 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_COMPOSITOR_PASS_HXX
|
||||
#define SG_COMPOSITOR_PASS_HXX
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <osg/Camera>
|
||||
#include <osg/View>
|
||||
|
||||
#include <simgear/scene/material/Effect.hxx>
|
||||
#include <simgear/structure/Singleton.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
namespace simgear {
|
||||
namespace compositor {
|
||||
|
||||
class Compositor;
|
||||
|
||||
/**
|
||||
* A Pass encapsulates a single render operation. In an OSG context, this is
|
||||
* best represented as a Camera attached to the Viewer as a slave camera.
|
||||
*
|
||||
* Passes can render directly to the framebuffer or to a texture via FBOs. Also,
|
||||
* the OpenGL state can be modified via the Effects framework and by exposing RTT
|
||||
* textures from previous passes.
|
||||
*
|
||||
* Every pass can be enabled and disabled via a property tree conditional
|
||||
* expression. This allows dynamic rendering pipelines where features can be
|
||||
* enabled or disabled in a coherent way by the user.
|
||||
*/
|
||||
struct Pass : public osg::Referenced {
|
||||
Pass() :
|
||||
useMastersSceneData(true),
|
||||
cull_mask(0xffffff),
|
||||
viewport_width_scale(0.0f),
|
||||
viewport_height_scale(0.0f) {}
|
||||
|
||||
bool isEnabled() const { return (condition == 0) || condition->test(); }
|
||||
|
||||
std::string name;
|
||||
std::string type;
|
||||
osg::ref_ptr<osg::Camera> camera;
|
||||
SGSharedPtr<SGCondition> condition;
|
||||
/** If null, there is no effect override for this pass. */
|
||||
osg::ref_ptr<Effect> effect_override;
|
||||
bool useMastersSceneData;
|
||||
osg::Node::NodeMask cull_mask;
|
||||
float viewport_width_scale;
|
||||
float viewport_height_scale;
|
||||
|
||||
struct PassUpdateCallback : public virtual osg::Referenced {
|
||||
public:
|
||||
virtual void updatePass(Pass &pass,
|
||||
const osg::Matrix &view_matrix,
|
||||
const osg::Matrix &proj_matrix) = 0;
|
||||
};
|
||||
|
||||
osg::ref_ptr<PassUpdateCallback> update_callback;
|
||||
};
|
||||
|
||||
class PassBuilder : public SGReferenced {
|
||||
public:
|
||||
virtual ~PassBuilder() {}
|
||||
|
||||
/**
|
||||
* \brief Build a pass.
|
||||
*
|
||||
* By default, this function implements commonly used features such as
|
||||
* input/output buffers, conditional support etc., but can be safely ignored
|
||||
* and overrided for more special passes.
|
||||
*
|
||||
* @param compositor The Compositor instance that owns the pass.
|
||||
* @param The root node of the pass property tree.
|
||||
* @return A Pass or a null pointer if an error occurred.
|
||||
*/
|
||||
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root);
|
||||
|
||||
static PassBuilder *find(const std::string &type) {
|
||||
auto itr = PassBuilderMapSingleton::instance()->_map.find(type);
|
||||
if (itr == PassBuilderMapSingleton::instance()->_map.end())
|
||||
return 0;
|
||||
return itr->second.ptr();
|
||||
}
|
||||
protected:
|
||||
typedef std::unordered_map<std::string, SGSharedPtr<PassBuilder>> PassBuilderMap;
|
||||
struct PassBuilderMapSingleton : public Singleton<PassBuilderMapSingleton> {
|
||||
PassBuilderMap _map;
|
||||
};
|
||||
template <typename T>
|
||||
friend struct RegisterPassBuilder;
|
||||
};
|
||||
|
||||
/**
|
||||
* An instance of this type registers a new pass type T with a name.
|
||||
* A global instance of this class must be created in CompositorPass.cxx to
|
||||
* register a new pass type.
|
||||
*/
|
||||
template <typename T>
|
||||
struct RegisterPassBuilder {
|
||||
RegisterPassBuilder(const std::string &name) {
|
||||
PassBuilder::PassBuilderMapSingleton::instance()->
|
||||
_map.insert(std::make_pair(name, new T));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Create a pass from a property tree definition.
|
||||
*
|
||||
* @param comp The Compositor instance that owns the pass.
|
||||
* @param node The root node of the pass property tree.
|
||||
* @return A Pass or a null pointer if an error occurred.
|
||||
*/
|
||||
Pass *buildPass(Compositor *compositor, const SGPropertyNode *root);
|
||||
|
||||
} // namespace compositor
|
||||
} // namespace simgear
|
||||
|
||||
#endif /* SG_COMPOSITOR_PASS_HXX */
|
||||
Reference in New Issue
Block a user