1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-21 11:23:51 +00:00

Geometry shader stereo functional

This commit is contained in:
Mads Buvik Sandvei 2020-09-14 22:17:07 +02:00
parent b3f2373875
commit 9c171869cb
22 changed files with 925 additions and 201 deletions

View file

@ -23,12 +23,16 @@
#include <components/sdlutil/sdlgraphicswindow.hpp> #include <components/sdlutil/sdlgraphicswindow.hpp>
#include <components/sdlutil/imagetosurface.hpp> #include <components/sdlutil/imagetosurface.hpp>
#include <components/shader/shadermanager.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/resource/stats.hpp> #include <components/resource/stats.hpp>
#include <components/compiler/extensions0.hpp> #include <components/compiler/extensions0.hpp>
#include <components/misc/stereo.hpp>
#include <components/sceneutil/workqueue.hpp> #include <components/sceneutil/workqueue.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
@ -692,12 +696,31 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
window->playVideo(logo, true); window->playVideo(logo, true);
} }
mStereoEnabled = true; //!< TODO: TEMP
// geometry shader must be enabled before the RenderingManager sets up any shaders
// therefore this part is separate from the rest of stereo setup.
if (mStereoEnabled)
{
mResourceSystem->getSceneManager()->getShaderManager().enableGeometryShader(true);
}
// Create the world // Create the world
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName, mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName,
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
mEnvironment.getWorld()->setupPlayer(); mEnvironment.getWorld()->setupPlayer();
// Set up stereo
if (mStereoEnabled)
{
// Mask in everything that does not currently use shaders.
// Remove that altogether when the sky finally uses them.
auto noShaderMask = MWRender::VisMask::Mask_Sky | MWRender::VisMask::Mask_Sun | MWRender::VisMask::Mask_WeatherParticles;
auto geometryShaderMask = mViewer->getCamera()->getCullMask() & ~noShaderMask;
mStereoView.reset(new Misc::StereoView(mViewer, geometryShaderMask, noShaderMask | MWRender::VisMask::Mask_Scene));
}
window->setStore(mEnvironment.getWorld()->getStore()); window->setStore(mEnvironment.getWorld()->getStore());
window->initUI(); window->initUI();

View file

@ -33,6 +33,11 @@ namespace Compiler
class Context; class Context;
} }
namespace Misc
{
class StereoView;
}
namespace MWScript namespace MWScript
{ {
class ScriptManager; class ScriptManager;
@ -85,6 +90,10 @@ namespace OMW
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
std::string mCellName; std::string mCellName;
std::vector<std::string> mContentFiles; std::vector<std::string> mContentFiles;
bool mStereoEnabled;
std::unique_ptr<Misc::StereoView> mStereoView;
bool mSkipMenu; bool mSkipMenu;
bool mUseSound; bool mUseSound;
bool mCompileAll; bool mCompileAll;

View file

@ -372,20 +372,6 @@ namespace MWRender
mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f);
mStateUpdater->setFogEnd(mViewDistance); mStateUpdater->setFogEnd(mViewDistance);
////// Indexed viewports and related uniforms
sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoViewOffsets", 2));
sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "stereoProjections", 2));
mUniformStereoViewOffsets = sceneRoot->getOrCreateStateSet()->getUniform("stereoViewOffsets");
mUniformStereoProjections = sceneRoot->getOrCreateStateSet()->getUniform("stereoProjections");
mUniformStereoViewOffsets->setElement(0, osg::Matrix::identity());
mUniformStereoViewOffsets->setElement(1, osg::Matrix::identity());
auto width = mViewer->getCamera()->getViewport()->width();
auto height = mViewer->getCamera()->getViewport()->height();
sceneRoot->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height));
sceneRoot->getOrCreateStateSet()->setAttribute(new osg::ViewportIndexed(1, width / 2, 0, width / 2, height));
////// Near far uniforms ////// Near far uniforms
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance));
@ -1243,9 +1229,6 @@ namespace MWRender
fov = mFieldOfViewOverride; fov = mFieldOfViewOverride;
mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance);
mUniformStereoProjections->setElement(0, mViewer->getCamera()->getProjectionMatrix());
mUniformStereoProjections->setElement(1, mViewer->getCamera()->getProjectionMatrix());
mUniformNear->set(mNearClip); mUniformNear->set(mNearClip);
mUniformFar->set(mViewDistance); mUniformFar->set(mViewDistance);

View file

