1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-30 13:15:34 +00:00

support postprocess distortion

This commit is contained in:
Cody Glassman 2023-11-10 08:02:53 -08:00
parent 51cb3b08cb
commit 187f63d3d3
25 changed files with 311 additions and 65 deletions

View file

@ -24,7 +24,7 @@ add_openmw_dir (mwrender
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover
postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples
actorutil actorutil distortion
) )
add_openmw_dir (mwinput add_openmw_dir (mwinput

View file

@ -0,0 +1,28 @@
#include "distortion.hpp"
#include <osg/FrameBufferObject>
namespace MWRender
{
void DistortionCallback::drawImplementation(
osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous)
{
osg::State* state = renderInfo.getState();
size_t frameId = state->getFrameStamp()->getFrameNumber() % 2;
mFBO[frameId]->apply(*state);
const osg::Texture* tex
= mFBO[frameId]->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture();
glViewport(0, 0, tex->getTextureWidth(), tex->getTextureHeight());
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
bin->drawImplementation(renderInfo, previous);
tex = mOriginalFBO[frameId]->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture();
glViewport(0, 0, tex->getTextureWidth(), tex->getTextureHeight());
mOriginalFBO[frameId]->apply(*state);
}
}

View file

@ -0,0 +1,28 @@
#include <array>
#include <osgUtil/RenderBin>
namespace osg
{
class FrameBufferObject;
}
namespace MWRender
{
class DistortionCallback : public osgUtil::RenderBin::DrawCallback
{
public:
void drawImplementation(
osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override;
void setFBO(const osg::ref_ptr<osg::FrameBufferObject>& fbo, size_t frameId) { mFBO[frameId] = fbo; }
void setOriginalFBO(const osg::ref_ptr<osg::FrameBufferObject>& fbo, size_t frameId)
{
mOriginalFBO[frameId] = fbo;
}
private:
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mFBO;
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mOriginalFBO;
};
}

View file

@ -345,11 +345,9 @@ namespace MWRender
bin->drawImplementation(renderInfo, previous); bin->drawImplementation(renderInfo, previous);
auto primaryFBO = postProcessor->getPrimaryFbo(frameId); auto primaryFBO = postProcessor->getPrimaryFbo(frameId);
primaryFBO->apply(*state);
if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state);
postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state);
else
primaryFBO->apply(*state);
// depth accumulation pass // depth accumulation pass
osg::ref_ptr<osg::StateSet> restore = bin->getStateSet(); osg::ref_ptr<osg::StateSet> restore = bin->getStateSet();
@ -357,8 +355,7 @@ namespace MWRender
bin->drawImplementation(renderInfo, previous); bin->drawImplementation(renderInfo, previous);
bin->setStateSet(restore); bin->setStateSet(restore);
if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) primaryFBO->apply(*state);
primaryFBO->apply(*state);
state->checkGLErrors("after DepthClearCallback::drawImplementation"); state->checkGLErrors("after DepthClearCallback::drawImplementation");
} }

View file

@ -242,6 +242,10 @@ namespace MWRender
if (mTextureNormals) if (mTextureNormals)
node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals);
if (mTextureDistortion)
node.mRootStateSet->setTextureAttribute(
PostProcessor::TextureUnits::Unit_Distortion, mTextureDistortion);
state.pushStateSet(node.mRootStateSet); state.pushStateSet(node.mRootStateSet);
state.apply(); state.apply();

View file

