1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-04-01 20:06:41 +00:00

Merge remote tracking branch multiview_test_branch

This commit is contained in:
Mads Buvik Sandvei 2020-12-09 19:42:15 +01:00
parent 85033bb647
commit c974e1dde3
23 changed files with 1064 additions and 60 deletions

View file

@ -20,6 +20,7 @@
#include <components/resource/scenemanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
#include "../widget/scenetoolmode.hpp"
@ -106,7 +107,7 @@ RenderWidget::~RenderWidget()
// before OSG 3.6.4, the default font was a static object, and if it wasn't attached to the scene when a graphics context was destroyed, it's program wouldn't be released.
// 3.6.4 moved it into the object cache, which meant it usually got released, but not here.
// 3.6.5 improved cleanup with osgViewer::CompositeViewer::removeView so it more reliably released associated state for objects in the object cache.
osg::ref_ptr<osg::GraphicsContext> graphicsContext = mView->getCamera()->getGraphicsContext();
osg::ref_ptr<osg::GraphicsContext> graphicsContext = SDLUtil::GraphicsWindowSDL2::findContext(*mView);
osgText::Font::getDefaultFont()->releaseGLObjects(graphicsContext->getState());
#endif
}
@ -134,7 +135,7 @@ osg::Camera *RenderWidget::getCamera()
void RenderWidget::toggleRenderStats()
{
osgViewer::GraphicsWindow* window =
static_cast<osgViewer::GraphicsWindow*>(mView->getCamera()->getGraphicsContext());
static_cast<osgViewer::GraphicsWindow*>(SDLUtil::GraphicsWindowSDL2::findContext(*mView));
window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S);
window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S);
@ -246,7 +247,7 @@ SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSyste
SceneWidget::~SceneWidget()
{
// Since we're holding on to the resources past the existence of this graphics context, we'll need to manually release the created objects
mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
mResourceSystem->releaseGLObjects(SDLUtil::GraphicsWindowSDL2::findContext(*mView)->getState());
}
void SceneWidget::setLighting(Lighting *lighting)

View file

@ -24,12 +24,16 @@
#include <components/sdlutil/sdlgraphicswindow.hpp>
#include <components/sdlutil/imagetosurface.hpp>
#include <components/shader/shadermanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/resource/stats.hpp>
#include <components/compiler/extensions0.hpp>
#include <components/misc/stereo.hpp>
#include <components/sceneutil/workqueue.hpp>
#include <components/files/configurationmanager.hpp>
@ -376,6 +380,9 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mEncoding(ToUTF8::WINDOWS_1252)
, mEncoder(nullptr)
, mScreenCaptureOperation(nullptr)
, mStereoEnabled(false)
, mStereoOverride(false)
, mStereoView(nullptr)
, mSkipMenu (false)
, mUseSound (true)
, mCompileAll (false)
@ -409,6 +416,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
OMW::Engine::~Engine()
{
mStereoView = nullptr;
mEnvironment.cleanup();
delete mScriptContext;
@ -761,12 +770,32 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
window->playVideo(logo, true);
}
// VR mode will override this setting by setting mStereoOverride.
mStereoEnabled = mStereoOverride || Settings::Manager::getBool("stereo enabled", "Stereo");
// 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().setStereoGeometryShaderEnabled(Misc::getStereoTechnique() == Misc::StereoView::Technique::GeometryShader_IndexedViewports);
}
// Create the world
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName,
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
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 = new Misc::StereoView(mViewer, Misc::getStereoTechnique(), geometryShaderMask, noShaderMask | MWRender::VisMask::Mask_Scene);
}
window->setStore(mEnvironment.getWorld()->getStore());
window->initUI();

View file

