Initial commit

Multiview shaders.

Refactor Frustum management

Rewrite shared shadow map

cull mask should respect stereo

Stereo savegame screencap

LocalMap refactoring

use the vertex buffer hint instead of the display list patch to enable/disable display lists

Character preview fixes
pull/3226/head
madsbuvi 3 years ago
parent 2061a0b66e
commit dd5901d351

@ -424,6 +424,14 @@ Windows_Ninja_Engine_Release:
<<: *engine-targets
config: "Release"
Windows_Ninja_Engine_Release_MultiView:
extends:
- .Windows_Ninja_Base
variables:
<<: *engine-targets
multiview: "-M"
config: "Release"
Windows_Ninja_Engine_Debug:
extends:
- .Windows_Ninja_Base
@ -506,7 +514,7 @@ Windows_Ninja_Tests_RelWithDebInfo:
- $env:CCACHE_BASEDIR = Get-Location
- $env:CCACHE_DIR = "$(Get-Location)\ccache"
- New-Item -Type Directory -Force -Path $env:CCACHE_DIR
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C
- sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview
- cd MSVC2019_64
- cmake --build . --config $config --target ($targets.Split(','))
- ccache --show-stats

@ -75,6 +75,7 @@ TEST_FRAMEWORK=""
GOOGLE_INSTALL_ROOT=""
INSTALL_PREFIX="."
BUILD_BENCHMARKS=""
OSG_MULTIVIEW_BUILD=""
ACTIVATE_MSVC=""
SINGLE_CONFIG=""
@ -140,6 +141,9 @@ while [ $# -gt 0 ]; do
b )
BUILD_BENCHMARKS=true ;;
M )
OSG_MULTIVIEW_BUILD=true ;;
h )
cat <<EOF
Usage: $0 [-cdehkpuvVi]
@ -178,6 +182,8 @@ Options:
CMake install prefix
-b
Build benchmarks
-M
Use a multiview build of OSG
EOF
wrappedExit 0
;;
@ -514,6 +520,16 @@ fi
ICU_VER="70_1"
OSG_ARCHIVE_NAME="OSGoS 3.6.5"
OSG_ARCHIVE="OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}"
OSG_ARCHIVE_REPO_URL="https://gitlab.com/OpenMW/openmw-deps/-/raw/main"
if ! [ -z $OSG_MULTIVIEW_BUILD ]; then
OSG_ARCHIVE_NAME="OSG-3.6-multiview"
OSG_ARCHIVE="OSG-3.6-multiview-ee297dce0-msvc${MSVC_REAL_YEAR}-win${BITS}"
OSG_ARCHIVE_REPO_URL="https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr-ovr_multiview"
fi
echo
echo "==================================="
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
@ -565,15 +581,15 @@ if [ -z $SKIP_DOWNLOAD ]; then
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.20.1.zip" \
"OpenAL-Soft-1.20.1.zip"
# OSGoS
download "OSGoS 3.6.5" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
# OSGoS https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr-ovr_multiview/windows/OSG-3.6-multiview-ee297dce0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z
download "${OSG_ARCHIVE_NAME}" \
"${OSG_ARCHIVE_REPO_URL}/windows/${OSG_ARCHIVE}.7z" \
"${OSG_ARCHIVE}.7z"
if [ -n "$PDBS" ]; then
download "OSGoS symbols" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
"OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
download "${OSG_ARCHIVE_NAME} symbols" \
"${OSG_ARCHIVE_REPO_URL}/windows/${OSG_ARCHIVE}-sym.7z" \
"${OSG_ARCHIVE}-sym.7z"
fi
# SDL2
@ -781,7 +797,7 @@ printf "OpenAL-Soft 1.20.1... "
cd $DEPS
echo
# OSGoS
printf "OSGoS 3.6.5... "
printf "${OSG_ARCHIVE_NAME}... "
{
cd $DEPS_INSTALL
if [ -d OSG ] && \
@ -792,9 +808,9 @@ printf "OSGoS 3.6.5... "
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf OSG
eval 7z x -y "${DEPS}/OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP
mv "OSGoS-3.6.5-b02abe2-msvc${MSVC_REAL_YEAR}-win${BITS}" OSG
eval 7z x -y "${DEPS}/${OSG_ARCHIVE}.7z" $STRIP
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/${OSG_ARCHIVE}-sym.7z" $STRIP
mv "${OSG_ARCHIVE}" OSG
fi
OSG_SDK="$(real_pwd)/OSG"
add_cmake_opts -DOSG_DIR="$OSG_SDK"
@ -804,8 +820,13 @@ printf "OSGoS 3.6.5... "
else
SUFFIX=""
fi
if ! [ -z $OSG_MULTIVIEW_BUILD ]; then
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{ot21-OpenThreads,zlib,libpng16}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg162-osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
else
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
fi
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll
done

@ -410,6 +410,11 @@ if(OSG_STATIC)
add_definitions(-DOSG_LIBRARY_STATIC)
endif()
include(cmake/CheckOsgMultiview.cmake)
if(HAVE_MULTIVIEW)
add_definitions(-DOSG_HAS_MULTIVIEW)
endif(HAVE_MULTIVIEW)
set(BOOST_COMPONENTS system filesystem program_options iostreams)
if(WIN32)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale)

@ -30,6 +30,9 @@
#include <components/compiler/extensions0.hpp>
#include <components/stereo/stereomanager.hpp>
#include <components/stereo/multiview.hpp>
#include <components/sceneutil/workqueue.hpp>
#include <components/files/configurationmanager.hpp>
@ -40,6 +43,7 @@
#include <components/sceneutil/screencapture.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/color.hpp>
#include <components/sceneutil/util.hpp>
#include "mwinput/inputmanagerimp.hpp"
@ -251,6 +255,18 @@ namespace
Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION);
}
};
class InitializeStereoOperation final : public osg::GraphicsOperation
{
public:
InitializeStereoOperation() : GraphicsOperation("InitializeStereoOperation", false)
{}
void operator()(osg::GraphicsContext* graphicsContext) override
{
Stereo::Manager::instance().initializeStereo(graphicsContext);
}
};
}
void OMW::Engine::executeLocalScripts()
@ -413,6 +429,9 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mEncoding(ToUTF8::WINDOWS_1252)
, mEncoder(nullptr)
, mScreenCaptureOperation(nullptr)
, mSelectDepthFormatOperation(new SceneUtil::SelectDepthFormatOperation())
, mSelectColorFormatOperation(new SceneUtil::Color::SelectColorFormatOperation())
, mStereoManager(nullptr)
, mSkipMenu (false)
, mUseSound (true)
, mCompileAll (false)
@ -448,6 +467,8 @@ OMW::Engine::~Engine()
if (mScreenCaptureOperation != nullptr)
mScreenCaptureOperation->stop();
mStereoManager = nullptr;
mEnvironment.cleanup();
delete mScriptContext;
@ -640,6 +661,15 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
if (Debug::shouldDebugOpenGL())
realizeOperations->add(new Debug::EnableGLDebugOperation());
realizeOperations->add(mSelectDepthFormatOperation);
realizeOperations->add(mSelectColorFormatOperation);
if (Stereo::getStereo())
{
realizeOperations->add(new InitializeStereoOperation());
Stereo::setVertexBufferHint();
}
mViewer->realize();
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
@ -674,11 +704,13 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mEnvironment.setStateManager (
std::make_unique<MWState::StateManager> (mCfgMgr.getUserDataPath() / "saves", mContentFiles));
createWindow(settings);
mStereoManager = std::make_unique<Stereo::Manager>(mViewer);
osg::ref_ptr<osg::Group> rootNode(new osg::Group);
mViewer->setSceneData(rootNode);
createWindow(settings);
mVFS = std::make_unique<VFS::Manager>(mFSStrict);
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
@ -757,22 +789,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false);
bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f);
bool enableReverseZ = false;
if (Settings::Manager::getBool("reverse z", "Camera"))
{
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);
#if OSG_VERSION_LESS_THAN(3, 6, 6)
// hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028
@ -784,6 +800,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
osg::ref_ptr<osg::Group> guiRoot = new osg::Group;
guiRoot->setName("GUI Root");
guiRoot->setNodeMask(MWRender::Mask_GUI);
mStereoManager->disableStereoForNode(guiRoot);
rootNode->addChild(guiRoot);
auto windowMgr = std::make_unique<MWGui::WindowManager>(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(),

@ -39,6 +39,11 @@ namespace MWLua
class LuaManager;
}
namespace Stereo
{
class Manager;
}
namespace Files
{
struct ConfigurationManager;
@ -49,6 +54,16 @@ namespace osgViewer
class ScreenCaptureHandler;
}
namespace SceneUtil
{
class SelectDepthFormatOperation;
namespace Color
{
class SelectColorFormatOperation;
}
}
struct SDL_Window;
namespace OMW
@ -69,9 +84,14 @@ namespace OMW
osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
osg::ref_ptr<SceneUtil::AsyncScreenCaptureOperation> mScreenCaptureOperation;
osg::ref_ptr<SceneUtil::SelectDepthFormatOperation> mSelectDepthFormatOperation;
osg::ref_ptr<SceneUtil::Color::SelectColorFormatOperation> mSelectColorFormatOperation;
std::string mCellName;
std::vector<std::string> mContentFiles;
std::vector<std::string> mGroundcoverFiles;
std::unique_ptr<Stereo::Manager> mStereoManager;
bool mSkipMenu;
bool mUseSound;
bool mCompileAll;

@ -367,6 +367,12 @@ namespace MWBase
virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0;
virtual void asyncPrepareSaveMap() = 0;
/// Sets the cull masks for all applicable views
virtual void setCullMask(uint32_t mask) = 0;
/// Same as viewer->getCamera()->getCullMask(), provided for consistency.
virtual uint32_t getCullMask() = 0;
};
}

@ -748,7 +748,11 @@ namespace MWGui
// ------------------------------------------------------------------------------------------
MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue)
#ifdef USE_OPENXR
: WindowPinnableBase("openmw_map_window_vr.layout")
#else
: WindowPinnableBase("openmw_map_window.layout")
#endif
, LocalMapBase(customMarkers, localMapRender)
, NoDrop(drag, mMainWidget)
, mGlobalMap(nullptr)

@ -572,17 +572,17 @@ namespace MWGui
void WindowManager::enableScene(bool enable)
{
unsigned int disablemask = MWRender::Mask_GUI|MWRender::Mask_PreCompile;
if (!enable && mViewer->getCamera()->getCullMask() != disablemask)
if (!enable && getCullMask() != disablemask)
{
mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask();
mOldCullMask = mViewer->getCamera()->getCullMask();
mOldCullMask = getCullMask();
mViewer->getUpdateVisitor()->setTraversalMask(disablemask);
mViewer->getCamera()->setCullMask(disablemask);
setCullMask(disablemask);
}
else if (enable && mViewer->getCamera()->getCullMask() == disablemask)
else if (enable && getCullMask() == disablemask)
{
mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask);
mViewer->getCamera()->setCullMask(mOldCullMask);
setCullMask(mOldCullMask);
}
}
@ -1226,6 +1226,21 @@ namespace MWGui
updateVisible();
}
void WindowManager::setCullMask(uint32_t mask)
{
mViewer->getCamera()->setCullMask(mask);
// We could check whether stereo is enabled here, but these methods are
// trivial and have no effect in mono or multiview so just call them regardless.
mViewer->getCamera()->setCullMaskLeft(mask);
mViewer->getCamera()->setCullMaskRight(mask);
}
uint32_t WindowManager::getCullMask()
{
return mViewer->getCamera()->getCullMask();
}
void WindowManager::popGuiMode(bool noSound)
{
if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop)

@ -582,6 +582,9 @@ namespace MWGui
void handleScheduledMessageBoxes();
void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force);
void setCullMask(uint32_t mask) override;
uint32_t getCullMask() override;
};
}