@ -48,6 +48,8 @@ namespace MWRender
void setTextureNormals(osg::ref_ptr<osg::Texture> tex) { mTextureNormals = tex; } void setTextureNormals(osg::ref_ptr<osg::Texture> tex) { mTextureNormals = tex; }
void setTextureDistortion(osg::ref_ptr<osg::Texture> tex) { mTextureDistortion = tex; }
void setCalculateAvgLum(bool enabled) { mAvgLum = enabled; } void setCalculateAvgLum(bool enabled) { mAvgLum = enabled; }
void setPostProcessing(bool enabled) { mPostprocessing = enabled; } void setPostProcessing(bool enabled) { mPostprocessing = enabled; }
@ -69,6 +71,7 @@ namespace MWRender
osg::ref_ptr<osg::Texture> mTextureScene; osg::ref_ptr<osg::Texture> mTextureScene;
osg::ref_ptr<osg::Texture> mTextureDepth; osg::ref_ptr<osg::Texture> mTextureDepth;
osg::ref_ptr<osg::Texture> mTextureNormals; osg::ref_ptr<osg::Texture> mTextureNormals;
osg::ref_ptr<osg::Texture> mTextureDistortion;
mutable bool mDirty = false; mutable bool mDirty = false;
mutable std::vector<fx::Types::RenderTarget> mDirtyAttachments; mutable std::vector<fx::Types::RenderTarget> mDirtyAttachments;

View file

