mirror of https://github.com/OpenMW/openmw.git
Initial commit
Multiview shaders. Refactor Frustum management Rewrite shared shadow map cull mask should respect stereo Stereo savegame screencap LocalMap refactoring use the vertex buffer hint instead of the display list patch to enable/disable display lists Character preview fixespull/3226/head
parent
2061a0b66e
commit
dd5901d351
@ -0,0 +1,26 @@
|
||||
set(TMP_ROOT ${CMAKE_BINARY_DIR}/try-compile)
|
||||
file(MAKE_DIRECTORY ${TMP_ROOT})
|
||||
|
||||
file(WRITE ${TMP_ROOT}/checkmultiview.cpp
|
||||
"
|
||||
#include <osg/Camera>
|
||||
int main(void)
|
||||
{
|
||||
(void)osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER;
|
||||
return 0;
|
||||
}
|
||||
")
|
||||
|
||||
message(STATUS "Checking if OSG supports multiview")
|
||||
|
||||
try_compile(RESULT_VAR
|
||||
${TMP_ROOT}/temp
|
||||
${TMP_ROOT}/checkmultiview.cpp
|
||||
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${OPENSCENEGRAPH_INCLUDE_DIRS}"
|
||||
)
|
||||
set(HAVE_MULTIVIEW ${RESULT_VAR})
|
||||
if(HAVE_MULTIVIEW)
|
||||
message(STATUS "Osg supports multiview")
|
||||
else(HAVE_MULTIVIEW)
|
||||
message(NOTICE "Osg does not support multiview, disabling use of GL_OVR_multiview")
|
||||
endif(HAVE_MULTIVIEW)
|
@ -0,0 +1,157 @@
|
||||
#include "color.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include <SDL_opengl_glext.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
|
||||
bool isColorFormat(GLenum format)
|
||||
{
|
||||
static constexpr std::array<GLenum, 42> formats = {
|
||||
GL_RGB,
|
||||
GL_RGB4,
|
||||
GL_RGB5,
|
||||
GL_RGB8,
|
||||
GL_RGB8_SNORM,
|
||||
GL_RGB10,
|
||||
GL_RGB12,
|
||||
GL_RGB16,
|
||||
GL_RGB16_SNORM,
|
||||
GL_SRGB,
|
||||
GL_SRGB8,
|
||||
GL_RGB16F,
|
||||
GL_RGB32F,
|
||||
GL_R11F_G11F_B10F,
|
||||
GL_RGB9_E5,
|
||||
GL_RGB8I,
|
||||
GL_RGB8UI,
|
||||
GL_RGB16I,
|
||||
GL_RGB16UI,
|
||||
GL_RGB32I,
|
||||
GL_RGB32UI,
|
||||
GL_RGBA,
|
||||
GL_RGBA2,
|
||||
GL_RGBA4,
|
||||
GL_RGB5_A1,
|
||||
GL_RGBA8,
|
||||
GL_RGBA8_SNORM,
|
||||
GL_RGB10_A2,
|
||||
GL_RGB10_A2UI,
|
||||
GL_RGBA12,
|
||||
GL_RGBA16,
|
||||
GL_RGBA16_SNORM,
|
||||
GL_SRGB_ALPHA8,
|
||||
GL_SRGB8_ALPHA8,
|
||||
GL_RGBA16F,
|
||||
GL_RGBA32F,
|
||||
GL_RGBA8I,
|
||||
GL_RGBA8UI,
|
||||
GL_RGBA16I,
|
||||
GL_RGBA16UI,
|
||||
GL_RGBA32I,
|
||||
GL_RGBA32UI,
|
||||
};
|
||||
|
||||
return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
|
||||
}
|
||||
|
||||
bool isFloatingPointColorFormat(GLenum format)
|
||||
{
|
||||
static constexpr std::array<GLenum, 5> formats = {
|
||||
GL_RGB16F,
|
||||
GL_RGB32F,
|
||||
GL_R11F_G11F_B10F,
|
||||
GL_RGBA16F,
|
||||
GL_RGBA32F,
|
||||
};
|
||||
|
||||
return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
|
||||
}
|
||||
|
||||
int getColorFormatChannelCount(GLenum format)
|
||||
{
|
||||
static constexpr std::array<GLenum, 21> formats = {
|
||||
GL_RGBA,
|
||||
GL_RGBA2,
|
||||
GL_RGBA4,
|
||||
GL_RGB5_A1,
|
||||
GL_RGBA8,
|
||||
GL_RGBA8_SNORM,
|
||||
GL_RGB10_A2,
|
||||
GL_RGB10_A2UI,
|
||||
GL_RGBA12,
|
||||
GL_RGBA16,
|
||||
GL_RGBA16_SNORM,
|
||||
GL_SRGB_ALPHA8,
|
||||
GL_SRGB8_ALPHA8,
|
||||
GL_RGBA16F,
|
||||
GL_RGBA32F,
|
||||
GL_RGBA8I,
|
||||
GL_RGBA8UI,
|
||||
GL_RGBA16I,
|
||||
GL_RGBA16UI,
|
||||
GL_RGBA32I,
|
||||
GL_RGBA32UI,
|
||||
};
|
||||
if (std::find(formats.cbegin(), formats.cend(), format) != formats.cend())
|
||||
return 4;
|
||||
return 3;
|
||||
}
|
||||
|
||||
void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType)
|
||||
{
|
||||
if (getColorFormatChannelCount(internalFormat == 4))
|
||||
sourceFormat = GL_RGBA;
|
||||
else
|
||||
sourceFormat = GL_RGB;
|
||||
|
||||
if (isFloatingPointColorFormat(internalFormat))
|
||||
sourceType = GL_FLOAT;
|
||||
else
|
||||
sourceType = GL_UNSIGNED_BYTE;
|
||||
}
|
||||
|
||||
namespace Color
|
||||
{
|
||||
GLenum sColorInternalFormat;
|
||||
GLenum sColorSourceFormat;
|
||||
GLenum sColorSourceType;
|
||||
|
||||
GLenum colorInternalFormat()
|
||||
{
|
||||
return sColorInternalFormat;
|
||||
}
|
||||
|
||||
GLenum colorSourceFormat()
|
||||
{
|
||||
return sColorSourceFormat;
|
||||
}
|
||||
|
||||
GLenum colorSourceType()
|
||||
{
|
||||
return sColorSourceType;
|
||||
}
|
||||
|
||||
void SelectColorFormatOperation::operator()([[maybe_unused]] osg::GraphicsContext* graphicsContext)
|
||||
{
|
||||
sColorInternalFormat = GL_RGB;
|
||||
|
||||
for (auto supportedFormat : mSupportedFormats)
|
||||
{
|
||||
if (isColorFormat(supportedFormat))
|
||||
{
|
||||
sColorInternalFormat = supportedFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getColorFormatSourceFormatAndType(sColorInternalFormat, sColorSourceFormat, sColorSourceType);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
#ifndef OPENMW_COMPONENTS_SCENEUTIL_COLOR_H
|
||||
#define OPENMW_COMPONENTS_SCENEUTIL_COLOR_H
|
||||
|
||||
#include <osg/GraphicsThread>
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
bool isColorFormat(GLenum format);
|
||||
bool isFloatingPointColorFormat(GLenum format);
|
||||
int getColorFormatChannelCount(GLenum format);
|
||||
void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType);
|
||||
|
||||
namespace Color
|
||||
{
|
||||
GLenum colorSourceFormat();
|
||||
GLenum colorSourceType();
|
||||
GLenum colorInternalFormat();
|
||||
|
||||
class SelectColorFormatOperation final : public osg::GraphicsOperation
|
||||
{
|
||||
public:
|
||||
SelectColorFormatOperation() : GraphicsOperation("SelectColorFormatOperation", false)
|
||||
{}
|
||||
|
||||
void operator()(osg::GraphicsContext* graphicsContext) override;
|
||||
|
||||
void setSupportedFormats(const std::vector<GLenum>& supportedFormats)
|
||||
{
|
||||
mSupportedFormats = supportedFormats;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<GLenum> mSupportedFormats;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef GL_RGB
|
||||
#define GL_RGB 0x1907
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA
|
||||
#define GL_RGBA 0x1908
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB4
|
||||
#define GL_RGB4 0x804F
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB5
|
||||
#define GL_RGB5 0x8050
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB8
|
||||
#define GL_RGB8 0x8051
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB8_SNORM
|
||||
#define GL_RGB8_SNORM 0x8F96
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB10
|
||||
#define GL_RGB10 0x8052
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB12
|
||||
#define GL_RGB12 0x8053
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB16
|
||||
#define GL_RGB16 0x8054
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB16_SNORM
|
||||
#define GL_RGB16_SNORM 0x8F9A
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA2
|
||||
#define GL_RGBA2 0x8055
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA4
|
||||
#define GL_RGBA4 0x8056
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB5_A1
|
||||
#define GL_RGB5_A1 0x8057
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA8
|
||||
#define GL_RGBA8 0x8058
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA8_SNORM
|
||||
#define GL_RGBA8_SNORM 0x8F97
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB10_A2
|
||||
#define GL_RGB10_A2 0x906F
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB10_A2UI
|
||||
#define GL_RGB10_A2UI 0x906F
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA12
|
||||
#define GL_RGBA12 0x805A
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA16
|
||||
#define GL_RGBA16 0x805B
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA16_SNORM
|
||||
#define GL_RGBA16_SNORM 0x8F9B
|
||||
#endif
|
||||
|
||||
#ifndef GL_SRGB
|
||||
#define GL_SRGB 0x8C40
|
||||
#endif
|
||||
|
||||
#ifndef GL_SRGB8
|
||||
#define GL_SRGB8 0x8C41
|
||||
#endif
|
||||
|
||||
#ifndef GL_SRGB_ALPHA8
|
||||
#define GL_SRGB_ALPHA8 0x8C42
|
||||
#endif
|
||||
|
||||
#ifndef GL_SRGB8_ALPHA8
|
||||
#define GL_SRGB8_ALPHA8 0x8C43
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB16F
|
||||
#define GL_RGB16F 0x881B
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA16F
|
||||
#define GL_RGBA16F 0x881A
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB32F
|
||||
#define GL_RGB32F 0x8815
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA32F
|
||||
#define GL_RGBA32F 0x8814
|
||||
#endif
|
||||
|
||||
#ifndef GL_R11F_G11F_B10F
|
||||
#define GL_R11F_G11F_B10F 0x8C3A
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef GL_RGB8I
|
||||
#define GL_RGB8I 0x8D8F
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB8UI
|
||||
#define GL_RGB8UI 0x8D7D
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB16I
|
||||
#define GL_RGB16I 0x8D89
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB16UI
|
||||
#define GL_RGB16UI 0x8D77
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB32I
|
||||
#define GL_RGB32I 0x8D83
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB32UI
|
||||
#define GL_RGB32UI 0x8D71
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA8I
|
||||
#define GL_RGBA8I 0x8D8E
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA8UI
|
||||
#define GL_RGBA8UI 0x8D7C
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA16I
|
||||
#define GL_RGBA16I 0x8D88
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA16UI
|
||||
#define GL_RGBA16UI 0x8D76
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA32I
|
||||
#define GL_RGBA32I 0x8D82
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA32UI
|
||||
#define GL_RGBA32UI 0x8D70
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
@ -0,0 +1,162 @@
|
||||
#include "stereomanager.hpp"
|
||||
#include "multiview.hpp"
|
||||
|
||||
#include <osg/io_utils>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Texture2DMultisample>
|
||||
#include <osg/Texture2DArray>
|
||||
#include <osg/DisplaySettings>
|
||||
|
||||
#include <osgUtil/CullVisitor>
|
||||
#include <osgUtil/RenderStage>
|
||||
|
||||
#include <osgViewer/Renderer>
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/sceneutil/visitor.hpp>
|
||||
#include <components/sceneutil/util.hpp>
|
||||
#include <components/sceneutil/depth.hpp>
|
||||
#include <components/sceneutil/color.hpp>
|
||||
#include <components/sceneutil/mwshadowtechnique.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include "frustum.hpp"
|
||||
|
||||
namespace Stereo
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
struct MultiviewFrustumCallback final : public osg::CullSettings::InitialFrustumCallback
|
||||
{
|
||||
MultiviewFrustumCallback(StereoFrustumManager* sfm)
|
||||
: mSfm(sfm)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void setInitialFrustum(osg::CullStack& cullStack, osg::Polytope& frustum) const override
|
||||
{
|
||||
auto cm = cullStack.getCullingMode();
|
||||
bool nearCulling = !!(cm & osg::CullSettings::NEAR_PLANE_CULLING);
|
||||
bool farCulling = !!(cm & osg::CullSettings::FAR_PLANE_CULLING);
|
||||
frustum.setToBoundingBox(mSfm->boundingBox(), nearCulling, farCulling);
|
||||
}
|
||||
|
||||
StereoFrustumManager* mSfm;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct ShadowFrustumCallback final : public SceneUtil::MWShadowTechnique::CustomFrustumCallback
|
||||
{
|
||||
ShadowFrustumCallback(StereoFrustumManager* parent) : mParent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) override
|
||||
{
|
||||
mParent->customFrustumCallback(cv, customClipSpace, sharedFrustumHint);
|
||||
}
|
||||
|
||||
StereoFrustumManager* mParent;
|
||||
};
|
||||
|
||||
void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb)
|
||||
{
|
||||
static const std::array<osg::Vec3d, 8> clipCorners = {{
|
||||
{-1.0, -1.0, -1.0},
|
||||
{ 1.0, -1.0, -1.0},
|
||||
{ 1.0, -1.0, 1.0},
|
||||
{-1.0, -1.0, 1.0},
|
||||
{-1.0, 1.0, -1.0},
|
||||
{ 1.0, 1.0, -1.0},
|
||||
{ 1.0, 1.0, 1.0},
|
||||
{-1.0, 1.0, 1.0}
|
||||
}};
|
||||
|
||||
osg::Matrix slaveClipToView;
|
||||
slaveClipToView.invert(slaveProjection);
|
||||
|
||||
for (const auto& clipCorner : clipCorners)
|
||||
{
|
||||
auto masterViewVertice = clipCorner * slaveClipToView;
|
||||
auto masterClipVertice = masterViewVertice * masterProjection;
|
||||
bb.expandBy(masterClipVertice);
|
||||
}
|
||||
}
|
||||
|
||||
StereoFrustumManager::StereoFrustumManager(osg::Camera* camera)
|
||||
: mCamera(camera)
|
||||
, mShadowTechnique(nullptr)
|
||||
, mShadowFrustumCallback(nullptr)
|
||||
{
|
||||
if (Stereo::getMultiview())
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
mMultiviewFrustumCallback = new MultiviewFrustumCallback(this);
|
||||
mCamera->setInitialFrustumCallback(mMultiviewFrustumCallback);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (Settings::Manager::getBool("shared shadow maps", "Stereo"))
|
||||
{
|
||||
mShadowFrustumCallback = new ShadowFrustumCallback(this);
|
||||
auto* renderer = static_cast<osgViewer::Renderer*>(mCamera->getRenderer());
|
||||
for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) })
|
||||
{
|
||||
mSharedFrustums[sceneView->getCullVisitorRight()] = sceneView->getCullVisitorLeft();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StereoFrustumManager::~StereoFrustumManager()
|
||||
{
|
||||
if (Stereo::getMultiview())
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
mCamera->setInitialFrustumCallback(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (mShadowTechnique)
|
||||
mShadowTechnique->setCustomFrustumCallback(nullptr);
|
||||
}
|
||||
|
||||
void StereoFrustumManager::setShadowTechnique(
|
||||
SceneUtil::MWShadowTechnique* shadowTechnique)
|
||||
{
|
||||
if (mShadowTechnique)
|
||||
mShadowTechnique->setCustomFrustumCallback(nullptr);
|
||||
mShadowTechnique = shadowTechnique;
|
||||
if (mShadowTechnique)
|
||||
mShadowTechnique->setCustomFrustumCallback(mShadowFrustumCallback);
|
||||
}
|
||||
|
||||
void StereoFrustumManager::customFrustumCallback(
|
||||
osgUtil::CullVisitor& cv,
|
||||
osg::BoundingBoxd& customClipSpace,
|
||||
osgUtil::CullVisitor*& sharedFrustumHint)
|
||||
{
|
||||
auto it = mSharedFrustums.find(&cv);
|
||||
if (it != mSharedFrustums.end())
|
||||
{
|
||||
sharedFrustumHint = it->second;
|
||||
}
|
||||
|
||||
customClipSpace = mBoundingBox;
|
||||
}
|
||||
|
||||
void StereoFrustumManager::update(std::array<osg::Matrix, 2> projections)
|
||||
{
|
||||
mBoundingBox.init();
|
||||
for (auto& projection : projections)
|
||||
joinBoundingBoxes(mCamera->getProjectionMatrix(), projection, mBoundingBox);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
#ifndef STEREO_FRUSTUM_H
|
||||
#define STEREO_FRUSTUM_H
|
||||
|
||||
#include <osg/Matrix>
|
||||
#include <osg/Vec3>
|
||||
#include <osg/Camera>
|
||||
#include <osg/StateSet>
|
||||
#include <osg/BoundingBox>
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <map>
|
||||
|
||||
#include <components/stereo/types.hpp>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class FrameBufferObject;
|
||||
class Texture2D;
|
||||
class Texture2DMultisample;
|
||||
class Texture2DArray;
|
||||
}
|
||||
|
||||
namespace osgViewer
|
||||
{
|
||||
class Viewer;
|
||||
}
|
||||
|
||||
namespace usgUtil
|
||||
{
|
||||
class CullVisitor;
|
||||
}
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
class MWShadowTechnique;
|
||||
}
|
||||
|
||||
namespace Stereo
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
struct MultiviewFrustumCallback;
|
||||
#endif
|
||||
|
||||
struct ShadowFrustumCallback;
|
||||
|
||||
void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb);
|
||||
|
||||
class StereoFrustumManager
|
||||
{
|
||||
public:
|
||||
StereoFrustumManager(osg::Camera* camera);
|
||||
~StereoFrustumManager();
|
||||
|
||||
void update(std::array<osg::Matrix, 2> projections);
|
||||
|
||||
const osg::BoundingBoxd& boundingBox() const { return mBoundingBox; }
|
||||
|
||||
void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique);
|
||||
|
||||
void customFrustumCallback(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint);
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Camera> mCamera;
|
||||
osg::ref_ptr<SceneUtil::MWShadowTechnique> mShadowTechnique;
|
||||
osg::ref_ptr<ShadowFrustumCallback> mShadowFrustumCallback;
|
||||
std::map< osgUtil::CullVisitor*, osgUtil::CullVisitor*> mSharedFrustums;
|
||||
osg::BoundingBoxd mBoundingBox;
|
||||
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
osg::ref_ptr<MultiviewFrustumCallback> mMultiviewFrustumCallback;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,484 @@
|
||||
#include "multiview.hpp"
|
||||
|
||||
#include <osg/FrameBufferObject>
|
||||
#include <osg/GLExtensions>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Texture2DMultisample>
|
||||
#include <osg/Texture2DArray>
|
||||
#include <osgUtil/RenderStage>
|
||||
#include <osgUtil/CullVisitor>
|
||||
|
||||
#include <components/sceneutil/nodecallback.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/stereo/stereomanager.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Stereo
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool getMultiviewSupportedImpl(unsigned int contextID)
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview"))
|
||||
{
|
||||
Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview\" not supported)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview2"))
|
||||
{
|
||||
Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview2\" not supported)";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
Log(Debug::Verbose) << "Disabling Multiview (OSG does not support multiview)";
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool getMultiviewSupported(unsigned int contextID)
|
||||
{
|
||||
static bool supported = getMultiviewSupportedImpl(contextID);
|
||||
return supported;
|
||||
}
|
||||
|
||||
bool getTextureViewSupportedImpl(unsigned int contextID)
|
||||
{
|
||||
if (!osg::isGLExtensionOrVersionSupported(contextID, "ARB_texture_view", 4.3))
|
||||
{
|
||||
Log(Debug::Verbose) << "Disabling texture views (opengl extension \"ARB_texture_view\" not supported)";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getTextureViewSupported(unsigned int contextID)
|
||||
{
|
||||
static bool supported = getTextureViewSupportedImpl(contextID);
|
||||
return supported;
|
||||
}
|
||||
|
||||
bool getMultiviewImpl(unsigned int contextID)
|
||||
{
|
||||
if (!Stereo::getStereo())
|
||||
{
|
||||
Log(Debug::Verbose) << "Disabling Multiview (disabled by config)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Settings::Manager::getBool("multiview", "Stereo"))
|
||||
{
|
||||
Log(Debug::Verbose) << "Disabling Multiview (disabled by config)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getMultiviewSupported(contextID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getTextureViewSupported(contextID))
|
||||
{
|
||||
Log(Debug::Verbose) << "Disabling Multiview (texture views not supported)";
|
||||
return false;
|
||||
}
|
||||
|
||||
Log(Debug::Verbose) << "Enabling Multiview";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getMultiview(unsigned int contextID)
|
||||
{
|
||||
static bool multiView = getMultiviewImpl(contextID);
|
||||
return multiView;
|
||||
}
|
||||
}
|
||||
|
||||
bool getTextureViewSupported()
|
||||
{
|
||||
return getTextureViewSupported(0);
|
||||
}
|
||||
|
||||
bool getMultiview()
|
||||
{
|
||||
return getMultiview(0);
|
||||
}
|
||||
|
||||
void configureExtensions(unsigned int contextID)
|
||||
{
|
||||
getTextureViewSupported(contextID);
|
||||
getMultiviewSupported(contextID);
|
||||
getMultiview(contextID);
|
||||
}
|
||||
|
||||
void setVertexBufferHint()
|
||||
{
|
||||
if (getStereo() && Settings::Manager::getBool("multiview", "Stereo"))
|
||||
{
|
||||
auto* ds = osg::DisplaySettings::instance().get();
|
||||
if (!Settings::Manager::getBool("allow display lists for multiview", "Stereo")
|
||||
&& ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE)
|
||||
{
|
||||
// Note that this only works if this code is executed before realize() is called on the viewer.
|
||||
// The hint is read by the state object only once, before the user realize operations are run.
|
||||
// Therefore we have to set this hint without access to a graphics context to let us determine
|
||||
// if multiview will actually be supported or not. So if the user has requested multiview, we
|
||||
// will just have to set it regardless.
|
||||
ds->setVertexBufferHint(osg::DisplaySettings::VertexBufferHint::VERTEX_BUFFER_OBJECT);
|
||||
Log(Debug::Verbose) << "Disabling display lists";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Texture2DViewSubloadCallback : public osg::Texture2D::SubloadCallback
|
||||
{
|
||||
public:
|
||||
Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer);
|
||||
|
||||
void load(const osg::Texture2D& texture, osg::State& state) const override;
|
||||
void subload(const osg::Texture2D& texture, osg::State& state) const override;
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Texture2DArray> mTextureArray;
|
||||
int mLayer;
|
||||
};
|
||||
|
||||
Texture2DViewSubloadCallback::Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer)
|
||||
: mTextureArray(textureArray)
|
||||
, mLayer(layer)
|
||||
{
|
||||
}
|
||||
|
||||
void Texture2DViewSubloadCallback::load(const osg::Texture2D& texture, osg::State& state) const
|
||||
{
|
||||
state.checkGLErrors("before Texture2DViewSubloadCallback::load()");
|
||||
|
||||
auto contextId = state.getContextID();
|
||||
auto* gl = osg::GLExtensions::Get(contextId, false);
|
||||
mTextureArray->apply(state);
|
||||
|
||||
auto sourceTextureObject = mTextureArray->getTextureObject(contextId);
|
||||
if (!sourceTextureObject)
|
||||
{
|
||||
Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2DArray did not have a texture object";
|
||||
return;
|
||||
}
|
||||
|
||||
auto targetTextureObject = texture.getTextureObject(contextId);
|
||||
if (!sourceTextureObject)
|
||||
{
|
||||
Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2D did not have a texture object";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// OSG already bound this texture ID, giving it a target.
|
||||
// Delete it and make a new texture ID.
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDeleteTextures(1, &targetTextureObject->_id);
|
||||
glGenTextures(1, &targetTextureObject->_id);
|
||||
|
||||
auto sourceId = sourceTextureObject->_id;
|
||||
auto targetId = targetTextureObject->_id;
|
||||
auto internalFormat = sourceTextureObject->_profile._internalFormat;
|
||||
auto levels = std::max(1, sourceTextureObject->_profile._numMipmapLevels);
|
||||
|
||||
{
|
||||
////// OSG BUG
|
||||
// Texture views require immutable storage.
|
||||
// OSG should always give immutable storage to sized internal formats, but does not do so for depth formats.
|
||||
// Fortunately, we can just call glTexStorage3D here to make it immutable. This probably discards depth info for that frame, but whatever.
|
||||
#ifndef GL_TEXTURE_IMMUTABLE_FORMAT
|
||||
#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F
|
||||
#endif
|
||||
// Store any current binding and re-apply it after so i don't mess with state.
|
||||
GLint oldBinding = 0;
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &oldBinding);
|
||||
|
||||
// Bind the source texture and check if it's immutable.
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, sourceId);
|
||||
GLint immutable = 0;
|
||||
glGetTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
|
||||
if(!immutable)
|
||||
{
|
||||
// It wasn't immutable, so make it immutable.
|
||||
gl->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, internalFormat, sourceTextureObject->_profile._width, sourceTextureObject->_profile._height, 2);
|
||||
state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTexStorage3D");
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, oldBinding);
|
||||
}
|
||||
|
||||
gl->glTextureView(targetId, GL_TEXTURE_2D, sourceId, internalFormat, 0, levels, mLayer, 1);
|
||||
state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTextureView");
|
||||
glBindTexture(GL_TEXTURE_2D, targetId);
|
||||
}
|
||||
|
||||
void Texture2DViewSubloadCallback::subload(const osg::Texture2D& texture, osg::State& state) const
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer)
|
||||
{
|
||||
if (!getTextureViewSupported())
|
||||
{
|
||||
Log(Debug::Error) << "createTextureView_Texture2DFromTexture2DArray: Tried to use a texture view but glTextureView is not supported";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D;
|
||||
texture2d->setSubloadCallback(new Texture2DViewSubloadCallback(textureArray, layer));
|
||||
texture2d->setTextureSize(textureArray->getTextureWidth(), textureArray->getTextureHeight());
|
||||
texture2d->setBorderColor(textureArray->getBorderColor());
|
||||
texture2d->setBorderWidth(textureArray->getBorderWidth());
|
||||
texture2d->setLODBias(textureArray->getLODBias());
|
||||
texture2d->setFilter(osg::Texture::FilterParameter::MAG_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MAG_FILTER));
|
||||
texture2d->setFilter(osg::Texture::FilterParameter::MIN_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MIN_FILTER));
|
||||
texture2d->setInternalFormat(textureArray->getInternalFormat());
|
||||
texture2d->setNumMipmapLevels(textureArray->getNumMipmapLevels());
|
||||
return texture2d;
|
||||
}
|
||||
|
||||
class UpdateRenderStagesCallback : public SceneUtil::NodeCallback<UpdateRenderStagesCallback, osg::Node*, osgUtil::CullVisitor*>
|
||||
{
|
||||
public:
|
||||
UpdateRenderStagesCallback(Stereo::MultiviewFramebuffer* multiviewFramebuffer)
|
||||
: mMultiviewFramebuffer(multiviewFramebuffer)
|
||||
{
|
||||
mViewport = new osg::Viewport(0, 0, multiviewFramebuffer->width(), multiviewFramebuffer->height());
|
||||
mViewportStateset = new osg::StateSet();
|
||||
mViewportStateset->setAttribute(mViewport.get());
|
||||
}
|
||||
|
||||
void operator()(osg::Node* node, osgUtil::CullVisitor* cv)
|
||||
{
|
||||
osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage();
|
||||
|
||||
bool msaa = mMultiviewFramebuffer->samples() > 1;
|
||||
|
||||
if (!Stereo::getMultiview())
|
||||
{
|
||||
auto eye = static_cast<int>(Stereo::Manager::instance().getEye(cv));
|
||||
|
||||
if (msaa)
|
||||
{
|
||||
renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerMsaaFbo(eye));
|
||||
renderStage->setMultisampleResolveFramebufferObject(mMultiviewFramebuffer->layerFbo(eye));
|
||||
}
|
||||
else
|
||||
{
|
||||
renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerFbo(eye));
|
||||
}
|
||||
}
|
||||
|
||||
// OSG tries to do a horizontal split, but we want to render to separate framebuffers instead.
|
||||
renderStage->setViewport(mViewport);
|
||||
cv->pushStateSet(mViewportStateset.get());
|
||||
traverse(node, cv);
|
||||
cv->popStateSet();
|
||||
}
|
||||
|
||||
private:
|
||||
Stereo::MultiviewFramebuffer* mMultiviewFramebuffer;
|
||||
osg::ref_ptr<osg::Viewport> mViewport;
|
||||
osg::ref_ptr<osg::StateSet> mViewportStateset;
|
||||
};
|
||||
|
||||
MultiviewFramebuffer::MultiviewFramebuffer(int width, int height, int samples)
|
||||
: mWidth(width)
|
||||
, mHeight(height)
|
||||
, mSamples(samples)
|
||||
, mMultiview(getMultiview())
|
||||
, mMultiviewFbo{ new osg::FrameBufferObject }
|
||||
, mLayerFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject }
|
||||
, mLayerMsaaFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject }
|
||||
{
|
||||
}
|
||||
|
||||
MultiviewFramebuffer::~MultiviewFramebuffer()
|
||||
{
|
||||
}
|
||||
|
||||
void MultiviewFramebuffer::attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat)
|
||||
{
|
||||
if (mMultiview)
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
mMultiviewColorTexture = createTextureArray(sourceFormat, sourceType, internalFormat);
|
||||
mMultiviewFbo->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMultiviewColorTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0));
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
mColorTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewColorTexture.get(), i);
|
||||
mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i]));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (mSamples > 1)
|
||||
{
|
||||
mMsaaColorTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat);
|
||||
mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMsaaColorTexture[i]));
|
||||
}
|
||||
mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat);
|
||||
mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiviewFramebuffer::attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat)
|
||||
{
|
||||
if (mMultiview)
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
mMultiviewDepthTexture = createTextureArray(sourceFormat, sourceType, internalFormat);
|
||||
mMultiviewFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMultiviewDepthTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0));
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
mDepthTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewDepthTexture.get(), i);
|
||||
mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i]));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (mSamples > 1)
|
||||
{
|
||||
mMsaaDepthTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat);
|
||||
mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMsaaDepthTexture[i]));
|
||||
}
|
||||
mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat);
|
||||
mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
osg::FrameBufferObject* MultiviewFramebuffer::multiviewFbo()
|
||||
{
|
||||
return mMultiviewFbo;
|
||||
}
|
||||
|
||||
osg::FrameBufferObject* MultiviewFramebuffer::layerFbo(int i)
|
||||
{
|
||||
return mLayerFbo[i];
|
||||
}
|
||||
|
||||
osg::FrameBufferObject* MultiviewFramebuffer::layerMsaaFbo(int i)
|
||||
{
|
||||
return mLayerMsaaFbo[i];
|
||||
}
|
||||
|
||||
osg::Texture2DArray* MultiviewFramebuffer::multiviewColorBuffer()
|
||||
{
|
||||
return mMultiviewColorTexture;
|
||||
}
|
||||
|
||||
osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i)
|
||||
{
|
||||
return mColorTexture[i];
|
||||
}
|
||||
|
||||
osg::Texture2D* MultiviewFramebuffer::layerDepthBuffer(int i)
|
||||
{
|
||||
return mDepthTexture[i];
|
||||
}
|
||||
void MultiviewFramebuffer::attachTo(osg::Camera* camera)
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
if (mMultiview)
|
||||
{
|
||||
if (mMultiviewColorTexture)
|
||||
{
|
||||
camera->attach(osg::Camera::COLOR_BUFFER, mMultiviewColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples);
|
||||
camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mMultiviewColorTexture->getInternalFormat();
|
||||
camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = false;
|
||||
}
|
||||
if (mMultiviewDepthTexture)
|
||||
{
|
||||
camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mMultiviewDepthTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples);
|
||||
camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mMultiviewDepthTexture->getInternalFormat();
|
||||
camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
||||
|
||||
if (!mCullCallback)
|
||||
mCullCallback = new UpdateRenderStagesCallback(this);
|
||||
camera->addCullCallback(mCullCallback);
|
||||
}
|
||||
|
||||
void MultiviewFramebuffer::detachFrom(osg::Camera* camera)
|
||||
{
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
if (mMultiview)
|
||||
{
|
||||
if (mMultiviewColorTexture)
|
||||
{
|
||||
camera->detach(osg::Camera::COLOR_BUFFER);
|
||||
}
|
||||
if (mMultiviewDepthTexture)
|
||||
{
|
||||
camera->detach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER);
|
||||
if (mCullCallback)
|
||||
camera->removeCullCallback(mCullCallback);
|
||||
}
|
||||
|
||||
osg::Texture2D* MultiviewFramebuffer::createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat)
|
||||
{
|
||||
osg::Texture2D* texture = new osg::Texture2D;
|
||||
texture->setTextureSize(mWidth, mHeight);
|
||||
texture->setSourceFormat(sourceFormat);
|
||||
texture->setSourceType(sourceType);
|
||||
texture->setInternalFormat(internalFormat);
|
||||
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);
|
||||
return texture;
|
||||
}
|
||||
|
||||
osg::Texture2DMultisample* MultiviewFramebuffer::createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat)
|
||||
{
|
||||
osg::Texture2DMultisample* texture = new osg::Texture2DMultisample;
|
||||
texture->setTextureSize(mWidth, mHeight);
|
||||
texture->setNumSamples(mSamples);
|
||||
texture->setSourceFormat(sourceFormat);
|
||||
texture->setSourceType(sourceType);
|
||||
texture->setInternalFormat(internalFormat);
|
||||
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);
|
||||
return texture;
|
||||
}
|
||||
|
||||
osg::Texture2DArray* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat)
|
||||
{
|
||||
osg::Texture2DArray* textureArray = new osg::Texture2DArray;
|
||||
textureArray->setTextureSize(mWidth, mHeight, 2);
|
||||
textureArray->setSourceFormat(sourceFormat);
|
||||
textureArray->setSourceType(sourceType);
|
||||
textureArray->setInternalFormat(internalFormat);
|
||||
textureArray->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
textureArray->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
textureArray->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
textureArray->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
textureArray->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);
|
||||
return textureArray;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
#ifndef STEREO_MULTIVIEW_H
|
||||
#define STEREO_MULTIVIEW_H
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/GL>
|
||||
#include <osg/Camera>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class FrameBufferObject;
|
||||
class Texture;
|
||||
class Texture2D;
|
||||
class Texture2DMultisample;
|
||||
class Texture2DArray;
|
||||
}
|
||||
|
||||
namespace Stereo
|
||||
{
|
||||
class UpdateRenderStagesCallback;
|
||||
|
||||
//! Check if TextureView is supported. Results are undefined if called before configureExtensions().
|
||||
bool getTextureViewSupported();
|
||||
|
||||
//! Check if Multiview should be used. Results are undefined if called before configureExtensions().
|
||||
bool getMultiview();
|
||||
|
||||
//! Use the provided context to check what extensions are supported and configure use of multiview based on extensions and settings.
|
||||
void configureExtensions(unsigned int contextID);
|
||||
|
||||
//! Sets the appropriate vertex buffer hint on OSG's display settings if needed
|
||||
void setVertexBufferHint();
|
||||
|
||||
//! Creates a Texture2D as a texture view into a Texture2DArray
|
||||
osg::ref_ptr<osg::Texture2D> createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer);
|
||||
|
||||
//! Class that manages the specifics of GL_OVR_Multiview aware framebuffers, separating the layers into separate framebuffers, and disabling
|
||||
class MultiviewFramebuffer
|
||||
{
|
||||
public:
|
||||
MultiviewFramebuffer(int width, int height, int samples);
|
||||
~MultiviewFramebuffer();
|
||||
|
||||
void attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat);
|
||||
void attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat);
|
||||
|
||||
osg::FrameBufferObject* multiviewFbo();
|
||||
osg::FrameBufferObject* layerFbo(int i);
|
||||
osg::FrameBufferObject* layerMsaaFbo(int i);
|
||||
osg::Texture2DArray* multiviewColorBuffer();
|
||||
osg::Texture2D* layerColorBuffer(int i);
|
||||
osg::Texture2D* layerDepthBuffer(int i);
|
||||
|
||||
void attachTo(osg::Camera* camera);
|
||||
void detachFrom(osg::Camera* camera);
|
||||
|
||||
int width() const { return mWidth; }
|
||||
int height() const { return mHeight; }
|
||||
int samples() const { return mSamples; };
|
||||
|
||||
private:
|
||||
osg::Texture2D* createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat);
|
||||
osg::Texture2DMultisample* createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat);
|
||||
osg::Texture2DArray* createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat);
|
||||
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
int mSamples;
|
||||
bool mMultiview;
|
||||
osg::ref_ptr<UpdateRenderStagesCallback> mCullCallback;
|
||||
osg::ref_ptr<osg::FrameBufferObject> mMultiviewFbo;
|
||||
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mLayerFbo;
|
||||
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mLayerMsaaFbo;
|
||||
osg::ref_ptr<osg::Texture2DArray> mMultiviewColorTexture;
|
||||
osg::ref_ptr<osg::Texture2DArray> mMultiviewDepthTexture;
|
||||
std::array<osg::ref_ptr<osg::Texture2D>, 2> mColorTexture;
|
||||
std::array<osg::ref_ptr<osg::Texture2DMultisample>, 2> mMsaaColorTexture;
|
||||
std::array<osg::ref_ptr<osg::Texture2D>, 2> mDepthTexture;
|
||||
std::array<osg::ref_ptr<osg::Texture2DMultisample>, 2> mMsaaDepthTexture;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,459 @@
|
||||
#include "stereomanager.hpp"
|
||||
#include "multiview.hpp"
|
||||
#include "frustum.hpp"
|
||||
|
||||
#include <osg/io_utils>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Texture2DMultisample>
|
||||
#include <osg/Texture2DArray>
|
||||
#include <osg/DisplaySettings>
|
||||
|
||||
#include <osgUtil/CullVisitor>
|
||||
#include <osgUtil/RenderStage>
|
||||
|
||||
#include <osgViewer/Renderer>
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/sceneutil/visitor.hpp>
|
||||
#include <components/sceneutil/util.hpp>
|
||||
#include <components/sceneutil/depth.hpp>
|
||||
#include <components/sceneutil/color.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
namespace Stereo
|
||||
{
|
||||
// Update stereo view/projection during update
|
||||
class StereoUpdateCallback final : public osg::Callback
|
||||
{
|
||||
public:
|
||||
StereoUpdateCallback(Manager* stereoView) : stereoView(stereoView) {}
|
||||
|
||||
bool run(osg::Object* object, osg::Object* data) override
|
||||
{
|
||||
auto b = traverse(object, data);
|
||||
stereoView->update();
|
||||
return b;
|
||||
}
|
||||
|
||||
Manager* stereoView;
|
||||
};
|
||||
|
||||
// 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
|
||||
{
|
||||
osg::Matrix dummy;
|
||||
auto* uProjectionMatrix = stateset->getUniform("projectionMatrix");
|
||||
if (uProjectionMatrix)
|
||||
uProjectionMatrix->set(mManager->computeEyeProjection(0, SceneUtil::AutoDepth::isReversed()));
|
||||
}
|
||||
|
||||
void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override
|
||||
{
|
||||
osg::Matrix dummy;
|
||||
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, "viewMatrixMultiView", 2));
|
||||
stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2));
|
||||
}
|
||||
|
||||
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/)
|
||||
{
|
||||
mManager->updateMultiviewStateset(stateset);
|
||||
}
|
||||
|
||||
private:
|
||||
Manager* mManager;
|
||||
};
|
||||
|
||||
static Manager* sInstance = nullptr;
|
||||
|
||||
Manager& Manager::instance()
|
||||
{
|
||||
return *sInstance;
|
||||
}
|
||||
|
||||
struct CustomViewCallback : public Manager::UpdateViewCallback
|
||||
{
|
||||
public:
|
||||
CustomViewCallback();
|
||||
|
||||
void updateView(View& left, View& right) override;
|
||||
|
||||
private:
|
||||
View mLeft;
|
||||
View mRight;
|
||||
};
|
||||
|
||||
Manager::Manager(osgViewer::Viewer* viewer)
|
||||
: mViewer(viewer)
|
||||
, mMainCamera(mViewer->getCamera())
|
||||
, mUpdateCallback(new StereoUpdateCallback(this))
|
||||
, mMasterProjectionMatrix(osg::Matrix::identity())
|
||||
, mEyeResolutionOverriden(false)
|
||||
, mEyeResolutionOverride(0,0)
|
||||
, mFrustumManager(nullptr)
|
||||
, mUpdateViewCallback(nullptr)
|
||||
{
|
||||
if (sInstance)
|
||||
throw std::logic_error("Double instance of Stereo::Manager");
|
||||
sInstance = this;
|
||||
|
||||
if (Settings::Manager::getBool("use custom view", "Stereo"))
|
||||
mUpdateViewCallback = std::make_shared<CustomViewCallback>();
|
||||
|
||||
if (Settings::Manager::getBool("use custom eye resolution", "Stereo"))
|
||||
{
|
||||
osg::Vec2i eyeResolution = osg::Vec2i();
|
||||
eyeResolution.x() = Settings::Manager::getInt("eye resolution x", "Stereo View");
|
||||
eyeResolution.y() = Settings::Manager::getInt("eye resolution y", "Stereo View");
|
||||
overrideEyeResolution(eyeResolution);
|
||||
}
|
||||
}
|
||||
|
||||
Manager::~Manager()
|
||||
{
|
||||
}
|
||||
|
||||
void Manager::initializeStereo(osg::GraphicsContext* gc)
|
||||
{
|
||||
mMainCamera->addUpdateCallback(mUpdateCallback);
|
||||
mFrustumManager = std::make_unique<StereoFrustumManager>(mViewer->getCamera());
|
||||
|
||||
auto ci = gc->getState()->getContextID();
|
||||
configureExtensions(ci);
|
||||
|
||||
if(getMultiview())
|
||||
setupOVRMultiView2Technique();
|
||||
else
|
||||
setupBruteForceTechnique();
|
||||
|
||||
updateStereoFramebuffer();
|
||||
|
||||
}
|
||||
|
||||
void Manager::shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const
|
||||
{
|
||||
if (getMultiview())
|
||||
{
|
||||
defines["GLSLVersion"] = "330 compatibility";
|
||||
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 = mMainCamera->getViewport()->width() / 2;
|
||||
auto height = 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
|
||||
{
|
||||
(void)view;
|
||||
return mManager->computeEyeView(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
|
||||
{
|
||||
(void)view;
|
||||
return mManager->computeEyeView(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()
|
||||
{
|
||||
auto samples = Settings::Manager::getInt("antialiasing", "Video");
|
||||
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()
|
||||
{
|
||||
double near_ = 1.f;
|
||||
double far_ = 10000.f;
|
||||
|
||||
near_ = Settings::Manager::getFloat("near clip", "Camera");
|
||||
far_ = Settings::Manager::getFloat("viewing distance", "Camera");
|
||||
auto projectionMatrix = mMainCamera->getProjectionMatrix();
|
||||
|
||||
if (mUpdateViewCallback)
|
||||
{
|
||||
mUpdateViewCallback->updateView(mView[0], mView[1]);
|
||||
auto viewMatrix = mMainCamera->getViewMatrix();
|
||||
mViewOffsetMatrix[0] = mView[0].viewMatrix(true);
|
||||
mViewOffsetMatrix[1] = mView[1].viewMatrix(true);
|
||||
mViewMatrix[0] = viewMatrix * mViewOffsetMatrix[0];
|
||||
mViewMatrix[1] = viewMatrix * mViewOffsetMatrix[1];
|
||||
mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false);
|
||||
mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false);
|
||||
if (SceneUtil::AutoDepth::isReversed())
|
||||
{
|
||||
mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true);
|
||||
mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, 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);
|
||||
projectionMatrix = masterView.perspectiveMatrix(near_, far_, false);
|
||||
mMainCamera->setProjectionMatrix(projectionMatrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* ds = osg::DisplaySettings::instance().get();
|
||||
auto viewMatrix = mMainCamera->getViewMatrix();
|
||||
mViewMatrix[0] = ds->computeLeftEyeViewImplementation(viewMatrix);
|
||||
mViewMatrix[1] = ds->computeRightEyeViewImplementation(viewMatrix);
|
||||
mViewOffsetMatrix[0] = osg::Matrix::inverse(viewMatrix) * mViewMatrix[0];
|
||||
mViewOffsetMatrix[1] = osg::Matrix::inverse(viewMatrix) * mViewMatrix[1];
|
||||
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(
|
||||
{
|
||||
mViewOffsetMatrix[0] * mProjectionMatrix[0],
|
||||
mViewOffsetMatrix[1] * mProjectionMatrix[1]
|
||||
});
|
||||
}
|
||||
|
||||
void Manager::updateMultiviewStateset(osg::StateSet* stateset)
|
||||
{
|
||||
// Update stereo uniforms
|
||||
auto * viewMatrixMultiViewUniform = stateset->getUniform("viewMatrixMultiView");
|
||||
auto * projectionMatrixMultiViewUniform = stateset->getUniform("projectionMatrixMultiView");
|
||||
|
||||
for (int view : {0, 1})
|
||||
{
|
||||
viewMatrixMultiViewUniform->setElement(view, mViewOffsetMatrix[view]);
|
||||
projectionMatrixMultiViewUniform->setElement(view, computeEyeProjection(view, SceneUtil::AutoDepth::isReversed()));
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb)
|
||||
{
|
||||
mUpdateViewCallback = 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::computeEyeView(int view) const
|
||||
{
|
||||
return mViewMatrix[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()
|
||||
{
|
||||
static bool stereo = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo();
|
||||
return stereo;
|
||||
}
|
||||
|
||||
CustomViewCallback::CustomViewCallback()
|
||||
{
|
||||
mLeft.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View");
|
||||
mLeft.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View");
|
||||
mLeft.pose.position.z() = Settings::Manager::getDouble("left eye offset z", "Stereo View");
|
||||
mLeft.pose.orientation.x() = Settings::Manager::getDouble("left eye orientation x", "Stereo View");
|
||||
mLeft.pose.orientation.y() = Settings::Manager::getDouble("left eye orientation y", "Stereo View");
|
||||
mLeft.pose.orientation.z() = Settings::Manager::getDouble("left eye orientation z", "Stereo View");
|
||||
mLeft.pose.orientation.w() = Settings::Manager::getDouble("left eye orientation w", "Stereo View");
|
||||
mLeft.fov.angleLeft = Settings::Manager::getDouble("left eye fov left", "Stereo View");
|
||||
mLeft.fov.angleRight = Settings::Manager::getDouble("left eye fov right", "Stereo View");
|
||||
mLeft.fov.angleUp = Settings::Manager::getDouble("left eye fov up", "Stereo View");
|
||||
mLeft.fov.angleDown = Settings::Manager::getDouble("left eye fov down", "Stereo View");
|
||||
|
||||
mRight.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View");
|
||||
mRight.pose.position.y() = Settings::Manager::getDouble("right eye offset y", "Stereo View");
|
||||
mRight.pose.position.z() = Settings::Manager::getDouble("right eye offset z", "Stereo View");
|
||||
mRight.pose.orientation.x() = Settings::Manager::getDouble("right eye orientation x", "Stereo View");
|
||||
mRight.pose.orientation.y() = Settings::Manager::getDouble("right eye orientation y", "Stereo View");
|
||||
mRight.pose.orientation.z() = Settings::Manager::getDouble("right eye orientation z", "Stereo View");
|
||||
mRight.pose.orientation.w() = Settings::Manager::getDouble("right eye orientation w", "Stereo View");
|
||||
mRight.fov.angleLeft = Settings::Manager::getDouble("right eye fov left", "Stereo View");
|
||||
mRight.fov.angleRight = Settings::Manager::getDouble("right eye fov right", "Stereo View");
|
||||
mRight.fov.angleUp = Settings::Manager::getDouble("right eye fov up", "Stereo View");
|
||||
mRight.fov.angleDown = Settings::Manager::getDouble("right eye fov down", "Stereo View");
|
||||
}
|
||||
|
||||
void CustomViewCallback::updateView(View& left, View& right)
|
||||
{
|
||||
left = mLeft;
|
||||
right = mRight;
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
#ifndef STEREO_MANAGER_H
|
||||
#define STEREO_MANAGER_H
|
||||
|
||||
#include <osg/Matrix>
|
||||
#include <osg/Vec3>
|
||||
#include <osg/Camera>
|
||||
#include <osg/StateSet>
|
||||
#include <osgUtil/CullVisitor>
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
|
||||
#include <components/stereo/types.hpp>
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class FrameBufferObject;
|
||||
class Texture2D;
|
||||
class Texture2DMultisample;
|
||||
class Texture2DArray;
|
||||
}
|
||||
|
||||
namespace osgViewer
|
||||
{
|
||||
class Viewer;
|
||||
}
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
class MWShadowTechnique;
|
||||
}
|
||||
|
||||
namespace Stereo
|
||||
{
|
||||
class MultiviewFramebuffer;
|
||||
class StereoFrustumManager;
|
||||
class MultiviewStereoStatesetUpdateCallback;
|
||||
|
||||
bool getStereo();
|
||||
|
||||
//! Class that provides tools for managing stereo mode
|
||||
class Manager
|
||||
{
|
||||
public:
|
||||
struct UpdateViewCallback
|
||||
{
|
||||
virtual ~UpdateViewCallback() = default;
|
||||
|
||||
//! Called during the update traversal of every frame to update stereo views.
|
||||
virtual void updateView(View& left, View& right) = 0;
|
||||
};
|
||||
|
||||
//! Gets the singleton instance
|
||||
static Manager& instance();
|
||||
|
||||
Manager(osgViewer::Viewer* viewer);
|
||||
~Manager();
|
||||
|
||||
//! Called during update traversal
|
||||
void update();
|
||||
|
||||
void initializeStereo(osg::GraphicsContext* gc);
|
||||
|
||||
//! Callback that updates stereo configuration during the update pass
|
||||
void setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb);
|
||||
|
||||
//! Set the cull callback on the appropriate camera object
|
||||
void setCullCallback(osg::ref_ptr<osg::NodeCallback> cb);
|
||||
|
||||
osg::Matrixd computeEyeProjection(int view, bool reverseZ) const;
|
||||
osg::Matrixd computeEyeView(int view) const;
|
||||
|
||||
//! Sets up any definitions necessary for stereo rendering
|
||||
void shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const;
|
||||
|
||||
const std::shared_ptr<MultiviewFramebuffer>& multiviewFramebuffer() { return mMultiviewFramebuffer; };
|
||||
|
||||
//! Sets rendering resolution of each eye to eyeResolution.
|
||||
//! Once set, there will no longer be any connection between rendering resolution and screen/window resolution.
|
||||
void overrideEyeResolution(const osg::Vec2i& eyeResolution);
|
||||
|
||||
//! Notify stereo manager that the screen/window resolution has changed.
|
||||
void screenResolutionChanged();
|
||||
|
||||
//! Get current eye resolution
|
||||
osg::Vec2i eyeResolution();
|
||||
|
||||
//! The projection intended for rendering. When reverse Z is enabled, this is not the same as the camera's projection matrix,
|
||||
//! and therefore must be provided to the manager explicitly.
|
||||
void setMasterProjectionMatrix(const osg::Matrix& projectionMatrix) { mMasterProjectionMatrix = projectionMatrix; }
|
||||
|
||||
//! Causes the subgraph represented by the node to draw to the full viewport.
|
||||
//! This has no effect if stereo is not enabled
|
||||
void disableStereoForNode(osg::Node* node);
|
||||
|
||||
void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique);
|
||||
|
||||
/// Determine which view the cull visitor belongs to
|
||||
Eye getEye(const osgUtil::CullVisitor* cv) const;
|
||||
|
||||
private:
|
||||
friend class MultiviewStereoStatesetUpdateCallback;
|
||||
void updateMultiviewStateset(osg::StateSet* stateset);
|
||||
void updateStereoFramebuffer();
|
||||
void setupBruteForceTechnique();
|
||||
void setupOVRMultiView2Technique();
|
||||
|
||||
osg::ref_ptr<osgViewer::Viewer> mViewer;
|
||||
osg::ref_ptr<osg::Camera> mMainCamera;
|
||||
osg::ref_ptr<osg::Callback> mUpdateCallback;
|
||||
std::string mError;
|
||||
osg::Matrix mMasterProjectionMatrix;
|
||||
std::shared_ptr<MultiviewFramebuffer> mMultiviewFramebuffer;
|
||||
bool mEyeResolutionOverriden;
|
||||
osg::Vec2i mEyeResolutionOverride;
|
||||
|
||||
std::array<View, 2> mView;
|
||||
std::array<osg::Matrix, 2> mViewMatrix;
|
||||
std::array<osg::Matrix, 2> mViewOffsetMatrix;
|
||||
std::array<osg::Matrix, 2> mProjectionMatrix;
|
||||
std::array<osg::Matrix, 2> mProjectionMatrixReverseZ;
|
||||
|
||||
std::unique_ptr<StereoFrustumManager> mFrustumManager;
|
||||
std::shared_ptr<UpdateViewCallback> mUpdateViewCallback;
|
||||
|
||||
using Identifier = osgUtil::CullVisitor::Identifier;
|
||||
osg::ref_ptr<Identifier> mIdentifierMain = new Identifier();
|
||||
osg::ref_ptr<Identifier> mIdentifierLeft = new Identifier();
|
||||
osg::ref_ptr<Identifier> mIdentifierRight = new Identifier();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,168 @@
|
||||
#include "types.hpp"
|
||||
|
||||
#include <osg/io_utils>
|
||||
|
||||
namespace Stereo
|
||||
{
|
||||
|
||||
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 View::viewMatrix(bool useGLConventions)
|
||||
{
|
||||
auto position = pose.position;
|
||||
auto orientation = pose.orientation;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
osg::Matrix View::perspectiveMatrix(float near, float far, bool reverseZ)
|
||||
{
|
||||
const float tanLeft = tanf(fov.angleLeft);
|
||||
const float tanRight = tanf(fov.angleRight);
|
||||
const float tanDown = tanf(fov.angleDown);
|
||||
const float tanUp = tanf(fov.angleUp);
|
||||
|
||||
const float tanWidth = tanRight - tanLeft;
|
||||
const float tanHeight = tanUp - tanDown;
|
||||
|
||||
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 (reverseZ) {
|
||||
matrix[2] = 0;
|
||||
matrix[6] = 0;
|
||||
matrix[10] = (2.f * near) / (far - near);
|
||||
matrix[14] = ((2.f * near) * far) / (far - near);
|
||||
}
|
||||
else {
|
||||
matrix[2] = 0;
|
||||
matrix[6] = 0;
|
||||
matrix[10] = -(far + near) / (far - near);
|
||||
matrix[14] = -(far * (2.f * near)) / (far - near);
|
||||
}
|
||||
|
||||
matrix[3] = 0;
|
||||
matrix[7] = 0;
|
||||
matrix[11] = -1;
|
||||
matrix[15] = 0;
|
||||
|
||||
return osg::Matrix(matrix);
|
||||
}
|
||||
|
||||
bool FieldOfView::operator==(const FieldOfView& rhs) const
|
||||
{
|
||||
return angleDown == rhs.angleDown
|
||||
&& angleUp == rhs.angleUp
|
||||
&& angleLeft == rhs.angleLeft
|
||||
&& angleRight == rhs.angleRight;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
#ifndef STEREO_TYPES_H
|
||||
#define STEREO_TYPES_H
|
||||
|
||||
#include <osg/Matrix>
|
||||
#include <osg/Vec3>
|
||||
|
||||
|
||||
namespace Stereo
|
||||
{
|
||||
enum class Eye
|
||||
{
|
||||
Left = 0,
|
||||
Right = 1,
|
||||
Center = 2
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct FieldOfView {
|
||||
float angleLeft{ 0.f };
|
||||
float angleRight{ 0.f };
|
||||
float angleUp{ 0.f };
|
||||
float angleDown{ 0.f };
|
||||
|
||||
bool operator==(const FieldOfView& rhs) const;
|
||||
};
|
||||
|
||||
struct View
|
||||
{
|
||||
Pose pose;
|
||||
FieldOfView fov;
|
||||
bool operator==(const View& rhs) const;
|
||||
|
||||
osg::Matrix viewMatrix(bool useGLConventions);
|
||||
osg::Matrix perspectiveMatrix(float near, float far, bool reverseZ);
|
||||
};
|
||||
|
||||
std::ostream& operator <<(std::ostream& os, const Pose& pose);
|
||||
std::ostream& operator <<(std::ostream& os, const FieldOfView& fov);
|
||||
std::ostream& operator <<(std::ostream& os, const View& view);
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,48 @@
|
||||
#ifndef MULTIVIEW_FRAGMENT
|
||||
#define MULTIVIEW_FRAGMENT
|
||||
|
||||
// This file either enables or disables GL_OVR_multiview2 related code.
|
||||
// For use in fragment shaders
|
||||
|
||||
// REQUIREMENT:
|
||||
// GLSL version: 330 or greater
|
||||
// GLSL profile: compatibility
|
||||
// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set.
|
||||
//
|
||||
// This file provides symbols for sampling stereo-aware textures. Without multiview, these texture uniforms are sampler2D,
|
||||
// while in stereo the same uniforms are sampler2DArray instead. The symbols defined in this file mask this difference, allowing
|
||||
// the same code to work in both cases. Use mw_stereoAwareSampler2D and mw_stereoAwareTexture2D, where you otherwise would use
|
||||
// sampler2D and texture2D()
|
||||
//
|
||||
// USAGE:
|
||||
// For stereo-aware textures, such as reflections, use the mw_stereoAwareSampler2D sampler and mw_stereoAwareTexture2D method
|
||||
// instead of the usual sampler2D and texture2D.
|
||||
//
|
||||
// Using water reflection as an example, the old code for these textures changes from
|
||||
// uniform sampler2D reflectionMap;
|
||||
// ...
|
||||
// vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb;
|
||||
//
|
||||
// to
|
||||
// uniform mw_stereoAwareSampler2D reflectionMap;
|
||||
// ...
|
||||
// vec3 reflection = mw_stereoAwareTexture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb;
|
||||
//
|
||||
|
||||
#if @useOVR_multiview
|
||||
|
||||
#extension GL_OVR_multiview : require
|
||||
#extension GL_OVR_multiview2 : require
|
||||
#extension GL_EXT_texture_array : require
|
||||
|
||||
#define mw_stereoAwareSampler2D sampler2DArray
|
||||
#define mw_stereoAwareTexture2D(texture, uv) texture2DArray(texture, vec3((uv), gl_ViewID_OVR))
|
||||
|
||||
#else // useOVR_multiview
|
||||
|
||||
#define mw_stereoAwareSampler2D sampler2D
|
||||
#define mw_stereoAwareTexture2D(texture, uv) texture2D(texture, uv)
|
||||
|
||||
#endif // useOVR_multiview
|
||||
|
||||
#endif // MULTIVIEW_FRAGMENT
|
@ -0,0 +1,80 @@
|
||||
#ifndef MULTIVIEW_VERTEX
|
||||
#define MULTIVIEW_VERTEX
|
||||
|
||||
// This file either enables or disables GL_OVR_multiview related code.
|
||||
// For use in vertex shaders
|
||||
|
||||
// REQUIREMENT:
|
||||
// GLSL version: 330 or greater
|
||||
// GLSL profile: compatibility
|
||||
// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set.
|
||||
|
||||
// USAGE:
|
||||
// To create a stereo-aware vertex shader, use the matrix accessor functions defined in this .glsl file to compute gl_Position.
|
||||
// For the vertex stage, usually only gl_Position needs to be computed with stereo awareness, while other variables such as viewPos
|
||||
// should be computed in the center camera's view space and take no stereo awareness.
|
||||
//
|
||||
// A typical gl_Position line will look like the following:
|
||||
// gl_Position = mw_stereoAwareProjectionMatrix() * (mw_stereoAwareModelViewMatrix() * gl_Vertex);
|
||||
//
|
||||
// If you need to perform intermediate computations before determining the final values of gl_Position and viewPos,
|
||||
// your code might look more like the following:
|
||||
// vec4 intermediateViewPos = gl_ModelViewMatrix * gl_Vertex;
|
||||
// vec4 viewPos = myWhateverCode(intermediateViewPos);
|
||||
// gl_Position = mw_stereoAwareProjectionMatrix() * mw_stereoAwareViewPosition(viewPos);
|
||||
//
|
||||
|
||||
#if @useOVR_multiview
|
||||
|
||||
#extension GL_OVR_multiview : require
|
||||
|
||||
#ifndef MULTIVIEW_FRAGMENT
|
||||
// Layout cannot be used in the fragment shader
|
||||
layout(num_views = @numViews) in;
|
||||
#endif
|
||||
|
||||
uniform mat4 projectionMatrixMultiView[@numViews];
|
||||
uniform mat4 viewMatrixMultiView[@numViews];
|
||||
|
||||
// NOTE:
|
||||
// stereo-aware inverse view matrices and normal matrices have not been implemented.
|
||||
// Some effects like specular highlights would need stereo aware normal matrices to be 100% correct.
|
||||
// But the difference is not likely to be noticeable unless you're actively looking for it.
|
||||
|
||||
mat4 mw_stereoAwareProjectionMatrix()
|
||||
{
|
||||
return projectionMatrixMultiView[gl_ViewID_OVR];
|
||||
}
|
||||
|
||||
mat4 mw_stereoAwareModelViewMatrix()
|
||||
{
|
||||
return viewMatrixMultiView[gl_ViewID_OVR] * gl_ModelViewMatrix;
|
||||
}
|
||||
|
||||
vec4 mw_stereoAwareViewPosition(vec4 viewPos)
|
||||
{
|
||||
return viewMatrixMultiView[gl_ViewID_OVR] * viewPos;
|
||||
}
|
||||
|
||||
#else // useOVR_multiview
|
||||
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
mat4 mw_stereoAwareProjectionMatrix()
|
||||
{
|
||||
return projectionMatrix;
|
||||
}
|
||||
|
||||
mat4 mw_stereoAwareModelViewMatrix()
|
||||
{
|
||||
return gl_ModelViewMatrix;
|
||||
}
|
||||
|
||||
vec4 mw_stereoAwareViewPosition(vec4 viewPos)
|
||||
{
|
||||
return viewPos;
|
||||
}
|
||||
|
||||
#endif // useOVR_multiview
|
||||
|
||||
#endif // MULTIVIEW_VERTEX
|
Loading…
Reference in New Issue