@ -37,6 +37,11 @@ namespace Compiler
class Context;
}
namespace Misc
{
class StereoView;
}
namespace MWScript
{
class ScriptManager;
@ -89,6 +94,11 @@ namespace OMW
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
std::string mCellName;
std::vector<std::string> mContentFiles;
bool mStereoEnabled;
bool mStereoOverride;
osg::ref_ptr<Misc::StereoView> mStereoView;
bool mSkipMenu;
bool mUseSound;
bool mCompileAll;

View file

@ -15,6 +15,7 @@
#include <osg/ComputeBoundsVisitor>
#include <osg/ShapeDrawable>
#include <osg/TextureCubeMap>
#include <osg/ViewportIndexed>
#include <osgUtil/LineSegmentIntersector>
@ -215,8 +216,9 @@ namespace MWRender
resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem);
resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders");
// Shadows and radial fog have problems with fixed-function mode
bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows");
resourceSystem->getSceneManager()->setForceShaders(forceShaders);
//bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows");
//resourceSystem->getSceneManager()->setForceShaders(forceShaders);
resourceSystem->getSceneManager()->setForceShaders(true);
// FIXME: calling dummy method because terrain needs to know whether lighting is clamped
resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders"));
resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders"));
@ -383,12 +385,14 @@ namespace MWRender
mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f);
mStateUpdater->setFogEnd(mViewDistance);
////// Near far uniforms
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance));
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false));
mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near");
mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far");
updateProjectionMatrix();
}

View file

@ -123,6 +123,8 @@ namespace MWRender
osg::Uniform* mUniformNear;
osg::Uniform* mUniformFar;
osg::Uniform* mUniformStereoViewOffsets;
osg::Uniform* mUniformStereoProjections;
void preloadCommonAssets();

View file

@ -10,6 +10,7 @@
#include <osg/PositionAttitudeTransform>
#include <osg/ClipNode>
#include <osg/FrontFace>
#include <osg/ViewportIndexed>
#include <osgDB/ReadFile>
@ -29,6 +30,7 @@
#include <components/sceneutil/waterutil.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/stereo.hpp>
#include <components/nifosg/controller.hpp>
@ -343,6 +345,7 @@ public:
unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water");
setViewport(0, 0, rttSize, rttSize);
// 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)
@ -596,6 +599,19 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
std::map<std::string, std::string> defineMap;
defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0")));
if (mResourceSystem->getSceneManager()->getShaderManager().stereoGeometryShaderEnabled())
{
defineMap["geometryShader"] = "1";
if (reflection)
{
Misc::enableStereoForCamera(reflection, true);
}
if (refraction)
{
Misc::enableStereoForCamera(refraction, true);
}
}
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> fragmentShader (shaderMgr.getShader("water_fragment.glsl", defineMap, osg::Shader::FRAGMENT));
@ -640,9 +656,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
shaderStateset->addUniform(mRainIntensityUniform.get());
osg::ref_ptr<osg::Program> program (new osg::Program);
program->addShader(vertexShader);
program->addShader(fragmentShader);
auto program = shaderMgr.getProgram(vertexShader, fragmentShader);
shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON);
node->setStateSet(shaderStateset);

View file

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

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