@ -10,6 +10,7 @@
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
#include <osg/ClipNode> #include <osg/ClipNode>
#include <osg/FrontFace> #include <osg/FrontFace>
#include <osg/ViewportIndexed>
#include <osgDB/ReadFile> #include <osgDB/ReadFile>
@ -29,6 +30,7 @@
#include <components/sceneutil/waterutil.hpp> #include <components/sceneutil/waterutil.hpp>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <components/misc/stereo.hpp>
#include <components/nifosg/controller.hpp> #include <components/nifosg/controller.hpp>
@ -246,6 +248,7 @@ public:
setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting);
setNodeMask(Mask_RenderToTexture); setNodeMask(Mask_RenderToTexture);
setViewport(0, 0, rttSize, rttSize); setViewport(0, 0, rttSize, rttSize);
Misc::enableStereoForCamera(this, true);
// No need for Update traversal since the scene is already updated as part of the main scene graph // No need for Update traversal since the scene is already updated as part of the main scene graph
// A double update would mess with the light collection (in addition to being plain redundant) // A double update would mess with the light collection (in addition to being plain redundant)
@ -341,6 +344,8 @@ public:
unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water");
setViewport(0, 0, rttSize, rttSize); setViewport(0, 0, rttSize, rttSize);
Misc::enableStereoForCamera(this, true);
// No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph
// A double update would mess with the light collection (in addition to being plain redundant) // A double update would mess with the light collection (in addition to being plain redundant)
@ -443,6 +448,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem
mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900); mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900);
mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setDrawCallback(new DepthClampCallback);
mWaterGeom->setNodeMask(Mask_Water); mWaterGeom->setNodeMask(Mask_Water);
mWaterGeom->setDataVariance(osg::Object::STATIC);
mWaterNode = new osg::PositionAttitudeTransform; mWaterNode = new osg::PositionAttitudeTransform;
mWaterNode->setName("Water Root"); mWaterNode->setName("Water Root");
@ -592,6 +598,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
// use a define map to conditionally compile the shader // use a define map to conditionally compile the shader
std::map<std::string, std::string> defineMap; std::map<std::string, std::string> defineMap;
defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0"))); defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0")));
defineMap["geometryShader"] = "1";
Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager();
osg::ref_ptr<osg::Shader> vertexShader (shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); osg::ref_ptr<osg::Shader> vertexShader (shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX));
@ -637,9 +644,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
shaderStateset->addUniform(mRainIntensityUniform.get()); shaderStateset->addUniform(mRainIntensityUniform.get());
osg::ref_ptr<osg::Program> program (new osg::Program); auto program = shaderMgr.getProgram(vertexShader, fragmentShader);
program->addShader(vertexShader);
program->addShader(fragmentShader);
shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON);
node->setStateSet(shaderStateset); node->setStateSet(shaderStateset);

View file

@ -86,7 +86,7 @@ add_component_dir (esmterrain
) )
add_component_dir (misc add_component_dir (misc
gcd constants utf8stream stringops resourcehelpers rng messageformatparser weakcache gcd constants utf8stream stringops resourcehelpers rng messageformatparser weakcache stereo
) )
add_component_dir (debug add_component_dir (debug

463
components/misc/stereo.cpp Normal file
View file

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

126
components/misc/stereo.hpp Normal file
View file

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

View file

@ -382,18 +382,30 @@ namespace Resource
return false; return false;
static std::vector<std::string> reservedNames; static std::vector<std::string> reservedNames;
if (reservedNames.empty()) static std::mutex reservedNamesMutex;
{ {
const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", std::lock_guard<std::mutex> lock(reservedNamesMutex);
"Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", if (reservedNames.empty())
"Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"}; {
// This keeps somehow accessing garbage so i rewrote it using safer types.
//const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm",
// "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle",
// "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"};
reservedNames = std::vector<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); //reservedNames = std::vector<std::string>(reserved, reserved + sizeof(reserved)/sizeof(const char*));
for (unsigned int i=0; i<sizeof(reserved)/sizeof(reserved[0]); ++i) //for (unsigned int i=0; i<sizeof(reserved)/sizeof(const char*); ++i)
reservedNames.push_back(std::string("Tri ") + reserved[i]); // reservedNames.push_back(std::string("Tri ") + reserved[i]);
std::sort(reservedNames.begin(), reservedNames.end(), Misc::StringUtils::ciLess); std::vector<std::string> r = { "Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm",
"Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle",
"Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera" };
reservedNames = std::vector<std::string>(r.begin(), r.end());
for (auto& reservedName : r)
reservedNames.emplace_back(std::string("Tri ") + reservedName);
std::sort(reservedNames.begin(), reservedNames.end(), Misc::StringUtils::ciLess);
}
} }
std::vector<std::string>::iterator it = Misc::StringUtils::partialBinarySearch(reservedNames.begin(), reservedNames.end(), name); std::vector<std::string>::iterator it = Misc::StringUtils::partialBinarySearch(reservedNames.begin(), reservedNames.end(), name);
@ -763,7 +775,7 @@ namespace Resource
Shader::ShaderVisitor *SceneManager::createShaderVisitor() Shader::ShaderVisitor *SceneManager::createShaderVisitor()
{ {
Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl", "objects_geometry.glsl"); Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl");
shaderVisitor->setForceShaders(mForceShaders); shaderVisitor->setForceShaders(mForceShaders);
shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps);
shaderVisitor->setNormalMapPattern(mNormalMapPattern); shaderVisitor->setNormalMapPattern(mNormalMapPattern);

