Sharing shadow maps across eyes.

Does not yet expand frustum.
pull/615/head
Mads Buvik Sandvei 5 years ago
parent b7bda3544b
commit e0b51af395

@ -173,6 +173,8 @@ if(BUILD_VR_OPENXR)
mwvr/vrsession.cpp
mwvr/vrframebuffer.hpp
mwvr/vrframebuffer.cpp
mwvr/vrshadow.hpp
mwvr/vrshadow.cpp
mwvr/vrtypes.hpp
mwvr/vrtypes.cpp
mwvr/vrview.hpp

@ -100,7 +100,7 @@ namespace MWVR
bool mSessionRunning = false;
std::mutex mFrameStateMutex{};
std::mutex mEventMutex{};
std::set<const char*> mEnabledExtensions;
std::set<std::string> mEnabledExtensions;
std::array<XrCompositionLayerDepthInfoKHR, 2> mLayerDepth;
};

@ -71,12 +71,31 @@ namespace MWVR
return fov.perspectiveMatrix(near_, far_);
}
osg::Matrix VRSession::viewMatrix(osg::Vec3 position, osg::Quat orientation)
{
position = position * Environment::get().unitsPerMeter();
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;
}
osg::Matrix VRSession::viewMatrix(FramePhase phase, Side side)
{
MWVR::Pose pose{};
pose = predictedPoses(phase).view[(int)side].pose;
if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame)
{
pose = predictedPoses(phase).eye[(int)side];
@ -89,25 +108,8 @@ namespace MWVR
return viewMatrix;
}
osg::Vec3 position = pose.position * Environment::get().unitsPerMeter();
osg::Quat orientation = pose.orientation;
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;
return viewMatrix(pose.position, pose.orientation);
}
bool VRSession::isRunning() const {
@ -228,10 +230,8 @@ namespace MWVR
void VRSession::prepareFrame()
{
std::unique_lock<std::mutex> lock(mMutex);
mFrames++;
assert(!mPredrawFrame);
auto* xr = Environment::get().getManager();
xr->handleEvents();
@ -347,7 +347,6 @@ namespace MWVR
void VRSession::movementAngles(float& yaw, float& pitch)
{
assert(mPredrawFrame);
if (!getFrame(FramePhase::Update))
beginPhase(FramePhase::Update);
auto frameMeta = getFrame(FramePhase::Update).get();
@ -363,8 +362,6 @@ namespace MWVR
getEulerAngles(frameMeta->mPredictedPoses.head.orientation, headYaw, headPitch, headsWillRoll);
getEulerAngles(frameMeta->mPredictedPoses.hands[(int)Side::LEFT_SIDE].orientation, handYaw, handPitch, handRoll);
//Log(Debug::Verbose) << "lhandViewYaw=" << yaw << ", headYaw=" << headYaw << ", handYaw=" << handYaw << ", diff=" << (handYaw - headYaw);
//Log(Debug::Verbose) << "lhandViewPitch=" << pitch << ", headPitch=" << headPitch << ", handPitch=" << handPitch << ", diff=" << (handPitch - headPitch);
yaw = handYaw - headYaw;
pitch = handPitch - headPitch;
}

@ -73,6 +73,7 @@ namespace MWVR
float playerScale() const { return mPlayerScale; }
float setPlayerScale(float scale) { return mPlayerScale = scale; }
osg::Matrix viewMatrix(osg::Vec3 position, osg::Quat orientation);
osg::Matrix viewMatrix(FramePhase phase, Side side);
osg::Matrix projectionMatrix(FramePhase phase, Side side);

@ -0,0 +1,111 @@
#include "vrenvironment.hpp"
#include "vrsession.hpp"
#include "vrshadow.hpp"
#include "../mwrender/vismask.hpp"
#include <components/sceneutil/mwshadowtechnique.hpp>
#include <cassert>
namespace MWVR
{
VrShadow::VrShadow(osgViewer::Viewer* viewer, int renderOrder)
: mViewer(viewer)
, mRenderOrder(renderOrder)
, mShadowMapCamera(nullptr)
, mUpdateCallback(new UpdateShadowMapSlaveCallback)
, mMasterConfig(new SharedShadowMapConfig)
, mSlaveConfig(new SharedShadowMapConfig)
{
mMasterConfig->_id = "VR";
mMasterConfig->_master = true;
mSlaveConfig->_id = "VR";
mSlaveConfig->_master = false;
}
void VrShadow::configureShadowsForCamera(osg::Camera* camera)
{
camera->setUserData(mSlaveConfig);
if (camera->getRenderOrderNum() < mRenderOrder)
camera->setRenderOrder(camera->getRenderOrder(), mRenderOrder + 1);
}
void VrShadow::configureShadows(bool enabled)
{
if (enabled)
{
if (!mShadowMapCamera)
{
mShadowMapCamera = new osg::Camera();
mShadowMapCamera->setName("ShadowMap");
mShadowMapCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mShadowMapCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
mShadowMapCamera->setRenderOrder(osg::Camera::PRE_RENDER, mRenderOrder);
mShadowMapCamera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
mShadowMapCamera->setAllowEventFocus(false);
mShadowMapCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
mShadowMapCamera->setViewport(0, 0, 640, 360);
mShadowMapCamera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext());
mShadowMapCamera->setCullMask(0);
mShadowMapCamera->setUserData(mMasterConfig);
mViewer->addSlave(mShadowMapCamera, true);
auto* slave = mViewer->findSlaveForCamera(mShadowMapCamera);
assert(slave);
slave->_updateSlaveCallback = mUpdateCallback;
}
}
else
{
if (mShadowMapCamera)
{
mViewer->removeSlave(mViewer->findSlaveIndexForCamera(mShadowMapCamera));
mShadowMapCamera = nullptr;
}
}
}
void UpdateShadowMapSlaveCallback::updateSlave(osg::View& view, osg::View::Slave& slave)
{
auto* camera = slave._camera.get();
auto* session = Environment::get().getSession();
auto viewMatrix = view.getCamera()->getViewMatrix();
auto& poses = session->predictedPoses(VRSession::FramePhase::Update);
auto& leftView = poses.view[(int)Side::LEFT_SIDE];
auto& rightView = poses.view[(int)Side::RIGHT_SIDE];
osg::Vec3d leftEye = leftView.pose.position;
osg::Vec3d rightEye = rightView.pose.position;
// The shadow map will be computed from a position P slightly behind the eyes L and R
// where it creates the minimum frustum encompassing both eyes' frustums.
// Compute Frustum angles. A simple min/max.
FieldOfView fov;
fov.angleLeft = std::min(leftView.fov.angleLeft, rightView.fov.angleLeft);
fov.angleRight = std::max(leftView.fov.angleRight, rightView.fov.angleRight);
fov.angleDown = std::min(leftView.fov.angleDown, rightView.fov.angleDown);
fov.angleUp = std::max(leftView.fov.angleUp, rightView.fov.angleUp);
// Use the law of sines on the triangle spanning PLR to determine P
double angleLeft = std::abs(fov.angleLeft);
double angleRight = std::abs(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 P = leftEye + directionLP * lengthLP;
// Generate the matrices
float near_ = Settings::Manager::getFloat("near clip", "Camera");
float far_ = Settings::Manager::getFloat("viewing distance", "Camera");
auto modifiedViewMatrix = viewMatrix * session->viewMatrix(P, osg::Quat(0, 0, 0, 1));
auto projectionMatrix = fov.perspectiveMatrix(near_, far_);
camera->setViewMatrix(modifiedViewMatrix);
camera->setProjectionMatrix(projectionMatrix);
slave.updateSlaveImplementation(view);
}
}

@ -0,0 +1,38 @@
#ifndef MWVR_VRSHADOW_H
#define MWVR_VRSHADOW_H
#include <osg/Camera>
#include <osgViewer/Viewer>
#include <components/sceneutil/mwshadowtechnique.hpp>
namespace MWVR
{
class UpdateShadowMapSlaveCallback : public osg::View::Slave::UpdateSlaveCallback
{
public:
void updateSlave(osg::View& view, osg::View::Slave& slave) override;
};
class VrShadow
{
using SharedShadowMapConfig = SceneUtil::MWShadowTechnique::SharedShadowMapConfig;
public:
VrShadow(osgViewer::Viewer* viewer, int renderOrder = 0);
void configureShadowsForCamera(osg::Camera* camera);
void configureShadows(bool enabled);
private:
osgViewer::Viewer* mViewer;
int mRenderOrder;
osg::ref_ptr<osg::Camera> mShadowMapCamera;
osg::ref_ptr< UpdateShadowMapSlaveCallback > mUpdateCallback;
osg::ref_ptr<SharedShadowMapConfig> mMasterConfig;
osg::ref_ptr<SharedShadowMapConfig> mSlaveConfig;
};
}
#endif

@ -36,13 +36,13 @@ namespace MWVR {
}
};
osg::Camera* VRView::createCamera(int eye, const osg::Vec4& clearColor, osg::GraphicsContext* gc)
osg::Camera* VRView::createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc)
{
osg::ref_ptr<osg::Camera> camera = new osg::Camera();
camera->setClearColor(clearColor);
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
camera->setRenderOrder(osg::Camera::PRE_RENDER, eye + 2);
camera->setRenderOrder(osg::Camera::PRE_RENDER, order);
camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
camera->setAllowEventFocus(false);
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);