@ -0,0 +1,529 @@
#include "stereo.hpp"
#include "stringops.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>
#include <components/settings/settings.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;
}
// Update stereo view/projection during update
class StereoUpdateCallback : public osg::Callback
{
public:
StereoUpdateCallback(StereoView* stereoView) : stereoView(stereoView) {}
bool run(osg::Object* object, osg::Object* data) override
{
auto b = traverse(object, data);
stereoView->update();
return b;
}
StereoView* stereoView;
};
// Update states during cull
class StereoStatesetUpdateCallback : public SceneUtil::StateSetUpdater
{
public:
StereoStatesetUpdateCallback(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->updateStateset(stateset);
}
private:
StereoView* stereoView;
};
StereoView::StereoView(osgViewer::Viewer* viewer, Technique technique, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask noShaderMask)
: osg::Group()
, mViewer(viewer)
, mMainCamera(mViewer->getCamera())
, mRoot(viewer->getSceneData()->asGroup())
, mTechnique(technique)
, mGeometryShaderMask(geometryShaderMask)
, mNoShaderMask(noShaderMask)
{
if (technique == Technique::None)
// Do nothing
return;
SceneUtil::FindByNameVisitor findScene("Scene Root");
mRoot->accept(findScene);
mScene = findScene.mFoundNode;
if (!mScene)
throw std::logic_error("Couldn't find scene root");
setName("Stereo 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->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->setName("Stereo Right");
mRightCamera->setDataVariance(osg::Object::STATIC);
// Update stereo statesets/matrices, but after the main camera updates.
auto mainCameraCB = mMainCamera->getUpdateCallback();
mMainCamera->removeUpdateCallback(mainCameraCB);
mMainCamera->addUpdateCallback(new StereoUpdateCallback(this));
mMainCamera->addUpdateCallback(mainCameraCB);
// Do a blank double buffering of camera statesets on update. Actual state updates are performed in StereoView::Update()
mLeftCamera->setUpdateCallback(new SceneUtil::StateSetUpdater());
mRightCamera->setUpdateCallback(new SceneUtil::StateSetUpdater());
if (mTechnique == Technique::GeometryShader_IndexedViewports)
{
setupGeometryShaderIndexedViewportTechnique();
}
else
{
setupBruteForceTechnique();
}
}
void StereoView::setupBruteForceTechnique()
{
mLeftCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
mLeftCamera->setClearColor(mMainCamera->getClearColor());
mLeftCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mLeftCamera->setCullMask(mMainCamera->getCullMask());
mRightCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
mRightCamera->setClearColor(mMainCamera->getClearColor());
mRightCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mRightCamera->setCullMask(mMainCamera->getCullMask());
// Slave cameras must have their viewports defined immediately
auto width = mMainCamera->getViewport()->width();
auto height = mMainCamera->getViewport()->height();
mLeftCamera->setViewport(0, 0, width / 2, height);
mRightCamera->setViewport(width / 2, 0, width / 2, height);
mViewer->stopThreading();
mViewer->addSlave(mLeftCamera, true);
mViewer->addSlave(mRightCamera, true);
mRightCamera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext());
mLeftCamera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext());
mViewer->getCamera()->setGraphicsContext(nullptr);
mViewer->realize();
}
void StereoView::setupGeometryShaderIndexedViewportTechnique()
{
mLeftCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
mLeftCamera->setClearMask(GL_NONE);
mLeftCamera->setCullMask(mNoShaderMask);
mRightCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
mRightCamera->setClearMask(GL_NONE);
mRightCamera->setCullMask(mNoShaderMask);
mMainCamera->setCullMask(mGeometryShaderMask);
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);
addCullCallback(new StereoStatesetUpdateCallback(this));
// Inject self as the root of the scene graph
mViewer->setSceneData(this);
}
void StereoView::update()
{
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(rightViewMatrix);
mLeftCamera->setViewMatrix(leftViewMatrix);
mRightCamera->setProjectionMatrix(rightProjectionMatrix);
mLeftCamera->setProjectionMatrix(leftProjectionMatrix);
auto width = mMainCamera->getViewport()->width();
auto height = mMainCamera->getViewport()->height();
if (mTechnique == Technique::GeometryShader_IndexedViewports)
{
// To correctly cull when drawing stereo using the geometry shader, the main camera must
// draw a fake view+perspective that includes the full frustums of both the left and right eyes.
// This frustum will be computed as a perspective frustum 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;
//frustumView.pose.position.x() += 1000;
// 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);
// Update camera with frustum matrices
mMainCamera->setViewMatrix(frustumViewMatrix);
mMainCamera->setProjectionMatrix(frustumProjectionMatrix);
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);
}
else
{
mLeftCamera->setClearColor(mMainCamera->getClearColor());
mRightCamera->setClearColor(mMainCamera->getClearColor());
mLeftCamera->setViewport(0, 0, width / 2, height);
mRightCamera->setViewport(width / 2, 0, width / 2, height);
}
}
void StereoView::updateStateset(osg::StateSet * stateset)
{
// Manage viewports in update to automatically catch window/resolution changes.
auto width = mMainCamera->getViewport()->width();
auto height = mMainCamera->getViewport()->height();
stateset->setAttribute(new osg::ViewportIndexed(0, 0, 0, width / 2, height));
stateset->setAttribute(new osg::ViewportIndexed(1, width / 2, 0, width / 2, height));
// Update stereo uniforms
auto frustumViewMatrixInverse = osg::Matrix::inverse(mMainCamera->getViewMatrix());
//auto frustumViewProjectionMatrixInverse = osg::Matrix::inverse(mMainCamera->getProjectionMatrix()) * osg::Matrix::inverse(mMainCamera->getViewMatrix());
auto* stereoViewMatrixUniform = stateset->getUniform("stereoViewMatrices");
auto* stereoViewProjectionsUniform = stateset->getUniform("stereoViewProjections");
stereoViewMatrixUniform->setElement(0, frustumViewMatrixInverse * mLeftCamera->getViewMatrix());
stereoViewMatrixUniform->setElement(1, frustumViewMatrixInverse * mRightCamera->getViewMatrix());
stereoViewProjectionsUniform->setElement(0, frustumViewMatrixInverse * mLeftCamera->getViewMatrix() * mLeftCamera->getProjectionMatrix());
stereoViewProjectionsUniform->setElement(1, frustumViewMatrixInverse * mRightCamera->getViewMatrix() * mRightCamera->getProjectionMatrix());
}
void StereoView::setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb)
{
this->cb = cb;
}
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));
}
StereoView::Technique getStereoTechnique(void)
{
auto stereoMethodString = Settings::Manager::getString("stereo method", "Stereo");
auto stereoMethodStringLowerCase = Misc::StringUtils::lowerCase(stereoMethodString);
if (stereoMethodStringLowerCase == "geometryshader")
{
return Misc::StereoView::Technique::GeometryShader_IndexedViewports;
}
if (stereoMethodStringLowerCase == "bruteforce")
{
return Misc::StereoView::Technique::BruteForce;
}
Log(Debug::Warning) << "Unknown stereo technique \"" << stereoMethodString << "\", defaulting to BruteForce";
return StereoView::Technique::BruteForce;
}
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 = 6656;
}
}

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

@ -0,0 +1,142 @@
#ifndef MISC_STEREO_H
#define MISC_STEREO_H
#include <osg/Matrix>
#include <osg/Vec3>
#include <osg/Camera>
#include <osg/StateSet>
#include <memory>
// 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);
};
enum class Technique
{
None = 0, //!< Stereo disabled (do nothing).
BruteForce, //!< Two slave cameras culling and drawing everything.
GeometryShader_IndexedViewports, //!< Frustum camera culls and draws stereo into indexed viewports using an automatically generated geometry shader.
};
//! 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.
//! \param geometryShaderMask should mask in all nodes that use shaders.
//! \param noShaderMask mask in all nodes that do not use shaders and must be rendered brute force.
//! \note the masks apply only to the GeometryShader_IndexdViewports technique and can be 0 for the BruteForce technique.
StereoView(osgViewer::Viewer* viewer, Technique technique, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask noShaderMask);
//! 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();
void updateStateset(osg::StateSet* stateset);
//! Callback that updates stereo configuration during the update pass
void setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb);
private:
void setupBruteForceTechnique();
void setupGeometryShaderIndexedViewportTechnique();
osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osg::Camera> mMainCamera;
osg::ref_ptr<osg::Group> mRoot;
osg::ref_ptr<osg::Group> mScene;
Technique mTechnique;
// Keeps state relevant to doing stereo via the geometry shader
osg::ref_ptr<osg::Group> mStereoGeometryShaderRoot{ new osg::Group };
osg::Node::NodeMask mGeometryShaderMask;
osg::Node::NodeMask mNoShaderMask;
// Keeps state and cameras relevant to doing stereo via brute force
osg::ref_ptr<osg::Group> mStereoBruteForceRoot{ new osg::Group };
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);
//! Reads settings to determine stereo technique
StereoView::Technique getStereoTechnique(void);
}
#endif

View file