@ -21,6 +21,7 @@
#include <components/resource/resourcesystem.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/shadow.hpp>
#include <components/sceneutil/rtt.hpp>
#include <components/settings/settings.hpp>
#include <components/sceneutil/nodecallback.hpp>
#include <components/sceneutil/depth.hpp>
@ -41,9 +42,10 @@ namespace MWRender
class DrawOnceCallback : public SceneUtil::NodeCallback<DrawOnceCallback>
{
public:
DrawOnceCallback ()
DrawOnceCallback(osg::Node* subgraph)
: mRendered(false)
, mLastRenderedFrame(0)
, mSubgraph(subgraph)
{
}
@ -61,6 +63,9 @@ namespace MWRender
nv->setFrameStamp(fs);
// Update keyframe controllers in the scene graph first...
// RTTNode does not continue update traversal, so manually continue the update traversal since we need it.
mSubgraph->accept(*nv);
traverse(node, nv);
nv->setFrameStamp(previousFramestamp);
@ -84,6 +89,7 @@ namespace MWRender
private:
bool mRendered;
unsigned int mLastRenderedFrame;
osg::ref_ptr<osg::Node> mSubgraph;
};
@ -138,6 +144,96 @@ namespace MWRender
}
};
class CharacterPreviewRTTNode : public SceneUtil::RTTNode
{
static constexpr float fovYDegrees = 12.3f;
static constexpr float znear = 0.1f;
static constexpr float zfar = 10000.f;
public:
CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY)
: RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, StereoAwareness::Unaware_MultiViewShaders)
, mAspectRatio(static_cast<float>(sizeX) / static_cast<float>(sizeY))
{
if (SceneUtil::AutoDepth::isReversed())
mPerspectiveMatrix = static_cast<osg::Matrixf>(SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar));
else
mPerspectiveMatrix = osg::Matrixf::perspective(fovYDegrees, mAspectRatio, znear, zfar);
mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix));
mViewMatrix = osg::Matrixf::identity();
setColorBufferInternalFormat(GL_RGBA);
}
void setDefaults(osg::Camera* camera) override
{
// hints that the camera is not relative to the master camera
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
camera->setViewport(0, 0, width(), height());
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setName("CharacterPreview");
camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES);
camera->setCullMask(~(Mask_UpdateVisitor));
SceneUtil::setCameraClearDepth(camera);
// hints that the camera is not relative to the master camera
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
camera->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar);
camera->setViewport(0, 0, width(), height());
camera->setRenderOrder(osg::Camera::PRE_RENDER);
#ifdef OSG_HAS_MULTIVIEW
if (shouldDoTextureArray())
{
auto* viewUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2);
auto* projUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2);
viewUniform->setElement(0, osg::Matrix::identity());
viewUniform->setElement(1, osg::Matrix::identity());
projUniform->setElement(0, mPerspectiveMatrix);
projUniform->setElement(1, mPerspectiveMatrix);
mGroup->getOrCreateStateSet()->addUniform(viewUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
mGroup->getOrCreateStateSet()->addUniform(projUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
}
#endif
camera->setNodeMask(Mask_RenderToTexture);
camera->addChild(mGroup);
};
void apply(osg::Camera* camera) override
{
if(mCameraStateset)
camera->setStateSet(mCameraStateset);
camera->setViewMatrix(mViewMatrix);
};
void addChild(osg::Node* node)
{
mGroup->addChild(node);
}
void setCameraStateset(osg::StateSet* stateset)
{
mCameraStateset = stateset;
}
void setViewMatrix(const osg::Matrixf& viewMatrix)
{
mViewMatrix = viewMatrix;
}
osg::ref_ptr<osg::Group> mGroup = new osg::Group;
osg::Matrixf mPerspectiveMatrix;
osg::Matrixf mViewMatrix;
osg::ref_ptr<osg::StateSet> mCameraStateset;
float mAspectRatio;
};
CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem,
const MWWorld::Ptr& character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt)
: mParent(parent)
@ -149,31 +245,11 @@ namespace MWRender
, mSizeX(sizeX)
, mSizeY(sizeY)
{
mTexture = new osg::Texture2D;
mTexture->setTextureSize(sizeX, sizeY);
mTexture->setInternalFormat(GL_RGBA);
mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
mTextureStateSet = new osg::StateSet;
mTextureStateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA));
mCamera = new osg::Camera;
// hints that the camera is not relative to the master camera
mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
mCamera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f));
mCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mCamera->setViewport(0, 0, sizeX, sizeY);
mCamera->setRenderOrder(osg::Camera::PRE_RENDER);
mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video"));
mCamera->setName("CharacterPreview");
mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES);
mCamera->setCullMask(~(Mask_UpdateVisitor));
mCamera->setNodeMask(Mask_RenderToTexture);
SceneUtil::setCameraClearDepth(mCamera);
mRTTNode = new CharacterPreviewRTTNode(sizeX, sizeY);
mRTTNode->setNodeMask(Mask_RenderToTexture);
bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP;
@ -191,14 +267,6 @@ namespace MWRender
defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
stateset->setAttribute(defaultMat);
const float fovYDegrees = 12.3f;
const float aspectRatio = static_cast<float>(sizeX) / static_cast<float>(sizeY);
const float znear = 0.1f;
const float zfar = 10000.f;
mCamera->setProjectionMatrixAsPerspective(fovYDegrees, aspectRatio, znear, zfar);
osg::Matrixf projectionMatrix = SceneUtil::AutoDepth::isReversed() ? static_cast<osg::Matrixf>(SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, aspectRatio, znear, zfar)) : static_cast<osg::Matrixf>(mCamera->getProjectionMatrix());
stateset->addUniform(new osg::Uniform("projectionMatrix", projectionMatrix));
SceneUtil::ShadowManager::disableShadowsForStateSet(stateset);
// assign large value to effectively turn off fog
@ -266,23 +334,22 @@ namespace MWRender
lightManager->addChild(lightSource);
mCamera->addChild(lightManager);
mRTTNode->addChild(lightManager);
mNode = new osg::PositionAttitudeTransform;
lightManager->addChild(mNode);
mDrawOnceCallback = new DrawOnceCallback;
mCamera->addUpdateCallback(mDrawOnceCallback);
mDrawOnceCallback = new DrawOnceCallback(mRTTNode->mGroup);
mRTTNode->addUpdateCallback(mDrawOnceCallback);
mParent->addChild(mCamera);
mParent->addChild(mRTTNode);
mCharacter.mCell = nullptr;
}
CharacterPreview::~CharacterPreview ()
{
mCamera->removeChildren(0, mCamera->getNumChildren());
mParent->removeChild(mCamera);
mParent->removeChild(mRTTNode);
}
int CharacterPreview::getTextureWidth() const
@ -308,7 +375,7 @@ namespace MWRender
osg::ref_ptr<osg::Texture2D> CharacterPreview::getTexture()
{
return mTexture;
return static_cast<osg::Texture2D*>(mRTTNode->getColorTexture(nullptr));
}
void CharacterPreview::rebuild()
@ -325,7 +392,7 @@ namespace MWRender
void CharacterPreview::redraw()
{
mCamera->setNodeMask(Mask_RenderToTexture);
mRTTNode->setNodeMask(Mask_RenderToTexture);
mDrawOnceCallback->redrawNextFrame();
}
@ -346,7 +413,7 @@ namespace MWRender
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
mViewport = new osg::Viewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY));
stateset->setAttributeAndModes(mViewport);
mCamera->setStateSet(stateset);
mRTTNode->setCameraStateset(stateset);
redraw();
}
@ -433,10 +500,11 @@ namespace MWRender
// Set the traversal number from the last draw, so that the frame switch used for RigGeometry double buffering works correctly
visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame());
osg::Node::NodeMask nodeMask = mCamera->getNodeMask();
mCamera->setNodeMask(~0u);
mCamera->accept(visitor);
mCamera->setNodeMask(nodeMask);
auto* camera = mRTTNode->getCamera(nullptr);
osg::Node::NodeMask nodeMask = camera->getNodeMask();
camera->setNodeMask(~0u);
camera->accept(visitor);
camera->setNodeMask(nodeMask);
if (intersector->containsIntersections())
{
@ -459,7 +527,8 @@ namespace MWRender
mNode->setScale(scale);
mCamera->setViewMatrixAsLookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0,0,1));
auto viewMatrix = osg::Matrixf::lookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0, 0, 1));
mRTTNode->setViewMatrix(viewMatrix);
}
// --------------------------------------------------------------------------------------------------
@ -492,7 +561,7 @@ namespace MWRender
rebuild();
}
class UpdateCameraCallback : public SceneUtil::NodeCallback<UpdateCameraCallback, osg::Camera*>
class UpdateCameraCallback : public SceneUtil::NodeCallback<UpdateCameraCallback, CharacterPreviewRTTNode*>
{
public:
UpdateCameraCallback(osg::ref_ptr<const osg::Node> nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset)
@ -502,10 +571,10 @@ namespace MWRender
{
}
void operator()(osg::Camera* cam, osg::NodeVisitor* nv)
void operator()(CharacterPreviewRTTNode* node, osg::NodeVisitor* nv)
{
// Update keyframe controllers in the scene graph first...
traverse(cam, nv);
traverse(node, nv);
// Now update camera utilizing the updated head position
osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths();
@ -514,7 +583,8 @@ namespace MWRender
osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
osg::Vec3 headOffset = worldMat.getTrans();
cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1));
auto viewMatrix = osg::Matrixf::lookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0, 0, 1));
node->setViewMatrix(viewMatrix);
}
private:
@ -531,13 +601,13 @@ namespace MWRender
// attach camera to follow the head node
if (mUpdateCameraCallback)
mCamera->removeUpdateCallback(mUpdateCameraCallback);
mRTTNode->removeUpdateCallback(mUpdateCameraCallback);
const osg::Node* head = mAnimation->getNode("Bip01 Head");
if (head)
{
mUpdateCameraCallback = new UpdateCameraCallback(head, mPosition, mLookAt);
mCamera->addUpdateCallback(mUpdateCameraCallback);
mRTTNode->addUpdateCallback(mUpdateCameraCallback);
}
else
Log(Debug::Error) << "Error: Bip01 Head node not found";

@ -26,6 +26,7 @@ namespace MWRender
class NpcAnimation;
class DrawOnceCallback;
class CharacterPreviewRTTNode;
class CharacterPreview
{
@ -56,10 +57,9 @@ namespace MWRender
osg::ref_ptr<osg::Group> mParent;
Resource::ResourceSystem* mResourceSystem;
osg::ref_ptr<osg::Texture2D> mTexture;
osg::ref_ptr<osg::StateSet> mTextureStateSet;
osg::ref_ptr<osg::Camera> mCamera;
osg::ref_ptr<DrawOnceCallback> mDrawOnceCallback;
osg::ref_ptr<CharacterPreviewRTTNode> mRTTNode;
osg::Vec3f mPosition;
osg::Vec3f mLookAt;

@ -15,12 +15,14 @@
#include <components/esm3/fogstate.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/misc/constants.hpp>
#include <components/stereo/multiview.hpp>
#include <components/settings/settings.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/shadow.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/nodecallback.hpp>
#include <components/sceneutil/rtt.hpp>
#include <components/files/memorystream.hpp>
#include <components/resource/scenemanager.hpp>
@ -34,36 +36,6 @@
namespace
{
class CameraLocalUpdateCallback : public SceneUtil::NodeCallback<CameraLocalUpdateCallback, osg::Camera*>
{
public:
CameraLocalUpdateCallback(MWRender::LocalMap* parent)
: mRendered(false)
, mParent(parent)
{
}
void operator()(osg::Camera* node, osg::NodeVisitor*)
{
if (mRendered)
node->setNodeMask(0);
if (!mRendered)
{
mRendered = true;
mParent->markForRemoval(node);
}
// Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's,
// so it has been updated already.
}
private:
bool mRendered;
MWRender::LocalMap* mParent;
};
float square(float val)
{
return val*val;
@ -82,6 +54,28 @@ namespace
namespace MWRender
{
class LocalMapRenderToTexture: public SceneUtil::RTTNode
{
public:
LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize,
float x, float y, const osg::Vec3d& upVector, float zmin, float zmax);
void setDefaults(osg::Camera* camera) override;
bool isActive() { return mActive; }
void setIsActive(bool active) { mActive = active; }
osg::Node* mSceneRoot;
osg::Matrix mProjectionMatrix;
osg::Matrix mViewMatrix;
bool mActive;
};
class CameraLocalUpdateCallback : public SceneUtil::NodeCallback<CameraLocalUpdateCallback, LocalMapRenderToTexture*>
{
public:
void operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv);
};
LocalMap::LocalMap(osg::Group* root)
: mRoot(root)
@ -104,10 +98,8 @@ LocalMap::LocalMap(osg::Group* root)
LocalMap::~LocalMap()
{
for (auto& camera : mActiveCameras)
removeCamera(camera);
for (auto& camera : mCamerasPendingRemoval)
removeCamera(camera);
for (auto& rtt : mLocalMapRTTs)
mRoot->removeChild(rtt);
}
const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle)
@ -173,93 +165,14 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell)
}
}
osg::ref_ptr<osg::Camera> LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax)
{
osg::ref_ptr<osg::Camera> camera (new osg::Camera);
if (SceneUtil::AutoDepth::isReversed())
camera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10));
else
camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10);
camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector);
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static);
camera->setNodeMask(Mask_RenderToTexture);
// Disable small feature culling, it's not going to be reliable for this camera
osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING);
camera->setCullingMode(cullingMode);
SceneUtil::setCameraClearDepth(camera);
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE);
stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast<osg::Matrixf>(camera->getProjectionMatrix())), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
// assign large value to effectively turn off fog
// shaders don't respect glDisable(GL_FOG)
osg::ref_ptr<osg::Fog> fog (new osg::Fog);
fog->setStart(10000000);
fog->setEnd(10000000);
stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE);
osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel;
lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f));
stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
osg::ref_ptr<osg::Light> light = new osg::Light;
light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f));
light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f));
light->setAmbient(osg::Vec4(0,0,0,1));
light->setSpecular(osg::Vec4(0,0,0,0));
light->setLightNum(0);
light->setConstantAttenuation(1.f);
light->setLinearAttenuation(0.f);
light->setQuadraticAttenuation(0.f);
osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource;
lightSource->setLight(light);
lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
SceneUtil::ShadowManager::disableShadowsForStateSet(stateset);
// override sun for local map
SceneUtil::configureStateSetSunOverride(static_cast<SceneUtil::LightManager*>(mSceneRoot.get()), light, stateset);
camera->addChild(lightSource);
camera->setStateSet(stateset);
camera->setViewport(0, 0, mMapResolution, mMapResolution);
camera->setUpdateCallback(new CameraLocalUpdateCallback(this));
return camera;
}
void LocalMap::setupRenderToTexture(osg::ref_ptr<osg::Camera> camera, int x, int y)
void LocalMap::setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax)
{
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
texture->setTextureSize(mMapResolution, mMapResolution);
texture->setInternalFormat(GL_RGB);
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);
mLocalMapRTTs.emplace_back(new LocalMapRenderToTexture(mSceneRoot, mMapResolution, mMapWorldSize, left, top, upVector, zmin, zmax));
SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, texture);
mRoot->addChild(mLocalMapRTTs.back());
camera->addChild(mSceneRoot);
mRoot->addChild(camera);
mActiveCameras.push_back(camera);
MapSegment& segment = mInterior? mInteriorSegments[std::make_pair(x, y)] : mExteriorSegments[std::make_pair(x, y)];
segment.mMapTexture = texture;
MapSegment& segment = mInterior? mInteriorSegments[std::make_pair(segment_x, segment_y)] : mExteriorSegments[std::make_pair(segment_x, segment_y)];
segment.mMapTexture = static_cast<osg::Texture2D*>(mLocalMapRTTs.back()->getColorTexture(nullptr));
}
void LocalMap::requestMap(const MWWorld::CellStore* cell)
@ -321,33 +234,19 @@ osg::ref_ptr<osg::Texture2D> LocalMap::getFogOfWarTexture(int x, int y)
return found->second.mFogOfWarTexture;
}
void LocalMap::removeCamera(osg::Camera *cam)
void LocalMap::cleanupCameras()
{
cam->removeChildren(0, cam->getNumChildren());
mRoot->removeChild(cam);
}
void LocalMap::markForRemoval(osg::Camera *cam)
auto it = mLocalMapRTTs.begin();
while (it != mLocalMapRTTs.end())
{
CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), cam);
if (found == mActiveCameras.end())
if (!(*it)->isActive())
{
Log(Debug::Error) << "Error: trying to remove an inactive camera";
return;
mRoot->removeChild(*it);
it = mLocalMapRTTs.erase(it);
}
mActiveCameras.erase(found);
mCamerasPendingRemoval.push_back(cam);
else
it++;
}
void LocalMap::cleanupCameras()
{
if (mCamerasPendingRemoval.empty())
return;
for (auto& camera : mCamerasPendingRemoval)
removeCamera(camera);
mCamerasPendingRemoval.clear();
}
void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell)
@ -361,9 +260,9 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell)
float zmin = bound.center().z() - bound.radius();
float zmax = bound.center().z() + bound.radius();
osg::ref_ptr<osg::Camera> camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize,
setupRenderToTexture(cell->getCell()->getGridX(), cell->getCell()->getGridY(),
x * mMapWorldSize + mMapWorldSize / 2.f, y * mMapWorldSize + mMapWorldSize / 2.f,
osg::Vec3d(0, 1, 0), zmin, zmax);
setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY());
MapSegment& segment = mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())];
if (!segment.mFogOfWarImage)
@ -501,12 +400,9 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell)
osg::Vec2f pos = osg::Vec2f(rotatedCenter.x(), rotatedCenter.y()) + center;
osg::ref_ptr<osg::Camera> camera = createOrthographicCamera(pos.x(), pos.y(),
mMapWorldSize, mMapWorldSize,
setupRenderToTexture(x, y, pos.x(), pos.y(),
osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax);
setupRenderToTexture(camera, x, y);
auto coords = std::make_pair(x,y);
MapSegment& segment = mInteriorSegments[coords];
if (!segment.mFogOfWarImage)
@ -682,6 +578,7 @@ void LocalMap::MapSegment::createFogOfWarTexture()
mFogOfWarTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
mFogOfWarTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
mFogOfWarTexture->setUnRefImageDataAfterApply(false);
mFogOfWarTexture->setImage(mFogOfWarImage);
}
void LocalMap::MapSegment::initFogOfWar()
@ -697,7 +594,6 @@ void LocalMap::MapSegment::initFogOfWar()
memcpy(mFogOfWarImage->data(), &data[0], data.size()*4);
createFogOfWarTexture();
mFogOfWarTexture->setImage(mFogOfWarImage);
}
void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm)
@ -730,7 +626,6 @@ void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm)
mFogOfWarImage->dirty();
createFogOfWarTexture();
mFogOfWarTexture->setImage(mFogOfWarImage);
mHasFogState = true;
}
@ -762,4 +657,107 @@ void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const
fog.mImageData = std::vector<char>(data.begin(), data.end());
}
LocalMapRenderToTexture::LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, float x, float y, const osg::Vec3d& upVector, float zmin, float zmax)
: RTTNode(res, res, 0, false, 0, StereoAwareness::Unaware_MultiViewShaders)
, mSceneRoot(sceneRoot)
, mActive(true)
{
if (SceneUtil::AutoDepth::isReversed())
mProjectionMatrix = SceneUtil::getReversedZProjectionMatrixAsOrtho(-mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10);
else
mProjectionMatrix.makeOrtho(-mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10);
mViewMatrix.makeLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector);
setUpdateCallback(new CameraLocalUpdateCallback);
}
void LocalMapRenderToTexture::setDefaults(osg::Camera* camera)
{
// Disable small feature culling, it's not going to be reliable for this camera
osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING | osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING);
camera->setCullingMode(cullingMode);
SceneUtil::setCameraClearDepth(camera);
camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static);
camera->setCullMaskLeft(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static);
camera->setCullMaskRight(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static);
camera->setNodeMask(Mask_RenderToTexture);
camera->setProjectionMatrix(mProjectionMatrix);
camera->setViewMatrix(mViewMatrix);
auto* stateset = camera->getOrCreateStateSet();
stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE);
stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast<osg::Matrixf>(mProjectionMatrix)), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
if (Stereo::getMultiview())
{
auto* viewUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2);
auto* projUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2);
viewUniform->setElement(0, osg::Matrix::identity());
viewUniform->setElement(1, osg::Matrix::identity());
projUniform->setElement(0, mProjectionMatrix);
projUniform->setElement(1, mProjectionMatrix);
stateset->addUniform(viewUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
stateset->addUniform(projUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
}
// assign large value to effectively turn off fog
// shaders don't respect glDisable(GL_FOG)
osg::ref_ptr<osg::Fog> fog(new osg::Fog);
fog->setStart(10000000);
fog->setEnd(10000000);
stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel;
lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f));
stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
osg::ref_ptr<osg::Light> light = new osg::Light;
light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f));
light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f));
light->setAmbient(osg::Vec4(0, 0, 0, 1));
light->setSpecular(osg::Vec4(0, 0, 0, 0));
light->setLightNum(0);
light->setConstantAttenuation(1.f);
light->setLinearAttenuation(0.f);
light->setQuadraticAttenuation(0.f);
osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource;
lightSource->setLight(light);
lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
SceneUtil::ShadowManager::disableShadowsForStateSet(stateset);
// override sun for local map
SceneUtil::configureStateSetSunOverride(static_cast<SceneUtil::LightManager*>(mSceneRoot), light, stateset);
camera->addChild(lightSource);
camera->addChild(mSceneRoot);
}
void CameraLocalUpdateCallback::operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv)
{
if (!node->isActive())
node->setNodeMask(0);
if (node->isActive())
{
node->setIsActive(false);
}
// Rtt-nodes do not forward update traversal to their cameras so we can traverse safely.
// Traverse in case there are nested callbacks.
traverse(node, nv);
}
}