@ -1,5 +1,5 @@
#ifndef OPENXR_VIEW_HPP
#define OPENXR_VIEW_HPP
#ifndef MWVR_VRVIEW_H
#define MWVR_VRVIEW_H
#include <cassert>
#include "openxrmanager.hpp"

@ -8,6 +8,10 @@
#include "../mwrender/vismask.hpp"
#include <osgViewer/Renderer>
#include <components/sceneutil/mwshadowtechnique.hpp>
namespace MWVR
{
@ -32,6 +36,7 @@ namespace MWVR
: mViewer(viewer)
, mPreDraw(new PredrawCallback(this))
, mPostDraw(new PostdrawCallback(this))
, mVrShadow(viewer, 1)
, mConfigured(false)
{
mViewer->setRealizeOperation(new RealizeOperation());
@ -81,14 +86,17 @@ namespace MWVR
osg::Vec4 clearColor = mainCamera->getClearColor();
auto config = xr->getRecommendedSwapchainConfig();
bool mirror = Settings::Manager::getBool("mirror texture", "VR");
mFlipMirrorTextureOrder = Settings::Manager::getBool("flip mirror texture order", "VR");
// TODO: If mirror is false either hide the window or paste something meaningful into it.
// E.g. Fanart of Dagoth UR wearing a VR headset
mVrShadow.configureShadows(Settings::Manager::getBool("enable shadows", "Shadows"));
for (unsigned i = 0; i < sViewNames.size(); i++)
{
auto view = new VRView(sViewNames[i], config[i], context->getState());
mViews[sViewNames[i]] = view;
auto camera = mCameras[sViewNames[i]] = view->createCamera(i, clearColor, context);
auto camera = mCameras[sViewNames[i]] = view->createCamera(i + 2, clearColor, context);
camera->setPreDrawCallback(mPreDraw);
camera->setFinalDrawCallback(mPostDraw);
camera->setCullMask(~MWRender::Mask_GUI & ~MWRender::Mask_SimpleWater & ~MWRender::Mask_UpdateVisitor);
@ -97,13 +105,17 @@ namespace MWVR
camera->setSmallFeatureCullingPixelSize(smallFeatureCullingPixelSize);
camera->setCullingMode(cullingMode);
mViewer->addSlave(camera, true);
mViewer->getSlave(i)._updateSlaveCallback = new VRView::UpdateSlaveCallback(view, context);
auto* slave = mViewer->findSlaveForCamera(camera);
assert(slave);
slave->_updateSlaveCallback = new VRView::UpdateSlaveCallback(view, context);
if (mirror)
mMsaaResolveMirrorTexture[i].reset(new VRFramebuffer(context->getState(),
view->swapchain().width(),
view->swapchain().height(),
0));
mVrShadow.configureShadowsForCamera(camera);
}
if (mirror)
@ -156,8 +168,10 @@ namespace MWVR
resolveTexture.bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
mViews[sViewNames[i]]->swapchain().renderBuffer()->blit(gc, 0, 0, resolveTexture.width(), resolveTexture.height());
mMirrorTexture->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
// Mirror the index when rendering to the mirror texture to allow cross eye mirror textures.
unsigned mirrorIndex = sViewNames.size() - 1 - i;
unsigned mirrorIndex = i;
if (mFlipMirrorTextureOrder)
mirrorIndex = sViewNames.size() - 1 - i;
resolveTexture.blit(gc, mirrorIndex * mirrorWidth, 0, (mirrorIndex + 1) * mirrorWidth, screenHeight);
}

@ -1,5 +1,5 @@
#ifndef MWVR_OPENRXVIEWER_H
#define MWVR_OPENRXVIEWER_H
#ifndef MWVR_VRVIEWER_H
#define MWVR_VRVIEWER_H
#include <memory>
#include <array>
@ -10,6 +10,7 @@
#include <osgViewer/Viewer>
#include "openxrmanager.hpp"
#include "vrshadow.hpp"
#include <components/sceneutil/positionattitudetransform.hpp>
@ -92,10 +93,12 @@ namespace MWVR
osg::GraphicsContext* mMainCameraGC{ nullptr };
std::unique_ptr<VRFramebuffer> mMsaaResolveMirrorTexture[2]{ };
std::unique_ptr<VRFramebuffer> mMirrorTexture{ nullptr };
VrShadow mVrShadow;
std::mutex mMutex;
std::mutex mMutex{};
bool mConfigured{ false };
bool mFlipMirrorTextureOrder{ false };
};
}

@ -900,6 +900,75 @@ MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(os
return vdd.release();
}
MWShadowTechnique::ViewDependentData* MWShadowTechnique::getSharedVdd(const SharedShadowMapConfig& config)
{
auto it = _viewDependentDataShareMap.find(config._id);
if (it != _viewDependentDataShareMap.end())
return it->second;
return nullptr;
}
void MWShadowTechnique::addSharedVdd(const SharedShadowMapConfig& config, ViewDependentData* vdd)
{
_viewDependentDataShareMap[config._id] = vdd;
}
void SceneUtil::MWShadowTechnique::shareShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs)
{
// Prepare for rendering shadows using the shadow map owned by rhs.
// To achieve this i first copy all data that is not specific to this cv's camera and thus read-only,
// trusting openmw and osg won't overwrite that data before this frame is done rendering.
// This works due to the double buffering of CullVisitors by osg, but also requires that cull passes are serialized (relative to one another).
// Then initialize new copies of the data that will be written with view-specific data
// (the stateset and the texgens).
lhs->_viewDependentShadowMap = rhs->_viewDependentShadowMap;
lhs->_stateset->clear();
lhs->_lightDataList = rhs->_lightDataList;
lhs->_numValidShadows = rhs->_numValidShadows;
ShadowDataList& sdl = lhs->getShadowDataList();
ShadowDataList previous_sdl;
previous_sdl.swap(sdl);
for (auto rhs_sd : rhs->getShadowDataList())
{
osg::ref_ptr<ShadowData> lhs_sd;
if (previous_sdl.empty())
{
OSG_INFO << "Create new ShadowData" << std::endl;
lhs_sd = new ShadowData(lhs);
}
else
{
OSG_INFO << "Taking ShadowData from from of previous_sdl" << std::endl;
lhs_sd = previous_sdl.front();
previous_sdl.erase(previous_sdl.begin());
}
lhs_sd->_camera = rhs_sd->_camera;
lhs_sd->_textureUnit = rhs_sd->_textureUnit;
lhs_sd->_texture = rhs_sd->_texture;
sdl.push_back(lhs_sd);
}
cv.pushStateSet(_shadowRecievingPlaceholderStateSet.get());
osg::ref_ptr<osgUtil::StateGraph> decoratorStateGraph = cv.getCurrentStateGraph();
cullShadowReceivingScene(&cv);
cv.popStateSet();
for (auto sd : sdl)
{
assignTexGenSettings(&cv, sd->_camera, sd->_textureUnit, sd->_texgen.get());
}
if (lhs->numValidShadows() > 0)
{
decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*lhs));
}
}
void MWShadowTechnique::update(osg::NodeVisitor& nv)
{
OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<<std::endl;
@ -925,6 +994,29 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
ViewDependentData* vdd = getViewDependentData(&cv);
// Use shared shadow map if applicable
auto* sharedConfig = dynamic_cast<SharedShadowMapConfig*>(cv.getCurrentCamera()->getUserData());
if (sharedConfig)
{
if (sharedConfig->_master)
{
addSharedVdd(*sharedConfig, vdd);
}
else
{
auto* sharedVdd = getSharedVdd(*sharedConfig);
if (sharedVdd)
{
OSG_INFO << "Using shared shadow map" << std::endl;
return shareShadowMap(cv, vdd, sharedVdd);
}
else
{
OSG_INFO << "Warning, view configured to reuse shared shadow map but no shadow map has been shared. Shadows will be generated instead." << std::endl;
}
}
}
if (!vdd)
{
OSG_INFO<<"Warning, now ViewDependentData created, unable to create shadows."<<std::endl;
@ -1397,6 +1489,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
_debugHud->draw(sd->_texture, sm_i, camera->getViewMatrix() * camera->getProjectionMatrix(), cv);
}
}
vdd->setNumValidShadows(numValidShadows);
if (numValidShadows>0)
{

@ -139,6 +139,17 @@ namespace SceneUtil {
// forward declare
class ViewDependentData;
/// Configuration of shadow maps shared by multiple views
struct SharedShadowMapConfig : public osg::Referenced
{
virtual ~SharedShadowMapConfig() {}
/// String identifier of the shared shadow map
std::string _id;
bool _master;
};
struct LightData : public osg::Referenced
{
LightData(ViewDependentData* vdd);
@ -193,7 +204,12 @@ namespace SceneUtil {
virtual void releaseGLObjects(osg::State* = 0) const;
unsigned int numValidShadows(void) const { return _numValidShadows; }
void setNumValidShadows(unsigned int numValidShadows) { _numValidShadows = numValidShadows; }
protected:
friend class MWShadowTechnique;
virtual ~ViewDependentData() {}
MWShadowTechnique* _viewDependentShadowMap;
@ -202,11 +218,16 @@ namespace SceneUtil {
LightDataList _lightDataList;
ShadowDataList _shadowDataList;
unsigned int _numValidShadows;
};
virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv);
ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv);
ViewDependentData* getSharedVdd(const SharedShadowMapConfig& config);
void addSharedVdd(const SharedShadowMapConfig& config, ViewDependentData* vdd);
void shareShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs);
@ -234,8 +255,10 @@ namespace SceneUtil {
virtual ~MWShadowTechnique();
typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr<ViewDependentData> > ViewDependentDataMap;
typedef std::map< std::string, osg::ref_ptr<ViewDependentData> > ViewDependentDataShareMap;
mutable OpenThreads::Mutex _viewDependentDataMapMutex;
ViewDependentDataMap _viewDependentDataMap;
ViewDependentDataShareMap _viewDependentDataShareMap;
osg::ref_ptr<osg::StateSet> _shadowRecievingPlaceholderStateSet;

@ -896,3 +896,6 @@ realistic combat maximum swing velocity = 4.0
# Enables controller vibrations when you hit or are hit.
haptics enabled = true
# Flip the order of eyes in the mirror texture (to allow cross eyed view)
flip mirror texture order = false
Loading…
Cancel
Save