@ -380,18 +380,30 @@ namespace Resource
return false;
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",
"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"};
std::lock_guard<std::mutex> lock(reservedNamesMutex);
if (reservedNames.empty())
{
// 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)
reservedNames.push_back(std::string("Tri ") + reserved[i]);
//for (unsigned int i=0; i<sizeof(reserved)/sizeof(const char*); ++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);

View file

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

View file

@ -1,5 +1,7 @@
#include "sdlgraphicswindow.hpp"
#include <osgViewer/viewer>
#include <SDL_video.h>
namespace SDLUtil
@ -225,6 +227,23 @@ void GraphicsWindowSDL2::setSyncToVBlank(bool on)
SDL_GL_MakeCurrent(oldWin, oldCtx);
}
osg::GraphicsContext* GraphicsWindowSDL2::findContext(osgViewer::View& view)
{
view.getCamera();
if (view.getCamera()->getGraphicsContext())
{
return view.getCamera()->getGraphicsContext();
}
for (auto i = 0; i < view.getNumSlaves(); i++)
{
if (view.getSlave(i)._camera->getGraphicsContext())
return view.getSlave(i)._camera->getGraphicsContext();
}
return nullptr;
}
void GraphicsWindowSDL2::setSwapInterval(bool enable)
{
if (enable)

View file

@ -81,6 +81,9 @@ public:
SDL_Window *mWindow;
};
/** Convenience function for finding the context among the main camera or slaves */
static osg::GraphicsContext* findContext(osgViewer::View& view);
private:
void setSwapInterval(bool enable);
};

View file

@ -1,4 +1,5 @@
#include "sdlinputwrapper.hpp"
#include "sdlgraphicswindow.hpp"
#include <components/debug/debuglog.hpp>
#include <components/settings/settings.hpp>
@ -213,11 +214,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
SDL_GetWindowSize(mSDLWindow, &w, &h);
int x,y;
SDL_GetWindowPosition(mSDLWindow, &x,&y);
{
auto* gc = mViewer->getCamera()->getGraphicsContext();
if (gc)
gc->resized(x, y, w, h);
}
GraphicsWindowSDL2::findContext(*mViewer)->resized(x,y,w,h);
mViewer->getEventQueue()->windowResize(x,y,w,h);

View file

