mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-05-01 14:27:59 +03:00
Merge branch 'Stereo-MR' into 'master'
Stereo See merge request OpenMW/openmw!1757
This commit is contained in:
commit
275bebb066
55 changed files with 3499 additions and 429 deletions
|
@ -61,7 +61,7 @@ add_component_dir (sceneutil
|
|||
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
|
||||
lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer
|
||||
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt
|
||||
screencapture depth
|
||||
screencapture depth color
|
||||
)
|
||||
|
||||
add_component_dir (nif
|
||||
|
@ -196,6 +196,10 @@ add_component_dir (misc
|
|||
compression osguservalues errorMarker color
|
||||
)
|
||||
|
||||
add_component_dir (stereo
|
||||
frustum multiview stereomanager types
|
||||
)
|
||||
|
||||
add_component_dir (debug
|
||||
debugging debuglog gldebug
|
||||
)
|
||||
|
|
157
components/sceneutil/color.cpp
Normal file
157
components/sceneutil/color.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
207
components/sceneutil/color.hpp
Normal file
207
components/sceneutil/color.hpp
Normal file
|
@ -0,0 +1,207 @@
|
|||
#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
|
||||
|
||||
#ifndef GL_RGB9_E5
|
||||
#define GL_RGB9_E5 0x8C3D
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#include <SDL_opengl_glext.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
namespace SceneUtil
|
||||
|
@ -56,4 +55,144 @@ namespace SceneUtil
|
|||
|
||||
return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
|
||||
}
|
||||
}
|
||||
|
||||
bool isDepthFormat(GLenum format)
|
||||
{
|
||||
constexpr std::array<GLenum, 8> formats = {
|
||||
GL_DEPTH_COMPONENT32F,
|
||||
GL_DEPTH_COMPONENT32F_NV,
|
||||
GL_DEPTH_COMPONENT16,
|
||||
GL_DEPTH_COMPONENT24,
|
||||
GL_DEPTH_COMPONENT32,
|
||||
GL_DEPTH32F_STENCIL8,
|
||||
GL_DEPTH32F_STENCIL8_NV,
|
||||
GL_DEPTH24_STENCIL8,
|
||||
};
|
||||
|
||||
return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
|
||||
}
|
||||
|
||||
bool isDepthStencilFormat(GLenum format)
|
||||
{
|
||||
constexpr std::array<GLenum, 8> formats = {
|
||||
GL_DEPTH32F_STENCIL8,
|
||||
GL_DEPTH32F_STENCIL8_NV,
|
||||
GL_DEPTH24_STENCIL8,
|
||||
};
|
||||
|
||||
return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
|
||||
}
|
||||
|
||||
void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType)
|
||||
{
|
||||
switch (internalFormat)
|
||||
{
|
||||
case GL_DEPTH_COMPONENT16:
|
||||
case GL_DEPTH_COMPONENT24:
|
||||
case GL_DEPTH_COMPONENT32:
|
||||
sourceType = GL_UNSIGNED_INT;
|
||||
sourceFormat = GL_DEPTH_COMPONENT;
|
||||
break;
|
||||
case GL_DEPTH_COMPONENT32F:
|
||||
case GL_DEPTH_COMPONENT32F_NV:
|
||||
sourceType = GL_FLOAT;
|
||||
sourceFormat = GL_DEPTH_COMPONENT;
|
||||
break;
|
||||
case GL_DEPTH24_STENCIL8:
|
||||
sourceType = GL_UNSIGNED_INT_24_8_EXT;
|
||||
sourceFormat = GL_DEPTH_STENCIL_EXT;
|
||||
break;
|
||||
case GL_DEPTH32F_STENCIL8:
|
||||
case GL_DEPTH32F_STENCIL8_NV:
|
||||
sourceType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
|
||||
sourceFormat = GL_DEPTH_STENCIL_EXT;
|
||||
break;
|
||||
default:
|
||||
sourceType = GL_UNSIGNED_INT;
|
||||
sourceFormat = GL_DEPTH_COMPONENT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat)
|
||||
{
|
||||
switch (internalFormat)
|
||||
{
|
||||
case GL_DEPTH24_STENCIL8:
|
||||
return GL_DEPTH_COMPONENT24;
|
||||
break;
|
||||
case GL_DEPTH32F_STENCIL8:
|
||||
return GL_DEPTH_COMPONENT32F;
|
||||
break;
|
||||
case GL_DEPTH32F_STENCIL8_NV:
|
||||
return GL_DEPTH_COMPONENT32F_NV;
|
||||
break;
|
||||
default:
|
||||
return internalFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SelectDepthFormatOperation::operator()(osg::GraphicsContext* graphicsContext)
|
||||
{
|
||||
bool enableReverseZ = false;
|
||||
|
||||
if (Settings::Manager::getBool("reverse z", "Camera"))
|
||||
{
|
||||
osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false);
|
||||
if (exts && exts->isClipControlSupported)
|
||||
{
|
||||
enableReverseZ = true;
|
||||
Log(Debug::Info) << "Using reverse-z depth buffer";
|
||||
}
|
||||
else
|
||||
Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer";
|
||||
}
|
||||
else
|
||||
Log(Debug::Info) << "Using standard depth buffer";
|
||||
|
||||
SceneUtil::AutoDepth::setReversed(enableReverseZ);
|
||||
|
||||
constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: ";
|
||||
std::vector<GLenum> requestedFormats;
|
||||
unsigned int contextID = graphicsContext->getState()->getContextID();
|
||||
if (SceneUtil::AutoDepth::isReversed())
|
||||
{
|
||||
if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float"))
|
||||
{
|
||||
requestedFormats.push_back(GL_DEPTH32F_STENCIL8);
|
||||
}
|
||||
else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float"))
|
||||
{
|
||||
requestedFormats.push_back(GL_DEPTH32F_STENCIL8_NV);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported.";
|
||||
}
|
||||
}
|
||||
|
||||
requestedFormats.push_back(GL_DEPTH24_STENCIL8);
|
||||
if (mSupportedFormats.empty())
|
||||
{
|
||||
SceneUtil::AutoDepth::setDepthFormat(requestedFormats.front());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto requestedFormat : requestedFormats)
|
||||
{
|
||||
if (std::find(mSupportedFormats.cbegin(), mSupportedFormats.cend(), requestedFormat) != mSupportedFormats.cend())
|
||||
{
|
||||
SceneUtil::AutoDepth::setDepthFormat(requestedFormat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AutoDepth::setDepthFormat(GLenum format)
|
||||
{
|
||||
sDepthInternalFormat = format;
|
||||
getDepthFormatSourceFormatAndType(sDepthInternalFormat, sDepthSourceFormat, sDepthSourceType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,26 @@
|
|||
#define GL_DEPTH32F_STENCIL8_NV 0x8DAC
|
||||
#endif
|
||||
|
||||
#ifndef GL_DEPTH32F_STENCIL8
|
||||
#define GL_DEPTH32F_STENCIL8 0x8CAD
|
||||
#endif
|
||||
|
||||
#ifndef GL_FLOAT_32_UNSIGNED_INT_24_8_REV
|
||||
#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD
|
||||
#endif
|
||||
|
||||
#ifndef GL_DEPTH24_STENCIL8
|
||||
#define GL_DEPTH24_STENCIL8 0x88F0
|
||||
#endif
|
||||
|
||||
#ifndef GL_DEPTH_STENCIL_EXT
|
||||
#define GL_DEPTH_STENCIL_EXT 0x84F9
|
||||
#endif
|
||||
|
||||
#ifndef GL_UNSIGNED_INT_24_8_EXT
|
||||
#define GL_UNSIGNED_INT_24_8_EXT 0x84FA
|
||||
#endif
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
// Sets camera clear depth to 0 if reversed depth buffer is in use, 1 otherwise.
|
||||
|
@ -28,6 +48,18 @@ namespace SceneUtil
|
|||
// Returns true if the GL format is a floating point depth format.
|
||||
bool isFloatingPointDepthFormat(GLenum format);
|
||||
|
||||
// Returns true if the GL format is a depth format
|
||||
bool isDepthFormat(GLenum format);
|
||||
|
||||
// Returns true if the GL format is a depth+stencil format
|
||||
bool isDepthStencilFormat(GLenum format);
|
||||
|
||||
// Returns the corresponding source format and type for the given internal format
|
||||
void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType);
|
||||
|
||||
// Converts depth-stencil formats to their corresponding depth formats.
|
||||
GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat);
|
||||
|
||||
// Brief wrapper around an osg::Depth that applies the reversed depth function when a reversed depth buffer is in use
|
||||
class AutoDepth : public osg::Depth
|
||||
{
|
||||
|
@ -72,9 +104,29 @@ namespace SceneUtil
|
|||
return AutoDepth::sReversed;
|
||||
}
|
||||
|
||||
static void setDepthFormat(GLenum format);
|
||||
|
||||
static GLenum depthInternalFormat()
|
||||
{
|
||||
return AutoDepth::sDepthInternalFormat;
|
||||
}
|
||||
|
||||
static GLenum depthSourceFormat()
|
||||
{
|
||||
return AutoDepth::sDepthSourceFormat;
|
||||
}
|
||||
|
||||
static GLenum depthSourceType()
|
||||
{
|
||||
return AutoDepth::sDepthSourceType;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static inline bool sReversed = false;
|
||||
static inline GLenum sDepthSourceFormat = GL_DEPTH_COMPONENT;
|
||||
static inline GLenum sDepthInternalFormat = GL_DEPTH_COMPONENT24;
|
||||
static inline GLenum sDepthSourceType = GL_UNSIGNED_INT;
|
||||
|
||||
osg::Depth::Function getReversedDepthFunction() const
|
||||
{
|
||||
|
@ -116,6 +168,23 @@ namespace SceneUtil
|
|||
traverse(node);
|
||||
}
|
||||
};
|
||||
|
||||
class SelectDepthFormatOperation : public osg::GraphicsOperation
|
||||
{
|
||||
public:
|
||||
SelectDepthFormatOperation() : GraphicsOperation("SelectDepthFormatOperation", false)
|
||||
{}
|
||||
|
||||
void operator()(osg::GraphicsContext* graphicsContext) override;
|
||||
|
||||
void setSupportedFormats(const std::vector<GLenum>& supportedFormats)
|
||||
{
|
||||
mSupportedFormats = supportedFormats;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<GLenum> mSupportedFormats;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -633,6 +633,7 @@ void MWShadowTechnique::ShadowData::releaseGLObjects(osg::State* state) const
|
|||
// Frustum
|
||||
//
|
||||
MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar):
|
||||
useCustomClipSpace(false),
|
||||
corners(8),
|
||||
faces(6),
|
||||
edges(12)
|
||||
|
@ -652,19 +653,40 @@ MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, d
|
|||
OSG_INFO<<"zNear = "<<zNear<<", zFar = "<<zFar<<std::endl;
|
||||
OSG_INFO<<"Projection matrix after clamping "<<projectionMatrix<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
corners[0].set(-1.0,-1.0,-1.0);
|
||||
corners[1].set(1.0,-1.0,-1.0);
|
||||
corners[2].set(1.0,-1.0,1.0);
|
||||
corners[3].set(-1.0,-1.0,1.0);
|
||||
corners[4].set(-1.0,1.0,-1.0);
|
||||
corners[5].set(1.0,1.0,-1.0);
|
||||
corners[6].set(1.0,1.0,1.0);
|
||||
corners[7].set(-1.0,1.0,1.0);
|
||||
void SceneUtil::MWShadowTechnique::Frustum::setCustomClipSpace(const osg::BoundingBoxd& clipCornersOverride)
|
||||
{
|
||||
useCustomClipSpace = true;
|
||||
customClipSpace = clipCornersOverride;
|
||||
}
|
||||
|
||||
void SceneUtil::MWShadowTechnique::Frustum::init()
|
||||
{
|
||||
osg::Matrixd clipToWorld;
|
||||
clipToWorld.invert(modelViewMatrix * projectionMatrix);
|
||||
|
||||
if (useCustomClipSpace)
|
||||
{
|
||||
corners.clear();
|
||||
// Add corners in the same order OSG expects them
|
||||
for (int i : {0, 1, 5, 4, 2, 3, 7, 6})
|
||||
{
|
||||
corners.push_back(customClipSpace.corner(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
corners[0].set(-1.0, -1.0, -1.0);
|
||||
corners[1].set(1.0, -1.0, -1.0);
|
||||
corners[2].set(1.0, -1.0, 1.0);
|
||||
corners[3].set(-1.0, -1.0, 1.0);
|
||||
corners[4].set(-1.0, 1.0, -1.0);
|
||||
corners[5].set(1.0, 1.0, -1.0);
|
||||
corners[6].set(1.0, 1.0, 1.0);
|
||||
corners[7].set(-1.0, 1.0, 1.0);
|
||||
}
|
||||
|
||||
// transform frustum corners from clipspace to world coords, and compute center
|
||||
for(Vertices::iterator itr = corners.begin();
|
||||
itr != corners.end();
|
||||
|
@ -899,7 +921,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh
|
|||
{
|
||||
// This can't be part of the constructor as OSG mandates that there be a trivial constructor available
|
||||
|
||||
osg::ref_ptr<osg::Shader> castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX);
|
||||
osg::ref_ptr<osg::Shader> castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", { }, osg::Shader::VERTEX);
|
||||
osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false);
|
||||
std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0";
|
||||
for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc)
|
||||
|
@ -931,6 +953,67 @@ MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(os
|
|||
return vdd.release();
|
||||
}
|
||||
|
||||
void SceneUtil::MWShadowTechnique::copyShadowMap(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;
|
||||
auto* stateset = lhs->getStateSet(cv.getTraversalNumber());
|
||||
stateset->clear();
|
||||
lhs->_lightDataList = rhs->_lightDataList;
|
||||
lhs->_numValidShadows = rhs->_numValidShadows;
|
||||
|
||||
ShadowDataList& sdl = lhs->getShadowDataList();
|
||||
ShadowDataList previous_sdl;
|
||||
previous_sdl.swap(sdl);
|
||||
for (const 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);
|
||||
}
|
||||
|
||||
assignTexGenSettings(cv, lhs);
|
||||
|
||||
if (lhs->_numValidShadows > 0)
|
||||
{
|
||||
prepareStateSetForRenderingShadow(*lhs, cv.getTraversalNumber());
|
||||
}
|
||||
}
|
||||
|
||||
void SceneUtil::MWShadowTechnique::setCustomFrustumCallback(CustomFrustumCallback* cfc)
|
||||
{
|
||||
_customFrustumCallback = cfc;
|
||||
}
|
||||
|
||||
void SceneUtil::MWShadowTechnique::assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd)
|
||||
{
|
||||
for (const auto& sd : vdd->getShadowDataList())
|
||||
{
|
||||
assignTexGenSettings(&cv, sd->_camera, sd->_textureUnit, sd->_texgen);
|
||||
}
|
||||
}
|
||||
|
||||
void MWShadowTechnique::update(osg::NodeVisitor& nv)
|
||||
{
|
||||
OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<<std::endl;
|
||||
|
@ -939,6 +1022,7 @@ void MWShadowTechnique::update(osg::NodeVisitor& nv)
|
|||
|
||||
void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
|
||||
{
|
||||
|
||||
if (!_enableShadows)
|
||||
{
|
||||
if (mSetDummyStateWhenDisabled)
|
||||
|
@ -1036,7 +1120,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
|
|||
// are all done correctly.
|
||||
cv.computeNearPlane();
|
||||
}
|
||||
|
||||
|
||||
// clamp the minZNear and maxZFar to those provided by ShadowSettings
|
||||
maxZFar = osg::minimum(settings->getMaximumShadowMapDistance(),maxZFar);
|
||||
if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio();
|
||||
|
@ -1047,6 +1131,36 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
|
|||
cv.setNearFarRatio(minZNear / maxZFar);
|
||||
|
||||
Frustum frustum(&cv, minZNear, maxZFar);
|
||||
if (_customFrustumCallback)
|
||||
{
|
||||
OSG_INFO << "Calling custom frustum callback" << std::endl;
|
||||
osgUtil::CullVisitor* sharedFrustumHint = nullptr;
|
||||
_customClipSpace.init();
|
||||
_customFrustumCallback->operator()(cv, _customClipSpace, sharedFrustumHint);
|
||||
frustum.setCustomClipSpace(_customClipSpace);
|
||||
if (sharedFrustumHint)
|
||||
{
|
||||
// user hinted another view shares its frustum
|
||||
std::lock_guard<std::mutex> lock(_viewDependentDataMapMutex);
|
||||
auto itr = _viewDependentDataMap.find(sharedFrustumHint);
|
||||
if (itr != _viewDependentDataMap.end())
|
||||
{
|
||||
OSG_INFO << "User provided a valid shared frustum hint, re-using previously generated shadow map" << std::endl;
|
||||
|
||||
copyShadowMap(cv, vdd, itr->second);
|
||||
|
||||
// return compute near far mode back to it's original settings
|
||||
cv.setComputeNearFarMode(cachedNearFarMode);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
OSG_INFO << "User provided a shared frustum hint, but it was not valid." << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frustum.init();
|
||||
if (_debugHud)
|
||||
{
|
||||
osg::ref_ptr<osg::Vec3Array> vertexArray = new osg::Vec3Array();
|
||||
|
@ -1066,7 +1180,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
|
|||
reducedNear = minZNear;
|
||||
reducedFar = maxZFar;
|
||||
}
|
||||
|
||||
|
||||
// return compute near far mode back to it's original settings
|
||||
cv.setComputeNearFarMode(cachedNearFarMode);
|
||||
|
||||
|
@ -1430,7 +1544,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
|
|||
vdsmCallback->getProjectionMatrix()->set(camera->getProjectionMatrix());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 4.4 compute main scene graph TexGen + uniform settings + setup state
|
||||
//
|
||||
assignTexGenSettings(&cv, camera.get(), textureUnit, sd->_texgen.get());
|
||||
|
@ -1459,6 +1573,8 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
|
|||
}
|
||||
}
|
||||
|
||||
vdd->setNumValidShadows(numValidShadows);
|
||||
|
||||
if (numValidShadows>0)
|
||||
{
|
||||
prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber());
|
||||
|
@ -1665,7 +1781,14 @@ osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustu
|
|||
OSG_INFO<<"computeLightViewFrustumPolytope()"<<std::endl;
|
||||
|
||||
osg::Polytope polytope;
|
||||
polytope.setToUnitFrustum();
|
||||
if (frustum.useCustomClipSpace)
|
||||
{
|
||||
polytope.setToBoundingBox(frustum.customClipSpace);
|
||||
}
|
||||
else
|
||||
{
|
||||
polytope.setToUnitFrustum();
|
||||
}
|
||||
|
||||
polytope.transformProvidingInverse( frustum.projectionMatrix );
|
||||
polytope.transformProvidingInverse( frustum.modelViewMatrix );
|
||||
|
|
|
@ -119,10 +119,15 @@ namespace SceneUtil {
|
|||
struct Frustum
|
||||
{
|
||||
Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar);
|
||||
void setCustomClipSpace(const osg::BoundingBoxd& clipCornersOverride);
|
||||
void init();
|
||||
|
||||
osg::Matrixd projectionMatrix;
|
||||
osg::Matrixd modelViewMatrix;
|
||||
|
||||
bool useCustomClipSpace;
|
||||
osg::BoundingBoxd customClipSpace;
|
||||
|
||||
typedef std::vector<osg::Vec3d> Vertices;
|
||||
Vertices corners;
|
||||
|
||||
|
@ -140,6 +145,18 @@ namespace SceneUtil {
|
|||
osg::Vec3d frustumCenterLine;
|
||||
};
|
||||
|
||||
/** Custom frustum callback allowing the application to request shadow maps covering a
|
||||
* different furstum than the camera normally would cover, by customizing the corners of the clip space. */
|
||||
struct CustomFrustumCallback : osg::Referenced
|
||||
{
|
||||
/** The callback operator.
|
||||
* Output the custum frustum to the boundingBox variable.
|
||||
* If sharedFrustumHint is set to a valid cull visitor, the shadow maps of that cull visitor will be re-used instead of recomputing new shadow maps
|
||||
* Note that the customClipSpace bounding box will be uninitialized when this operator is called. If it is not initalized, or a valid shared frustum hint set,
|
||||
* the resulting shadow map may be invalid. */
|
||||
virtual void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) = 0;
|
||||
};
|
||||
|
||||
// forward declare
|
||||
class ViewDependentData;
|
||||
|
||||
|
@ -197,7 +214,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;
|
||||
|
@ -206,13 +228,19 @@ namespace SceneUtil {
|
|||
|
||||
LightDataList _lightDataList;
|
||||
ShadowDataList _shadowDataList;
|
||||
|
||||
unsigned int _numValidShadows;
|
||||
};
|
||||
|
||||
virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv);
|
||||
|
||||
ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv);
|
||||
|
||||
void copyShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs);
|
||||
|
||||
void setCustomFrustumCallback(CustomFrustumCallback* cfc);
|
||||
|
||||
void assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd);
|
||||
|
||||
virtual void createShaders();
|
||||
|
||||
|
@ -246,6 +274,8 @@ namespace SceneUtil {
|
|||
typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr<ViewDependentData> > ViewDependentDataMap;
|
||||
mutable std::mutex _viewDependentDataMapMutex;
|
||||
ViewDependentDataMap _viewDependentDataMap;
|
||||
osg::ref_ptr<CustomFrustumCallback> _customFrustumCallback;
|
||||
osg::BoundingBoxd _customClipSpace;
|
||||
|
||||
osg::ref_ptr<osg::StateSet> _shadowRecievingPlaceholderStateSet;
|
||||
|
||||
|
|
|
@ -4,11 +4,16 @@
|
|||
#include <osg/Node>
|
||||
#include <osg/NodeVisitor>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Texture2DArray>
|
||||
#include <osgUtil/CullVisitor>
|
||||
|
||||
#include <components/sceneutil/nodecallback.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/sceneutil/depth.hpp>
|
||||
#include <components/sceneutil/color.hpp>
|
||||
#include <components/stereo/multiview.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/stereo/stereomanager.hpp>
|
||||
|
||||
namespace SceneUtil
|
||||
{
|
||||
|
@ -22,11 +27,15 @@ namespace SceneUtil
|
|||
}
|
||||
};
|
||||
|
||||
RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, int renderOrderNum, bool doPerViewMapping)
|
||||
RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, int renderOrderNum, StereoAwareness stereoAwareness)
|
||||
: mTextureWidth(textureWidth)
|
||||
, mTextureHeight(textureHeight)
|
||||
, mSamples(samples)
|
||||
, mGenerateMipmaps(generateMipmaps)
|
||||
, mColorBufferInternalFormat(Color::colorInternalFormat())
|
||||
, mDepthBufferInternalFormat(AutoDepth::depthInternalFormat())
|
||||
, mRenderOrderNum(renderOrderNum)
|
||||
, mDoPerViewMapping(doPerViewMapping)
|
||||
, mStereoAwareness(stereoAwareness)
|
||||
{
|
||||
addCullCallback(new CullCallback);
|
||||
setCullingActive(false);
|
||||
|
@ -34,28 +43,139 @@ namespace SceneUtil
|
|||
|
||||
RTTNode::~RTTNode()
|
||||
{
|
||||
for (auto& vdd : mViewDependentDataMap)
|
||||
{
|
||||
auto* camera = vdd.second->mCamera.get();
|
||||
if (camera)
|
||||
{
|
||||
camera->removeChildren(0, camera->getNumChildren());
|
||||
}
|
||||
}
|
||||
mViewDependentDataMap.clear();
|
||||
}
|
||||
|
||||
void RTTNode::cull(osgUtil::CullVisitor* cv)
|
||||
{
|
||||
auto frameNumber = cv->getFrameStamp()->getFrameNumber();
|
||||
auto* vdd = getViewDependentData(cv);
|
||||
apply(vdd->mCamera);
|
||||
vdd->mCamera->accept(*cv);
|
||||
if (frameNumber > vdd->mFrameNumber)
|
||||
{
|
||||
apply(vdd->mCamera);
|
||||
auto& sm = Stereo::Manager::instance();
|
||||
if (sm.getEye(cv) == Stereo::Eye::Left)
|
||||
applyLeft(vdd->mCamera);
|
||||
if (sm.getEye(cv) == Stereo::Eye::Right)
|
||||
applyRight(vdd->mCamera);
|
||||
vdd->mCamera->accept(*cv);
|
||||
}
|
||||
vdd->mFrameNumber = frameNumber;
|
||||
}
|
||||
|
||||
void RTTNode::setColorBufferInternalFormat(GLint internalFormat)
|
||||
{
|
||||
mColorBufferInternalFormat = internalFormat;
|
||||
}
|
||||
|
||||
void RTTNode::setDepthBufferInternalFormat(GLint internalFormat)
|
||||
{
|
||||
mDepthBufferInternalFormat = internalFormat;
|
||||
}
|
||||
|
||||
bool RTTNode::shouldDoPerViewMapping()
|
||||
{
|
||||
if(mStereoAwareness != StereoAwareness::Aware)
|
||||
return false;
|
||||
if (!Stereo::getMultiview())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RTTNode::shouldDoTextureArray()
|
||||
{
|
||||
if (mStereoAwareness == StereoAwareness::Unaware)
|
||||
return false;
|
||||
if (Stereo::getMultiview())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RTTNode::shouldDoTextureView()
|
||||
{
|
||||
if (mStereoAwareness != StereoAwareness::Unaware_MultiViewShaders)
|
||||
return false;
|
||||
if (Stereo::getMultiview())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
osg::Texture2DArray* RTTNode::createTextureArray(GLint internalFormat)
|
||||
{
|
||||
osg::Texture2DArray* textureArray = new osg::Texture2DArray;
|
||||
textureArray->setTextureSize(mTextureWidth, mTextureHeight, 2);
|
||||
textureArray->setInternalFormat(internalFormat);
|
||||
GLenum sourceFormat = 0;
|
||||
GLenum sourceType = 0;
|
||||
if (SceneUtil::isDepthFormat(internalFormat))
|
||||
{
|
||||
SceneUtil::getDepthFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType);
|
||||
}
|
||||
else
|
||||
{
|
||||
SceneUtil::getColorFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType);
|
||||
}
|
||||
textureArray->setSourceFormat(sourceFormat);
|
||||
textureArray->setSourceType(sourceType);
|
||||
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;
|
||||
}
|
||||
|
||||
osg::Texture2D* RTTNode::createTexture(GLint internalFormat)
|
||||
{
|
||||
osg::Texture2D* texture = new osg::Texture2D;
|
||||
texture->setTextureSize(mTextureWidth, mTextureHeight);
|
||||
texture->setInternalFormat(internalFormat);
|
||||
GLenum sourceFormat = 0;
|
||||
GLenum sourceType = 0;
|
||||
if (SceneUtil::isDepthFormat(internalFormat))
|
||||
{
|
||||
SceneUtil::getDepthFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType);
|
||||
}
|
||||
else
|
||||
{
|
||||
SceneUtil::getColorFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType);
|
||||
}
|
||||
texture->setSourceFormat(sourceFormat);
|
||||
texture->setSourceType(sourceType);
|
||||
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::Texture* RTTNode::getColorTexture(osgUtil::CullVisitor* cv)
|
||||
{
|
||||
return getViewDependentData(cv)->mCamera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._texture;
|
||||
return getViewDependentData(cv)->mColorTexture;
|
||||
}
|
||||
|
||||
osg::Texture* RTTNode::getDepthTexture(osgUtil::CullVisitor* cv)
|
||||
{
|
||||
return getViewDependentData(cv)->mCamera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._texture;
|
||||
return getViewDependentData(cv)->mDepthTexture;
|
||||
}
|
||||
|
||||
osg::Camera* RTTNode::getCamera(osgUtil::CullVisitor* cv)
|
||||
{
|
||||
return getViewDependentData(cv)->mCamera;
|
||||
}
|
||||
|
||||
RTTNode::ViewDependentData* RTTNode::getViewDependentData(osgUtil::CullVisitor* cv)
|
||||
{
|
||||
if (!mDoPerViewMapping)
|
||||
if (!shouldDoPerViewMapping())
|
||||
// Always setting it to null is an easy way to disable per-view mapping when mDoPerViewMapping is false.
|
||||
// This is safe since the visitor is never dereferenced.
|
||||
cv = nullptr;
|
||||
|
@ -63,7 +183,8 @@ namespace SceneUtil
|
|||
if (mViewDependentDataMap.count(cv) == 0)
|
||||
{
|
||||
auto camera = new osg::Camera();
|
||||
mViewDependentDataMap[cv].reset(new ViewDependentData);
|
||||
auto vdd = std::make_shared<ViewDependentData>();
|
||||
mViewDependentDataMap[cv] = vdd;
|
||||
mViewDependentDataMap[cv]->mCamera = camera;
|
||||
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER, mRenderOrderNum);
|
||||
|
@ -72,34 +193,63 @@ namespace SceneUtil
|
|||
camera->setViewport(0, 0, mTextureWidth, mTextureHeight);
|
||||
SceneUtil::setCameraClearDepth(camera);
|
||||
|
||||
setDefaults(mViewDependentDataMap[cv]->mCamera.get());
|
||||
setDefaults(camera);
|
||||
|
||||
// Create any buffer attachments not added in setDefaults
|
||||
if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0)
|
||||
if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER))
|
||||
vdd->mColorTexture = camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._texture;
|
||||
if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER))
|
||||
vdd->mDepthTexture = camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._texture;
|
||||
|
||||
#ifdef OSG_HAS_MULTIVIEW
|
||||
if (shouldDoTextureArray())
|
||||
{
|
||||
auto colorBuffer = new osg::Texture2D;
|
||||
colorBuffer->setTextureSize(mTextureWidth, mTextureHeight);
|
||||
colorBuffer->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
colorBuffer->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
colorBuffer->setInternalFormat(GL_RGB);
|
||||
colorBuffer->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
colorBuffer->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER, colorBuffer);
|
||||
SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, colorBuffer);
|
||||
// Create any buffer attachments not added in setDefaults
|
||||
if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0)
|
||||
{
|
||||
vdd->mColorTexture = createTextureArray(mColorBufferInternalFormat);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, mGenerateMipmaps, mSamples);
|
||||
SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, mGenerateMipmaps);
|
||||
}
|
||||
|
||||
if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0)
|
||||
{
|
||||
vdd->mDepthTexture = createTextureArray(mDepthBufferInternalFormat);
|
||||
camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples);
|
||||
}
|
||||
|
||||
if (shouldDoTextureView())
|
||||
{
|
||||
// In this case, shaders being set to multiview forces us to render to a multiview framebuffer even though we don't need that.
|
||||
// This forces us to make Texture2DArray. To make this possible to sample as a Texture2D, make a Texture2D view into the texture array.
|
||||
vdd->mColorTexture = Stereo::createTextureView_Texture2DFromTexture2DArray(static_cast<osg::Texture2DArray*>(vdd->mColorTexture.get()), 0);
|
||||
vdd->mDepthTexture = Stereo::createTextureView_Texture2DFromTexture2DArray(static_cast<osg::Texture2DArray*>(vdd->mDepthTexture.get()), 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// Create any buffer attachments not added in setDefaults
|
||||
if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0)
|
||||
{
|
||||
vdd->mColorTexture = createTexture(mColorBufferInternalFormat);
|
||||
camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps, mSamples);
|
||||
SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps);
|
||||
}
|
||||
|
||||
if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0)
|
||||
{
|
||||
vdd->mDepthTexture = createTexture(mDepthBufferInternalFormat);
|
||||
camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, 0, false, mSamples);
|
||||
}
|
||||
}
|
||||
|
||||
if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0)
|
||||
// OSG appears not to properly initialize this metadata. So when multisampling is enabled, OSG will use incorrect formats for the resolve buffers.
|
||||
if (mSamples > 1)
|
||||
{
|
||||
auto depthBuffer = new osg::Texture2D;
|
||||
depthBuffer->setTextureSize(mTextureWidth, mTextureHeight);
|
||||
depthBuffer->setSourceFormat(GL_DEPTH_STENCIL_EXT);
|
||||
depthBuffer->setInternalFormat(GL_DEPTH24_STENCIL8_EXT);
|
||||
depthBuffer->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
depthBuffer->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
depthBuffer->setSourceType(GL_UNSIGNED_INT_24_8_EXT);
|
||||
depthBuffer->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
depthBuffer->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, depthBuffer);
|
||||
camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mColorBufferInternalFormat;
|
||||
camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = mGenerateMipmaps;
|
||||
camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mDepthBufferInternalFormat;
|
||||
camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = mGenerateMipmaps;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
namespace osg
|
||||
{
|
||||
class Texture2D;
|
||||
class Texture2DArray;
|
||||
class Camera;
|
||||
}
|
||||
|
||||
|
@ -19,6 +20,8 @@ namespace osgUtil
|
|||
|
||||
namespace SceneUtil
|
||||
{
|
||||
class CreateTextureViewsCallback;
|
||||
|
||||
/// @brief Implements per-view RTT operations.
|
||||
/// @par With a naive RTT implementation, subsequent views of multiple views will overwrite the results of the previous views, leading to
|
||||
/// the results of the last view being broadcast to all views. An error in all cases where the RTT result depends on the view.
|
||||
|
@ -32,37 +35,73 @@ namespace SceneUtil
|
|||
class RTTNode : public osg::Node
|
||||
{
|
||||
public:
|
||||
RTTNode(uint32_t textureWidth, uint32_t textureHeight, int renderOrderNum, bool doPerViewMapping);
|
||||
enum class StereoAwareness
|
||||
{
|
||||
Unaware, //! RTT does not vary by view. A single RTT context is created
|
||||
Aware, //! RTT varies by view. One RTT context per view is created. Textures are automatically created as arrays if multiview is enabled.
|
||||
Unaware_MultiViewShaders, //! RTT does not vary by view, but renders with multiview shaders and needs to create texture arrays if multiview is enabled.
|
||||
};
|
||||
|
||||
RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, int renderOrderNum, StereoAwareness stereoAwareness);
|
||||
~RTTNode();
|
||||
|
||||
osg::Texture* getColorTexture(osgUtil::CullVisitor* cv);
|
||||
|
||||
osg::Texture* getDepthTexture(osgUtil::CullVisitor* cv);
|
||||
|
||||
osg::Camera* getCamera(osgUtil::CullVisitor* cv);
|
||||
|
||||
/// Set default settings - optionally override in derived classes
|
||||
virtual void setDefaults(osg::Camera* camera) {};
|
||||
|
||||
/// Apply state - to override in derived classes
|
||||
/// @note Due to the view mapping approach you *have* to apply all camera settings, even if they have not changed since the last frame.
|
||||
virtual void setDefaults(osg::Camera* camera) {};
|
||||
|
||||
/// Set default settings - optionally override in derived classes
|
||||
virtual void apply(osg::Camera* camera) {};
|
||||
|
||||
/// Apply any state specific to the Left view. Default implementation does nothing. Called after apply()
|
||||
virtual void applyLeft(osg::Camera* camera) {}
|
||||
/// Apply any state specific to the Right view. Default implementation does nothing. Called after apply()
|
||||
virtual void applyRight(osg::Camera* camera) {}
|
||||
|
||||
void cull(osgUtil::CullVisitor* cv);
|
||||
|
||||
uint32_t width() const { return mTextureWidth; }
|
||||
uint32_t height() const { return mTextureHeight; }
|
||||
uint32_t samples() const { return mSamples; }
|
||||
bool generatesMipmaps() const { return mGenerateMipmaps; }
|
||||
|
||||
void setColorBufferInternalFormat(GLint internalFormat);
|
||||
void setDepthBufferInternalFormat(GLint internalFormat);
|
||||
|
||||
protected:
|
||||
bool shouldDoPerViewMapping();
|
||||
bool shouldDoTextureArray();
|
||||
bool shouldDoTextureView();
|
||||
osg::Texture2DArray* createTextureArray(GLint internalFormat);
|
||||
osg::Texture2D* createTexture(GLint internalFormat);
|
||||
|
||||
private:
|
||||
friend class CreateTextureViewsCallback;
|
||||
struct ViewDependentData
|
||||
{
|
||||
osg::ref_ptr<osg::Camera> mCamera;
|
||||
osg::ref_ptr<osg::Texture> mColorTexture;
|
||||
osg::ref_ptr<osg::Texture> mDepthTexture;
|
||||
unsigned int mFrameNumber = 0;
|
||||
};
|
||||
|
||||
ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv);
|
||||
|
||||
typedef std::map< osgUtil::CullVisitor*, std::unique_ptr<ViewDependentData> > ViewDependentDataMap;
|
||||
typedef std::map< osgUtil::CullVisitor*, std::shared_ptr<ViewDependentData> > ViewDependentDataMap;
|
||||
ViewDependentDataMap mViewDependentDataMap;
|
||||
uint32_t mTextureWidth;
|
||||
uint32_t mTextureHeight;
|
||||
uint32_t mSamples;
|
||||
bool mGenerateMipmaps;
|
||||
GLint mColorBufferInternalFormat;
|
||||
GLint mDepthBufferInternalFormat;
|
||||
int mRenderOrderNum;
|
||||
bool mDoPerViewMapping;
|
||||
StereoAwareness mStereoAwareness;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/stereo/stereomanager.hpp>
|
||||
|
||||
#include "mwshadowtechnique.hpp"
|
||||
|
||||
|
@ -101,6 +102,7 @@ namespace SceneUtil
|
|||
mIndoorShadowCastingMask(indoorShadowCastingMask)
|
||||
{
|
||||
mShadowedScene->setShadowTechnique(mShadowTechnique);
|
||||
Stereo::Manager::instance().setShadowTechnique(mShadowTechnique);
|
||||
|
||||
mShadowedScene->addChild(sceneRoot);
|
||||
rootNode->addChild(mShadowedScene);
|
||||
|
@ -117,6 +119,7 @@ namespace SceneUtil
|
|||
|
||||
ShadowManager::~ShadowManager()
|
||||
{
|
||||
Stereo::Manager::instance().setShadowTechnique(nullptr);
|
||||
}
|
||||
|
||||
Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines()
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "statesetupdater.hpp"
|
||||
|
||||
#include <components/stereo/stereomanager.hpp>
|
||||
|
||||
#include <osg/Node>
|
||||
#include <osg/NodeVisitor>
|
||||
#include <osgUtil/CullVisitor>
|
||||
|
@ -38,6 +40,12 @@ namespace SceneUtil
|
|||
{
|
||||
auto stateset = getCvDependentStateset(cv);
|
||||
apply(stateset, cv);
|
||||
auto& sm = Stereo::Manager::instance();
|
||||
if (sm.getEye(cv) == Stereo::Eye::Left)
|
||||
applyLeft(stateset, cv);
|
||||
if (sm.getEye(cv) == Stereo::Eye::Right)
|
||||
applyRight(stateset, cv);
|
||||
|
||||
cv->pushStateSet(stateset);
|
||||
traverse(node, cv);
|
||||
cv->popStateSet();
|
||||
|
|
|
@ -41,6 +41,11 @@ namespace SceneUtil
|
|||
/// even if it has not changed since the last frame.
|
||||
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) {}
|
||||
|
||||
/// Apply any state specific to the Left view. Default implementation does nothing. Called after apply() \note requires the updater be a cull callback
|
||||
virtual void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) {}
|
||||
/// Apply any state specific to the Right view. Default implementation does nothing. Called after apply() \note requires the updater be a cull callback
|
||||
virtual void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) {}
|
||||
|
||||
/// Set default state - optionally override in derived classes
|
||||
/// @par May be used e.g. to allocate StateAttributes.
|
||||
virtual void setDefaults(osg::StateSet* stateset) {}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/stereo/stereomanager.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
#include <components/sceneutil/riggeometry.hpp>
|
||||
|
@ -647,6 +648,8 @@ namespace Shader
|
|||
|
||||
defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0";
|
||||
|
||||
Stereo::Manager::instance().shaderStereoDefines(defineMap);
|
||||
|
||||
std::string shaderPrefix;
|
||||
if (!node.getUserValue("shaderPrefix", shaderPrefix))
|
||||
shaderPrefix = mDefaultShaderPrefix;
|
||||
|
|
162
components/stereo/frustum.cpp
Normal file
162
components/stereo/frustum.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
76
components/stereo/frustum.hpp
Normal file
76
components/stereo/frustum.hpp
Normal file
|
@ -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
|
484
components/stereo/multiview.cpp
Normal file
484
components/stereo/multiview.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
85
components/stereo/multiview.hpp
Normal file
85
components/stereo/multiview.hpp
Normal file
|
@ -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
|
463
components/stereo/stereomanager.cpp
Normal file
463
components/stereo/stereomanager.cpp
Normal file
|
@ -0,0 +1,463 @@
|
|||
#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["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];
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
135
components/stereo/stereomanager.hpp
Normal file
135
components/stereo/stereomanager.hpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
#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;
|
||||
osg::Matrixd computeEyeViewOffset(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
|
168
components/stereo/types.cpp
Normal file
168
components/stereo/types.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
61
components/stereo/types.hpp
Normal file
61
components/stereo/types.hpp
Normal file
|
@ -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
|
|
@ -7,6 +7,7 @@
|
|||
#include <osg/TexMat>
|
||||
#include <osg/BlendFunc>
|
||||
|
||||
#include <components/stereo/stereomanager.hpp>
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
#include <components/sceneutil/depth.hpp>
|
||||
|
||||
|
@ -251,6 +252,8 @@ namespace Terrain
|
|||
defineMap["specularMap"] = it->mSpecular ? "1" : "0";
|
||||
defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0";
|
||||
|
||||
Stereo::Manager::instance().shaderStereoDefines(defineMap);
|
||||
|
||||
osg::ref_ptr<osg::Shader> vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX);
|
||||
osg::ref_ptr<osg::Shader> fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT);
|
||||
if (!vertexShader || !fragmentShader)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue