Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
15 kB · 421 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422#include "stereomanager.hpp"#include "frustum.hpp"#include "multiview.hpp"
#include <osg/DisplaySettings>#include <osg/Texture2D>#include <osg/Texture2DArray>#include <osg/Texture2DMultisample>#include <osg/io_utils>
#include <osgUtil/CullVisitor>#include <osgUtil/RenderStage>
#include <osgViewer/Renderer>#include <osgViewer/Viewer>
#include <map>#include <string>
#include <components/misc/constants.hpp>
#include <components/sceneutil/color.hpp>#include <components/sceneutil/depth.hpp>#include <components/sceneutil/statesetupdater.hpp>
#include <components/settings/values.hpp>
namespace Stereo{ // Update stereo view/projection during update class StereoUpdateCallback final : public osg::Callback { public: StereoUpdateCallback(Manager* stereoView) : mStereoView(stereoView) { }
bool run(osg::Object* object, osg::Object* data) override { auto b = traverse(object, data); mStereoView->update(); return b; }
Manager* mStereoView; };
// Update states during cull class BruteForceStereoStatesetUpdateCallback final : public SceneUtil::StateSetUpdater { public: BruteForceStereoStatesetUpdateCallback(Manager* manager) : mManager(manager) { }
protected: virtual void setDefaults(osg::StateSet* stateset) override { stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); }
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override {}
void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override { auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(mManager->computeEyeProjection(0, SceneUtil::AutoDepth::isReversed())); }
void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override { auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(mManager->computeEyeProjection(1, SceneUtil::AutoDepth::isReversed())); }
private: Manager* mManager; };
// Update states during cull class MultiviewStereoStatesetUpdateCallback : public SceneUtil::StateSetUpdater { public: MultiviewStereoStatesetUpdateCallback(Manager* manager) : mManager(manager) { }
protected: virtual void setDefaults(osg::StateSet* stateset) { stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "invProjectionMatrixMultiView", 2)); }
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) { mManager->updateMultiviewStateset(stateset); }
private: Manager* mManager; };
static bool sStereoEnabled = false;
static Manager* sInstance = nullptr;
Manager& Manager::instance() { return *sInstance; }
Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo, float near, float far) : mViewer(viewer) , mMainCamera(mViewer->getCamera()) , mUpdateCallback(new StereoUpdateCallback(this)) , mMasterProjectionMatrix(osg::Matrixd::identity()) , mEyeResolutionOverriden(false) , mEyeResolutionOverride(0, 0) , mNear(near) , mFar(far) , mFrustumManager(nullptr) , mUpdateViewCallback(nullptr) { if (sInstance) throw std::logic_error("Double instance of Stereo::Manager"); sInstance = this; sStereoEnabled = enableStereo; }
Manager::~Manager() {}
void Manager::initializeStereo(osg::GraphicsContext* gc, bool enableMultiview, bool sharedShadowMaps) { auto ci = gc->getState()->getContextID(); configureExtensions(ci, enableMultiview);
mMainCamera->addUpdateCallback(mUpdateCallback); mFrustumManager = std::make_unique<StereoFrustumManager>(sharedShadowMaps, mViewer->getCamera());
if (getMultiview()) setupOVRMultiView2Technique(); else setupBruteForceTechnique();
updateStereoFramebuffer(); }
void shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) { if (getMultiview()) { defines["useOVR_multiview"] = "1"; defines["numViews"] = "2"; } else { defines["useOVR_multiview"] = "0"; defines["numViews"] = "1"; } }
void Manager::overrideEyeResolution(const osg::Vec2i& eyeResolution) { mEyeResolutionOverride = eyeResolution; mEyeResolutionOverriden = true;
// if (mMultiviewFramebuffer) // updateStereoFramebuffer(); }
void Manager::screenResolutionChanged() { updateStereoFramebuffer(); }
osg::Vec2i Manager::eyeResolution() { if (mEyeResolutionOverriden) return mEyeResolutionOverride; auto width = static_cast<int>(mMainCamera->getViewport()->width() / 2); auto height = static_cast<int>(mMainCamera->getViewport()->height());
return osg::Vec2i(width, height); }
void Manager::disableStereoForNode(osg::Node* node) { // Re-apply the main camera's full viewport to return to full screen rendering. node->getOrCreateStateSet()->setAttribute(mMainCamera->getViewport()); }
void Manager::setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique) { if (mFrustumManager) mFrustumManager->setShadowTechnique(shadowTechnique); }
void Manager::setupBruteForceTechnique() { auto* ds = osg::DisplaySettings::instance().get(); ds->setStereo(true); ds->setStereoMode(osg::DisplaySettings::StereoMode::HORIZONTAL_SPLIT); ds->setUseSceneViewForStereoHint(true);
mMainCamera->addCullCallback(new BruteForceStereoStatesetUpdateCallback(this));
struct ComputeStereoMatricesCallback : public osgUtil::SceneView::ComputeStereoMatricesCallback { ComputeStereoMatricesCallback(Manager* sv) : mManager(sv) { }
osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection) const override { (void)projection; return mManager->computeEyeProjection(0, false); }
osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const override { return view * mManager->computeEyeViewOffset(0); }
osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection) const override { (void)projection; return mManager->computeEyeProjection(1, false); }
osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const override { return view * mManager->computeEyeViewOffset(1); }
Manager* mManager; };
auto* renderer = static_cast<osgViewer::Renderer*>(mMainCamera->getRenderer()); for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) }) { sceneView->setComputeStereoMatricesCallback(new ComputeStereoMatricesCallback(this));
auto* cvMain = sceneView->getCullVisitor(); auto* cvLeft = sceneView->getCullVisitorLeft(); auto* cvRight = sceneView->getCullVisitorRight(); if (!cvMain) sceneView->setCullVisitor(cvMain = new osgUtil::CullVisitor()); if (!cvLeft) sceneView->setCullVisitor(cvLeft = cvMain->clone()); if (!cvRight) sceneView->setCullVisitor(cvRight = cvMain->clone());
// Osg by default gives cullVisitorLeft and cullVisitor the same identifier. // So we make our own to avoid confusion cvMain->setIdentifier(mIdentifierMain); cvLeft->setIdentifier(mIdentifierLeft); cvRight->setIdentifier(mIdentifierRight); } }
void Manager::setupOVRMultiView2Technique() { auto* ds = osg::DisplaySettings::instance().get(); ds->setStereo(false);
mMainCamera->addCullCallback(new MultiviewStereoStatesetUpdateCallback(this)); }
void Manager::updateStereoFramebuffer() { // VR-TODO: in VR, still need to have this framebuffer attached before the postprocessor is created // auto samples = /*do not use Settings here*/; // auto eyeRes = eyeResolution();
// if (mMultiviewFramebuffer) // mMultiviewFramebuffer->detachFrom(mMainCamera); // mMultiviewFramebuffer = std::make_shared<MultiviewFramebuffer>(static_cast<int>(eyeRes.x()), // static_cast<int>(eyeRes.y()), samples); // mMultiviewFramebuffer->attachColorComponent(SceneUtil::Color::colorSourceFormat(), // SceneUtil::Color::colorSourceType(), SceneUtil::Color::colorInternalFormat()); // mMultiviewFramebuffer->attachDepthComponent(SceneUtil::AutoDepth::depthSourceFormat(), // SceneUtil::AutoDepth::depthSourceType(), SceneUtil::AutoDepth::depthInternalFormat()); // mMultiviewFramebuffer->attachTo(mMainCamera); }
void Manager::update() { if (mUpdateViewCallback) { mUpdateViewCallback->updateView(mView[0], mView[1]); mViewOffsetMatrix[0] = mView[0].viewMatrix(true); mViewOffsetMatrix[1] = mView[1].viewMatrix(true); mProjectionMatrix[0] = mView[0].perspectiveMatrix(mNear, mFar, false); mProjectionMatrix[1] = mView[1].perspectiveMatrix(mNear, mFar, false); if (SceneUtil::AutoDepth::isReversed()) { mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(mNear, mFar, true); mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(mNear, mFar, true); }
View masterView; masterView.fov.angleDown = std::min(mView[0].fov.angleDown, mView[1].fov.angleDown); masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp); masterView.fov.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft); masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight); auto projectionMatrix = masterView.perspectiveMatrix(mNear, mFar, false); mMainCamera->setProjectionMatrix(projectionMatrix); } else { auto* ds = osg::DisplaySettings::instance().get(); const auto& projectionMatrix = mMainCamera->getProjectionMatrix(); auto s = ds->getEyeSeparation() * Constants::UnitsPerMeter; mViewOffsetMatrix[0] = osg::Matrixd(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, s, 0.0, 0.0, 1.0); mViewOffsetMatrix[1] = osg::Matrixd(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -s, 0.0, 0.0, 1.0); mProjectionMatrix[0] = ds->computeLeftEyeProjectionImplementation(projectionMatrix); mProjectionMatrix[1] = ds->computeRightEyeProjectionImplementation(projectionMatrix); if (SceneUtil::AutoDepth::isReversed()) { mProjectionMatrixReverseZ[0] = ds->computeLeftEyeProjectionImplementation(mMasterProjectionMatrix); mProjectionMatrixReverseZ[1] = ds->computeRightEyeProjectionImplementation(mMasterProjectionMatrix); } }
mFrustumManager->update({ mProjectionMatrix[0], mProjectionMatrix[1] }); }
void Manager::updateMultiviewStateset(osg::StateSet* stateset) { std::array<osg::Matrix, 2> projectionMatrices;
for (int view : { 0, 1 }) projectionMatrices[view] = computeEyeViewOffset(view) * computeEyeProjection(view, SceneUtil::AutoDepth::isReversed());
Stereo::setMultiviewMatrices(stateset, projectionMatrices, true); }
void Manager::setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb) { mUpdateViewCallback = std::move(cb); }
void Manager::setCullCallback(osg::ref_ptr<osg::NodeCallback> cb) { mMainCamera->setCullCallback(cb); }
osg::Matrixd Manager::computeEyeProjection(int view, bool reverseZ) const { return reverseZ ? mProjectionMatrixReverseZ[view] : mProjectionMatrix[view]; }
osg::Matrixd Manager::computeEyeViewOffset(int view) const { return mViewOffsetMatrix[view]; }
Eye Manager::getEye(const osgUtil::CullVisitor* cv) const { if (cv->getIdentifier() == mIdentifierMain) return Eye::Center; if (cv->getIdentifier() == mIdentifierLeft) return Eye::Left; if (cv->getIdentifier() == mIdentifierRight) return Eye::Right; return Eye::Center; }
bool getStereo() { return sStereoEnabled; }
Manager::CustomViewCallback::CustomViewCallback(View& left, View& right) : mLeft(left) , mRight(right) { }
void Manager::CustomViewCallback::updateView(View& left, View& right) { left = mLeft; right = mRight; }
InitializeStereoOperation::InitializeStereoOperation(const Settings& settings) : GraphicsOperation("InitializeStereoOperation", false) , mMultiview(settings.mMultiview) , mSharedShadowMaps(settings.mSharedShadowMaps) , mCustomView(settings.mCustomView) , mEyeResolution(settings.mEyeResolution) { // Ideally, this would have belonged to the operator(). But the vertex buffer // hint has to be set before realize is called on the osg viewer, and so has to // be done here instead. Stereo::setVertexBufferHint(settings.mMultiview, settings.mAllowDisplayListsForMultiview); }
void InitializeStereoOperation::operator()(osg::GraphicsContext* graphicsContext) { auto& sm = Stereo::Manager::instance();
if (mCustomView.has_value()) sm.setUpdateViewCallback( std::make_shared<Stereo::Manager::CustomViewCallback>(mCustomView->mLeft, mCustomView->mRight));
if (mEyeResolution.has_value()) sm.overrideEyeResolution(*mEyeResolution);
sm.initializeStereo(graphicsContext, mMultiview, mSharedShadowMaps); }}