@ -29,7 +29,9 @@
#include "../mwgui/postprocessorhud.hpp" #include "../mwgui/postprocessorhud.hpp"
#include "distortion.hpp"
#include "pingpongcull.hpp" #include "pingpongcull.hpp"
#include "renderbin.hpp"
#include "renderingmanager.hpp" #include "renderingmanager.hpp"
#include "sky.hpp" #include "sky.hpp"
#include "transparentpass.hpp" #include "transparentpass.hpp"
@ -103,6 +105,8 @@ namespace
return Stereo::createMultiviewCompatibleAttachment(texture); return Stereo::createMultiviewCompatibleAttachment(texture);
} }
constexpr float DistortionRatio = 0.25;
} }
namespace MWRender namespace MWRender
@ -118,6 +122,7 @@ namespace MWRender
, mUsePostProcessing(Settings::postProcessing().mEnabled) , mUsePostProcessing(Settings::postProcessing().mEnabled)
, mSamples(Settings::video().mAntialiasing) , mSamples(Settings::video().mAntialiasing)
, mPingPongCull(new PingPongCull(this)) , mPingPongCull(new PingPongCull(this))
, mDistortionCallback(new DistortionCallback)
{ {
auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager(); auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager();
@ -141,18 +146,45 @@ namespace MWRender
mHUDCamera->setCullCallback(new HUDCullCallback); mHUDCamera->setCullCallback(new HUDCullCallback);
mViewer->getCamera()->addCullCallback(mPingPongCull); mViewer->getCamera()->addCullCallback(mPingPongCull);
if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass) // resolves the multisampled depth buffer and optionally draws an additional depth postpass
{ mTransparentDepthPostPass
mTransparentDepthPostPass = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(),
= new TransparentDepthBinCallback(shaderManager, Settings::postProcessing().mTransparentPostpass); Settings::postProcessing().mTransparentPostpass);
osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass);
}
osg::ref_ptr<osgUtil::RenderBin> distortionRenderBin
= new osgUtil::RenderBin(osgUtil::RenderBin::SORT_BACK_TO_FRONT);
// This is silly to have to do, but if nothing is drawn then the drawcallback is never called and the distortion
// texture will never be cleared
osg::ref_ptr<osg::Node> dummyNodeToClear = new osg::Node;
dummyNodeToClear->setCullingActive(false);
dummyNodeToClear->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Distortion, "Distortion");
rootNode->addChild(dummyNodeToClear);
distortionRenderBin->setDrawCallback(mDistortionCallback);
distortionRenderBin->getStateSet()->setDefine("DISTORTION", "1", osg::StateAttribute::ON);
// Give the renderbin access to the opaque depth sampler so it can write its occlusion
// Distorted geometry is drawn with ALWAYS depth function and depths writes disbled.
const int unitSoftEffect
= shaderManager.reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture);
distortionRenderBin->getStateSet()->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect));
osgUtil::RenderBin::addRenderBinPrototype("Distortion", distortionRenderBin);
auto defines = shaderManager.getGlobalDefines();
defines["distorionRTRatio"] = std::to_string(DistortionRatio);
shaderManager.setGlobalDefines(defines);
createObjectsForFrame(0); createObjectsForFrame(0);
createObjectsForFrame(1); createObjectsForFrame(1);
populateTechniqueFiles(); populateTechniqueFiles();
auto distortion = loadTechnique("internal_distortion");
distortion->setInternal(true);
distortion->setLocked(true);
mInternalTechniques.push_back(distortion);
osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext();
osg::GLExtensions* ext = gc->getState()->get<osg::GLExtensions>(); osg::GLExtensions* ext = gc->getState()->get<osg::GLExtensions>();
@ -171,19 +203,6 @@ namespace MWRender
else else
Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders."; Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders.";
if (Settings::shaders().mSoftParticles)
{
for (int i = 0; i < 2; ++i)
{
if (Stereo::getMultiview())
mTextures[i][Tex_OpaqueDepth] = new osg::Texture2DArray;
else
mTextures[i][Tex_OpaqueDepth] = new osg::Texture2D;
mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
}
}
mGLSLVersion = ext->glslLanguageVersion * 100; mGLSLVersion = ext->glslLanguageVersion * 100;
mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330;
mStateUpdater = new fx::StateUpdater(mUBO); mStateUpdater = new fx::StateUpdater(mUBO);
@ -281,17 +300,15 @@ namespace MWRender
mCanvases[frameId]->setCalculateAvgLum(mHDR); mCanvases[frameId]->setCalculateAvgLum(mHDR);
mCanvases[frameId]->setTextureScene(getTexture(Tex_Scene, frameId)); mCanvases[frameId]->setTextureScene(getTexture(Tex_Scene, frameId));
if (mTransparentDepthPostPass) mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId));
mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId)); mCanvases[frameId]->setTextureDistortion(getTexture(Tex_Distortion, frameId));
else
mCanvases[frameId]->setTextureDepth(getTexture(Tex_Depth, frameId));
if (mTransparentDepthPostPass) mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary];
{ mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample];
mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth];
mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample];
mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; mDistortionCallback->setFBO(mFbos[frameId][FBO_Distortion], frameId);
} mDistortionCallback->setOriginalFBO(mFbos[frameId][FBO_Primary], frameId);
size_t frame = cv->getTraversalNumber(); size_t frame = cv->getTraversalNumber();
@ -441,6 +458,13 @@ namespace MWRender
textures[Tex_Normal]->setSourceFormat(GL_RGB); textures[Tex_Normal]->setSourceFormat(GL_RGB);
textures[Tex_Normal]->setInternalFormat(GL_RGB); textures[Tex_Normal]->setInternalFormat(GL_RGB);
textures[Tex_Distortion]->setSourceFormat(GL_RGB);
textures[Tex_Distortion]->setInternalFormat(GL_RGB);
Stereo::setMultiviewCompatibleTextureSize(
textures[Tex_Distortion], width * DistortionRatio, height * DistortionRatio);
textures[Tex_Distortion]->dirtyTextureObject();
auto setupDepth = [](osg::Texture* tex) { auto setupDepth = [](osg::Texture* tex) {
tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); tex->setSourceFormat(GL_DEPTH_STENCIL_EXT);
tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); tex->setSourceType(SceneUtil::AutoDepth::depthSourceType());
@ -448,16 +472,8 @@ namespace MWRender
}; };
setupDepth(textures[Tex_Depth]); setupDepth(textures[Tex_Depth]);
setupDepth(textures[Tex_OpaqueDepth]);
if (!mTransparentDepthPostPass) textures[Tex_OpaqueDepth]->setName("opaqueTexMap");
{
textures[Tex_OpaqueDepth] = nullptr;
}
else
{
setupDepth(textures[Tex_OpaqueDepth]);
textures[Tex_OpaqueDepth]->setName("opaqueTexMap");
}
auto& fbos = mFbos[frameId]; auto& fbos = mFbos[frameId];
@ -487,6 +503,7 @@ namespace MWRender
auto normalRB = createFrameBufferAttachmentFromTemplate( auto normalRB = createFrameBufferAttachmentFromTemplate(
Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples); Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples);
fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB);
fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB);
} }
auto depthRB = createFrameBufferAttachmentFromTemplate( auto depthRB = createFrameBufferAttachmentFromTemplate(
Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples);
@ -510,12 +527,13 @@ namespace MWRender
Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal]));
} }
if (textures[Tex_OpaqueDepth]) fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject;
{ fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER,
fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth]));
fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER,
Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth])); fbos[FBO_Distortion] = new osg::FrameBufferObject;
} fbos[FBO_Distortion]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
Stereo::createMultiviewCompatibleAttachment(textures[Tex_Distortion]));
#ifdef __APPLE__ #ifdef __APPLE__
if (textures[Tex_OpaqueDepth]) if (textures[Tex_OpaqueDepth])
@ -575,6 +593,7 @@ namespace MWRender
node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", Unit_LastShader)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", Unit_LastShader));
node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastPass", Unit_LastPass)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastPass", Unit_LastPass));
node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth));
node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDistortion", Unit_Distortion));
if (mNormals) if (mNormals)
node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals));
@ -582,6 +601,8 @@ namespace MWRender
if (technique->getHDR()) if (technique->getHDR())
node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation)); node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation));
node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDistortion", Unit_Distortion));
int texUnit = Unit_NextFree; int texUnit = Unit_NextFree;
// user-defined samplers // user-defined samplers
@ -681,7 +702,7 @@ namespace MWRender
disableTechnique(technique, false); disableTechnique(technique, false);
int pos = std::min<int>(location.value_or(mTechniques.size()), mTechniques.size()); int pos = std::min<int>(location.value_or(mTechniques.size()) + mInternalTechniques.size(), mTechniques.size());
mTechniques.insert(mTechniques.begin() + pos, technique); mTechniques.insert(mTechniques.begin() + pos, technique);
dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug); dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug);
@ -747,6 +768,11 @@ namespace MWRender
{ {
mTechniques.clear(); mTechniques.clear();
for (const auto& technique : mInternalTechniques)
{
mTechniques.push_back(technique);
}
for (const std::string& techniqueName : Settings::postProcessing().mChain.get()) for (const std::string& techniqueName : Settings::postProcessing().mChain.get())
{ {
if (techniqueName.empty()) if (techniqueName.empty())
@ -764,7 +790,7 @@ namespace MWRender
for (const auto& technique : mTechniques) for (const auto& technique : mTechniques)
{ {
if (!technique || technique->getDynamic()) if (!technique || technique->getDynamic() || technique->getInternal())
continue; continue;
chain.push_back(technique->getName()); chain.push_back(technique->getName());
} }

View file

@ -50,12 +50,13 @@ namespace MWRender
class PingPongCull; class PingPongCull;
class PingPongCanvas; class PingPongCanvas;
class TransparentDepthBinCallback; class TransparentDepthBinCallback;
class DistortionCallback;
class PostProcessor : public osg::Group class PostProcessor : public osg::Group
{ {
public: public:
using FBOArray = std::array<osg::ref_ptr<osg::FrameBufferObject>, 5>; using FBOArray = std::array<osg::ref_ptr<osg::FrameBufferObject>, 6>;
using TextureArray = std::array<osg::ref_ptr<osg::Texture>, 5>; using TextureArray = std::array<osg::ref_ptr<osg::Texture>, 6>;
using TechniqueList = std::vector<std::shared_ptr<fx::Technique>>; using TechniqueList = std::vector<std::shared_ptr<fx::Technique>>;
enum TextureIndex enum TextureIndex
@ -64,7 +65,8 @@ namespace MWRender
Tex_Scene_LDR, Tex_Scene_LDR,
Tex_Depth, Tex_Depth,
Tex_OpaqueDepth, Tex_OpaqueDepth,
Tex_Normal Tex_Normal,
Tex_Distortion,
}; };
enum FBOIndex enum FBOIndex
@ -73,7 +75,8 @@ namespace MWRender
FBO_Multisample, FBO_Multisample,
FBO_FirstPerson, FBO_FirstPerson,
FBO_OpaqueDepth, FBO_OpaqueDepth,
FBO_Intercept FBO_Intercept,
FBO_Distortion,
}; };
enum TextureUnits enum TextureUnits
@ -83,6 +86,7 @@ namespace MWRender
Unit_Depth, Unit_Depth,
Unit_EyeAdaptation, Unit_EyeAdaptation,
Unit_Normals, Unit_Normals,
Unit_Distortion,
Unit_NextFree Unit_NextFree
}; };
@ -223,6 +227,7 @@ namespace MWRender
TechniqueList mTechniques; TechniqueList mTechniques;
TechniqueList mTemplates; TechniqueList mTemplates;
TechniqueList mQueuedTemplates; TechniqueList mQueuedTemplates;
TechniqueList mInternalTechniques;
std::unordered_map<std::string, std::filesystem::path> mTechniqueFileMap; std::unordered_map<std::string, std::filesystem::path> mTechniqueFileMap;
@ -258,6 +263,7 @@ namespace MWRender
osg::ref_ptr<PingPongCull> mPingPongCull; osg::ref_ptr<PingPongCull> mPingPongCull;
std::array<osg::ref_ptr<PingPongCanvas>, 2> mCanvases; std::array<osg::ref_ptr<PingPongCanvas>, 2> mCanvases;
osg::ref_ptr<TransparentDepthBinCallback> mTransparentDepthPostPass; osg::ref_ptr<TransparentDepthBinCallback> mTransparentDepthPostPass;
osg::ref_ptr<DistortionCallback> mDistortionCallback;
fx::DispatchArray mTemplateData; fx::DispatchArray mTemplateData;
}; };

