Geometry shader stereo functional
parent
b3f2373875
commit
9c171869cb
@ -0,0 +1,463 @@
|
||||
#include "stereo.hpp"
|
||||
|
||||
#include <osg/io_utils>
|
||||
#include <osg/ViewportIndexed>
|
||||
|
||||
#include <osgUtil/CullVisitor>
|
||||
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/sceneutil/visitor.hpp>
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
Pose Pose::operator+(const Pose& rhs)
|
||||
{
|
||||
Pose pose = *this;
|
||||
pose.position += this->orientation * rhs.position;
|
||||
pose.orientation = rhs.orientation * this->orientation;
|
||||
return pose;
|
||||
}
|
||||
|
||||
const Pose& Pose::operator+=(const Pose& rhs)
|
||||
{
|
||||
*this = *this + rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Pose Pose::operator*(float scalar)
|
||||
{
|
||||
Pose pose = *this;
|
||||
pose.position *= scalar;
|
||||
return pose;
|
||||
}
|
||||
|
||||
const Pose& Pose::operator*=(float scalar)
|
||||
{
|
||||
*this = *this * scalar;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Pose Pose::operator/(float scalar)
|
||||
{
|
||||
Pose pose = *this;
|
||||
pose.position /= scalar;
|
||||
return pose;
|
||||
}
|
||||
const Pose& Pose::operator/=(float scalar)
|
||||
{
|
||||
*this = *this / scalar;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Pose::operator==(const Pose& rhs) const
|
||||
{
|
||||
return position == rhs.position && orientation == rhs.orientation;
|
||||
}
|
||||
|
||||
osg::Matrix Pose::viewMatrix(bool useGLConventions)
|
||||
{
|
||||
if (useGLConventions)
|
||||
{
|
||||
// When applied as an offset to an existing view matrix,
|
||||
// that view matrix will already convert points to a camera space
|
||||
// with opengl conventions. So we need to convert offsets to opengl
|
||||
// conventions.
|
||||
float y = position.y();
|
||||
float z = position.z();
|
||||
position.y() = z;
|
||||
position.z() = -y;
|
||||
|
||||
y = orientation.y();
|
||||
z = orientation.z();
|
||||
orientation.y() = z;
|
||||
orientation.z() = -y;
|
||||
|
||||
osg::Matrix viewMatrix;
|
||||
viewMatrix.setTrans(-position);
|
||||
viewMatrix.postMultRotate(orientation.conj());
|
||||
return viewMatrix;
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0);
|
||||
osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1);
|
||||
osg::Matrix viewMatrix;
|
||||
viewMatrix.makeLookAt(position, position + forward, up);
|
||||
|
||||
return viewMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
bool FieldOfView::operator==(const FieldOfView& rhs) const
|
||||
{
|
||||
return angleDown == rhs.angleDown
|
||||
&& angleUp == rhs.angleUp
|
||||
&& angleLeft == rhs.angleLeft
|
||||
&& angleRight == rhs.angleRight;
|
||||
}
|
||||
|
||||
// near and far named with an underscore because of windows' headers galaxy brain defines.
|
||||
osg::Matrix FieldOfView::perspectiveMatrix(float near_, float far_)
|
||||
{
|
||||
const float tanLeft = tanf(angleLeft);
|
||||
const float tanRight = tanf(angleRight);
|
||||
const float tanDown = tanf(angleDown);
|
||||
const float tanUp = tanf(angleUp);
|
||||
|
||||
const float tanWidth = tanRight - tanLeft;
|
||||
const float tanHeight = tanUp - tanDown;
|
||||
|
||||
const float offset = near_;
|
||||
|
||||
float matrix[16] = {};
|
||||
|
||||
matrix[0] = 2 / tanWidth;
|
||||
matrix[4] = 0;
|
||||
matrix[8] = (tanRight + tanLeft) / tanWidth;
|
||||
matrix[12] = 0;
|
||||
|
||||
matrix[1] = 0;
|
||||
matrix[5] = 2 / tanHeight;
|
||||
matrix[9] = (tanUp + tanDown) / tanHeight;
|
||||
matrix[13] = 0;
|
||||
|
||||
if (far_ <= near_) {
|
||||
matrix[2] = 0;
|
||||
matrix[6] = 0;
|
||||
matrix[10] = -1;
|
||||
matrix[14] = -(near_ + offset);
|
||||
}
|
||||
else {
|
||||
matrix[2] = 0;
|
||||
matrix[6] = 0;
|
||||
matrix[10] = -(far_ + offset) / (far_ - near_);
|
||||
matrix[14] = -(far_ * (near_ + offset)) / (far_ - near_);
|
||||
}
|
||||
|
||||
matrix[3] = 0;
|
||||
matrix[7] = 0;
|
||||
matrix[11] = -1;
|
||||
matrix[15] = 0;
|
||||
|
||||
return osg::Matrix(matrix);
|
||||
}
|
||||
|
||||
bool View::operator==(const View& rhs) const
|
||||
{
|
||||
return pose == rhs.pose && fov == rhs.fov;
|
||||
}
|
||||
|
||||
std::ostream& operator <<(
|
||||
std::ostream& os,
|
||||
const Pose& pose)
|
||||
{
|
||||
os << "position=" << pose.position << ", orientation=" << pose.orientation;
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator <<(
|
||||
std::ostream& os,
|
||||
const FieldOfView& fov)
|
||||
{
|
||||
os << "left=" << fov.angleLeft << ", right=" << fov.angleRight << ", down=" << fov.angleDown << ", up=" << fov.angleUp;
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator <<(
|
||||
std::ostream& os,
|
||||
const View& view)
|
||||
{
|
||||
os << "pose=< " << view.pose << " >, fov=< " << view.fov << " >";
|
||||
return os;
|
||||
}
|
||||
|
||||
/// Why are you like this
|
||||
class TestCullCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
TestCullCallback() {}
|
||||
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
//Log(Debug::Verbose) << "Cull: " << node->getName();
|
||||
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
|
||||
traverse(node, nv);
|
||||
}
|
||||
};
|
||||
|
||||
class StereoUpdateCallback : public osg::Callback
|
||||
{
|
||||
public:
|
||||
StereoUpdateCallback(StereoView* node) : mNode(node) {}
|
||||
|
||||
bool run(osg::Object* object, osg::Object* data) override
|
||||
{
|
||||
//Log(Debug::Verbose) << "StereoUpdateCallback";
|
||||
auto b = traverse(object, data);
|
||||
//mNode->update();
|
||||
return b;
|
||||
}
|
||||
|
||||
StereoView* mNode;
|
||||
};
|
||||
|
||||
class StereoUpdater : public SceneUtil::StateSetUpdater
|
||||
{
|
||||
public:
|
||||
StereoUpdater(StereoView* view)
|
||||
: stereoView(view)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void setDefaults(osg::StateSet* stateset)
|
||||
{
|
||||
auto stereoViewMatrixUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoViewMatrices", 2);
|
||||
stateset->addUniform(stereoViewMatrixUniform, osg::StateAttribute::OVERRIDE);
|
||||
auto stereoViewProjectionsUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoViewProjections", 2);
|
||||
stateset->addUniform(stereoViewProjectionsUniform);
|
||||
auto geometryPassthroughUniform = new osg::Uniform("geometryPassthrough", false);
|
||||
stateset->addUniform(geometryPassthroughUniform);
|
||||
}
|
||||
|
||||
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/)
|
||||
{
|
||||
stereoView->update(stateset);
|
||||
}
|
||||
|
||||
private:
|
||||
StereoView* stereoView;
|
||||
};
|
||||
|
||||
StereoView::StereoView(osgViewer::Viewer* viewer, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask bruteForceMask)
|
||||
: osg::Group()
|
||||
, mViewer(viewer)
|
||||
, mMainCamera(mViewer->getCamera())
|
||||
, mRoot(viewer->getSceneData()->asGroup())
|
||||
, mGeometryShaderMask(geometryShaderMask)
|
||||
, mBruteForceMask(bruteForceMask)
|
||||
{
|
||||
SceneUtil::FindByNameVisitor findScene("Scene Root");
|
||||
mRoot->accept(findScene);
|
||||
mScene = findScene.mFoundNode;
|
||||
if (!mScene)
|
||||
throw std::logic_error("Couldn't find scene root");
|
||||
|
||||
setName("Sky Root");
|
||||
mRoot->setDataVariance(osg::Object::STATIC);
|
||||
setDataVariance(osg::Object::STATIC);
|
||||
mLeftCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
|
||||
mLeftCamera->setProjectionResizePolicy(osg::Camera::FIXED);
|
||||
mLeftCamera->setProjectionMatrix(osg::Matrix::identity());
|
||||
mLeftCamera->setViewMatrix(osg::Matrix::identity());
|
||||
mLeftCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
|
||||
mLeftCamera->setClearMask(GL_NONE);
|
||||
mLeftCamera->setCullMask(bruteForceMask);
|
||||
mLeftCamera->setName("Stereo Left");
|
||||
mLeftCamera->setDataVariance(osg::Object::STATIC);
|
||||
mRightCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
|
||||
mRightCamera->setProjectionResizePolicy(osg::Camera::FIXED);
|
||||
mRightCamera->setProjectionMatrix(osg::Matrix::identity());
|
||||
mRightCamera->setViewMatrix(osg::Matrix::identity());
|
||||
mRightCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
|
||||
mRightCamera->setClearMask(GL_NONE);
|
||||
mRightCamera->setCullMask(bruteForceMask);
|
||||
mRightCamera->setName("Stereo Right");
|
||||
mRightCamera->setDataVariance(osg::Object::STATIC);
|
||||
|
||||
mMainCamera->setCullMask(geometryShaderMask);
|
||||
|
||||
// Inject self as the root of the scene graph, and split into geometry-shader stereo and brute force stereo.
|
||||
addChild(mStereoGeometryShaderRoot);
|
||||
mStereoGeometryShaderRoot->addChild(mRoot);
|
||||
addChild(mStereoBruteForceRoot);
|
||||
mStereoBruteForceRoot->addChild(mLeftCamera);
|
||||
mLeftCamera->addChild(mScene); // Use scene directly to avoid redundant shadow computation.
|
||||
mStereoBruteForceRoot->addChild(mRightCamera);
|
||||
mRightCamera->addChild(mScene);
|
||||
viewer->setSceneData(this);
|
||||
addCullCallback(new StereoUpdater(this));
|
||||
|
||||
// Do a blank double buffering of camera statesets on update. StereoView::Update() apply actual changes during cull;
|
||||
mLeftCamera->setUpdateCallback(new SceneUtil::StateSetUpdater());
|
||||
mRightCamera->setUpdateCallback(new SceneUtil::StateSetUpdater());
|
||||
}
|
||||
|
||||
void StereoView::update(osg::StateSet* stateset)
|
||||
{
|
||||
auto viewMatrix = mViewer->getCamera()->getViewMatrix();
|
||||
auto projectionMatrix = mViewer->getCamera()->getProjectionMatrix();
|
||||
|
||||
View left{};
|
||||
View right{};
|
||||
double near = 1.f;
|
||||
double far = 10000.f;
|
||||
if (!cb)
|
||||
{
|
||||
Log(Debug::Error) << "No update view callback. Stereo rendering will not work.";
|
||||
}
|
||||
cb->updateView(left, right, near, far);
|
||||
|
||||
osg::Vec3d leftEye = left.pose.position;
|
||||
osg::Vec3d rightEye = right.pose.position;
|
||||
|
||||
osg::Matrix leftViewOffset = left.pose.viewMatrix(true);
|
||||
osg::Matrix rightViewOffset = right.pose.viewMatrix(true);
|
||||
|
||||
osg::Matrix leftViewMatrix = viewMatrix * leftViewOffset;
|
||||
osg::Matrix rightViewMatrix = viewMatrix * rightViewOffset;
|
||||
|
||||
osg::Matrix leftProjectionMatrix = left.fov.perspectiveMatrix(near, far);
|
||||
osg::Matrix rightProjectionMatrix = right.fov.perspectiveMatrix(near, far);
|
||||
|
||||
mRightCamera->setViewMatrix(leftViewMatrix);
|
||||
mLeftCamera->setViewMatrix(rightViewMatrix);
|
||||
mRightCamera->setProjectionMatrix(leftProjectionMatrix);
|
||||
mLeftCamera->setProjectionMatrix(rightProjectionMatrix);
|
||||
|
||||
// Manage viewports in update to automatically catch window/resolution changes.
|
||||
auto width = mMainCamera->getViewport()->width();
|
||||
auto height = mMainCamera->getViewport()->height();
|
||||
mLeftCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height), osg::StateAttribute::OVERRIDE);
|
||||
mRightCamera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, width / 2, 0, width / 2, height), osg::StateAttribute::OVERRIDE);
|
||||
stateset->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height));
|
||||
stateset->setAttribute(new osg::ViewportIndexed(1, width / 2, 0, width / 2, height));
|
||||
|
||||
// The persepctive frustum will be computed from a position P slightly behind the eyes L and R
|
||||
// where it creates the minimum frustum encompassing both eyes' frustums.
|
||||
// NOTE: I make an assumption that the eyes lie in a horizontal plane relative to the base view,
|
||||
// and lie mirrored around the Y axis (straight ahead).
|
||||
// Re-think this if that turns out to be a bad assumption
|
||||
View frustumView;
|
||||
|
||||
// Compute Frustum angles. A simple min/max.
|
||||
/* Example values for reference:
|
||||
Left:
|
||||
angleLeft -0.767549932 float
|
||||
angleRight 0.620896876 float
|
||||
angleDown -0.837898076 float
|
||||
angleUp 0.726982594 float
|
||||
|
||||
Right:
|
||||
angleLeft -0.620896876 float
|
||||
angleRight 0.767549932 float
|
||||
angleDown -0.837898076 float
|
||||
angleUp 0.726982594 float
|
||||
*/
|
||||
frustumView.fov.angleLeft = std::min(left.fov.angleLeft, right.fov.angleLeft);
|
||||
frustumView.fov.angleRight = std::max(left.fov.angleRight, right.fov.angleRight);
|
||||
frustumView.fov.angleDown = std::min(left.fov.angleDown, right.fov.angleDown);
|
||||
frustumView.fov.angleUp = std::max(left.fov.angleUp, right.fov.angleUp);
|
||||
|
||||
// Check that the case works for this approach
|
||||
auto maxAngle = std::max(frustumView.fov.angleRight - frustumView.fov.angleLeft, frustumView.fov.angleUp - frustumView.fov.angleDown);
|
||||
if (maxAngle > osg::PI)
|
||||
{
|
||||
Log(Debug::Error) << "Total FOV exceeds 180 degrees. Case cannot be culled in single-pass VR. Disabling culling to cope. Consider switching to dual-pass VR.";
|
||||
mMainCamera->setCullingActive(false);
|
||||
return;
|
||||
// TODO: An explicit frustum projection could cope, so implement that later. Guarantee you there will be VR headsets with total fov > 180 in the future. Maybe already.
|
||||
}
|
||||
|
||||
// Use the law of sines on the triangle spanning PLR to determine P
|
||||
double angleLeft = std::abs(frustumView.fov.angleLeft);
|
||||
double angleRight = std::abs(frustumView.fov.angleRight);
|
||||
double lengthRL = (rightEye - leftEye).length();
|
||||
double ratioRL = lengthRL / std::sin(osg::PI - angleLeft - angleRight);
|
||||
double lengthLP = ratioRL * std::sin(angleRight);
|
||||
|
||||
osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0);
|
||||
osg::Vec3d LP = directionLP * lengthLP;
|
||||
frustumView.pose.position = leftEye + LP;
|
||||
|
||||
// Base view position is 0.0, by definition.
|
||||
// The length of the vector P is therefore the required offset to near/far.
|
||||
auto nearFarOffset = frustumView.pose.position.length();
|
||||
|
||||
// Generate the frustum matrices
|
||||
auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true);
|
||||
auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset);
|
||||
auto frustumViewMatrixInverse = osg::Matrix::inverse(projectionMatrix) * osg::Matrix::inverse(viewMatrix);
|
||||
|
||||
// Update camera with frustum matrices
|
||||
mMainCamera->setViewMatrix(frustumViewMatrix);
|
||||
mMainCamera->setProjectionMatrix(frustumProjectionMatrix);
|
||||
|
||||
mStereoGeometryShaderRoot->setStateSet(stateset);
|
||||
|
||||
|
||||
// Create and/or update stereo uniforms
|
||||
auto* stereoViewMatrixUniform = stateset->getUniform("stereoViewMatrices");
|
||||
auto* stereoViewProjectionsUniform = stateset->getUniform("stereoViewProjections");
|
||||
|
||||
stereoViewMatrixUniform->setElement(1, frustumViewMatrixInverse * leftViewMatrix);
|
||||
stereoViewMatrixUniform->setElement(0, frustumViewMatrixInverse * rightViewMatrix);
|
||||
stereoViewProjectionsUniform->setElement(1, frustumViewMatrixInverse * rightViewMatrix * leftProjectionMatrix);
|
||||
stereoViewProjectionsUniform->setElement(0, frustumViewMatrixInverse * rightViewMatrix * rightProjectionMatrix);
|
||||
}
|
||||
|
||||
void StereoView::setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb)
|
||||
{
|
||||
this->cb = cb;
|
||||
}
|
||||
|
||||
void StereoView::useSlaveCameraAtIndex(int index)
|
||||
{
|
||||
if (mViewer->getNumSlaves() <= index)
|
||||
{
|
||||
Log(Debug::Error) << "Requested slave at index " << index << " but no such slave exists";
|
||||
return;
|
||||
}
|
||||
|
||||
mMainCamera = mViewer->getSlave(index)._camera;
|
||||
mMainCamera->setCullMask(mGeometryShaderMask);
|
||||
}
|
||||
|
||||
void disableStereoForCamera(osg::Camera* camera)
|
||||
{
|
||||
auto* viewport = camera->getViewport();
|
||||
camera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, viewport->x(), viewport->y(), viewport->width(), viewport->height()), osg::StateAttribute::OVERRIDE);
|
||||
camera->getOrCreateStateSet()->addUniform(new osg::Uniform("geometryPassthrough", true), osg::StateAttribute::OVERRIDE);
|
||||
}
|
||||
|
||||
void enableStereoForCamera(osg::Camera* camera, bool horizontalSplit)
|
||||
{
|
||||
auto* viewport = camera->getViewport();
|
||||
auto x1 = viewport->x();
|
||||
auto y1 = viewport->y();
|
||||
auto width = viewport->width();
|
||||
auto height = viewport->height();
|
||||
|
||||
auto x2 = x1;
|
||||
auto y2 = y1;
|
||||
|
||||
if (horizontalSplit)
|
||||
{
|
||||
width /= 2;
|
||||
x2 += width;
|
||||
}
|
||||
else
|
||||
{
|
||||
height /= 2;
|
||||
y2 += height;
|
||||
}
|
||||
|
||||
camera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, x1, y1, width, height));
|
||||
camera->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(1, x2, y2, width, height));
|
||||
camera->getOrCreateStateSet()->addUniform(new osg::Uniform("geometryPassthrough", false));
|
||||
}
|
||||
void StereoView::DefaultUpdateViewCallback::updateView(View& left, View& right, double& near, double& far)
|
||||
{
|
||||
left.pose.position = osg::Vec3(-2.2, 0, 0);
|
||||
right.pose.position = osg::Vec3(2.2, 0, 0);
|
||||
left.fov = { -0.767549932, 0.620896876, -0.837898076, 0.726982594 };
|
||||
right.fov = { -0.620896876, 0.767549932, -0.837898076, 0.726982594 };
|
||||
near = 1;
|
||||
far = 10000;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
#ifndef MISC_STEREO_H
|
||||
#define MISC_STEREO_H
|
||||
|
||||
#include <osg/Matrix>
|
||||
#include <osg/Vec3>
|
||||
#include <osg/Camera>
|
||||
#include <osg/StateSet>
|
||||
|
||||
// Some cursed headers like to define these
|
||||
#if defined(near) || defined(far)
|
||||
#undef near
|
||||
#undef far
|
||||
#endif
|
||||
|
||||
namespace osgViewer
|
||||
{
|
||||
class Viewer;
|
||||
}
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
//! Represents the relative pose in space of some object
|
||||
struct Pose
|
||||
{
|
||||
//! Position in space
|
||||
osg::Vec3 position{ 0,0,0 };
|
||||
//! Orientation in space.
|
||||
osg::Quat orientation{ 0,0,0,1 };
|
||||
|
||||
//! Add one pose to another
|
||||
Pose operator+(const Pose& rhs);
|
||||
const Pose& operator+=(const Pose& rhs);
|
||||
|
||||
//! Scale a pose (does not affect orientation)
|
||||
Pose operator*(float scalar);
|
||||
const Pose& operator*=(float scalar);
|
||||
Pose operator/(float scalar);
|
||||
const Pose& operator/=(float scalar);
|
||||
|
||||
bool operator==(const Pose& rhs) const;
|
||||
|
||||
osg::Matrix viewMatrix(bool useGLConventions);
|
||||
};
|
||||
|
||||
//! Fov that defines all 4 angles from center
|
||||
struct FieldOfView {
|
||||
float angleLeft{ -osg::PI_2 };
|
||||
float angleRight{ osg::PI_2 };
|
||||
float angleDown{ -osg::PI_2 };
|
||||
float angleUp{ osg::PI_2 };
|
||||
|
||||
bool operator==(const FieldOfView& rhs) const;
|
||||
|
||||
//! Generate a perspective matrix from this fov
|
||||
osg::Matrix perspectiveMatrix(float near, float far);
|
||||
};
|
||||
|
||||
//! Represents an eye including both pose and fov.
|
||||
struct View
|
||||
{
|
||||
Pose pose;
|
||||
FieldOfView fov;
|
||||
bool operator==(const View& rhs) const;
|
||||
};
|
||||
|
||||
//! Represent two eyes. The eyes are in relative terms, and are assumed to lie on the horizon plane.
|
||||
struct StereoView : public osg::Group
|
||||
{
|
||||
struct UpdateViewCallback
|
||||
{
|
||||
//! Called during the update traversal of every frame to source updated stereo values.
|
||||
virtual void updateView(View& left, View& right, double& near, double& far) = 0;
|
||||
};
|
||||
|
||||
//! Default implementation of UpdateViewCallback that just provides some hardcoded values for debugging purposes
|
||||
struct DefaultUpdateViewCallback : public UpdateViewCallback
|
||||
{
|
||||
virtual void updateView(View& left, View& right, double& near, double& far);
|
||||
};
|
||||
|
||||
//! Adds two cameras in stereo to the mainCamera.
|
||||
//! All nodes matching the mask are rendered in stereo using brute force via two camera transforms, the rest are rendered in stereo via a geometry shader.
|
||||
//! \note The mask is removed from the mainCamera, so do not put Scene in this mask.
|
||||
//! \note Brute force does not support shadows. But that's fine because currently this only applies to things that don't use shaders and that's only the sky, which will use shaders in the future.
|
||||
StereoView(osgViewer::Viewer* viewer, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask bruteForceMask);
|
||||
|
||||
//! Updates uniforms with the view and projection matrices of each stereo view, and replaces the camera's view and projection matrix
|
||||
//! with a view and projection that closely envelopes the frustums of the two eyes.
|
||||
void update(osg::StateSet* stateset);
|
||||
|
||||
//! Callback that updates stereo configuration during the update pass
|
||||
void setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb);
|
||||
|
||||
//! Use the slave camera at index instead of the main viewer camera.
|
||||
void useSlaveCameraAtIndex(int index);
|
||||
|
||||
osg::ref_ptr<osgViewer::Viewer> mViewer;
|
||||
osg::ref_ptr<osg::Camera> mMainCamera;
|
||||
osg::ref_ptr<osg::Group> mRoot;
|
||||
osg::ref_ptr<osg::Group> mScene;
|
||||
|
||||
// Keeps state relevant to doing stereo via the geometry shader
|
||||
osg::ref_ptr<osg::Group> mStereoGeometryShaderRoot{ new osg::Group };
|
||||
osg::Node::NodeMask mGeometryShaderMask;
|
||||
|
||||
// Keeps state and cameras relevant to doing stereo via brute force
|
||||
osg::ref_ptr<osg::Group> mStereoBruteForceRoot{ new osg::Group };
|
||||
osg::Node::NodeMask mBruteForceMask;
|
||||
osg::ref_ptr<osg::Camera> mLeftCamera{ new osg::Camera };
|
||||
osg::ref_ptr<osg::Camera> mRightCamera{ new osg::Camera };
|
||||
|
||||
// Camera viewports
|
||||
bool flipViewOrder{ true };
|
||||
|
||||
// Updates stereo configuration during the update pass
|
||||
std::shared_ptr<UpdateViewCallback> cb{ new DefaultUpdateViewCallback };
|
||||
};
|
||||
|
||||
//! Overrides all stereo-related states/uniforms to disable stereo for the scene rendered by camera
|
||||
void disableStereoForCamera(osg::Camera* camera);
|
||||
|
||||
//! Overrides all stereo-related states/uniforms to enable stereo for the scene rendered by camera
|
||||
void enableStereoForCamera(osg::Camera* camera, bool horizontalSplit);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,12 +0,0 @@
|
||||
// Because GLSL is an abomination we have to mangle the names of all
|
||||
// vertex outputs when using a geometry shader.
|
||||
#ifndef INTERFACE_UTIL_GLSL
|
||||
#define INTERFACE_UTIL_GLSL
|
||||
|
||||
#if 1 // Placeholder
|
||||
#define VS_NAME(name) vertex_##name
|
||||
#else
|
||||
#define VS_NAME(name) name
|
||||
#endif
|
||||
|
||||
#endif // INTERFACE_UTIL_GLSL
|
@ -1,17 +0,0 @@
|
||||
#version 330 core
|
||||
#extension GL_NV_viewport_array : enable
|
||||
#extension GL_ARB_gpu_shader5 : enable
|
||||
layout (triangles, invocations = 2) in;
|
||||
layout (triangle_strip, max_vertices = 3) out;
|
||||
|
||||
#include "interface_util.glsl"
|
||||
|
||||
void main() {
|
||||
for(int i = 0; i < gl_in.length(); i++)
|
||||
{
|
||||
gl_ViewportIndex = gl_InvocationID;
|
||||
gl_Position = gl_in[i].gl_Position;
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
#version 330 core
|
||||
#extension GL_NV_viewport_array : enable
|
||||
#extension GL_ARB_gpu_shader5 : enable
|
||||
layout (triangles, invocations = 2) in;
|
||||
layout (triangle_strip, max_vertices = 3) out;
|
||||
|
||||
#include "interface_util.glsl"
|
||||
|
||||
#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL)
|
||||
|
||||
#if !PER_PIXEL_LIGHTING
|
||||
centroid in vec4 VS_NAME(lighting)[];
|
||||
centroid in vec3 VS_NAME(shadowDiffuseLighting)[];
|
||||
centroid out vec4 lighting;
|
||||
centroid out vec3 shadowDiffuseLighting;
|
||||
#endif
|
||||
|
||||
// TERRAIN INPUT
|
||||
in vec2 VS_NAME(uv)[];
|
||||
in float VS_NAME(euclideanDepth)[];
|
||||
in float VS_NAME(linearDepth)[];
|
||||
centroid in vec4 VS_NAME(passColor)[];
|
||||
in vec3 VS_NAME(passViewPos)[];
|
||||
in vec3 VS_NAME(passNormal)[];
|
||||
|
||||
#if(@shadows_enabled)
|
||||
#endif
|
||||
|
||||
// TERRAIN OUTPUT
|
||||
out vec2 uv;
|
||||
out float euclideanDepth;
|
||||
out float linearDepth;
|
||||
centroid out vec4 passColor;
|
||||
out vec3 passViewPos;
|
||||
out vec3 passNormal;
|
||||
|
||||
void main() {
|
||||
for(int i = 0; i < gl_in.length(); i++)
|
||||
{
|
||||
gl_ViewportIndex = gl_InvocationID;
|
||||
gl_Position = gl_in[i].gl_Position;
|
||||
uv = VS_NAME(uv)[i];
|
||||
euclideanDepth = VS_NAME(euclideanDepth)[i];
|
||||
linearDepth = VS_NAME(linearDepth)[i];
|
||||
|
||||
#if !PER_PIXEL_LIGHTING
|
||||
lighting = VS_NAME(lighting)[i];
|
||||
shadowDiffuseLighting = VS_NAME(shadowDiffuseLighting)[i];
|
||||
#endif
|
||||
|
||||
passColor = VS_NAME(passColor)[i];
|
||||
passViewPos = VS_NAME(passViewPos)[i];
|
||||
passNormal = VS_NAME(passNormal)[i];
|
||||
|
||||
#if(@shadows_enabled)
|
||||
#endif
|
||||
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
Loading…
Reference in New Issue