@ -30,6 +30,8 @@ namespace osg
namespace MWRender
{
class LocalMapRenderToTexture;
///
/// \brief Local map rendering
///
@ -58,13 +60,6 @@ namespace MWRender
osg::ref_ptr<osg::Texture2D> getFogOfWarTexture (int x, int y);
void removeCamera(osg::Camera* cam);
/**
* Indicates a camera has been queued for rendering and can be cleaned up in the next frame. For internal use only.
*/
void markForRemoval(osg::Camera* cam);
/**
* Removes cameras that have already been rendered. Should be called every frame to ensure that
* we do not render the same map more than once. Note, this cleanup is difficult to implement in an
@ -104,11 +99,8 @@ namespace MWRender
osg::ref_ptr<osg::Group> mRoot;
osg::ref_ptr<osg::Node> mSceneRoot;
typedef std::vector< osg::ref_ptr<osg::Camera> > CameraVector;
CameraVector mActiveCameras;
CameraVector mCamerasPendingRemoval;
typedef std::vector< osg::ref_ptr<LocalMapRenderToTexture> > RTTVector;
RTTVector mLocalMapRTTs;
typedef std::set<std::pair<int, int> > Grid;
Grid mCurrentGrid;
@ -152,8 +144,7 @@ namespace MWRender
void requestExteriorMap(const MWWorld::CellStore* cell);
void requestInteriorMap(const MWWorld::CellStore* cell);
osg::ref_ptr<osg::Camera> createOrthographicCamera(float left, float top, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax);
void setupRenderToTexture(osg::ref_ptr<osg::Camera> camera, int x, int y);
void setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax);
bool mInterior;
osg::BoundingBox mBounds;

@ -6,15 +6,20 @@
#include <osg/Camera>
#include <osg/Callback>
#include <osg/Texture2D>
#include <osg/Texture2DArray>
#include <osg/FrameBufferObject>
#include <osgViewer/Viewer>
#include <components/settings/settings.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/color.hpp>
#include <components/sceneutil/nodecallback.hpp>
#include <components/debug/debuglog.hpp>
#include <components/stereo/multiview.hpp>
#include <components/stereo/stereomanager.hpp>
#include "vismask.hpp"
namespace
@ -38,8 +43,8 @@ namespace
class CullCallback : public SceneUtil::NodeCallback<CullCallback, osg::Node*, osgUtil::CullVisitor*>
{
public:
CullCallback()
: mLastFrameNumber(0)
CullCallback(MWRender::PostProcessor* pp)
: mPostProcessor(pp)
{
}
@ -47,36 +52,21 @@ namespace
{
osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage();
unsigned int frame = cv->getTraversalNumber();
if (frame != mLastFrameNumber)
{
mLastFrameNumber = frame;
MWRender::PostProcessor* postProcessor = dynamic_cast<MWRender::PostProcessor*>(cv->getCurrentCamera()->getUserData());
if (!postProcessor)
{
Log(Debug::Error) << "Failed retrieving user data for master camera: FBO setup failed";
traverse(node, cv);
return;
}
if (!postProcessor->getMsaaFbo())
if (!mPostProcessor->getMsaaFbo())
{
renderStage->setFrameBufferObject(postProcessor->getFbo());
renderStage->setFrameBufferObject(mPostProcessor->getFbo());
}
else
{
renderStage->setMultisampleResolveFramebufferObject(postProcessor->getFbo());
renderStage->setFrameBufferObject(postProcessor->getMsaaFbo());
}
renderStage->setMultisampleResolveFramebufferObject(mPostProcessor->getFbo());
renderStage->setFrameBufferObject(mPostProcessor->getMsaaFbo());
}
traverse(node, cv);
}
private:
unsigned int mLastFrameNumber;
MWRender::PostProcessor* mPostProcessor;
};
struct ResizedCallback : osg::GraphicsContext::ResizedCallback
@ -157,15 +147,13 @@ namespace MWRender
PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode)
: mViewer(viewer)
, mRootNode(new osg::Group)
, mDepthFormat(GL_DEPTH24_STENCIL8_EXT)
{
bool softParticles = Settings::Manager::getBool("soft particles", "Shaders");
if (!SceneUtil::AutoDepth::isReversed() && !softParticles)
if (!SceneUtil::AutoDepth::isReversed() && !softParticles && !Stereo::getStereo())
return;
osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext();
unsigned int contextID = gc->getState()->getContextID();
osg::GLExtensions* ext = gc->getState()->get<osg::GLExtensions>();
constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: ";
@ -184,24 +172,19 @@ namespace MWRender
if (SceneUtil::AutoDepth::isReversed())
{
if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float"))
mDepthFormat = GL_DEPTH32F_STENCIL8;
else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float"))
mDepthFormat = GL_DEPTH32F_STENCIL8_NV;
else
if(SceneUtil::AutoDepth::depthSourceType() != GL_FLOAT_32_UNSIGNED_INT_24_8_REV)
{
// TODO: Once we have post-processing implemented we want to skip this return and continue with setup.
// Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no
// benefits if no floating point depth formats are supported.
Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported.";
if (!softParticles)
if (!softParticles && !Stereo::getStereo())
return;
}
}
int width = viewer->getCamera()->getViewport()->width();
int height = viewer->getCamera()->getViewport()->height();
auto* traits = gc->getTraits();
int width = traits->width;
int height = traits->height;
createTexturesAndCamera(width, height);
resize(width, height);
@ -210,15 +193,17 @@ namespace MWRender
mRootNode->addChild(rootNode);
mViewer->setSceneData(mRootNode);
if (!Stereo::getStereo())
{
// We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate
// RTT camera this would not be needed.
mViewer->getCamera()->addCullCallback(new CullCallback);
mViewer->getCamera()->addCullCallback(new CullCallback(this));
mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex);
mViewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mDepthTex);
}
mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this));
mViewer->getCamera()->setUserData(this);
}
void PostProcessor::resize(int width, int height)
@ -260,15 +245,89 @@ namespace MWRender
mViewer->getCamera()->resize(width, height);
mHUDCamera->resize(width, height);
if (Stereo::getStereo())
Stereo::Manager::instance().screenResolutionChanged();
}
class HUDCameraStatesetUpdater final : public SceneUtil::StateSetUpdater
{
public:
public:
HUDCameraStatesetUpdater(osg::ref_ptr<osg::Camera> HUDCamera, osg::ref_ptr<osg::Program> program, osg::ref_ptr<osg::Texture2D> sceneTex)
: mHUDCamera(HUDCamera)
, mProgram(program)
, mSceneTex(sceneTex)
{
}
void setDefaults(osg::StateSet* stateset) override
{
stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON);
stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON);
stateset->addUniform(new osg::Uniform("sceneTex", 0));
stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
if (osg::DisplaySettings::instance()->getStereo())
{
stateset->setAttribute(new osg::Viewport);
stateset->addUniform(new osg::Uniform("viewportIndex", 0));
}
}
void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override
{
if (Stereo::getMultiview())
{
auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer();
stateset->setTextureAttributeAndModes(0, multiviewFbo->multiviewColorBuffer(), osg::StateAttribute::ON);
}
}
void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override
{
auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer();
stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(0), osg::StateAttribute::ON);
auto viewport = static_cast<osg::Viewport*>(stateset->getAttribute(osg::StateAttribute::VIEWPORT));
auto fullViewport = mHUDCamera->getViewport();
viewport->setViewport(
0,
0,
fullViewport->width() / 2,
fullViewport->height()
);
}
void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override
{
auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer();
stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(1), osg::StateAttribute::ON);
auto viewport = static_cast<osg::Viewport*>(stateset->getAttribute(osg::StateAttribute::VIEWPORT));
auto fullViewport = mHUDCamera->getViewport();
viewport->setViewport(
fullViewport->width() / 2,
0,
fullViewport->width() / 2,
fullViewport->height()
);
}
private:
osg::ref_ptr<osg::Camera> mHUDCamera;
osg::ref_ptr<osg::Program> mProgram;
osg::ref_ptr<osg::Texture2D> mSceneTex;
};
void PostProcessor::createTexturesAndCamera(int width, int height)
{
mDepthTex = new osg::Texture2D;
mDepthTex->setTextureSize(width, height);
mDepthTex->setSourceFormat(GL_DEPTH_STENCIL_EXT);
mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_UNSIGNED_INT_24_8_EXT);
mDepthTex->setInternalFormat(mDepthFormat);
mDepthTex->setSourceFormat(SceneUtil::AutoDepth::depthSourceFormat());
mDepthTex->setSourceType(SceneUtil::AutoDepth::depthSourceType());
mDepthTex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat());
mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
mDepthTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
@ -283,9 +342,9 @@ namespace MWRender
mSceneTex = new osg::Texture2D;
mSceneTex->setTextureSize(width, height);
mSceneTex->setSourceFormat(GL_RGB);
mSceneTex->setSourceType(GL_UNSIGNED_BYTE);
mSceneTex->setInternalFormat(GL_RGB);
mSceneTex->setSourceFormat(SceneUtil::Color::colorSourceFormat());
mSceneTex->setSourceType(SceneUtil::Color::colorSourceType());
mSceneTex->setInternalFormat(SceneUtil::Color::colorInternalFormat());
mSceneTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
mSceneTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
mSceneTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
@ -296,6 +355,7 @@ namespace MWRender
mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER);
mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0));
mHUDCamera->setClearMask(0);
mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1));
mHUDCamera->setAllowEventFocus(false);
mHUDCamera->setViewport(0, 0, width, height);
@ -325,8 +385,28 @@ namespace MWRender
}
)GLSL";
constexpr char fragSrcMultiview[] = R"GLSL(
#version 330 compatibility
#extension GL_EXT_texture_array : require
varying vec2 uv;
uniform sampler2DArray sceneTex;
void main()
{
vec3 array_uv = vec3(uv.x * 2, uv.y, 0);
if(array_uv.x >= 1.0)
{
array_uv.x -= 1.0;
array_uv.z = 1;
}
gl_FragData[0] = texture2DArray(sceneTex, array_uv);
}
)GLSL";
osg::ref_ptr<osg::Shader> vertShader = new osg::Shader(osg::Shader::VERTEX, vertSrc);
osg::ref_ptr<osg::Shader> fragShader = new osg::Shader(osg::Shader::FRAGMENT, fragSrc);
osg::ref_ptr<osg::Shader> fragShader = new osg::Shader(osg::Shader::FRAGMENT, Stereo::getMultiview() ? fragSrcMultiview : fragSrc);
osg::ref_ptr<osg::Program> program = new osg::Program;
program->addShader(vertShader);
@ -334,13 +414,8 @@ namespace MWRender
mHUDCamera->addChild(createFullScreenTri());
mHUDCamera->setNodeMask(Mask_RenderToTexture);
auto* stateset = mHUDCamera->getOrCreateStateSet();
stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON);
stateset->setAttributeAndModes(program, osg::StateAttribute::ON);
stateset->addUniform(new osg::Uniform("sceneTex", 0));
stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
mHUDCamera->setCullCallback(new HUDCameraStatesetUpdater(mHUDCamera, program, mSceneTex));
}
}

@ -7,11 +7,18 @@
#include <osg/Camera>
#include <osg/ref_ptr>
#include <memory>
namespace osgViewer
{
class Viewer;
}
namespace Stereo
{
class MultiviewFramebuffer;
}
namespace MWRender
{
class PostProcessor : public osg::Referenced
@ -23,7 +30,6 @@ namespace MWRender
auto getFbo() { return mFbo; }
auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; }
int getDepthFormat() { return mDepthFormat; }
osg::ref_ptr<osg::Texture2D> getOpaqueDepthTex() { return mOpaqueDepthTex; }
void resize(int width, int height);
@ -35,6 +41,7 @@ namespace MWRender
osg::ref_ptr<osg::Group> mRootNode;
osg::ref_ptr<osg::Camera> mHUDCamera;
std::shared_ptr<Stereo::MultiviewFramebuffer> mMultiviewFbo;
osg::ref_ptr<osg::FrameBufferObject> mMsaaFbo;
osg::ref_ptr<osg::FrameBufferObject> mFbo;
osg::ref_ptr<osg::RenderBuffer> mFirstPersonDepthRBProxy;
@ -42,9 +49,8 @@ namespace MWRender
osg::ref_ptr<osg::Texture2D> mSceneTex;
osg::ref_ptr<osg::Texture2D> mDepthTex;
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex;
int mDepthFormat;
};
}
#endif

@ -21,6 +21,9 @@
#include <components/debug/debuglog.hpp>
#include <components/stereo/stereomanager.hpp>
#include <components/stereo/multiview.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/keyframemanager.hpp>
@ -31,6 +34,7 @@
#include <components/settings/settings.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/statesetupdater.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
@ -45,6 +49,7 @@
#include <components/detournavigator/navigator.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/groundcoverstore.hpp"
@ -70,6 +75,54 @@
namespace MWRender
{
class PerViewUniformStateUpdater final : public SceneUtil::StateSetUpdater
{
public:
public:
PerViewUniformStateUpdater()
{
}
void setDefaults(osg::StateSet* stateset) override
{
stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{}));
}
void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override
{
auto* uProjectionMatrix = stateset->getUniform("projectionMatrix");
if (uProjectionMatrix)
uProjectionMatrix->set(mProjectionMatrix);
}
void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override
{
auto* uProjectionMatrix = stateset->getUniform("projectionMatrix");
if (uProjectionMatrix)
uProjectionMatrix->set(Stereo::Manager::instance().computeEyeProjection(0, SceneUtil::AutoDepth::isReversed()));
}
void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override
{
auto* uProjectionMatrix = stateset->getUniform("projectionMatrix");
if (uProjectionMatrix)
uProjectionMatrix->set(Stereo::Manager::instance().computeEyeProjection(1, SceneUtil::AutoDepth::isReversed()));
}
void setProjectionMatrix(const osg::Matrixf& projectionMatrix)
{
mProjectionMatrix = projectionMatrix;
}
const osg::Matrixf& projectionMatrix() const
{
return mProjectionMatrix;
}
private:
osg::Matrixf mProjectionMatrix;
};
class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater
{
public:
@ -84,7 +137,6 @@ namespace MWRender
void setDefaults(osg::StateSet* stateset) override
{
stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{}));
stateset->addUniform(new osg::Uniform("linearFac", 0.f));
stateset->addUniform(new osg::Uniform("near", 0.f));
stateset->addUniform(new osg::Uniform("far", 0.f));
@ -98,10 +150,6 @@ namespace MWRender
void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override
{
auto* uProjectionMatrix = stateset->getUniform("projectionMatrix");
if (uProjectionMatrix)
uProjectionMatrix->set(mProjectionMatrix);
auto* uLinearFac = stateset->getUniform("linearFac");
if (uLinearFac)
uLinearFac->set(mLinearFac);
@ -130,11 +178,6 @@ namespace MWRender
}
}
void setProjectionMatrix(const osg::Matrixf& projectionMatrix)
{
mProjectionMatrix = projectionMatrix;
}
void setLinearFac(float linearFac)
{
mLinearFac = linearFac;
@ -167,7 +210,6 @@ namespace MWRender
private:
osg::Matrixf mProjectionMatrix;
float mLinearFac;
float mNear;
float mFar;
@ -312,14 +354,16 @@ namespace MWRender
auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders"));
resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem);
// Shadows and radial fog have problems with fixed-function mode
// Shadows and radial fog have problems with fixed-function mode.
bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders")
|| Settings::Manager::getBool("soft particles", "Shaders")
|| Settings::Manager::getBool("force shaders", "Shaders")
|| Settings::Manager::getBool("enable shadows", "Shadows")
|| lightingMethod != SceneUtil::LightingMethod::FFP
|| reverseZ;
|| reverseZ
|| Stereo::getMultiview();
resourceSystem->getSceneManager()->setForceShaders(forceShaders);
// FIXME: calling dummy method because terrain needs to know whether lighting is clamped
resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders"));
resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders"));
@ -368,6 +412,9 @@ namespace MWRender
globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0";
globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0";
globalDefines["useGPUShader4"] = "0";
globalDefines["GLSLVersion"] = "120";
globalDefines["useOVR_multiview"] = "0";
globalDefines["numViews"] = "1";
for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++)
globalDefines[itr->first] = itr->second;
@ -453,11 +500,14 @@ namespace MWRender
mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover);
rootNode->addUpdateCallback(mSharedUniformStateUpdater);
mPerViewUniformStateUpdater = new PerViewUniformStateUpdater();
rootNode->addCullCallback(mPerViewUniformStateUpdater);
mPostProcessor = new PostProcessor(viewer, mRootNode);
resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat());
resourceSystem->getSceneManager()->setDepthFormat(SceneUtil::AutoDepth::depthInternalFormat());
resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex());
if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(mPostProcessor->getDepthFormat()))
if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(SceneUtil::AutoDepth::depthInternalFormat()))
Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it.";
// water goes after terrain for correct waterculling order
@ -514,7 +564,8 @@ namespace MWRender
mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
mViewer->getCamera()->setCullingMode(cullingMode);
mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater));
auto mask = ~(Mask_UpdateVisitor | Mask_SimpleWater);
MWBase::Environment::get().getWindowManager()->setCullMask(mask);
NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor);
NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect);
Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models"));
@ -537,6 +588,7 @@ namespace MWRender
SceneUtil::setCameraClearDepth(mViewer->getCamera());
updateProjectionMatrix();
mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
@ -753,14 +805,15 @@ namespace MWRender
}
else if (mode == Render_Scene)
{
unsigned int mask = mViewer->getCamera()->getCullMask();
auto* wm = MWBase::Environment::get().getWindowManager();
unsigned int mask = wm->getCullMask();
bool enabled = !(mask&sToggleWorldMask);
if (enabled)
mask |= sToggleWorldMask;
else
mask &= ~sToggleWorldMask;
mWater->showWorld(enabled);
mViewer->getCamera()->setCullMask(mask);
wm->setCullMask(mask);
return enabled;
}
else if (mode == Render_NavMesh)
@ -1155,14 +1208,23 @@ namespace MWRender
if (SceneUtil::AutoDepth::isReversed())
{
mSharedUniformStateUpdater->setLinearFac(-mNearClip / (mViewDistance - mNearClip) - 1.f);
mSharedUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance));
mPerViewUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance));
}
else
mSharedUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix());
mPerViewUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix());
mSharedUniformStateUpdater->setNear(mNearClip);
mSharedUniformStateUpdater->setFar(mViewDistance);
if (Stereo::getStereo())
{
auto res = Stereo::Manager::instance().eyeResolution();
mSharedUniformStateUpdater->setScreenRes(res.x(), res.y());
Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->projectionMatrix());
}
else
{
mSharedUniformStateUpdater->setScreenRes(width, height);
}
// Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear.
// Limit FOV here just for sure, otherwise viewing distance can be too high.

@ -76,6 +76,7 @@ namespace MWRender
{
class StateUpdater;
class SharedUniformStateUpdater;
class PerViewUniformStateUpdater;
class EffectManager;
class ScreenshotManager;
@ -255,6 +256,7 @@ namespace MWRender
void updateRecastMesh();
osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors);
osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor;
@ -292,6 +294,7 @@ namespace MWRender
osg::ref_ptr<StateUpdater> mStateUpdater;
osg::ref_ptr<SharedUniformStateUpdater> mSharedUniformStateUpdater;
osg::ref_ptr<PerViewUniformStateUpdater> mPerViewUniformStateUpdater;
osg::Vec4f mAmbientColor;
float mMinimumAmbientLuminance;

@ -13,6 +13,8 @@
#include <components/resource/scenemanager.hpp>
#include <components/shader/shadermanager.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/stereo/stereomanager.hpp>
#include <components/stereo/multiview.hpp>
#include <components/settings/settings.hpp>
@ -85,6 +87,12 @@ namespace MWRender
{
int screenW = renderInfo.getCurrentCamera()->getViewport()->width();
int screenH = renderInfo.getCurrentCamera()->getViewport()->height();
if (Stereo::getStereo())
{
auto eyeRes = Stereo::Manager::instance().eyeResolution();
screenW = eyeRes.x();
screenH = eyeRes.y();
}
double imageaspect = (double)mWidth/(double)mHeight;
int leftPadding = std::max(0, static_cast<int>(screenW - screenH * imageaspect) / 2);
int topPadding = std::max(0, static_cast<int>(screenH - screenW / imageaspect) / 2);
@ -94,13 +102,19 @@ namespace MWRender
// Ensure we are reading from the resolved framebuffer and not the multisampled render buffer. Also ensure that the readbuffer is set correctly with rendeirng to FBO.
// glReadPixel() cannot read from multisampled targets
PostProcessor* postProcessor = dynamic_cast<PostProcessor*>(renderInfo.getCurrentCamera()->getUserData());
if (postProcessor && postProcessor->getFbo())
{
osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false);
if (ext)
{
ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, postProcessor->getFbo()->getHandle(renderInfo.getContextID()));
osg::FrameBufferObject* fbo = nullptr;
if (Stereo::getStereo())
fbo = Stereo::Manager::instance().multiviewFramebuffer()->layerFbo(0);
else if (postProcessor)
fbo = postProcessor->getFbo();
if (fbo)
{
ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo->getHandle(renderInfo.getContextID()));
renderInfo.getState()->glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
}
}
@ -346,7 +360,7 @@ namespace MWRender
rttCamera->addChild(mWater->getReflectionNode());
rttCamera->addChild(mWater->getRefractionNode());
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson));
rttCamera->setCullMask(MWBase::Environment::get().getWindowManager()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson));
rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

@ -23,6 +23,7 @@
#include <components/misc/rng.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/stereo/stereomanager.hpp>
#include <components/nifosg/particle.hpp>
@ -332,8 +333,10 @@ namespace MWRender
if (mSceneManager->getForceShaders())
{
auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", {}, osg::Shader::VERTEX);
auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", {}, osg::Shader::FRAGMENT);
Shader::ShaderManager::DefineMap defines = {};
Stereo::Manager::instance().shaderStereoDefines(defines);
auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", defines, osg::Shader::VERTEX);
auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", defines, osg::Shader::FRAGMENT);
auto program = mSceneManager->getShaderManager().getProgram(vertex, fragment);
mEarlyRenderBinRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", -1));
mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(program, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);

@ -10,6 +10,7 @@
#include <osg/PositionAttitudeTransform>
#include <osg/ClipNode>
#include <osg/FrontFace>
#include <osg/ViewportIndexed>
#include <osgDB/ReadFile>
@ -33,6 +34,7 @@
#include <components/misc/constants.hpp>
#include <components/misc/stringops.hpp>
#include <components/stereo/stereomanager.hpp>
#include <components/nifosg/controller.hpp>
@ -44,6 +46,8 @@
#include "../mwworld/cellstore.hpp"
#include "../mwbase/environment.hpp"
#include "vismask.hpp"
#include "ripplesimulation.hpp"
#include "renderbin.hpp"
@ -260,7 +264,7 @@ class Refraction : public SceneUtil::RTTNode
{
public:
Refraction(uint32_t rttSize)
: RTTNode(rttSize, rttSize, 1, false)
: RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware)
, mNodeMask(Refraction::sDefaultCullMask)
{
mClipCullNode = new ClipCullNode;
@ -335,7 +339,7 @@ class Reflection : public SceneUtil::RTTNode
{
public:
Reflection(uint32_t rttSize, bool isInterior)
: RTTNode(rttSize, rttSize, 0, false)
: RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware)
{
setInterior(isInterior);
mClipCullNode = new ClipCullNode;
@ -458,6 +462,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem
mWaterGeom->setDrawCallback(new DepthClampCallback);
mWaterGeom->setNodeMask(Mask_Water);
mWaterGeom->setDataVariance(osg::Object::STATIC);
mWaterGeom->setName("Water Geometry");
mWaterNode = new osg::PositionAttitudeTransform;
mWaterNode->setName("Water Root");
@ -468,6 +473,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem
osg::ref_ptr<osg::Geometry> geom2 (osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES));
createSimpleWaterStateSet(geom2, Fallback::Map::getFloat("Water_Map_Alpha"));
geom2->setNodeMask(Mask_SimpleWater);
geom2->setName("Simple Water Geometry");
mWaterNode->addChild(geom2);
mSceneRoot->addChild(mWaterNode);
@ -679,6 +685,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2);
defineMap["rain_ripple_detail"] = std::to_string(rippleDetail);
Stereo::Manager::instance().shaderStereoDefines(defineMap);
Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager();
osg::ref_ptr<osg::Shader> vertexShader(shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX));
@ -695,6 +702,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
mRainIntensityUpdater = new RainIntensityUpdater();
node->setUpdateCallback(mRainIntensityUpdater);

@ -0,0 +1,26 @@
set(TMP_ROOT ${CMAKE_BINARY_DIR}/try-compile)
file(MAKE_DIRECTORY ${TMP_ROOT})
file(WRITE ${TMP_ROOT}/checkmultiview.cpp
"
#include <osg/Camera>
int main(void)
{
(void)osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER;
return 0;
}
")
message(STATUS "Checking if OSG supports multiview")
try_compile(RESULT_VAR
${TMP_ROOT}/temp
${TMP_ROOT}/checkmultiview.cpp
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${OPENSCENEGRAPH_INCLUDE_DIRS}"
)
set(HAVE_MULTIVIEW ${RESULT_VAR})
if(HAVE_MULTIVIEW)
message(STATUS "Osg supports multiview")
else(HAVE_MULTIVIEW)
message(NOTICE "Osg does not support multiview, disabling use of GL_OVR_multiview")
endif(HAVE_MULTIVIEW)

@ -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
)

@ -0,0 +1,157 @@
#include "color.hpp"
#include <algorithm>
#include <array>
#include <SDL_opengl_glext.h>
#include <components/debug/debuglog.hpp>
#include <components/settings/settings.hpp>
namespace SceneUtil
{
bool isColorFormat(GLenum format)
{
static constexpr std::array<GLenum, 42> formats = {
GL_RGB,
GL_RGB4,
GL_RGB5,
GL_RGB8,
GL_RGB8_SNORM,
GL_RGB10,
GL_RGB12,
GL_RGB16,
GL_RGB16_SNORM,
GL_SRGB,
GL_SRGB8,
GL_RGB16F,
GL_RGB32F,
GL_R11F_G11F_B10F,
GL_RGB9_E5,
GL_RGB8I,
GL_RGB8UI,
GL_RGB16I,
GL_RGB16UI,
GL_RGB32I,
GL_RGB32UI,
GL_RGBA,
GL_RGBA2,
GL_RGBA4,
GL_RGB5_A1,
GL_RGBA8,
GL_RGBA8_SNORM,
GL_RGB10_A2,
GL_RGB10_A2UI,
GL_RGBA12,
GL_RGBA16,
GL_RGBA16_SNORM,
GL_SRGB_ALPHA8,
GL_SRGB8_ALPHA8,
GL_RGBA16F,
GL_RGBA32F,
GL_RGBA8I,
GL_RGBA8UI,
GL_RGBA16I,
GL_RGBA16UI,
GL_RGBA32I,
GL_RGBA32UI,
};
return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
}
bool isFloatingPointColorFormat(GLenum format)
{
static constexpr std::array<GLenum, 5> formats = {
GL_RGB16F,
GL_RGB32F,
GL_R11F_G11F_B10F,
GL_RGBA16F,
GL_RGBA32F,
};
return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
}
int getColorFormatChannelCount(GLenum format)
{
static constexpr std::array<GLenum, 21> formats = {
GL_RGBA,
GL_RGBA2,
GL_RGBA4,
GL_RGB5_A1,
GL_RGBA8,
GL_RGBA8_SNORM,
GL_RGB10_A2,
GL_RGB10_A2UI,
GL_RGBA12,
GL_RGBA16,
GL_RGBA16_SNORM,
GL_SRGB_ALPHA8,
GL_SRGB8_ALPHA8,
GL_RGBA16F,
GL_RGBA32F,
GL_RGBA8I,
GL_RGBA8UI,
GL_RGBA16I,
GL_RGBA16UI,
GL_RGBA32I,
GL_RGBA32UI,
};
if (std::find(formats.cbegin(), formats.cend(), format) != formats.cend())
return 4;
return 3;
}
void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType)
{
if (getColorFormatChannelCount(internalFormat == 4))
sourceFormat = GL_RGBA;
else
sourceFormat = GL_RGB;
if (isFloatingPointColorFormat(internalFormat))
sourceType = GL_FLOAT;
else
sourceType = GL_UNSIGNED_BYTE;
}
namespace Color
{
GLenum sColorInternalFormat;
GLenum sColorSourceFormat;
GLenum sColorSourceType;
GLenum colorInternalFormat()
{
return sColorInternalFormat;
}
GLenum colorSourceFormat()
{
return sColorSourceFormat;
}
GLenum colorSourceType()
{
return sColorSourceType;
}
void SelectColorFormatOperation::operator()([[maybe_unused]] osg::GraphicsContext* graphicsContext)
{
sColorInternalFormat = GL_RGB;
for (auto supportedFormat : mSupportedFormats)
{
if (isColorFormat(supportedFormat))
{
sColorInternalFormat = supportedFormat;
break;
}
}
getColorFormatSourceFormatAndType(sColorInternalFormat, sColorSourceFormat, sColorSourceType);
}
}
}

@ -0,0 +1,204 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_COLOR_H
#define OPENMW_COMPONENTS_SCENEUTIL_COLOR_H
#include <osg/GraphicsThread>
namespace SceneUtil
{
bool isColorFormat(GLenum format);
bool isFloatingPointColorFormat(GLenum format);
int getColorFormatChannelCount(GLenum format);
void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType);
namespace Color
{
GLenum colorSourceFormat();
GLenum colorSourceType();
GLenum colorInternalFormat();
class SelectColorFormatOperation final : public osg::GraphicsOperation
{
public:
SelectColorFormatOperation() : GraphicsOperation("SelectColorFormatOperation", false)
{}
void operator()(osg::GraphicsContext* graphicsContext) override;
void setSupportedFormats(const std::vector<GLenum>& supportedFormats)
{
mSupportedFormats = supportedFormats;
}
private:
std::vector<GLenum> mSupportedFormats;
};
}
}
#ifndef GL_RGB
#define GL_RGB 0x1907
#endif
#ifndef GL_RGBA
#define GL_RGBA 0x1908
#endif
#ifndef GL_RGB4
#define GL_RGB4 0x804F
#endif
#ifndef GL_RGB5
#define GL_RGB5 0x8050
#endif
#ifndef GL_RGB8
#define GL_RGB8 0x8051
#endif
#ifndef GL_RGB8_SNORM
#define GL_RGB8_SNORM 0x8F96
#endif
#ifndef GL_RGB10
#define GL_RGB10 0x8052
#endif
#ifndef GL_RGB12
#define GL_RGB12 0x8053
#endif
#ifndef GL_RGB16
#define GL_RGB16 0x8054
#endif
#ifndef GL_RGB16_SNORM
#define GL_RGB16_SNORM 0x8F9A
#endif
#ifndef GL_RGBA2
#define GL_RGBA2 0x8055
#endif
#ifndef GL_RGBA4
#define GL_RGBA4 0x8056
#endif
#ifndef GL_RGB5_A1
#define GL_RGB5_A1 0x8057
#endif
#ifndef GL_RGBA8
#define GL_RGBA8 0x8058
#endif
#ifndef GL_RGBA8_SNORM
#define GL_RGBA8_SNORM 0x8F97
#endif
#ifndef GL_RGB10_A2
#define GL_RGB10_A2 0x906F
#endif
#ifndef GL_RGB10_A2UI
#define GL_RGB10_A2UI 0x906F
#endif
#ifndef GL_RGBA12
#define GL_RGBA12 0x805A
#endif
#ifndef GL_RGBA16
#define GL_RGBA16 0x805B
#endif
#ifndef GL_RGBA16_SNORM
#define GL_RGBA16_SNORM 0x8F9B
#endif
#ifndef GL_SRGB
#define GL_SRGB 0x8C40
#endif
#ifndef GL_SRGB8
#define GL_SRGB8 0x8C41
#endif
#ifndef GL_SRGB_ALPHA8
#define GL_SRGB_ALPHA8 0x8C42
#endif
#ifndef GL_SRGB8_ALPHA8
#define GL_SRGB8_ALPHA8 0x8C43
#endif
#ifndef GL_RGB16F
#define GL_RGB16F 0x881B
#endif
#ifndef GL_RGBA16F
#define GL_RGBA16F 0x881A
#endif
#ifndef GL_RGB32F
#define GL_RGB32F 0x8815
#endif
#ifndef GL_RGBA32F
#define GL_RGBA32F 0x8814
#endif
#ifndef GL_R11F_G11F_B10F
#define GL_R11F_G11F_B10F 0x8C3A
#endif
#ifndef GL_RGB8I
#define GL_RGB8I 0x8D8F
#endif
#ifndef GL_RGB8UI
#define GL_RGB8UI 0x8D7D
#endif
#ifndef GL_RGB16I
#define GL_RGB16I 0x8D89
#endif
#ifndef GL_RGB16UI
#define GL_RGB16UI 0x8D77
#endif
#ifndef GL_RGB32I
#define GL_RGB32I 0x8D83
#endif
#ifndef GL_RGB32UI
#define GL_RGB32UI 0x8D71
#endif
#ifndef GL_RGBA8I
#define GL_RGBA8I 0x8D8E
#endif
#ifndef GL_RGBA8UI
#define GL_RGBA8UI 0x8D7C
#endif
#ifndef GL_RGBA16I
#define GL_RGBA16I 0x8D88
#endif
#ifndef GL_RGBA16UI
#define GL_RGBA16UI 0x8D76
#endif
#ifndef GL_RGBA32I
#define GL_RGBA32I 0x8D82
#endif
#ifndef GL_RGBA32UI
#define GL_RGBA32UI 0x8D70
#endif
#endif

@ -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,7 +653,30 @@ 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;
}
}
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);
@ -661,9 +685,7 @@ MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, d
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);
osg::Matrixd clipToWorld;
clipToWorld.invert(modelViewMatrix * projectionMatrix);
}
// transform frustum corners from clipspace to world coords, and compute center
for(Vertices::iterator itr = corners.begin();
@ -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", { {"GLSLVersion", "120"} }, 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)
@ -910,7 +932,8 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh
program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)},
{"alphaToCoverage", "0"},
{"adjustCoverage", "1"},
{"useGPUShader4", useGPUShader4}
{"useGPUShader4", useGPUShader4},
{"GLSLVersion", "120"}
}, osg::Shader::FRAGMENT));
}
}
@ -931,6 +954,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 +1023,7 @@ void MWShadowTechnique::update(osg::NodeVisitor& nv)
void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
{
if (!_enableShadows)
{
if (mSetDummyStateWhenDisabled)
@ -1047,6 +1132,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();
@ -1459,6 +1574,8 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
}
}
vdd->setNumValidShadows(numValidShadows);
if (numValidShadows>0)
{
prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber());
@ -1665,7 +1782,14 @@ osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustu
OSG_INFO<<"computeLightViewFrustumPolytope()"<<std::endl;
osg::Polytope polytope;
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);
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);
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())
{
// 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)
{
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);
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)
{
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);
vdd->mDepthTexture = createTexture(mDepthBufferInternalFormat);
camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, 0, false, mSamples);
}
}
// 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)
{
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);
/// 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.
/// Set default settings - optionally override in derived classes
virtual void setDefaults(osg::Camera* camera) {};
/// Set default settings - optionally override in derived classes
/// 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 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;

@ -0,0 +1,162 @@
#include "stereomanager.hpp"
#include "multiview.hpp"
#include <osg/io_utils>
#include <osg/Texture2D>
#include <osg/Texture2DMultisample>
#include <osg/Texture2DArray>
#include <osg/DisplaySettings>
#include <osgUtil/CullVisitor>
#include <osgUtil/RenderStage>
#include <osgViewer/Renderer>
#include <osgViewer/Viewer>
#include <iostream>
#include <map>
#include <string>
#include <components/debug/debuglog.hpp>
#include <components/sceneutil/statesetupdater.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/color.hpp>
#include <components/sceneutil/mwshadowtechnique.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/stringops.hpp>
#include "frustum.hpp"
namespace Stereo
{
#ifdef OSG_HAS_MULTIVIEW
struct MultiviewFrustumCallback final : public osg::CullSettings::InitialFrustumCallback
{
MultiviewFrustumCallback(StereoFrustumManager* sfm)
: mSfm(sfm)
{
}
void setInitialFrustum(osg::CullStack& cullStack, osg::Polytope& frustum) const override
{
auto cm = cullStack.getCullingMode();
bool nearCulling = !!(cm & osg::CullSettings::NEAR_PLANE_CULLING);
bool farCulling = !!(cm & osg::CullSettings::FAR_PLANE_CULLING);
frustum.setToBoundingBox(mSfm->boundingBox(), nearCulling, farCulling);
}
StereoFrustumManager* mSfm;
};
#endif
struct ShadowFrustumCallback final : public SceneUtil::MWShadowTechnique::CustomFrustumCallback
{
ShadowFrustumCallback(StereoFrustumManager* parent) : mParent(parent)
{
}
void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) override
{
mParent->customFrustumCallback(cv, customClipSpace, sharedFrustumHint);
}
StereoFrustumManager* mParent;
};
void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb)
{
static const std::array<osg::Vec3d, 8> clipCorners = {{
{-1.0, -1.0, -1.0},
{ 1.0, -1.0, -1.0},
{ 1.0, -1.0, 1.0},
{-1.0, -1.0, 1.0},
{-1.0, 1.0, -1.0},
{ 1.0, 1.0, -1.0},
{ 1.0, 1.0, 1.0},
{-1.0, 1.0, 1.0}
}};
osg::Matrix slaveClipToView;
slaveClipToView.invert(slaveProjection);
for (const auto& clipCorner : clipCorners)
{
auto masterViewVertice = clipCorner * slaveClipToView;
auto masterClipVertice = masterViewVertice * masterProjection;
bb.expandBy(masterClipVertice);
}
}
StereoFrustumManager::StereoFrustumManager(osg::Camera* camera)
: mCamera(camera)
, mShadowTechnique(nullptr)
, mShadowFrustumCallback(nullptr)
{
if (Stereo::getMultiview())
{
#ifdef OSG_HAS_MULTIVIEW
mMultiviewFrustumCallback = new MultiviewFrustumCallback(this);
mCamera->setInitialFrustumCallback(mMultiviewFrustumCallback);
#endif
}
if (Settings::Manager::getBool("shared shadow maps", "Stereo"))
{
mShadowFrustumCallback = new ShadowFrustumCallback(this);
auto* renderer = static_cast<osgViewer::Renderer*>(mCamera->getRenderer());
for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) })
{
mSharedFrustums[sceneView->getCullVisitorRight()] = sceneView->getCullVisitorLeft();
}
}
}
StereoFrustumManager::~StereoFrustumManager()
{
if (Stereo::getMultiview())
{
#ifdef OSG_HAS_MULTIVIEW
mCamera->setInitialFrustumCallback(nullptr);
#endif
}
if (mShadowTechnique)
mShadowTechnique->setCustomFrustumCallback(nullptr);
}
void StereoFrustumManager::setShadowTechnique(
SceneUtil::MWShadowTechnique* shadowTechnique)
{
if (mShadowTechnique)
mShadowTechnique->setCustomFrustumCallback(nullptr);
mShadowTechnique = shadowTechnique;
if (mShadowTechnique)
mShadowTechnique->setCustomFrustumCallback(mShadowFrustumCallback);
}
void StereoFrustumManager::customFrustumCallback(
osgUtil::CullVisitor& cv,
osg::BoundingBoxd& customClipSpace,
osgUtil::CullVisitor*& sharedFrustumHint)
{
auto it = mSharedFrustums.find(&cv);
if (it != mSharedFrustums.end())
{
sharedFrustumHint = it->second;
}
customClipSpace = mBoundingBox;
}
void StereoFrustumManager::update(std::array<osg::Matrix, 2> projections)
{
mBoundingBox.init();
for (auto& projection : projections)
joinBoundingBoxes(mCamera->getProjectionMatrix(), projection, mBoundingBox);
}
}

@ -0,0 +1,76 @@
#ifndef STEREO_FRUSTUM_H
#define STEREO_FRUSTUM_H
#include <osg/Matrix>
#include <osg/Vec3>
#include <osg/Camera>
#include <osg/StateSet>
#include <osg/BoundingBox>
#include <memory>
#include <array>
#include <map>
#include <components/stereo/types.hpp>
namespace osg
{
class FrameBufferObject;
class Texture2D;
class Texture2DMultisample;
class Texture2DArray;
}
namespace osgViewer
{
class Viewer;
}
namespace usgUtil
{
class CullVisitor;
}
namespace SceneUtil
{
class MWShadowTechnique;
}
namespace Stereo
{
#ifdef OSG_HAS_MULTIVIEW
struct MultiviewFrustumCallback;
#endif
struct ShadowFrustumCallback;
void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb);
class StereoFrustumManager
{
public:
StereoFrustumManager(osg::Camera* camera);
~StereoFrustumManager();
void update(std::array<osg::Matrix, 2> projections);
const osg::BoundingBoxd& boundingBox() const { return mBoundingBox; }
void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique);
void customFrustumCallback(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint);
private:
osg::ref_ptr<osg::Camera> mCamera;
osg::ref_ptr<SceneUtil::MWShadowTechnique> mShadowTechnique;
osg::ref_ptr<ShadowFrustumCallback> mShadowFrustumCallback;
std::map< osgUtil::CullVisitor*, osgUtil::CullVisitor*> mSharedFrustums;
osg::BoundingBoxd mBoundingBox;
#ifdef OSG_HAS_MULTIVIEW
osg::ref_ptr<MultiviewFrustumCallback> mMultiviewFrustumCallback;
#endif
};
}
#endif

@ -0,0 +1,484 @@
#include "multiview.hpp"
#include <osg/FrameBufferObject>
#include <osg/GLExtensions>
#include <osg/Texture2D>
#include <osg/Texture2DMultisample>
#include <osg/Texture2DArray>
#include <osgUtil/RenderStage>
#include <osgUtil/CullVisitor>
#include <components/sceneutil/nodecallback.hpp>
#include <components/settings/settings.hpp>
#include <components/debug/debuglog.hpp>
#include <components/stereo/stereomanager.hpp>
#include <algorithm>
namespace Stereo
{
namespace
{
bool getMultiviewSupportedImpl(unsigned int contextID)
{
#ifdef OSG_HAS_MULTIVIEW
if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview"))
{
Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview\" not supported)";
return false;
}
if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview2"))
{
Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview2\" not supported)";
return false;
}
return true;
#else
Log(Debug::Verbose) << "Disabling Multiview (OSG does not support multiview)";
return false;
#endif
}
bool getMultiviewSupported(unsigned int contextID)
{
static bool supported = getMultiviewSupportedImpl(contextID);
return supported;
}
bool getTextureViewSupportedImpl(unsigned int contextID)
{
if (!osg::isGLExtensionOrVersionSupported(contextID, "ARB_texture_view", 4.3))
{
Log(Debug::Verbose) << "Disabling texture views (opengl extension \"ARB_texture_view\" not supported)";
return false;
}
return true;
}
bool getTextureViewSupported(unsigned int contextID)
{
static bool supported = getTextureViewSupportedImpl(contextID);
return supported;
}
bool getMultiviewImpl(unsigned int contextID)
{
if (!Stereo::getStereo())
{
Log(Debug::Verbose) << "Disabling Multiview (disabled by config)";
return false;
}
if (!Settings::Manager::getBool("multiview", "Stereo"))
{
Log(Debug::Verbose) << "Disabling Multiview (disabled by config)";
return false;
}
if (!getMultiviewSupported(contextID))
{
return false;
}
if (!getTextureViewSupported(contextID))
{
Log(Debug::Verbose) << "Disabling Multiview (texture views not supported)";
return false;
}
Log(Debug::Verbose) << "Enabling Multiview";
return true;
}
bool getMultiview(unsigned int contextID)
{
static bool multiView = getMultiviewImpl(contextID);
return multiView;
}
}
bool getTextureViewSupported()
{
return getTextureViewSupported(0);
}
bool getMultiview()
{
return getMultiview(0);
}
void configureExtensions(unsigned int contextID)
{
getTextureViewSupported(contextID);
getMultiviewSupported(contextID);
getMultiview(contextID);
}
void setVertexBufferHint()
{
if (getStereo() && Settings::Manager::getBool("multiview", "Stereo"))
{
auto* ds = osg::DisplaySettings::instance().get();
if (!Settings::Manager::getBool("allow display lists for multiview", "Stereo")
&& ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE)
{
// Note that this only works if this code is executed before realize() is called on the viewer.
// The hint is read by the state object only once, before the user realize operations are run.
// Therefore we have to set this hint without access to a graphics context to let us determine
// if multiview will actually be supported or not. So if the user has requested multiview, we
// will just have to set it regardless.
ds->setVertexBufferHint(osg::DisplaySettings::VertexBufferHint::VERTEX_BUFFER_OBJECT);
Log(Debug::Verbose) << "Disabling display lists";
}
}
}
class Texture2DViewSubloadCallback : public osg::Texture2D::SubloadCallback
{
public:
Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer);
void load(const osg::Texture2D& texture, osg::State& state) const override;
void subload(const osg::Texture2D& texture, osg::State& state) const override;
private:
osg::ref_ptr<osg::Texture2DArray> mTextureArray;
int mLayer;
};
Texture2DViewSubloadCallback::Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer)
: mTextureArray(textureArray)
, mLayer(layer)
{
}
void Texture2DViewSubloadCallback::load(const osg::Texture2D& texture, osg::State& state) const
{
state.checkGLErrors("before Texture2DViewSubloadCallback::load()");
auto contextId = state.getContextID();
auto* gl = osg::GLExtensions::Get(contextId, false);
mTextureArray->apply(state);
auto sourceTextureObject = mTextureArray->getTextureObject(contextId);
if (!sourceTextureObject)
{
Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2DArray did not have a texture object";
return;
}
auto targetTextureObject = texture.getTextureObject(contextId);
if (!sourceTextureObject)
{
Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2D did not have a texture object";
return;
}
// OSG already bound this texture ID, giving it a target.
// Delete it and make a new texture ID.
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTextures(1, &targetTextureObject->_id);
glGenTextures(1, &targetTextureObject->_id);
auto sourceId = sourceTextureObject->_id;
auto targetId = targetTextureObject->_id;
auto internalFormat = sourceTextureObject->_profile._internalFormat;
auto levels = std::max(1, sourceTextureObject->_profile._numMipmapLevels);
{
////// OSG BUG
// Texture views require immutable storage.
// OSG should always give immutable storage to sized internal formats, but does not do so for depth formats.
// Fortunately, we can just call glTexStorage3D here to make it immutable. This probably discards depth info for that frame, but whatever.
#ifndef GL_TEXTURE_IMMUTABLE_FORMAT
#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F
#endif
// Store any current binding and re-apply it after so i don't mess with state.
GLint oldBinding = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &oldBinding);
// Bind the source texture and check if it's immutable.
glBindTexture(GL_TEXTURE_2D_ARRAY, sourceId);
GLint immutable = 0;
glGetTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable);
if(!immutable)
{
// It wasn't immutable, so make it immutable.
gl->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, internalFormat, sourceTextureObject->_profile._width, sourceTextureObject->_profile._height, 2);
state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTexStorage3D");
}
glBindTexture(GL_TEXTURE_2D_ARRAY, oldBinding);
}
gl->glTextureView(targetId, GL_TEXTURE_2D, sourceId, internalFormat, 0, levels, mLayer, 1);
state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTextureView");
glBindTexture(GL_TEXTURE_2D, targetId);
}
void Texture2DViewSubloadCallback::subload(const osg::Texture2D& texture, osg::State& state) const
{
// Nothing to do
}
osg::ref_ptr<osg::Texture2D> createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer)
{
if (!getTextureViewSupported())
{
Log(Debug::Error) << "createTextureView_Texture2DFromTexture2DArray: Tried to use a texture view but glTextureView is not supported";
return nullptr;
}
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D;
texture2d->setSubloadCallback(new Texture2DViewSubloadCallback(textureArray, layer));
texture2d->setTextureSize(textureArray->getTextureWidth(), textureArray->getTextureHeight());
texture2d->setBorderColor(textureArray->getBorderColor());
texture2d->setBorderWidth(textureArray->getBorderWidth());
texture2d->setLODBias(textureArray->getLODBias());
texture2d->setFilter(osg::Texture::FilterParameter::MAG_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MAG_FILTER));
texture2d->setFilter(osg::Texture::FilterParameter::MIN_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MIN_FILTER));
texture2d->setInternalFormat(textureArray->getInternalFormat());
texture2d->setNumMipmapLevels(textureArray->getNumMipmapLevels());
return texture2d;
}
class UpdateRenderStagesCallback : public SceneUtil::NodeCallback<UpdateRenderStagesCallback, osg::Node*, osgUtil::CullVisitor*>
{
public:
UpdateRenderStagesCallback(Stereo::MultiviewFramebuffer* multiviewFramebuffer)
: mMultiviewFramebuffer(multiviewFramebuffer)
{
mViewport = new osg::Viewport(0, 0, multiviewFramebuffer->width(), multiviewFramebuffer->height());
mViewportStateset = new osg::StateSet();
mViewportStateset->setAttribute(mViewport.get());
}
void operator()(osg::Node* node, osgUtil::CullVisitor* cv)
{
osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage();
bool msaa = mMultiviewFramebuffer->samples() > 1;
if (!Stereo::getMultiview())
{
auto eye = static_cast<int>(Stereo::Manager::instance().getEye(cv));
if (msaa)
{
renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerMsaaFbo(eye));
renderStage->setMultisampleResolveFramebufferObject(mMultiviewFramebuffer->layerFbo(eye));
}
else
{
renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerFbo(eye));
}
}
// OSG tries to do a horizontal split, but we want to render to separate framebuffers instead.
renderStage->setViewport(mViewport);
cv->pushStateSet(mViewportStateset.get());
traverse(node, cv);
cv->popStateSet();
}
private:
Stereo::MultiviewFramebuffer* mMultiviewFramebuffer;
osg::ref_ptr<osg::Viewport> mViewport;
osg::ref_ptr<osg::StateSet> mViewportStateset;
};
MultiviewFramebuffer::MultiviewFramebuffer(int width, int height, int samples)
: mWidth(width)
, mHeight(height)
, mSamples(samples)
, mMultiview(getMultiview())
, mMultiviewFbo{ new osg::FrameBufferObject }
, mLayerFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject }
, mLayerMsaaFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject }
{
}
MultiviewFramebuffer::~MultiviewFramebuffer()
{
}
void MultiviewFramebuffer::attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat)
{
if (mMultiview)
{
#ifdef OSG_HAS_MULTIVIEW
mMultiviewColorTexture = createTextureArray(sourceFormat, sourceType, internalFormat);
mMultiviewFbo->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMultiviewColorTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0));
for (unsigned i = 0; i < 2; i++)
{
mColorTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewColorTexture.get(), i);
mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i]));
}
#endif
}
else
{
for (unsigned i = 0; i < 2; i++)
{
if (mSamples > 1)
{
mMsaaColorTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat);
mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMsaaColorTexture[i]));
}
mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat);
mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i]));
}
}
}
void MultiviewFramebuffer::attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat)
{
if (mMultiview)
{
#ifdef OSG_HAS_MULTIVIEW
mMultiviewDepthTexture = createTextureArray(sourceFormat, sourceType, internalFormat);
mMultiviewFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMultiviewDepthTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0));
for (unsigned i = 0; i < 2; i++)
{
mDepthTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewDepthTexture.get(), i);
mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i]));
}
#endif
}
else
{
for (unsigned i = 0; i < 2; i++)
{
if (mSamples > 1)
{
mMsaaDepthTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat);
mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMsaaDepthTexture[i]));
}
mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat);
mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i]));
}
}
}
osg::FrameBufferObject* MultiviewFramebuffer::multiviewFbo()
{
return mMultiviewFbo;
}
osg::FrameBufferObject* MultiviewFramebuffer::layerFbo(int i)
{
return mLayerFbo[i];
}
osg::FrameBufferObject* MultiviewFramebuffer::layerMsaaFbo(int i)
{
return mLayerMsaaFbo[i];
}
osg::Texture2DArray* MultiviewFramebuffer::multiviewColorBuffer()
{
return mMultiviewColorTexture;
}
osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i)
{
return mColorTexture[i];
}
osg::Texture2D* MultiviewFramebuffer::layerDepthBuffer(int i)
{
return mDepthTexture[i];
}
void MultiviewFramebuffer::attachTo(osg::Camera* camera)
{
#ifdef OSG_HAS_MULTIVIEW
if (mMultiview)
{
if (mMultiviewColorTexture)
{
camera->attach(osg::Camera::COLOR_BUFFER, mMultiviewColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples);
camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mMultiviewColorTexture->getInternalFormat();
camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = false;
}
if (mMultiviewDepthTexture)
{
camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mMultiviewDepthTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples);
camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mMultiviewDepthTexture->getInternalFormat();
camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = false;
}
}
#endif
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
if (!mCullCallback)
mCullCallback = new UpdateRenderStagesCallback(this);
camera->addCullCallback(mCullCallback);
}
void MultiviewFramebuffer::detachFrom(osg::Camera* camera)
{
#ifdef OSG_HAS_MULTIVIEW
if (mMultiview)
{
if (mMultiviewColorTexture)
{
camera->detach(osg::Camera::COLOR_BUFFER);
}
if (mMultiviewDepthTexture)
{
camera->detach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER);
}
}
#endif
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER);
if (mCullCallback)
camera->removeCullCallback(mCullCallback);
}
osg::Texture2D* MultiviewFramebuffer::createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat)
{
osg::Texture2D* texture = new osg::Texture2D;
texture->setTextureSize(mWidth, mHeight);
texture->setSourceFormat(sourceFormat);
texture->setSourceType(sourceType);
texture->setInternalFormat(internalFormat);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);
return texture;
}
osg::Texture2DMultisample* MultiviewFramebuffer::createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat)
{
osg::Texture2DMultisample* texture = new osg::Texture2DMultisample;
texture->setTextureSize(mWidth, mHeight);
texture->setNumSamples(mSamples);
texture->setSourceFormat(sourceFormat);
texture->setSourceType(sourceType);
texture->setInternalFormat(internalFormat);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);
return texture;
}
osg::Texture2DArray* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat)
{
osg::Texture2DArray* textureArray = new osg::Texture2DArray;
textureArray->setTextureSize(mWidth, mHeight, 2);
textureArray->setSourceFormat(sourceFormat);
textureArray->setSourceType(sourceType);
textureArray->setInternalFormat(internalFormat);
textureArray->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
textureArray->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
textureArray->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
textureArray->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
textureArray->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);
return textureArray;
}
}

@ -0,0 +1,85 @@
#ifndef STEREO_MULTIVIEW_H
#define STEREO_MULTIVIEW_H
#include <osg/ref_ptr>
#include <osg/GL>
#include <osg/Camera>
#include <array>
#include <memory>
namespace osg
{
class FrameBufferObject;
class Texture;
class Texture2D;
class Texture2DMultisample;
class Texture2DArray;
}
namespace Stereo
{
class UpdateRenderStagesCallback;
//! Check if TextureView is supported. Results are undefined if called before configureExtensions().
bool getTextureViewSupported();
//! Check if Multiview should be used. Results are undefined if called before configureExtensions().
bool getMultiview();
//! Use the provided context to check what extensions are supported and configure use of multiview based on extensions and settings.
void configureExtensions(unsigned int contextID);
//! Sets the appropriate vertex buffer hint on OSG's display settings if needed
void setVertexBufferHint();
//! Creates a Texture2D as a texture view into a Texture2DArray
osg::ref_ptr<osg::Texture2D> createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer);
//! Class that manages the specifics of GL_OVR_Multiview aware framebuffers, separating the layers into separate framebuffers, and disabling
class MultiviewFramebuffer
{
public:
MultiviewFramebuffer(int width, int height, int samples);
~MultiviewFramebuffer();
void attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat);
void attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat);
osg::FrameBufferObject* multiviewFbo();
osg::FrameBufferObject* layerFbo(int i);
osg::FrameBufferObject* layerMsaaFbo(int i);
osg::Texture2DArray* multiviewColorBuffer();
osg::Texture2D* layerColorBuffer(int i);
osg::Texture2D* layerDepthBuffer(int i);
void attachTo(osg::Camera* camera);
void detachFrom(osg::Camera* camera);
int width() const { return mWidth; }
int height() const { return mHeight; }
int samples() const { return mSamples; };
private:
osg::Texture2D* createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat);
osg::Texture2DMultisample* createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat);
osg::Texture2DArray* createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat);
int mWidth;
int mHeight;
int mSamples;
bool mMultiview;
osg::ref_ptr<UpdateRenderStagesCallback> mCullCallback;
osg::ref_ptr<osg::FrameBufferObject> mMultiviewFbo;
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mLayerFbo;
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mLayerMsaaFbo;
osg::ref_ptr<osg::Texture2DArray> mMultiviewColorTexture;
osg::ref_ptr<osg::Texture2DArray> mMultiviewDepthTexture;
std::array<osg::ref_ptr<osg::Texture2D>, 2> mColorTexture;
std::array<osg::ref_ptr<osg::Texture2DMultisample>, 2> mMsaaColorTexture;
std::array<osg::ref_ptr<osg::Texture2D>, 2> mDepthTexture;
std::array<osg::ref_ptr<osg::Texture2DMultisample>, 2> mMsaaDepthTexture;
};
}
#endif

@ -0,0 +1,459 @@
#include "stereomanager.hpp"
#include "multiview.hpp"
#include "frustum.hpp"
#include <osg/io_utils>
#include <osg/Texture2D>
#include <osg/Texture2DMultisample>
#include <osg/Texture2DArray>
#include <osg/DisplaySettings>
#include <osgUtil/CullVisitor>
#include <osgUtil/RenderStage>
#include <osgViewer/Renderer>
#include <osgViewer/Viewer>
#include <iostream>
#include <map>
#include <string>
#include <components/debug/debuglog.hpp>
#include <components/sceneutil/statesetupdater.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/color.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/stringops.hpp>
namespace Stereo
{
// Update stereo view/projection during update
class StereoUpdateCallback final : public osg::Callback
{
public:
StereoUpdateCallback(Manager* stereoView) : stereoView(stereoView) {}
bool run(osg::Object* object, osg::Object* data) override
{
auto b = traverse(object, data);
stereoView->update();
return b;
}
Manager* stereoView;
};
// Update states during cull
class BruteForceStereoStatesetUpdateCallback final : public SceneUtil::StateSetUpdater
{
public:
BruteForceStereoStatesetUpdateCallback(Manager* manager)
: mManager(manager)
{
}
protected:
virtual void setDefaults(osg::StateSet* stateset) override
{
stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{}));
}
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override
{
}
void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override
{
osg::Matrix dummy;
auto* uProjectionMatrix = stateset->getUniform("projectionMatrix");
if (uProjectionMatrix)
uProjectionMatrix->set(mManager->computeEyeProjection(0, SceneUtil::AutoDepth::isReversed()));
}
void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override
{
osg::Matrix dummy;
auto* uProjectionMatrix = stateset->getUniform("projectionMatrix");
if (uProjectionMatrix)
uProjectionMatrix->set(mManager->computeEyeProjection(1, SceneUtil::AutoDepth::isReversed()));
}
private:
Manager* mManager;
};
// Update states during cull
class MultiviewStereoStatesetUpdateCallback : public SceneUtil::StateSetUpdater
{
public:
MultiviewStereoStatesetUpdateCallback(Manager* manager)
: mManager(manager)
{
}
protected:
virtual void setDefaults(osg::StateSet* stateset)
{
stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2));
stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2));
}
virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/)
{
mManager->updateMultiviewStateset(stateset);
}
private:
Manager* mManager;
};
static Manager* sInstance = nullptr;
Manager& Manager::instance()
{
return *sInstance;
}
struct CustomViewCallback : public Manager::UpdateViewCallback
{
public:
CustomViewCallback();
void updateView(View& left, View& right) override;
private:
View mLeft;
View mRight;
};
Manager::Manager(osgViewer::Viewer* viewer)
: mViewer(viewer)
, mMainCamera(mViewer->getCamera())
, mUpdateCallback(new StereoUpdateCallback(this))
, mMasterProjectionMatrix(osg::Matrix::identity())
, mEyeResolutionOverriden(false)
, mEyeResolutionOverride(0,0)
, mFrustumManager(nullptr)
, mUpdateViewCallback(nullptr)
{
if (sInstance)
throw std::logic_error("Double instance of Stereo::Manager");
sInstance = this;
if (Settings::Manager::getBool("use custom view", "Stereo"))
mUpdateViewCallback = std::make_shared<CustomViewCallback>();
if (Settings::Manager::getBool("use custom eye resolution", "Stereo"))
{
osg::Vec2i eyeResolution = osg::Vec2i();
eyeResolution.x() = Settings::Manager::getInt("eye resolution x", "Stereo View");
eyeResolution.y() = Settings::Manager::getInt("eye resolution y", "Stereo View");
overrideEyeResolution(eyeResolution);
}
}
Manager::~Manager()
{
}
void Manager::initializeStereo(osg::GraphicsContext* gc)
{
mMainCamera->addUpdateCallback(mUpdateCallback);
mFrustumManager = std::make_unique<StereoFrustumManager>(mViewer->getCamera());
auto ci = gc->getState()->getContextID();
configureExtensions(ci);
if(getMultiview())
setupOVRMultiView2Technique();
else
setupBruteForceTechnique();
updateStereoFramebuffer();
}
void Manager::shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const
{
if (getMultiview())
{
defines["GLSLVersion"] = "330 compatibility";
defines["useOVR_multiview"] = "1";
defines["numViews"] = "2";
}
else
{
defines["useOVR_multiview"] = "0";
defines["numViews"] = "1";
}
}
void Manager::overrideEyeResolution(const osg::Vec2i& eyeResolution)
{
mEyeResolutionOverride = eyeResolution;
mEyeResolutionOverriden = true;
if (mMultiviewFramebuffer)
updateStereoFramebuffer();
}
void Manager::screenResolutionChanged()
{
updateStereoFramebuffer();
}
osg::Vec2i Manager::eyeResolution()
{
if (mEyeResolutionOverriden)
return mEyeResolutionOverride;
auto width = mMainCamera->getViewport()->width() / 2;
auto height = mMainCamera->getViewport()->height();
return osg::Vec2i(width, height);
}
void Manager::disableStereoForNode(osg::Node* node)
{
// Re-apply the main camera's full viewport to return to full screen rendering.
node->getOrCreateStateSet()->setAttribute(mMainCamera->getViewport());
}
void Manager::setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique)
{
if (mFrustumManager)
mFrustumManager->setShadowTechnique(shadowTechnique);
}
void Manager::setupBruteForceTechnique()
{
auto* ds = osg::DisplaySettings::instance().get();
ds->setStereo(true);
ds->setStereoMode(osg::DisplaySettings::StereoMode::HORIZONTAL_SPLIT);
ds->setUseSceneViewForStereoHint(true);
mMainCamera->addCullCallback(new BruteForceStereoStatesetUpdateCallback(this));
struct ComputeStereoMatricesCallback : public osgUtil::SceneView::ComputeStereoMatricesCallback
{
ComputeStereoMatricesCallback(Manager* sv)
: mManager(sv)
{
}
osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection) const override
{
(void)projection;
return mManager->computeEyeProjection(0, false);
}
osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const override
{
(void)view;
return mManager->computeEyeView(0);
}
osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection) const override
{
(void)projection;
return mManager->computeEyeProjection(1, false);
}
osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const override
{
(void)view;
return mManager->computeEyeView(1);
}
Manager* mManager;
};
auto* renderer = static_cast<osgViewer::Renderer*>(mMainCamera->getRenderer());
for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) })
{
sceneView->setComputeStereoMatricesCallback(new ComputeStereoMatricesCallback(this));
auto* cvMain = sceneView->getCullVisitor();
auto* cvLeft = sceneView->getCullVisitorLeft();
auto* cvRight = sceneView->getCullVisitorRight();
if (!cvMain)
sceneView->setCullVisitor(cvMain = new osgUtil::CullVisitor());
if (!cvLeft)
sceneView->setCullVisitor(cvLeft = cvMain->clone());
if (!cvRight)
sceneView->setCullVisitor(cvRight = cvMain->clone());
// Osg by default gives cullVisitorLeft and cullVisitor the same identifier.
// So we make our own to avoid confusion
cvMain->setIdentifier(mIdentifierMain);
cvLeft->setIdentifier(mIdentifierLeft);
cvRight->setIdentifier(mIdentifierRight);
}
}
void Manager::setupOVRMultiView2Technique()
{
auto* ds = osg::DisplaySettings::instance().get();
ds->setStereo(false);
mMainCamera->addCullCallback(new MultiviewStereoStatesetUpdateCallback(this));
}
void Manager::updateStereoFramebuffer()
{
auto samples = Settings::Manager::getInt("antialiasing", "Video");
auto eyeRes = eyeResolution();
if (mMultiviewFramebuffer)
mMultiviewFramebuffer->detachFrom(mMainCamera);
mMultiviewFramebuffer = std::make_shared<MultiviewFramebuffer>(static_cast<int>(eyeRes.x()), static_cast<int>(eyeRes.y()), samples);
mMultiviewFramebuffer->attachColorComponent(SceneUtil::Color::colorSourceFormat(), SceneUtil::Color::colorSourceType(), SceneUtil::Color::colorInternalFormat());
mMultiviewFramebuffer->attachDepthComponent(SceneUtil::AutoDepth::depthSourceFormat(), SceneUtil::AutoDepth::depthSourceType(), SceneUtil::AutoDepth::depthInternalFormat());
mMultiviewFramebuffer->attachTo(mMainCamera);
}
void Manager::update()
{
double near_ = 1.f;
double far_ = 10000.f;
near_ = Settings::Manager::getFloat("near clip", "Camera");
far_ = Settings::Manager::getFloat("viewing distance", "Camera");
auto projectionMatrix = mMainCamera->getProjectionMatrix();
if (mUpdateViewCallback)
{
mUpdateViewCallback->updateView(mView[0], mView[1]);
auto viewMatrix = mMainCamera->getViewMatrix();
mViewOffsetMatrix[0] = mView[0].viewMatrix(true);
mViewOffsetMatrix[1] = mView[1].viewMatrix(true);
mViewMatrix[0] = viewMatrix * mViewOffsetMatrix[0];
mViewMatrix[1] = viewMatrix * mViewOffsetMatrix[1];
mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false);
mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false);
if (SceneUtil::AutoDepth::isReversed())
{
mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true);
mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, true);
}
View masterView;
masterView.fov.angleDown = std::min(mView[0].fov.angleDown, mView[1].fov.angleDown);
masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp);
masterView.fov.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft);
masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight);
projectionMatrix = masterView.perspectiveMatrix(near_, far_, false);
mMainCamera->setProjectionMatrix(projectionMatrix);
}
else
{
auto* ds = osg::DisplaySettings::instance().get();
auto viewMatrix = mMainCamera->getViewMatrix();
mViewMatrix[0] = ds->computeLeftEyeViewImplementation(viewMatrix);
mViewMatrix[1] = ds->computeRightEyeViewImplementation(viewMatrix);
mViewOffsetMatrix[0] = osg::Matrix::inverse(viewMatrix) * mViewMatrix[0];
mViewOffsetMatrix[1] = osg::Matrix::inverse(viewMatrix) * mViewMatrix[1];
mProjectionMatrix[0] = ds->computeLeftEyeProjectionImplementation(projectionMatrix);
mProjectionMatrix[1] = ds->computeRightEyeProjectionImplementation(projectionMatrix);
if (SceneUtil::AutoDepth::isReversed())
{
mProjectionMatrixReverseZ[0] = ds->computeLeftEyeProjectionImplementation(mMasterProjectionMatrix);
mProjectionMatrixReverseZ[1] = ds->computeRightEyeProjectionImplementation(mMasterProjectionMatrix);
}
}
mFrustumManager->update(
{
mViewOffsetMatrix[0] * mProjectionMatrix[0],
mViewOffsetMatrix[1] * mProjectionMatrix[1]
});
}
void Manager::updateMultiviewStateset(osg::StateSet* stateset)
{
// Update stereo uniforms
auto * viewMatrixMultiViewUniform = stateset->getUniform("viewMatrixMultiView");
auto * projectionMatrixMultiViewUniform = stateset->getUniform("projectionMatrixMultiView");
for (int view : {0, 1})
{
viewMatrixMultiViewUniform->setElement(view, mViewOffsetMatrix[view]);
projectionMatrixMultiViewUniform->setElement(view, computeEyeProjection(view, SceneUtil::AutoDepth::isReversed()));
}
}
void Manager::setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb)
{
mUpdateViewCallback = cb;
}
void Manager::setCullCallback(osg::ref_ptr<osg::NodeCallback> cb)
{
mMainCamera->setCullCallback(cb);
}
osg::Matrixd Manager::computeEyeProjection(int view, bool reverseZ) const
{
return reverseZ ? mProjectionMatrixReverseZ[view] : mProjectionMatrix[view];
}
osg::Matrixd Manager::computeEyeView(int view) const
{
return mViewMatrix[view];
}
Eye Manager::getEye(const osgUtil::CullVisitor* cv) const
{
if (cv->getIdentifier() == mIdentifierMain)
return Eye::Center;
if (cv->getIdentifier() == mIdentifierLeft)
return Eye::Left;
if (cv->getIdentifier() == mIdentifierRight)
return Eye::Right;
return Eye::Center;
}
bool getStereo()
{
static bool stereo = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo();
return stereo;
}
CustomViewCallback::CustomViewCallback()
{
mLeft.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View");
mLeft.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View");
mLeft.pose.position.z() = Settings::Manager::getDouble("left eye offset z", "Stereo View");
mLeft.pose.orientation.x() = Settings::Manager::getDouble("left eye orientation x", "Stereo View");
mLeft.pose.orientation.y() = Settings::Manager::getDouble("left eye orientation y", "Stereo View");
mLeft.pose.orientation.z() = Settings::Manager::getDouble("left eye orientation z", "Stereo View");
mLeft.pose.orientation.w() = Settings::Manager::getDouble("left eye orientation w", "Stereo View");
mLeft.fov.angleLeft = Settings::Manager::getDouble("left eye fov left", "Stereo View");
mLeft.fov.angleRight = Settings::Manager::getDouble("left eye fov right", "Stereo View");
mLeft.fov.angleUp = Settings::Manager::getDouble("left eye fov up", "Stereo View");
mLeft.fov.angleDown = Settings::Manager::getDouble("left eye fov down", "Stereo View");
mRight.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View");
mRight.pose.position.y() = Settings::Manager::getDouble("right eye offset y", "Stereo View");
mRight.pose.position.z() = Settings::Manager::getDouble("right eye offset z", "Stereo View");
mRight.pose.orientation.x() = Settings::Manager::getDouble("right eye orientation x", "Stereo View");
mRight.pose.orientation.y() = Settings::Manager::getDouble("right eye orientation y", "Stereo View");
mRight.pose.orientation.z() = Settings::Manager::getDouble("right eye orientation z", "Stereo View");
mRight.pose.orientation.w() = Settings::Manager::getDouble("right eye orientation w", "Stereo View");
mRight.fov.angleLeft = Settings::Manager::getDouble("right eye fov left", "Stereo View");
mRight.fov.angleRight = Settings::Manager::getDouble("right eye fov right", "Stereo View");
mRight.fov.angleUp = Settings::Manager::getDouble("right eye fov up", "Stereo View");
mRight.fov.angleDown = Settings::Manager::getDouble("right eye fov down", "Stereo View");
}
void CustomViewCallback::updateView(View& left, View& right)
{
left = mLeft;
right = mRight;
}
}

@ -0,0 +1,134 @@
#ifndef STEREO_MANAGER_H
#define STEREO_MANAGER_H
#include <osg/Matrix>
#include <osg/Vec3>
#include <osg/Camera>
#include <osg/StateSet>
#include <osgUtil/CullVisitor>
#include <memory>
#include <array>
#include <components/stereo/types.hpp>
#include <components/shader/shadermanager.hpp>
namespace osg
{
class FrameBufferObject;
class Texture2D;
class Texture2DMultisample;
class Texture2DArray;
}
namespace osgViewer
{
class Viewer;
}
namespace SceneUtil
{
class MWShadowTechnique;
}
namespace Stereo
{
class MultiviewFramebuffer;
class StereoFrustumManager;
class MultiviewStereoStatesetUpdateCallback;
bool getStereo();
//! Class that provides tools for managing stereo mode
class Manager
{
public:
struct UpdateViewCallback
{
virtual ~UpdateViewCallback() = default;
//! Called during the update traversal of every frame to update stereo views.
virtual void updateView(View& left, View& right) = 0;
};
//! Gets the singleton instance
static Manager& instance();
Manager(osgViewer::Viewer* viewer);
~Manager();
//! Called during update traversal
void update();
void initializeStereo(osg::GraphicsContext* gc);
//! Callback that updates stereo configuration during the update pass
void setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb);
//! Set the cull callback on the appropriate camera object
void setCullCallback(osg::ref_ptr<osg::NodeCallback> cb);
osg::Matrixd computeEyeProjection(int view, bool reverseZ) const;
osg::Matrixd computeEyeView(int view) const;
//! Sets up any definitions necessary for stereo rendering
void shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const;
const std::shared_ptr<MultiviewFramebuffer>& multiviewFramebuffer() { return mMultiviewFramebuffer; };
//! Sets rendering resolution of each eye to eyeResolution.
//! Once set, there will no longer be any connection between rendering resolution and screen/window resolution.
void overrideEyeResolution(const osg::Vec2i& eyeResolution);
//! Notify stereo manager that the screen/window resolution has changed.
void screenResolutionChanged();
//! Get current eye resolution
osg::Vec2i eyeResolution();
//! The projection intended for rendering. When reverse Z is enabled, this is not the same as the camera's projection matrix,
//! and therefore must be provided to the manager explicitly.
void setMasterProjectionMatrix(const osg::Matrix& projectionMatrix) { mMasterProjectionMatrix = projectionMatrix; }
//! Causes the subgraph represented by the node to draw to the full viewport.
//! This has no effect if stereo is not enabled
void disableStereoForNode(osg::Node* node);
void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique);
/// Determine which view the cull visitor belongs to
Eye getEye(const osgUtil::CullVisitor* cv) const;
private:
friend class MultiviewStereoStatesetUpdateCallback;
void updateMultiviewStateset(osg::StateSet* stateset);
void updateStereoFramebuffer();
void setupBruteForceTechnique();
void setupOVRMultiView2Technique();
osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osg::Camera> mMainCamera;
osg::ref_ptr<osg::Callback> mUpdateCallback;
std::string mError;
osg::Matrix mMasterProjectionMatrix;
std::shared_ptr<MultiviewFramebuffer> mMultiviewFramebuffer;
bool mEyeResolutionOverriden;
osg::Vec2i mEyeResolutionOverride;
std::array<View, 2> mView;
std::array<osg::Matrix, 2> mViewMatrix;
std::array<osg::Matrix, 2> mViewOffsetMatrix;
std::array<osg::Matrix, 2> mProjectionMatrix;
std::array<osg::Matrix, 2> mProjectionMatrixReverseZ;
std::unique_ptr<StereoFrustumManager> mFrustumManager;
std::shared_ptr<UpdateViewCallback> mUpdateViewCallback;
using Identifier = osgUtil::CullVisitor::Identifier;
osg::ref_ptr<Identifier> mIdentifierMain = new Identifier();
osg::ref_ptr<Identifier> mIdentifierLeft = new Identifier();
osg::ref_ptr<Identifier> mIdentifierRight = new Identifier();
};
}
#endif

@ -0,0 +1,168 @@
#include "types.hpp"
#include <osg/io_utils>
namespace Stereo
{
Pose Pose::operator+(const Pose& rhs)
{
Pose pose = *this;
pose.position += this->orientation * rhs.position;
pose.orientation = rhs.orientation * this->orientation;
return pose;
}
const Pose& Pose::operator+=(const Pose& rhs)
{
*this = *this + rhs;
return *this;
}
Pose Pose::operator*(float scalar)
{
Pose pose = *this;
pose.position *= scalar;
return pose;
}
const Pose& Pose::operator*=(float scalar)
{
*this = *this * scalar;
return *this;
}
Pose Pose::operator/(float scalar)
{
Pose pose = *this;
pose.position /= scalar;
return pose;
}
const Pose& Pose::operator/=(float scalar)
{
*this = *this / scalar;
return *this;
}
bool Pose::operator==(const Pose& rhs) const
{
return position == rhs.position && orientation == rhs.orientation;
}
osg::Matrix View::viewMatrix(bool useGLConventions)
{
auto position = pose.position;
auto orientation = pose.orientation;
if (useGLConventions)
{
// When applied as an offset to an existing view matrix,
// that view matrix will already convert points to a camera space
// with opengl conventions. So we need to convert offsets to opengl
// conventions.
float y = position.y();
float z = position.z();
position.y() = z;
position.z() = -y;
y = orientation.y();
z = orientation.z();
orientation.y() = z;
orientation.z() = -y;
osg::Matrix viewMatrix;
viewMatrix.setTrans(-position);
viewMatrix.postMultRotate(orientation.conj());
return viewMatrix;
}
else
{
osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0);
osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1);
osg::Matrix viewMatrix;
viewMatrix.makeLookAt(position, position + forward, up);
return viewMatrix;
}
}
osg::Matrix View::perspectiveMatrix(float near, float far, bool reverseZ)
{
const float tanLeft = tanf(fov.angleLeft);
const float tanRight = tanf(fov.angleRight);
const float tanDown = tanf(fov.angleDown);
const float tanUp = tanf(fov.angleUp);
const float tanWidth = tanRight - tanLeft;
const float tanHeight = tanUp - tanDown;
float matrix[16] = {};
matrix[0] = 2 / tanWidth;
matrix[4] = 0;
matrix[8] = (tanRight + tanLeft) / tanWidth;
matrix[12] = 0;
matrix[1] = 0;
matrix[5] = 2 / tanHeight;
matrix[9] = (tanUp + tanDown) / tanHeight;
matrix[13] = 0;
if (reverseZ) {
matrix[2] = 0;
matrix[6] = 0;
matrix[10] = (2.f * near) / (far - near);
matrix[14] = ((2.f * near) * far) / (far - near);
}
else {
matrix[2] = 0;
matrix[6] = 0;
matrix[10] = -(far + near) / (far - near);
matrix[14] = -(far * (2.f * near)) / (far - near);
}
matrix[3] = 0;
matrix[7] = 0;
matrix[11] = -1;
matrix[15] = 0;
return osg::Matrix(matrix);
}
bool FieldOfView::operator==(const FieldOfView& rhs) const
{
return angleDown == rhs.angleDown
&& angleUp == rhs.angleUp
&& angleLeft == rhs.angleLeft
&& angleRight == rhs.angleRight;
}
bool View::operator==(const View& rhs) const
{
return pose == rhs.pose && fov == rhs.fov;
}
std::ostream& operator <<(
std::ostream& os,
const Pose& pose)
{
os << "position=" << pose.position << ", orientation=" << pose.orientation;
return os;
}
std::ostream& operator <<(
std::ostream& os,
const FieldOfView& fov)
{
os << "left=" << fov.angleLeft << ", right=" << fov.angleRight << ", down=" << fov.angleDown << ", up=" << fov.angleUp;
return os;
}
std::ostream& operator <<(
std::ostream& os,
const View& view)
{
os << "pose=< " << view.pose << " >, fov=< " << view.fov << " >";
return os;
}
}

@ -0,0 +1,61 @@
#ifndef STEREO_TYPES_H
#define STEREO_TYPES_H
#include <osg/Matrix>
#include <osg/Vec3>
namespace Stereo
{
enum class Eye
{
Left = 0,
Right = 1,
Center = 2
};
struct Pose
{
//! Position in space
osg::Vec3 position{ 0,0,0 };
//! Orientation in space.
osg::Quat orientation{ 0,0,0,1 };
//! Add one pose to another
Pose operator+(const Pose& rhs);
const Pose& operator+=(const Pose& rhs);
//! Scale a pose (does not affect orientation)
Pose operator*(float scalar);
const Pose& operator*=(float scalar);
Pose operator/(float scalar);
const Pose& operator/=(float scalar);
bool operator==(const Pose& rhs) const;
};
struct FieldOfView {
float angleLeft{ 0.f };
float angleRight{ 0.f };
float angleUp{ 0.f };
float angleDown{ 0.f };
bool operator==(const FieldOfView& rhs) const;
};
struct View
{
Pose pose;
FieldOfView fov;
bool operator==(const View& rhs) const;
osg::Matrix viewMatrix(bool useGLConventions);
osg::Matrix perspectiveMatrix(float near, float far, bool reverseZ);
};
std::ostream& operator <<(std::ostream& os, const Pose& pose);
std::ostream& operator <<(std::ostream& os, const FieldOfView& fov);
std::ostream& operator <<(std::ostream& os, const View& view);
}
#endif

@ -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)

@ -1129,3 +1129,60 @@ lua debug = false
# Set the maximum number of threads used for Lua scripts.
# If zero, Lua scripts are processed in the main thread.
lua num threads = 1
[Stereo]
# Enable/disable stereo view. This setting is ignored in VR.
stereo enabled = false
# If enabled, OpenMW will use the GL_OVR_MultiView and GL_OVR_MultiView2 extensions where possible.
multiview = false
# May accelerate the BruteForce method when shadows are enabled
shared shadow maps = true
# If false, OpenMW-VR will disable display lists when using multiview. Necessary on some buggy drivers, but may incur a slight performance penalty.
allow display lists for multiview = false
# If false, the default OSG horizontal split will be used for stereo
# If true, the config defined in the [Stereo View] settings category will be used
# Note: This option is ignored in VR, and exists primarily for debugging purposes
use custom view = false
# If true, overrides rendering resolution for each eye.
# Note: This option is ignored in VR, and exists primarily for debugging purposes
use custom eye resolution = false
[Stereo View]
# The default values are based on an HP Reverb G2 HMD
eye resolution x = 3128
eye resolution y = 3060
# Left eye offset from center, expressed in MW units (1 meter = ~70)
left eye offset x = -2.35
left eye offset y = 0.0
left eye offset z = 0.0
# Left eye orientation, expressed as a quaternion
left eye orientation x = 0.0
left eye orientation y = 0.0
left eye orientation z = 0.0
left eye orientation w = 1.0
# Left eye field of view, expressed in radians
left eye fov left = -0.86
left eye fov right = 0.78
left eye fov up = 0.8
left eye fov down = -0.8
# Left eye offset from center, expressed in MW units (1 meter = ~70)
right eye offset x = 2.35
right eye offset y = 0.0
right eye offset z = 0.0
# Left eye orientation, expressed as a quaternion
right eye orientation x = 0.0
right eye orientation y = 0.0
right eye orientation z = 0.0
right eye orientation w = 1.0
# Left eye field of view
right eye fov left = -0.78
right eye fov right = 0.86
right eye fov up = 0.8
right eye fov down = -0.8

@ -40,6 +40,8 @@ set(SHADER_FILES
gui_fragment.glsl
debug_vertex.glsl
debug_fragment.glsl
multiview_fragment.glsl
multiview_vertex.glsl
sky_vertex.glsl
sky_fragment.glsl
skypasses.glsl

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#include "vertexcolors.glsl"

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#include "openmw_vertex.h.glsl"

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require

@ -1,4 +1,6 @@
#version 120
#version @GLSLVersion
#include "multiview_vertex.glsl"
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
uniform sampler2D diffuseMap;

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
varying vec2 diffuseMapUV;
varying vec4 passColor;

@ -0,0 +1,48 @@
#ifndef MULTIVIEW_FRAGMENT
#define MULTIVIEW_FRAGMENT
// This file either enables or disables GL_OVR_multiview2 related code.
// For use in fragment shaders
// REQUIREMENT:
// GLSL version: 330 or greater
// GLSL profile: compatibility
// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set.
//
// This file provides symbols for sampling stereo-aware textures. Without multiview, these texture uniforms are sampler2D,
// while in stereo the same uniforms are sampler2DArray instead. The symbols defined in this file mask this difference, allowing
// the same code to work in both cases. Use mw_stereoAwareSampler2D and mw_stereoAwareTexture2D, where you otherwise would use
// sampler2D and texture2D()
//
// USAGE:
// For stereo-aware textures, such as reflections, use the mw_stereoAwareSampler2D sampler and mw_stereoAwareTexture2D method
// instead of the usual sampler2D and texture2D.
//
// Using water reflection as an example, the old code for these textures changes from
// uniform sampler2D reflectionMap;
// ...
// vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb;
//
// to
// uniform mw_stereoAwareSampler2D reflectionMap;
// ...
// vec3 reflection = mw_stereoAwareTexture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb;
//
#if @useOVR_multiview
#extension GL_OVR_multiview : require
#extension GL_OVR_multiview2 : require
#extension GL_EXT_texture_array : require
#define mw_stereoAwareSampler2D sampler2DArray
#define mw_stereoAwareTexture2D(texture, uv) texture2DArray(texture, vec3((uv), gl_ViewID_OVR))
#else // useOVR_multiview
#define mw_stereoAwareSampler2D sampler2D
#define mw_stereoAwareTexture2D(texture, uv) texture2D(texture, uv)
#endif // useOVR_multiview
#endif // MULTIVIEW_FRAGMENT

@ -0,0 +1,80 @@
#ifndef MULTIVIEW_VERTEX
#define MULTIVIEW_VERTEX
// This file either enables or disables GL_OVR_multiview related code.
// For use in vertex shaders
// REQUIREMENT:
// GLSL version: 330 or greater
// GLSL profile: compatibility
// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set.
// USAGE:
// To create a stereo-aware vertex shader, use the matrix accessor functions defined in this .glsl file to compute gl_Position.
// For the vertex stage, usually only gl_Position needs to be computed with stereo awareness, while other variables such as viewPos
// should be computed in the center camera's view space and take no stereo awareness.
//
// A typical gl_Position line will look like the following:
// gl_Position = mw_stereoAwareProjectionMatrix() * (mw_stereoAwareModelViewMatrix() * gl_Vertex);
//
// If you need to perform intermediate computations before determining the final values of gl_Position and viewPos,
// your code might look more like the following:
// vec4 intermediateViewPos = gl_ModelViewMatrix * gl_Vertex;
// vec4 viewPos = myWhateverCode(intermediateViewPos);
// gl_Position = mw_stereoAwareProjectionMatrix() * mw_stereoAwareViewPosition(viewPos);
//
#if @useOVR_multiview
#extension GL_OVR_multiview : require
#ifndef MULTIVIEW_FRAGMENT
// Layout cannot be used in the fragment shader
layout(num_views = @numViews) in;
#endif
uniform mat4 projectionMatrixMultiView[@numViews];
uniform mat4 viewMatrixMultiView[@numViews];
// NOTE:
// stereo-aware inverse view matrices and normal matrices have not been implemented.
// Some effects like specular highlights would need stereo aware normal matrices to be 100% correct.
// But the difference is not likely to be noticeable unless you're actively looking for it.
mat4 mw_stereoAwareProjectionMatrix()
{
return projectionMatrixMultiView[gl_ViewID_OVR];
}
mat4 mw_stereoAwareModelViewMatrix()
{
return viewMatrixMultiView[gl_ViewID_OVR] * gl_ModelViewMatrix;
}
vec4 mw_stereoAwareViewPosition(vec4 viewPos)
{
return viewMatrixMultiView[gl_ViewID_OVR] * viewPos;
}
#else // useOVR_multiview
uniform mat4 projectionMatrix;
mat4 mw_stereoAwareProjectionMatrix()
{
return projectionMatrix;
}
mat4 mw_stereoAwareModelViewMatrix()
{
return gl_ModelViewMatrix;
}
vec4 mw_stereoAwareViewPosition(vec4 viewPos)
{
return viewPos;
}
#endif // useOVR_multiview
#endif // MULTIVIEW_VERTEX

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#pragma import_defines(FORCE_OPAQUE)
#if @useUBO

@ -1,4 +1,6 @@
#version 120
#version @GLSLVersion
#include "multiview_vertex.glsl"
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
@ -9,7 +11,6 @@
#endif
#include "openmw_vertex.h.glsl"
#if @diffuseMap
varying vec2 diffuseMapUV;
#endif

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#pragma import_defines(FORCE_OPAQUE)
#if @useGPUShader4

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#include "openmw_vertex.h.glsl"

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#pragma import_defines(FORCE_OPAQUE)
#if @useUBO

@ -1,4 +1,6 @@
#version 120
#version @GLSLVersion
#include "multiview_vertex.glsl"
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
@ -9,7 +11,6 @@
#endif
#include "openmw_vertex.h.glsl"
#if @diffuseMap
varying vec2 diffuseMapUV;
#endif

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
varying vec2 uv;
uniform samplerCube cubeMap;

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
varying vec2 uv;

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
varying vec2 diffuseMapUV;

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#include "skypasses.glsl"

@ -1,4 +1,6 @@
#version 120
#version @GLSLVersion
#include "multiview_vertex.glsl"
#include "openmw_vertex.h.glsl"
@ -9,6 +11,20 @@ uniform int pass;
varying vec4 passColor;
varying vec2 diffuseMapUV;
mat4 selectModelViewMatrix()
{
#if @useOVR_multiview
mat4 viewOffsetMatrix = viewMatrixMultiView[gl_ViewID_OVR];
// Sky geometries aren't actually all that distant. So delete view translation to keep them looking distant.
viewOffsetMatrix[3][0] = 0;
viewOffsetMatrix[3][1] = 0;
viewOffsetMatrix[3][2] = 0;
return viewOffsetMatrix * gl_ModelViewMatrix;
#else
return gl_ModelViewMatrix;
#endif
}
void main()
{
gl_Position = mw_modelToClip(gl_Vertex);

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require

@ -1,4 +1,6 @@
#version 120
#version @GLSLVersion
#include "multiview_vertex.glsl"
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require
@ -9,7 +11,6 @@
#endif
#include "openmw_vertex.h.glsl"
varying vec2 uv;
varying float euclideanDepth;
varying float linearDepth;

@ -1,4 +1,6 @@
#version 120
#version @GLSLVersion
#include "multiview_fragment.glsl"
#if @useUBO
#extension GL_ARB_uniform_buffer_object : require

@ -1,4 +1,4 @@
#version 120
#version @GLSLVersion
#include "openmw_vertex.h.glsl"

Loading…
Cancel
Save