@ -3,6 +3,7 @@
#include <fstream>
#include <algorithm>
#include <sstream>
#include <regex>
#include <osg/Program>
@ -15,7 +16,7 @@
namespace Shader
{
void ShaderManager::setShaderPath(const std::string &path)
void ShaderManager::setShaderPath(const std::string& path)
{
mPath = path;
}
@ -138,6 +139,109 @@ namespace Shader
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::string& geometryTemplate, const std::vector<DeclarationMeta>& declarations)
{
if (geometryTemplate.empty())
return "";
static std::map<std::string, std::string> overriddenForwardStatements =
{
{"linearDepth", "linearDepth = gl_Position.z;"},
{"euclideanDepth", "euclideanDepth = length(viewPos.xyz);"},
{"passViewPos", "passViewPos = viewPos.xyz;"},
{
"screenCoordsPassthrough",
" 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(viewport == 1)\n"
" screenCoordsPassthrough.x += 0.5 * screenCoordsPassthrough.z;\n"
}
};
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 << "[3];\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 << "[vertex];\n";
identifiers.insert(declaration.identifier);
}
// 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 "";
}
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());
return geometryShader;
}
bool parseFors(std::string& source, const std::string& templateName)
{
const char escapeCharacter = '$';
@ -176,7 +280,7 @@ namespace Shader
std::string list = source.substr(listStart, listEnd - listStart);
std::vector<std::string> listElements;
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 contentEnd = source.find("$endforeach", contentStart);
@ -235,7 +339,7 @@ namespace Shader
Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF";
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 globalDefineFound = globalDefines.find(define);
if (define == "foreach")
@ -282,39 +386,17 @@ namespace Shader
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);
// read the template if we haven't already
TemplateMap::iterator templateIt = mShaderTemplates.find(templateName);
if (templateIt == mShaderTemplates.end())
{
boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName);
boost::filesystem::ifstream stream;
stream.open(path);
if (stream.fail())
{
Log(Debug::Error) << "Failed to open " << path.string();
return nullptr;
}
std::stringstream buffer;
buffer << stream.rdbuf();
// parse includes
int fileNumber = 1;
std::string source = buffer.str();
if (!addLineDirectivesAfterConditionalBlocks(source)
|| !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {}))
return nullptr;
templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first;
}
ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(templateName, defines));
if (shaderIt == mShaders.end())
{
std::string shaderSource = templateIt->second;
std::string shaderSource = getTemplateSource(templateName);
if (shaderSource.empty())
return nullptr;
if (!parseDefines(shaderSource, defines, mGlobalDefines, templateName) || !parseFors(shaderSource, templateName))
{
// Add to the cache anyway to avoid logging the same error over and over.
@ -322,12 +404,32 @@ namespace Shader
return nullptr;
}
osg::ref_ptr<osg::Shader> shader (new osg::Shader(shaderType));
shader->setShaderSource(shaderSource);
osg::ref_ptr<osg::Shader> shader(new osg::Shader(shaderType));
// Assign a unique name to allow the SharedStateManager to compare shaders efficiently
static unsigned int counter = 0;
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 geometryTemplate = getTemplateSource("stereo_geometry.glsl");
std::string geometryShaderSource = generateGeometryShader(geometryTemplate, 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;
}
return shaderIt->second;
@ -339,9 +441,16 @@ namespace Shader
ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader));
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(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;
}
return found->second;
@ -352,10 +461,20 @@ namespace Shader
return DefineMap(mGlobalDefines);
}
void ShaderManager::setGlobalDefines(DefineMap & globalDefines)
void ShaderManager::setStereoGeometryShaderEnabled(bool enabled)
{
mGeometryShadersEnabled = enabled;
}
bool ShaderManager::stereoGeometryShaderEnabled() const
{
return mGeometryShadersEnabled;
}
void ShaderManager::setGlobalDefines(DefineMap& globalDefines)
{
mGlobalDefines = globalDefines;
for (auto shaderMapElement: mShaders)
for (auto shaderMapElement : mShaders)
{
std::string templateId = shaderMapElement.first.first;
ShaderManager::DefineMap defines = shaderMapElement.first.second;
@ -372,7 +491,7 @@ namespace Shader
}
}
void ShaderManager::releaseGLObjects(osg::State *state)
void ShaderManager::releaseGLObjects(osg::State* state)
{
std::lock_guard<std::mutex> lock(mMutex);
for (auto shader : mShaders)
@ -384,4 +503,33 @@ namespace Shader
program.second->releaseGLObjects(state);
}
std::string ShaderManager::getTemplateSource(const std::string& templateName)
{
// read the template if we haven't already
TemplateMap::iterator templateIt = mShaderTemplates.find(templateName);
if (templateIt == mShaderTemplates.end())
{
boost::filesystem::path path = (boost::filesystem::path(mPath) / templateName);
boost::filesystem::ifstream stream;
stream.open(path);
if (stream.fail())
{
Log(Debug::Error) << "Failed to open " << path.string();
return std::string();
}
std::stringstream buffer;
buffer << stream.rdbuf();
// parse includes
int fileNumber = 1;
std::string source = buffer.str();
if (!addLineDirectivesAfterConditionalBlocks(source)
|| !parseIncludes(boost::filesystem::path(mPath), source, templateName, fileNumber, {}))
return std::string();
templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first;
}
return templateIt->second;
}
}

View file

@ -36,6 +36,14 @@ namespace Shader
/// Get (a copy of) the DefineMap used to construct all shaders
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 setStereoGeometryShaderEnabled(bool enabled);
bool stereoGeometryShaderEnabled() const;
/// Set the DefineMap used to construct all shaders
/// @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.
@ -44,6 +52,8 @@ namespace Shader
void releaseGLObjects(osg::State* state);
private:
std::string getTemplateSource(const std::string& templateName);
std::string mPath;
DefineMap mGlobalDefines;
@ -56,6 +66,10 @@ namespace Shader
typedef std::map<MapKey, osg::ref_ptr<osg::Shader> > ShaderMap;
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;
ProgramMap mPrograms;

