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:
parent
943fe06173
commit
2952463112
12 changed files with 255 additions and 118 deletions
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -92,6 +92,7 @@ namespace OMW
|
|||
std::vector<std::string> mContentFiles;
|
||||
|
||||
bool mStereoEnabled;
|
||||
bool mStereoOverride;
|
||||
std::unique_ptr<Misc::StereoView> mStereoView;
|
||||
|
||||
bool mSkipMenu;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue