1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-21 08:39:42 +00:00

Configurability. Brute force option.

This commit is contained in:
Mads Buvik Sandvei 2020-09-19 20:55:06 +02:00
parent 943fe06173
commit 2952463112
12 changed files with 255 additions and 118 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

@ -365,6 +365,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mEncoding(ToUTF8::WINDOWS_1252)
, mEncoder(nullptr)
, mScreenCaptureOperation(nullptr)
, mStereoEnabled(false)
, mStereoOverride(false)
, mSkipMenu (false)
, mUseSound (true)
, mCompileAll (false)
@ -398,6 +400,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
OMW::Engine::~Engine()
{
mStereoView = nullptr;
mEnvironment.cleanup();
delete mScriptContext;
@ -696,13 +700,14 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
window->playVideo(logo, true);
}
mStereoEnabled = true; //!< TODO: TEMP
// 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().enableGeometryShader(true);
mResourceSystem->getSceneManager()->getShaderManager().setStereoGeometryShaderEnabled(Misc::getStereoTechnique() == Misc::StereoView::Technique::GeometryShader_IndexedViewports);
}
// Create the world
@ -718,7 +723,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
// 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));
mStereoView.reset(new Misc::StereoView(mViewer, Misc::getStereoTechnique(), geometryShaderMask, noShaderMask | MWRender::VisMask::Mask_Scene));
}
window->setStore(mEnvironment.getWorld()->getStore());

View file

@ -92,6 +92,7 @@ namespace OMW
std::vector<std::string> mContentFiles;
bool mStereoEnabled;
bool mStereoOverride;
std::unique_ptr<Misc::StereoView> mStereoView;
bool mSkipMenu;

View file

@ -248,7 +248,6 @@ public:
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);
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
// A double update would mess with the light collection (in addition to being plain redundant)
@ -344,7 +343,6 @@ public:
unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water");
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
@ -598,7 +596,19 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
// use a define map to conditionally compile the shader
std::map<std::string, std::string> defineMap;
defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0")));
defineMap["geometryShader"] = "1";
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));

View file

@ -1,4 +1,5 @@
#include "stereo.hpp"
#include "stringops.hpp"
#include <osg/io_utils>
#include <osg/ViewportIndexed>
@ -10,9 +11,12 @@
#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)
@ -221,65 +225,110 @@ namespace Misc
StereoView* stereoView;
};
StereoView::StereoView(osgViewer::Viewer* viewer, osg::Node::NodeMask geometryShaderMask, osg::Node::NodeMask bruteForceMask)
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)
, mBruteForceMask(bruteForceMask)
, 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("Sky 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->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);
// Do a blank double buffering of camera statesets on update. Actual stateset updates are performed in StereoView::Update()
mLeftCamera->setUpdateCallback(new SceneUtil::StateSetUpdater());
mRightCamera->setUpdateCallback(new SceneUtil::StateSetUpdater());
// 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()
@ -314,70 +363,83 @@ namespace Misc
mRightCamera->setProjectionMatrix(rightProjectionMatrix);
mLeftCamera->setProjectionMatrix(leftProjectionMatrix);
// 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;
//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);
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);
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)
@ -405,18 +467,6 @@ namespace Misc
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();
@ -450,6 +500,23 @@ namespace Misc
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);
@ -457,6 +524,6 @@ namespace Misc
left.fov = { -0.767549932, 0.620896876, -0.837898076, 0.726982594 };
right.fov = { -0.620896876, 0.767549932, -0.837898076, 0.726982594 };
near = 1;
far = 10000;
far = 6656;
}
}

View file

@ -78,11 +78,19 @@ namespace Misc
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.
//! \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);
//! \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.
@ -92,21 +100,23 @@ namespace Misc
//! 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);
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::Node::NodeMask mBruteForceMask;
osg::ref_ptr<osg::Camera> mLeftCamera{ new osg::Camera };
osg::ref_ptr<osg::Camera> mRightCamera{ new osg::Camera };
@ -122,6 +132,9 @@ namespace Misc
//! 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

@ -1,5 +1,7 @@
#include "sdlgraphicswindow.hpp"
#include <osgViewer/viewer>
#include <SDL_video.h>
namespace SDLUtil
@ -202,6 +204,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,7 +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);
mViewer->getCamera()->getGraphicsContext()->resized(x,y,w,h);
GraphicsWindowSDL2::findContext(*mViewer)->resized(x,y,w,h);
mViewer->getEventQueue()->windowResize(x,y,w,h);

View file

@ -515,11 +515,16 @@ namespace Shader
return DefineMap(mGlobalDefines);
}
void ShaderManager::enableGeometryShader(bool enabled)
void ShaderManager::setStereoGeometryShaderEnabled(bool enabled)
{
mGeometryShadersEnabled = enabled;
}
bool ShaderManager::stereoGeometryShaderEnabled() const
{
return mGeometryShadersEnabled;
}
void ShaderManager::setGlobalDefines(DefineMap& globalDefines)
{
mGlobalDefines = globalDefines;

View file

@ -41,7 +41,8 @@ namespace 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);
void setStereoGeometryShaderEnabled(bool enabled);
bool stereoGeometryShaderEnabled() const;
/// Set the DefineMap used to construct all shaders
/// @param defines The DefineMap to use

View file

@ -901,3 +901,14 @@ 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