View file

@ -13,7 +13,8 @@ namespace MWRender
RenderBin_DepthSorted = 10, // osg::StateSet::TRANSPARENT_BIN RenderBin_DepthSorted = 10, // osg::StateSet::TRANSPARENT_BIN
RenderBin_OcclusionQuery = 11, RenderBin_OcclusionQuery = 11,
RenderBin_FirstPerson = 12, RenderBin_FirstPerson = 12,
RenderBin_SunGlare = 13 RenderBin_SunGlare = 13,
RenderBin_Distortion = 14,
}; };
} }

View file

@ -502,6 +502,7 @@ namespace MWRender
sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat);
sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f));
sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f));
sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f));
mFog = std::make_unique<FogManager>(); mFog = std::make_unique<FogManager>();

View file

@ -91,6 +91,7 @@ uniform @builtinSampler omw_SamplerLastShader;
uniform @builtinSampler omw_SamplerLastPass; uniform @builtinSampler omw_SamplerLastPass;
uniform @builtinSampler omw_SamplerDepth; uniform @builtinSampler omw_SamplerDepth;
uniform @builtinSampler omw_SamplerNormals; uniform @builtinSampler omw_SamplerNormals;
uniform @builtinSampler omw_SamplerDistortion;
uniform vec4 omw_PointLights[@pointLightCount]; uniform vec4 omw_PointLights[@pointLightCount];
uniform int omw_PointLightsCount; uniform int omw_PointLightsCount;

View file

@ -175,6 +175,9 @@ namespace fx
void setLocked(bool locked) { mLocked = locked; } void setLocked(bool locked) { mLocked = locked; }
bool getLocked() const { return mLocked; } bool getLocked() const { return mLocked; }
void setInternal(bool internal) { mInternal = internal; }
bool getInternal() const { return mInternal; }
private: private:
[[noreturn]] void error(const std::string& msg); [[noreturn]] void error(const std::string& msg);
@ -295,6 +298,7 @@ namespace fx
bool mDynamic = false; bool mDynamic = false;
bool mLocked = false; bool mLocked = false;
bool mInternal = false;
}; };
template <> template <>

View file

@ -108,6 +108,8 @@ namespace Nif
enum BSShaderFlags1 enum BSShaderFlags1
{ {
BSSFlag1_Specular = 0x00000001, BSSFlag1_Specular = 0x00000001,
BSSFlag1_Refraction = 0x00008000,
BSSFlag1_FireRefraction = 0x00010000,
BSSFlag1_Decal = 0x04000000, BSSFlag1_Decal = 0x04000000,
BSSFlag1_DepthTest = 0x80000000, BSSFlag1_DepthTest = 0x80000000,
}; };
@ -148,6 +150,8 @@ namespace Nif
bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; }
bool depthTest() const { return mShaderFlags1 & BSSFlag1_DepthTest; } bool depthTest() const { return mShaderFlags1 & BSSFlag1_DepthTest; }
bool depthWrite() const { return mShaderFlags2 & BSSFlag2_DepthWrite; } bool depthWrite() const { return mShaderFlags2 & BSSFlag2_DepthWrite; }
bool refraction() const { return mShaderFlags1 & BSSFlag1_Refraction; }
bool fireRefraction() const { return mShaderFlags1 & BSSFlag1_FireRefraction; }
}; };
struct BSShaderLightingProperty : BSShaderProperty struct BSShaderLightingProperty : BSShaderProperty