View file

@ -18,6 +18,8 @@
#include "mwshadowtechnique.hpp" #include "mwshadowtechnique.hpp"
#include <components/misc/stereo.hpp>
#include <osgShadow/ShadowedScene> #include <osgShadow/ShadowedScene>
#include <osg/CullFace> #include <osg/CullFace>
#include <osg/Geometry> #include <osg/Geometry>
@ -575,6 +577,9 @@ MWShadowTechnique::ShadowData::ShadowData(MWShadowTechnique::ViewDependentData*
// set viewport // set viewport
_camera->setViewport(0,0,textureSize.x(),textureSize.y()); _camera->setViewport(0,0,textureSize.x(),textureSize.y());
// Shadow casting should not obey indexed viewports
Misc::disableStereoForCamera(_camera);
if (debug) if (debug)
{ {

View file

@ -3,6 +3,7 @@
#include <fstream> #include <fstream>
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
#include <regex>
#include <osg/Program> #include <osg/Program>
@ -15,7 +16,7 @@
namespace Shader namespace Shader
{ {
void ShaderManager::setShaderPath(const std::string &path) void ShaderManager::setShaderPath(const std::string& path)
{ {
mPath = path; mPath = path;
} }
@ -34,6 +35,9 @@ namespace Shader
foundPos = source.find_first_of("\n\r", foundPos); foundPos = source.find_first_of("\n\r", foundPos);
foundPos = source.find_first_not_of("\n\r", foundPos); foundPos = source.find_first_not_of("\n\r", foundPos);
if (foundPos == std::string::npos)
break;
size_t lineDirectivePosition = source.rfind("#line", foundPos); size_t lineDirectivePosition = source.rfind("#line", foundPos);
int lineNumber; int lineNumber;
if (lineDirectivePosition != std::string::npos) if (lineDirectivePosition != std::string::npos)
@ -58,45 +62,39 @@ namespace Shader
return true; return true;
} }
bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& templateName) // Recursively replaces include statements with the actual source of the included files.
// Adjusts #line statements accordingly and detects cyclic includes.
// includingFiles is the set of files that include this file directly or indirectly, and is intentionally not a reference to allow automatic cleanup.
static bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& fileName, int& fileNumber, std::set<boost::filesystem::path> includingFiles)
{ {
// An include is cyclic if it is being included by itself
if (includingFiles.insert(shaderPath / fileName).second == false)
{
Log(Debug::Error) << "Shader " << fileName << " error: Detected cyclic #includes";
return false;
}
Misc::StringUtils::replaceAll(source, "\r\n", "\n"); Misc::StringUtils::replaceAll(source, "\r\n", "\n");
std::set<boost::filesystem::path> includedFiles;
size_t foundPos = 0; size_t foundPos = 0;
int fileNumber = 1;
while ((foundPos = source.find("#include")) != std::string::npos) while ((foundPos = source.find("#include")) != std::string::npos)
{ {
size_t start = source.find('"', foundPos); size_t start = source.find('"', foundPos);
if (start == std::string::npos || start == source.size()-1) if (start == std::string::npos || start == source.size() - 1)
{ {
Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include"; Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include";
return false; return false;
} }
size_t end = source.find('"', start+1); size_t end = source.find('"', start + 1);
if (end == std::string::npos) if (end == std::string::npos)
{ {
Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include"; Log(Debug::Error) << "Shader " << fileName << " error: Invalid #include";
return false; return false;
} }
std::string includeFilename = source.substr(start+1, end-(start+1)); std::string includeFilename = source.substr(start + 1, end - (start + 1));
boost::filesystem::path includePath = shaderPath / includeFilename; boost::filesystem::path includePath = shaderPath / includeFilename;
boost::filesystem::ifstream includeFstream;
includeFstream.open(includePath);
if (includeFstream.fail())
{
Log(Debug::Error) << "Shader " << templateName << " error: Failed to open include " << includePath.string();
return false;
}
std::stringstream buffer;
buffer << includeFstream.rdbuf();
std::string stringRepresentation = buffer.str();
addLineDirectivesAfterConditionalBlocks(stringRepresentation);
// insert #line directives so we get correct line numbers in compiler errors
int includedFileNumber = fileNumber++;
// Determine the line number that will be used for the #line directive following the included source
size_t lineDirectivePosition = source.rfind("#line", foundPos); size_t lineDirectivePosition = source.rfind("#line", foundPos);
int lineNumber; int lineNumber;
if (lineDirectivePosition != std::string::npos) if (lineDirectivePosition != std::string::npos)
@ -113,20 +111,182 @@ namespace Shader
} }
lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n');
// Include the file recursively
boost::filesystem::ifstream includeFstream;
includeFstream.open(includePath);
if (includeFstream.fail())
{
Log(Debug::Error) << "Shader " << fileName << " error: Failed to open include " << includePath.string();
return false;
}
int includedFileNumber = fileNumber++;
std::stringstream buffer;
buffer << includeFstream.rdbuf();
std::string stringRepresentation = buffer.str();
if (!addLineDirectivesAfterConditionalBlocks(stringRepresentation)
|| !parseIncludes(shaderPath, stringRepresentation, includeFilename, fileNumber, includingFiles))
{
Log(Debug::Error) << "In file included from " << fileName << "." << lineNumber;
return false;
}
std::stringstream toInsert; std::stringstream toInsert;
toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n"; toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n";
source.replace(foundPos, (end-foundPos+1), toInsert.str()); source.replace(foundPos, (end - foundPos + 1), toInsert.str());
if (includedFiles.insert(includePath).second == false)
{
Log(Debug::Error) << "Shader " << templateName << " error: Detected cyclic #includes";
return false;
}
} }
return true; return true;
} }
struct DeclarationMeta
{
std::string interpolationType;
std::string interfaceKeyword;
std::string type;
std::string identifier;
std::string mangledIdentifier;
};
// Mangle identifiers of the interface declarations of this shader source, updating all identifiers, and returning a list of the declarations for use in generating
// a geometry shader.
// IN/OUT source: The source to mangle
// IN interfaceKeywordPattern: A regular expression matching all interface keywords to look for (e.g. "out|varying" when mangling output variables). Must not contain subexpressions.
// IN mangleString: Identifiers are mangled by prepending this string. Must be a valid identifier prefix.
// OUT declarations: All mangled declarations are added to this vector. Includes interpolation, interface, and type information as well as both the mangled and unmangled identifier.
static void mangleInterface(std::string& source, const std::string& interfaceKeywordPattern, const std::string& mangleString, std::vector<DeclarationMeta>& declarations)
{
std::string commentPattern = "//.*";
std::regex commentRegex(commentPattern);
std::string commentlessSource = std::regex_replace(source, commentRegex, "");
std::string identifierPattern = "[a-zA-Z_][0-9a-zA-Z_]*";
std::string declarationPattern = "(centroid|flat)?\\s*\\b(" + interfaceKeywordPattern + ")\\s+(" + identifierPattern + ")\\s+(" + identifierPattern + ")\\s*;";
std::regex declarationRegex(declarationPattern);
std::vector<std::smatch> matches(std::sregex_iterator(commentlessSource.begin(), commentlessSource.end(), declarationRegex), std::sregex_iterator());
std::string replacementPattern;
for (auto& match : matches)
{
declarations.emplace_back(DeclarationMeta{ match[1].str(), match[2].str(), match[3].str(), match[4].str(), mangleString + match[4].str() });
if (!replacementPattern.empty())
replacementPattern += "|";
replacementPattern = replacementPattern + "(" + declarations.back().identifier + "\\b)";
}
if (!replacementPattern.empty())
{
std::regex replacementRegex(replacementPattern);
source = std::regex_replace(source, replacementRegex, mangleString + "$&");
}
}
static std::string generateGeometryShader(const std::vector<DeclarationMeta>& declarations)
{
static const char* geometryTemplate =
"#version 150 compatibility\n"
"#extension GL_NV_viewport_array : enable\n"
"#extension GL_ARB_gpu_shader5 : enable\n"
"layout (triangles, invocations = 2) in;\n"
"layout (triangle_strip, max_vertices = 3) out;\n"
"\n"
"// Geometry Shader Inputs\n"
"@INPUTS\n"
"\n"
"// Geometry Shader Outputs\n"
"@OUTPUTS\n"
"\n"
"// Stereo matrices\n"
"uniform mat4 stereoViewMatrices[2];\n"
"uniform mat4 stereoViewProjections[2];\n"
"\n"
"void main() {\n"
" for(int i = 0; i < gl_in.length(); i++)\n"
" {\n"
" gl_ViewportIndex = gl_InvocationID;\n"
" // Re-project\n"
" gl_Position = stereoViewProjections[gl_InvocationID] * gl_in[i].gl_Position;\n"
" vec4 viewPos = stereoViewMatrices[gl_InvocationID] * gl_in[i].gl_Position;\n"
" gl_ClipVertex = vec4(viewPos.xyz,1);\n"
"\n"
" // Input -> output\n"
"@FORWARDING\n"
"\n"
" // TODO: deal with passNormal, depths, etc.\n"
"@EXTRA\n"
" EmitVertex();\n"
" }\n"
"\n"
" EndPrimitive();\n"
"}\n"
;
static std::map<std::string, std::string> overriddenForwardStatements =
{
{"linearDepth", "linearDepth = gl_Position.z;"},
{"euclideanDepth", "euclideanDepth = length(viewPos.xyz);"},
{"passViewPos", "passViewPos = viewPos.xyz;"},
};
std::stringstream ssInputDeclarations;
std::stringstream ssOutputDeclarations;
std::stringstream ssForwardStatements;
std::stringstream ssExtraStatements;
std::set<std::string> identifiers;
for (auto& declaration : declarations)
{
if (!declaration.interpolationType.empty())
{
ssInputDeclarations << declaration.interpolationType << " ";
ssOutputDeclarations << declaration.interpolationType << " ";
}
ssInputDeclarations << "in " << declaration.type << " " << declaration.mangledIdentifier << "[];\n";
ssOutputDeclarations << "out " << declaration.type << " " << declaration.identifier << ";\n";
if (overriddenForwardStatements.count(declaration.identifier) > 0)
ssForwardStatements << overriddenForwardStatements[declaration.identifier] << ";\n";
else
ssForwardStatements << " " << declaration.identifier << " = " << declaration.mangledIdentifier << "[i];\n";
identifiers.insert(declaration.identifier);
}
Log(Debug::Verbose) << "Forward statements: \n" << ssForwardStatements.str();
// passViewPos output is required
//if (identifiers.find("passViewPos") == identifiers.end())
//{
// Log(Debug::Error) << "Vertex shader is missing 'vec3 passViewPos' on its interface. Geometry shader will NOT work.";
// return "";
//}
if (identifiers.find("screenCoordsPassthrough") != identifiers.end())
{
// TODO: This corrects basic sampling but the screenCoordsOffset value in the fragment shader is still fucked.
static const char* screenCordsAssignmentCode =
" mat4 scalemat = mat4(0.25, 0.0, 0.0, 0.0,\n"
" 0.0, -0.5, 0.0, 0.0,\n"
" 0.0, 0.0, 0.5, 0.0,\n"
" 0.25, 0.5, 0.5, 1.0);\n"
" vec4 texcoordProj = ((scalemat) * (gl_Position));\n"
" screenCoordsPassthrough = texcoordProj.xyw;\n"
" if(gl_InvocationID == 1)\n"
" screenCoordsPassthrough.x += 0.5 * screenCoordsPassthrough.z;\n"
;
ssExtraStatements << screenCordsAssignmentCode;
}
//if()
std::string geometryShader = geometryTemplate;
geometryShader = std::regex_replace(geometryShader, std::regex("@INPUTS"), ssInputDeclarations.str());
geometryShader = std::regex_replace(geometryShader, std::regex("@OUTPUTS"), ssOutputDeclarations.str());
geometryShader = std::regex_replace(geometryShader, std::regex("@FORWARDING"), ssForwardStatements.str());
geometryShader = std::regex_replace(geometryShader, std::regex("@EXTRA"), ssExtraStatements.str());
return geometryShader;
}
bool parseFors(std::string& source, const std::string& templateName) bool parseFors(std::string& source, const std::string& templateName)
{ {
const char escapeCharacter = '$'; const char escapeCharacter = '$';
@ -165,7 +325,7 @@ namespace Shader
std::string list = source.substr(listStart, listEnd - listStart); std::string list = source.substr(listStart, listEnd - listStart);
std::vector<std::string> listElements; std::vector<std::string> listElements;
if (list != "") if (list != "")
Misc::StringUtils::split (list, listElements, ","); Misc::StringUtils::split(list, listElements, ",");
size_t contentStart = source.find_first_not_of("\n\r", listEnd); size_t contentStart = source.find_first_not_of("\n\r", listEnd);
size_t contentEnd = source.find("$endforeach", contentStart); size_t contentEnd = source.find("$endforeach", contentStart);
@ -224,7 +384,7 @@ namespace Shader
Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF";
return false; return false;
} }
std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); std::string define = source.substr(foundPos + 1, endPos - (foundPos + 1));
ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); ShaderManager::DefineMap::const_iterator defineFound = defines.find(define);
ShaderManager::DefineMap::const_iterator globalDefineFound = globalDefines.find(define); ShaderManager::DefineMap::const_iterator globalDefineFound = globalDefines.find(define);
if (define == "foreach") if (define == "foreach")
@ -271,7 +431,7 @@ namespace Shader
return true; return true;
} }
osg::ref_ptr<osg::Shader> ShaderManager::getShader(const std::string &templateName, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) osg::ref_ptr<osg::Shader> ShaderManager::getShader(const std::string& templateName, const ShaderManager::DefineMap& defines, osg::Shader::Type shaderType)
{ {
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);
@ -279,21 +439,22 @@ namespace Shader
TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); TemplateMap::iterator templateIt = mShaderTemplates.find(templateName);
if (templateIt == mShaderTemplates.end()) if (templateIt == mShaderTemplates.end())
{ {
boost::filesystem::path p = (boost::filesystem::path(mPath) / templateName); boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName);
boost::filesystem::ifstream stream; boost::filesystem::ifstream stream;
stream.open(p); stream.open(path);
if (stream.fail()) if (stream.fail())
{ {
Log(Debug::Error) << "Failed to open " << p.string(); Log(Debug::Error) << "Failed to open " << path.string();
return nullptr; return nullptr;
} }
std::stringstream buffer; std::stringstream buffer;
buffer << stream.rdbuf(); buffer << stream.rdbuf();
// parse includes // parse includes
int fileNumber = 1;
std::string source = buffer.str(); std::string source = buffer.str();
if (!addLineDirectivesAfterConditionalBlocks(source) if (!addLineDirectivesAfterConditionalBlocks(source)
|| !parseIncludes(boost::filesystem::path(mPath), source, templateName)) || !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {}))
return nullptr; return nullptr;
templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first; templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first;
@ -310,12 +471,31 @@ namespace Shader
return nullptr; return nullptr;
} }
osg::ref_ptr<osg::Shader> shader (new osg::Shader(shaderType)); osg::ref_ptr<osg::Shader> shader(new osg::Shader(shaderType));
shader->setShaderSource(shaderSource);
// Assign a unique name to allow the SharedStateManager to compare shaders efficiently // Assign a unique name to allow the SharedStateManager to compare shaders efficiently
static unsigned int counter = 0; static unsigned int counter = 0;
shader->setName(std::to_string(counter++)); shader->setName(std::to_string(counter++));
if (mGeometryShadersEnabled && defines.count("geometryShader") && defines.find("geometryShader")->second == "1" && shaderType == osg::Shader::VERTEX)
{
std::vector<DeclarationMeta> declarations;
mangleInterface(shaderSource, "out|varying", "vertex_", declarations);
std::string geometryShaderSource = generateGeometryShader(declarations);
if (!geometryShaderSource.empty())
{
osg::ref_ptr<osg::Shader> geometryShader(new osg::Shader(osg::Shader::GEOMETRY));
geometryShader->setShaderSource(geometryShaderSource);
geometryShader->setName(shader->getName() + ".geom");
mGeometryShaders[shader] = geometryShader;
}
else
{
Log(Debug::Error) << "Failed to generate geometry shader for " << templateName;
}
}
shader->setShaderSource(shaderSource);
shaderIt = mShaders.insert(std::make_pair(std::make_pair(templateName, defines), shader)).first; shaderIt = mShaders.insert(std::make_pair(std::make_pair(templateName, defines), shader)).first;
} }
return shaderIt->second; return shaderIt->second;
@ -327,9 +507,16 @@ namespace Shader
ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader));
if (found == mPrograms.end()) if (found == mPrograms.end())
{ {
osg::ref_ptr<osg::Program> program (new osg::Program); osg::ref_ptr<osg::Program> program(new osg::Program);
program->addShader(vertexShader); program->addShader(vertexShader);
program->addShader(fragmentShader); program->addShader(fragmentShader);
auto git = mGeometryShaders.find(vertexShader);
if (git != mGeometryShaders.end())
{
program->addShader(git->second);
}
found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first;
} }
return found->second; return found->second;
@ -340,10 +527,15 @@ namespace Shader
return DefineMap(mGlobalDefines); return DefineMap(mGlobalDefines);
} }
void ShaderManager::setGlobalDefines(DefineMap & globalDefines) void ShaderManager::enableGeometryShader(bool enabled)
{
mGeometryShadersEnabled = enabled;
}
void ShaderManager::setGlobalDefines(DefineMap& globalDefines)
{ {
mGlobalDefines = globalDefines; mGlobalDefines = globalDefines;
for (auto shaderMapElement: mShaders) for (auto shaderMapElement : mShaders)
{ {
std::string templateId = shaderMapElement.first.first; std::string templateId = shaderMapElement.first.first;
ShaderManager::DefineMap defines = shaderMapElement.first.second; ShaderManager::DefineMap defines = shaderMapElement.first.second;
@ -360,7 +552,7 @@ namespace Shader
} }
} }
void ShaderManager::releaseGLObjects(osg::State *state) void ShaderManager::releaseGLObjects(osg::State* state)
{ {
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);
for (auto shader : mShaders) for (auto shader : mShaders)

View file

@ -36,6 +36,13 @@ namespace Shader
/// Get (a copy of) the DefineMap used to construct all shaders /// Get (a copy of) the DefineMap used to construct all shaders
DefineMap getGlobalDefines(); DefineMap getGlobalDefines();
/// Enable or disable automatic stereo geometry shader.
/// If enabled, a stereo geometry shader will be automatically generated for any vertex shader
/// whose defines include "geometryShader" set to "1".
/// This geometry shader is automatically included in any program using that vertex shader.
/// \note Does not affect programs that have already been created, set this during startup.
void enableGeometryShader(bool enabled);
/// Set the DefineMap used to construct all shaders /// Set the DefineMap used to construct all shaders
/// @param defines The DefineMap to use /// @param defines The DefineMap to use
/// @note This will change the source code for any shaders already created, potentially causing problems if they're being used to render a frame. It is recommended that any associated Viewers have their threading stopped while this function is running if any shaders are in use. /// @note This will change the source code for any shaders already created, potentially causing problems if they're being used to render a frame. It is recommended that any associated Viewers have their threading stopped while this function is running if any shaders are in use.
@ -59,6 +66,10 @@ namespace Shader
typedef std::map<MapKey, osg::ref_ptr<osg::Shader> > ShaderMap; typedef std::map<MapKey, osg::ref_ptr<osg::Shader> > ShaderMap;
ShaderMap mShaders; ShaderMap mShaders;
typedef std::map<osg::ref_ptr<osg::Shader>, osg::ref_ptr<osg::Shader> > GeometryShaderMap;
GeometryShaderMap mGeometryShaders;
bool mGeometryShadersEnabled{ false };
typedef std::map<std::pair<osg::ref_ptr<osg::Shader>, osg::ref_ptr<osg::Shader> >, osg::ref_ptr<osg::Program> > ProgramMap; typedef std::map<std::pair<osg::ref_ptr<osg::Shader>, osg::ref_ptr<osg::Shader> >, osg::ref_ptr<osg::Program> > ProgramMap;
ProgramMap mPrograms; ProgramMap mPrograms;

View file

@ -37,7 +37,7 @@ namespace Shader
} }
ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate, const std::string& defaultGsTemplate) ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mForceShaders(false) , mForceShaders(false)
, mAllowedToModifyStateSets(true) , mAllowedToModifyStateSets(true)
@ -47,7 +47,6 @@ namespace Shader
, mImageManager(imageManager) , mImageManager(imageManager)
, mDefaultVsTemplate(defaultVsTemplate) , mDefaultVsTemplate(defaultVsTemplate)
, mDefaultFsTemplate(defaultFsTemplate) , mDefaultFsTemplate(defaultFsTemplate)
, mDefaultGsTemplate(defaultGsTemplate)
{ {
mRequirements.push_back(ShaderRequirements()); mRequirements.push_back(ShaderRequirements());
} }
@ -345,17 +344,16 @@ namespace Shader
} }
defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0";
defineMap["geometryShader"] = "1";
writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode));
osg::ref_ptr<osg::Shader> vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr<osg::Shader> vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX));
osg::ref_ptr<osg::Shader> fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); osg::ref_ptr<osg::Shader> fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT));
osg::ref_ptr<osg::Shader> geometryShader (mShaderManager.getShader(mDefaultGsTemplate, defineMap, osg::Shader::GEOMETRY));
//osg::ref_ptr<osg::Shader> geometryShader = nullptr;
if (vertexShader && fragmentShader) if (vertexShader && fragmentShader)
{ {
writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader, geometryShader), osg::StateAttribute::ON); writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON);
for (std::map<int, std::string>::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) for (std::map<int, std::string>::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt)
{ {

View file

@ -17,7 +17,7 @@ namespace Shader
class ShaderVisitor : public osg::NodeVisitor class ShaderVisitor : public osg::NodeVisitor
{ {
public: public:
ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate, const std::string& defaultGsTemplate); ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate);
/// By default, only bump mapped objects will have a shader added to them. /// By default, only bump mapped objects will have a shader added to them.
/// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map.
@ -89,7 +89,6 @@ namespace Shader
std::string mDefaultVsTemplate; std::string mDefaultVsTemplate;
std::string mDefaultFsTemplate; std::string mDefaultFsTemplate;
std::string mDefaultGsTemplate;
void createProgram(const ShaderRequirements& reqs); void createProgram(const ShaderRequirements& reqs);
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);

View file

@ -232,10 +232,9 @@ namespace Terrain
defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0";
defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0";
defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0";
defineMap["geometryShader"] = "1";
osg::ref_ptr<osg::Shader> vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); osg::ref_ptr<osg::Shader> vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> geometryShader = shaderManager->getShader("terrain_geometry.glsl", defineMap, osg::Shader::GEOMETRY);
//osg::ref_ptr<osg::Shader> geometryShader = nullptr;
osg::ref_ptr<osg::Shader> fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); osg::ref_ptr<osg::Shader> fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT);
if (!vertexShader || !fragmentShader) if (!vertexShader || !fragmentShader)
{ {
@ -243,7 +242,7 @@ namespace Terrain
return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize);
} }
stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader, geometryShader)); stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader));
stateset->addUniform(new osg::Uniform("colorMode", 2)); stateset->addUniform(new osg::Uniform("colorMode", 2));
} }
else else