View file

@ -319,6 +319,7 @@ namespace Shader
}
defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0";
defineMap["geometryShader"] = "1";
writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode));

View file

@ -232,6 +232,7 @@ namespace Terrain
defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0";
defineMap["specularMap"] = it->mSpecular ? "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> fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT);

View file

@ -932,6 +932,17 @@ object shadows = false
# Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.
enable indoor shadows = true
[Stereo]
# Enable/disable stereo view. This setting is ignored in VR.
stereo enabled = false
# Method used to render stereo if enabled
# Must be one of the following: BruteForce, GeometryShader
# BruteForce: Generates stereo using two cameras and two cull/render passes. Choose this if your game is GPU-bound.
# GeometryShader: Generates stereo in a single pass using automatically generated geometry shaders. May break custom shaders. Choose this if your game is CPU-bound.
stereo method = GeometryShader
[Physics]
# Set the number of background threads used for physics.
# If no background threads are used, physics calculations are processed in the main thread

View file

@ -22,6 +22,7 @@ set(SHADER_FILES
shadows_fragment.glsl
shadowcasting_vertex.glsl
shadowcasting_fragment.glsl
stereo_geometry.glsl
)
copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}")

View file

@ -0,0 +1,58 @@
#version 150 compatibility
#extension GL_ARB_viewport_array : require
//#ifdef GL_ARB_gpu_shader5 // Ref: AnyOldName3: This slightly faster path is broken on Vega 56
#if 0
#extension GL_ARB_gpu_shader5 : enable
#define ENABLE_GL_ARB_gpu_shader5
#endif
#ifdef ENABLE_GL_ARB_gpu_shader5
layout (triangles, invocations = 2) in;
layout (triangle_strip, max_vertices = 3) out;
#else
layout (triangles) in;
layout (triangle_strip, max_vertices = 6) out;
#endif
// Geometry Shader Inputs
@INPUTS
// Geometry Shader Outputs
@OUTPUTS
// Stereo matrices
uniform mat4 stereoViewMatrices[2];
uniform mat4 stereoViewProjections[2];
void perVertex(int vertex, int viewport)
{
gl_ViewportIndex = viewport;
// Re-project
gl_Position = stereoViewProjections[viewport] * vec4(vertex_passViewPos[vertex],1);
vec4 viewPos = stereoViewMatrices[viewport] * vec4(vertex_passViewPos[vertex],1);
gl_ClipVertex = vec4(viewPos.xyz,1);
// Input -> output
@FORWARDING
EmitVertex();
}
void perViewport(int viewport)
{
for(int vertex = 0; vertex < gl_in.length(); vertex++)
{
perVertex(vertex, viewport);
}
EndPrimitive();
}
void main() {
#ifdef ENABLE_GL_ARB_gpu_shader5
int viewport = gl_InvocationID;
#else
for(int viewport = 0; viewport < 2; viewport++)
#endif
perViewport(viewport);
}

View file

@ -1,4 +1,4 @@
#version 120
#version 130
varying vec2 uv;
varying float euclideanDepth;

View file

@ -3,12 +3,15 @@
varying vec3 screenCoordsPassthrough;
varying vec4 position;
varying float linearDepth;
varying vec3 passViewPos;
#include "shadows_vertex.glsl"
void main(void)
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
vec4 viewPos = gl_ModelViewMatrix * gl_Vertex;
passViewPos = viewPos.xyz;
mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0,
0.0, -0.5, 0.0, 0.0,
@ -22,5 +25,5 @@ void main(void)
linearDepth = gl_Position.z;
setupShadowCoords(gl_ModelViewMatrix * gl_Vertex, normalize((gl_NormalMatrix * gl_Normal).xyz));
setupShadowCoords(viewPos, normalize((gl_NormalMatrix * gl_Normal).xyz));
}