View file

@ -2381,6 +2381,8 @@ namespace NifOsg
textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures);
} }
handleTextureControllers(texprop, composite, imageManager, stateset, animflags); handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
if (texprop->refraction())
SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength);
break; break;
} }
case Nif::RC_BSShaderNoLightingProperty: case Nif::RC_BSShaderNoLightingProperty:
@ -2438,6 +2440,8 @@ namespace NifOsg
if (texprop->treeAnim()) if (texprop->treeAnim())
stateset->addUniform(new osg::Uniform("useTreeAnim", true)); stateset->addUniform(new osg::Uniform("useTreeAnim", true));
handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite());
if (texprop->refraction())
SceneUtil::setupDistortion(*node, texprop->mRefractionStrength);
break; break;
} }
case Nif::RC_BSEffectShaderProperty: case Nif::RC_BSEffectShaderProperty:

View file

@ -649,6 +649,7 @@ namespace Resource
node->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); node->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f));
node->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1, 1, 1, 1))); node->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1, 1, 1, 1)));
node->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); node->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false));
node->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f));
} }
node->setUserValue(Misc::OsgUserValues::sFileHash, node->setUserValue(Misc::OsgUserValues::sFileHash,

View file

@ -29,6 +29,19 @@ namespace SceneUtil
node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true);
} }
void setupDistortion(osg::Node& node, float distortionStrength)
{
static const osg::ref_ptr<SceneUtil::AutoDepth> depth
= new SceneUtil::AutoDepth(osg::Depth::ALWAYS, 0, 1, false);
osg::StateSet* stateset = node.getOrCreateStateSet();
stateset->setRenderBinDetails(14, "Distortion", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
stateset->addUniform(new osg::Uniform("distortionStrength", distortionStrength));
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
}
void ProcessExtraDataVisitor::apply(osg::Node& node) void ProcessExtraDataVisitor::apply(osg::Node& node)
{ {
if (!mSceneMgr->getSoftParticles()) if (!mSceneMgr->getSoftParticles())
@ -54,6 +67,12 @@ namespace SceneUtil
setupSoftEffect(node, size, falloff, falloffDepth); setupSoftEffect(node, size, falloff, falloffDepth);
} }
else if (key == "distortion")
{
auto strength = it.second["strength"].as<float>(0.1f);
setupDistortion(node, strength);
}
} }
node.setUserValue(Misc::OsgUserValues::sExtraData, std::string{}); node.setUserValue(Misc::OsgUserValues::sExtraData, std::string{});

View file

@ -16,6 +16,7 @@ namespace osg
namespace SceneUtil namespace SceneUtil
{ {
void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth); void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth);
void setupDistortion(osg::Node& node, float distortionStrength);
class ProcessExtraDataVisitor : public osg::NodeVisitor class ProcessExtraDataVisitor : public osg::NodeVisitor
{ {

View file

@ -54,3 +54,38 @@ Example usage.
} }
} }
} }
Distortion
----------
This effect is used to imitate effects such as refraction and heat distortion. A common use case is to assign a normal map to the
diffuse slot to a material and add uv scrolling. The red and green channels of the texture are used to offset the final scene texture.
Blue and alpha channels are ignored.
To use this feature the :ref:`post processing` setting must be enabled.
This setting can either be activated in the OpenMW launcher, in-game, or changed in `settings.cfg`:
::
[Post Processing]
enabled = false
Variables.
+---------+--------------------------------------------------------------------------------------------------------+---------+---------+
| Name | Description | Type | Default |
+---------+--------------------------------------------------------------------------------------------------------+---------+---------+
| strength| The strength of the distortion effect. Scales linearly. | float | 0.1 |
+---------+--------------------------------------------------------------------------------------------------------+---------+---------+
Example usage.
::
omw:data {
"shader" : {
"distortion" : {
"strength": 0.12,
}
}
}