View file

@ -11,21 +11,17 @@ set(SHADER_FILES
water_fragment.glsl water_fragment.glsl
water_nm.png water_nm.png
objects_vertex.glsl objects_vertex.glsl
objects_geometry.glsl
objects_fragment.glsl objects_fragment.glsl
terrain_vertex.glsl terrain_vertex.glsl
terrain_geometry.glsl
terrain_fragment.glsl terrain_fragment.glsl
lighting.glsl lighting.glsl
parallax.glsl parallax.glsl
s360_fragment.glsl s360_fragment.glsl
s360_vertex.glsl s360_vertex.glsl
shadows_vertex.glsl shadows_vertex.glsl
shadows_geometry.glsl
shadows_fragment.glsl shadows_fragment.glsl
shadowcasting_vertex.glsl shadowcasting_vertex.glsl
shadowcasting_fragment.glsl shadowcasting_fragment.glsl
interface_util.glsl
) )
copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}")

View file

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

View file

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

View file

@ -1,7 +1,5 @@
#version 120 #version 120
#include "interface_util.glsl"
#if @diffuseMap #if @diffuseMap
varying vec2 diffuseMapUV; varying vec2 diffuseMapUV;
#endif #endif

View file

@ -110,6 +110,4 @@ void main()
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
applyShadowDebugOverlay(); applyShadowDebugOverlay();
//gl_FragData[0] = vec4(passNormal,1);
} }

View file

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

View file

@ -1,47 +1,44 @@
#version 120 #version 130
#include "interface_util.glsl" varying vec2 uv;
varying float euclideanDepth;
varying vec2 VS_NAME(uv); varying float linearDepth;
varying float VS_NAME(euclideanDepth);
varying float VS_NAME(linearDepth);
#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL)
#if !PER_PIXEL_LIGHTING #if !PER_PIXEL_LIGHTING
centroid varying vec4 VS_NAME(lighting); centroid varying vec4 lighting;
centroid varying vec3 VS_NAME(shadowDiffuseLighting); centroid varying vec3 shadowDiffuseLighting;
#endif #endif
centroid varying vec4 VS_NAME(passColor); centroid varying vec4 passColor;
varying vec3 VS_NAME(passViewPos); varying vec3 passViewPos;
varying vec3 VS_NAME(passNormal); varying vec3 passNormal;
#include "shadows_vertex.glsl" #include "shadows_vertex.glsl"
#include "lighting.glsl" #include "lighting.glsl"
void main(void) void main(void)
{ {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex);
gl_ClipVertex = viewPos; gl_ClipVertex = viewPos;
VS_NAME(euclideanDepth) = length(viewPos.xyz); euclideanDepth = length(viewPos.xyz);
VS_NAME(linearDepth) = gl_Position.z; linearDepth = gl_Position.z;
#if (!PER_PIXEL_LIGHTING || @shadows_enabled) #if (!PER_PIXEL_LIGHTING || @shadows_enabled)
vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz);
#endif #endif
#if !PER_PIXEL_LIGHTING #if !PER_PIXEL_LIGHTING
VS_NAME(lighting) = doLighting(viewPos.xyz, viewNormal, gl_Color, VS_NAME(shadowDiffuseLighting)); lighting = doLighting(viewPos.xyz, viewNormal, gl_Color, shadowDiffuseLighting);
#endif #endif
VS_NAME(passColor) = gl_Color; passColor = gl_Color;
VS_NAME(passNormal) = gl_Normal.xyz; passNormal = gl_Normal.xyz;
VS_NAME(passViewPos) = viewPos.xyz; passViewPos = viewPos.xyz;
VS_NAME(uv) = gl_MultiTexCoord0.xy; uv = gl_MultiTexCoord0.xy;
#if (@shadows_enabled) #if (@shadows_enabled)
setupShadowCoords(viewPos, viewNormal); setupShadowCoords(viewPos, viewNormal);