View file

@ -96,6 +96,7 @@ set(BUILTIN_DATA_FILES
shaders/adjustments.omwfx shaders/adjustments.omwfx
shaders/bloomlinear.omwfx shaders/bloomlinear.omwfx
shaders/debug.omwfx shaders/debug.omwfx
shaders/internal_distortion.omwfx
mygui/core.skin mygui/core.skin
mygui/core.xml mygui/core.xml

View file

@ -0,0 +1,25 @@
fragment main {
omw_In vec2 omw_TexCoord;
void main()
{
const float multiplier = 0.14;
vec2 offset = omw_Texture2D(omw_SamplerDistortion, omw_TexCoord).rg;
offset *= multiplier;
offset = clamp(offset, vec2(-1.0), vec2(1.0));
float occlusionFactor = omw_Texture2D(omw_SamplerDistortion, omw_TexCoord+offset).b;
omw_FragColor = mix(omw_GetLastShader(omw_TexCoord + offset), omw_GetLastShader(omw_TexCoord), occlusionFactor);
}
}
technique {
description = "Internal refraction shader for OpenMW";
version = "1.0";
author = "OpenMW";
passes = main;
flags = hidden;
}

View file

@ -16,6 +16,7 @@ set(SHADER_FILES
lib/particle/occlusion.glsl lib/particle/occlusion.glsl
lib/util/quickstep.glsl lib/util/quickstep.glsl
lib/util/coordinates.glsl lib/util/coordinates.glsl
lib/util/distortion.glsl
lib/core/fragment.glsl lib/core/fragment.glsl
lib/core/fragment.h.glsl lib/core/fragment.h.glsl
lib/core/fragment_multiview.glsl lib/core/fragment_multiview.glsl

View file

@ -1,5 +1,5 @@
#version 120 #version 120
#pragma import_defines(FORCE_OPAQUE) #pragma import_defines(FORCE_OPAQUE, DISTORTION)
#if @useUBO #if @useUBO
#extension GL_ARB_uniform_buffer_object : require #extension GL_ARB_uniform_buffer_object : require
@ -26,6 +26,8 @@ uniform sampler2D normalMap;
varying vec2 normalMapUV; varying vec2 normalMapUV;
#endif #endif
uniform sampler2D opaqueDepthTex;
varying float euclideanDepth; varying float euclideanDepth;
varying float linearDepth; varying float linearDepth;
@ -38,9 +40,11 @@ uniform float alphaRef;
uniform float emissiveMult; uniform float emissiveMult;
uniform float specStrength; uniform float specStrength;
uniform bool useTreeAnim; uniform bool useTreeAnim;
uniform float distortionStrength;
#include "lib/light/lighting.glsl" #include "lib/light/lighting.glsl"
#include "lib/material/alpha.glsl" #include "lib/material/alpha.glsl"
#include "lib/util/distortion.glsl"
#include "compatibility/vertexcolors.glsl" #include "compatibility/vertexcolors.glsl"
#include "compatibility/shadows_fragment.glsl" #include "compatibility/shadows_fragment.glsl"
@ -51,6 +55,15 @@ void main()
{ {
#if @diffuseMap #if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
#if defined(DISTORTION) && DISTORTION
vec2 screenCoords = gl_FragCoord.xy / (screenRes * @distorionRTRatio);
gl_FragData[0].a = getDiffuseColor().a;
gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, texture2D(opaqueDepthTex, screenCoords).x);
return;
#endif
gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV);
#else #else
gl_FragData[0] = vec4(1.0); gl_FragData[0] = vec4(1.0);

View file

@ -1,5 +1,5 @@
#version 120 #version 120
#pragma import_defines(FORCE_OPAQUE) #pragma import_defines(FORCE_OPAQUE, DISTORTION)
#if @useUBO #if @useUBO
#extension GL_ARB_uniform_buffer_object : require #extension GL_ARB_uniform_buffer_object : require
@ -66,6 +66,7 @@ uniform vec2 screenRes;
uniform float near; uniform float near;
uniform float far; uniform float far;
uniform float alphaRef; uniform float alphaRef;
uniform float distortionStrength;
#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL)
@ -91,6 +92,7 @@ varying vec4 passTangent;
#include "lib/light/lighting.glsl" #include "lib/light/lighting.glsl"
#include "lib/material/parallax.glsl" #include "lib/material/parallax.glsl"
#include "lib/material/alpha.glsl" #include "lib/material/alpha.glsl"
#include "lib/util/distortion.glsl"
#include "fog.glsl" #include "fog.glsl"
#include "vertexcolors.glsl" #include "vertexcolors.glsl"
@ -100,7 +102,6 @@ varying vec4 passTangent;
#if @softParticles #if @softParticles
#include "lib/particle/soft.glsl" #include "lib/particle/soft.glsl"
uniform sampler2D opaqueDepthTex;
uniform float particleSize; uniform float particleSize;
uniform bool particleFade; uniform bool particleFade;
uniform float softFalloffDepth; uniform float softFalloffDepth;
@ -112,6 +113,8 @@ uniform sampler2D orthoDepthMap;
varying vec3 orthoDepthMapCoord; varying vec3 orthoDepthMapCoord;
#endif #endif
uniform sampler2D opaqueDepthTex;
void main() void main()
{ {
#if @particleOcclusion #if @particleOcclusion
@ -133,8 +136,17 @@ void main()
offset = getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), height, flipY); offset = getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), height, flipY);
#endif #endif
vec2 screenCoords = gl_FragCoord.xy / screenRes;
#if @diffuseMap #if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV + offset); gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV + offset);
#if defined(DISTORTION) && DISTORTION
gl_FragData[0].a = getDiffuseColor().a;
gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, texture2D(opaqueDepthTex, screenCoords / @distorionRTRatio).x);
return;
#endif
#if @diffuseParallax #if @diffuseParallax
gl_FragData[0].a = 1.0; gl_FragData[0].a = 1.0;
#else #else
@ -234,7 +246,6 @@ void main()
gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far);
vec2 screenCoords = gl_FragCoord.xy / screenRes;
#if !defined(FORCE_OPAQUE) && @softParticles #if !defined(FORCE_OPAQUE) && @softParticles
gl_FragData[0].a *= calcSoftParticleFade( gl_FragData[0].a *= calcSoftParticleFade(
viewVec, viewVec,

View file

@ -0,0 +1,32 @@
#ifndef LIB_UTIL_DISTORTION
#define LIB_UTIL_DISTORTION
vec4 applyDistortion(in vec4 color, in float strength, in float pixelDepth, in float sceneDepth)
{
vec4 distortion = color;
float invOcclusion = 1.0;
// TODO: Investigate me. Alpha-clipping is enabled for refraction for what seems an arbitrary threshold, even when
// there are no associated NIF properties.
if (distortion.a < 0.1)
discard;
distortion.b = 0.0;
#if @reverseZ
if (pixelDepth < sceneDepth)
#else
if (pixelDepth > sceneDepth)
#endif
{
invOcclusion = 0.0;
distortion.b = 1.0;
}
distortion.rg = color.rg * 2.0 - 1.0;
distortion.rg *= invOcclusion * strength;
return distortion;
}
#endif

View file

@ -4,8 +4,8 @@
float quickstep(float x) float quickstep(float x)
{ {
x = clamp(x, 0.0, 1.0); x = clamp(x, 0.0, 1.0);
x = 1.0 - x*x; x = 1.0 - x * x;
x = 1.0 - x*x; x = 1.0 - x * x;
return x; return x;
} }