From 187f63d3d3568fd8dfc7104ddf72d35f94acb4ba Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Fri, 10 Nov 2023 08:02:53 -0800 Subject: [PATCH] support postprocess distortion --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/distortion.cpp | 28 ++++ apps/openmw/mwrender/distortion.hpp | 28 ++++ apps/openmw/mwrender/npcanimation.cpp | 9 +- apps/openmw/mwrender/pingpongcanvas.cpp | 4 + apps/openmw/mwrender/pingpongcanvas.hpp | 3 + apps/openmw/mwrender/postprocessor.cpp | 120 +++++++++++------- apps/openmw/mwrender/postprocessor.hpp | 14 +- apps/openmw/mwrender/renderbin.hpp | 3 +- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/fx/pass.cpp | 1 + components/fx/technique.hpp | 4 + components/nif/property.hpp | 4 + components/nifosg/nifloader.cpp | 4 + components/resource/scenemanager.cpp | 1 + components/sceneutil/extradata.cpp | 19 +++ components/sceneutil/extradata.hpp | 1 + .../modding/custom-shader-effects.rst | 35 +++++ files/data/CMakeLists.txt | 1 + files/data/shaders/internal_distortion.omwfx | 25 ++++ files/shaders/CMakeLists.txt | 1 + files/shaders/compatibility/bs/default.frag | 15 ++- files/shaders/compatibility/objects.frag | 17 ++- files/shaders/lib/util/distortion.glsl | 32 +++++ files/shaders/lib/util/quickstep.glsl | 4 +- 25 files changed, 311 insertions(+), 65 deletions(-) create mode 100644 apps/openmw/mwrender/distortion.cpp create mode 100644 apps/openmw/mwrender/distortion.hpp create mode 100644 files/data/shaders/internal_distortion.omwfx create mode 100644 files/shaders/lib/util/distortion.glsl diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index db44b91159..373de3683d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -24,7 +24,7 @@ add_openmw_dir (mwrender bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples - actorutil + actorutil distortion ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/distortion.cpp b/apps/openmw/mwrender/distortion.cpp new file mode 100644 index 0000000000..2ca2ace65b --- /dev/null +++ b/apps/openmw/mwrender/distortion.cpp @@ -0,0 +1,28 @@ +#include "distortion.hpp" + +#include + +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); + } +} diff --git a/apps/openmw/mwrender/distortion.hpp b/apps/openmw/mwrender/distortion.hpp new file mode 100644 index 0000000000..736f4ea6f2 --- /dev/null +++ b/apps/openmw/mwrender/distortion.hpp @@ -0,0 +1,28 @@ +#include + +#include + +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& fbo, size_t frameId) { mFBO[frameId] = fbo; } + void setOriginalFBO(const osg::ref_ptr& fbo, size_t frameId) + { + mOriginalFBO[frameId] = fbo; + } + + private: + std::array, 2> mFBO; + std::array, 2> mOriginalFBO; + }; +} diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 469978e6eb..d1cd5fed60 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -345,11 +345,9 @@ namespace MWRender bin->drawImplementation(renderInfo, previous); auto primaryFBO = postProcessor->getPrimaryFbo(frameId); + primaryFBO->apply(*state); - if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) - postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); - else - primaryFBO->apply(*state); + postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); // depth accumulation pass osg::ref_ptr restore = bin->getStateSet(); @@ -357,8 +355,7 @@ namespace MWRender bin->drawImplementation(renderInfo, previous); bin->setStateSet(restore); - if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) - primaryFBO->apply(*state); + primaryFBO->apply(*state); state->checkGLErrors("after DepthClearCallback::drawImplementation"); } diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 4fecdf87f9..9c8b08adfd 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -242,6 +242,10 @@ namespace MWRender if (mTextureNormals) node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); + if (mTextureDistortion) + node.mRootStateSet->setTextureAttribute( + PostProcessor::TextureUnits::Unit_Distortion, mTextureDistortion); + state.pushStateSet(node.mRootStateSet); state.apply(); diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index a03e3591ae..f7212a3f18 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -48,6 +48,8 @@ namespace MWRender void setTextureNormals(osg::ref_ptr tex) { mTextureNormals = tex; } + void setTextureDistortion(osg::ref_ptr tex) { mTextureDistortion = tex; } + void setCalculateAvgLum(bool enabled) { mAvgLum = enabled; } void setPostProcessing(bool enabled) { mPostprocessing = enabled; } @@ -69,6 +71,7 @@ namespace MWRender osg::ref_ptr mTextureScene; osg::ref_ptr mTextureDepth; osg::ref_ptr mTextureNormals; + osg::ref_ptr mTextureDistortion; mutable bool mDirty = false; mutable std::vector mDirtyAttachments; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 2c77981244..1aaeb460b7 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -29,7 +29,9 @@ #include "../mwgui/postprocessorhud.hpp" +#include "distortion.hpp" #include "pingpongcull.hpp" +#include "renderbin.hpp" #include "renderingmanager.hpp" #include "sky.hpp" #include "transparentpass.hpp" @@ -103,6 +105,8 @@ namespace return Stereo::createMultiviewCompatibleAttachment(texture); } + + constexpr float DistortionRatio = 0.25; } namespace MWRender @@ -118,6 +122,7 @@ namespace MWRender , mUsePostProcessing(Settings::postProcessing().mEnabled) , mSamples(Settings::video().mAntialiasing) , mPingPongCull(new PingPongCull(this)) + , mDistortionCallback(new DistortionCallback) { auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager(); @@ -141,18 +146,45 @@ namespace MWRender mHUDCamera->setCullCallback(new HUDCullCallback); mViewer->getCamera()->addCullCallback(mPingPongCull); - if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass) - { - mTransparentDepthPostPass - = new TransparentDepthBinCallback(shaderManager, Settings::postProcessing().mTransparentPostpass); - osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); - } + // resolves the multisampled depth buffer and optionally draws an additional depth postpass + mTransparentDepthPostPass + = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), + Settings::postProcessing().mTransparentPostpass); + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); + + osg::ref_ptr 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 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(1); populateTechniqueFiles(); + auto distortion = loadTechnique("internal_distortion"); + distortion->setInternal(true); + distortion->setLocked(true); + mInternalTechniques.push_back(distortion); + osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GLExtensions* ext = gc->getState()->get(); @@ -171,19 +203,6 @@ namespace MWRender else 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; mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; mStateUpdater = new fx::StateUpdater(mUBO); @@ -281,17 +300,15 @@ namespace MWRender mCanvases[frameId]->setCalculateAvgLum(mHDR); mCanvases[frameId]->setTextureScene(getTexture(Tex_Scene, frameId)); - if (mTransparentDepthPostPass) - mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId)); - else - mCanvases[frameId]->setTextureDepth(getTexture(Tex_Depth, frameId)); + mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId)); + mCanvases[frameId]->setTextureDistortion(getTexture(Tex_Distortion, frameId)); - if (mTransparentDepthPostPass) - { - mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; - mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; - mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; - } + mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; + 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(); @@ -441,6 +458,13 @@ namespace MWRender textures[Tex_Normal]->setSourceFormat(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) { tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); @@ -448,16 +472,8 @@ namespace MWRender }; setupDepth(textures[Tex_Depth]); - - if (!mTransparentDepthPostPass) - { - textures[Tex_OpaqueDepth] = nullptr; - } - else - { - setupDepth(textures[Tex_OpaqueDepth]); - textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); - } + setupDepth(textures[Tex_OpaqueDepth]); + textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); auto& fbos = mFbos[frameId]; @@ -487,6 +503,7 @@ namespace MWRender auto normalRB = createFrameBufferAttachmentFromTemplate( Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); } auto depthRB = createFrameBufferAttachmentFromTemplate( Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); @@ -510,12 +527,13 @@ namespace MWRender 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, - Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth])); - } + fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; + 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__ 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_SamplerLastPass", Unit_LastPass)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDistortion", Unit_Distortion)); if (mNormals) node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals)); @@ -582,6 +601,8 @@ namespace MWRender if (technique->getHDR()) node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDistortion", Unit_Distortion)); + int texUnit = Unit_NextFree; // user-defined samplers @@ -681,7 +702,7 @@ namespace MWRender disableTechnique(technique, false); - int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); + int pos = std::min(location.value_or(mTechniques.size()) + mInternalTechniques.size(), mTechniques.size()); mTechniques.insert(mTechniques.begin() + pos, technique); dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug); @@ -747,6 +768,11 @@ namespace MWRender { mTechniques.clear(); + for (const auto& technique : mInternalTechniques) + { + mTechniques.push_back(technique); + } + for (const std::string& techniqueName : Settings::postProcessing().mChain.get()) { if (techniqueName.empty()) @@ -764,7 +790,7 @@ namespace MWRender for (const auto& technique : mTechniques) { - if (!technique || technique->getDynamic()) + if (!technique || technique->getDynamic() || technique->getInternal()) continue; chain.push_back(technique->getName()); } diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index e9f19bf6b5..2630467f95 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -50,12 +50,13 @@ namespace MWRender class PingPongCull; class PingPongCanvas; class TransparentDepthBinCallback; + class DistortionCallback; class PostProcessor : public osg::Group { public: - using FBOArray = std::array, 5>; - using TextureArray = std::array, 5>; + using FBOArray = std::array, 6>; + using TextureArray = std::array, 6>; using TechniqueList = std::vector>; enum TextureIndex @@ -64,7 +65,8 @@ namespace MWRender Tex_Scene_LDR, Tex_Depth, Tex_OpaqueDepth, - Tex_Normal + Tex_Normal, + Tex_Distortion, }; enum FBOIndex @@ -73,7 +75,8 @@ namespace MWRender FBO_Multisample, FBO_FirstPerson, FBO_OpaqueDepth, - FBO_Intercept + FBO_Intercept, + FBO_Distortion, }; enum TextureUnits @@ -83,6 +86,7 @@ namespace MWRender Unit_Depth, Unit_EyeAdaptation, Unit_Normals, + Unit_Distortion, Unit_NextFree }; @@ -223,6 +227,7 @@ namespace MWRender TechniqueList mTechniques; TechniqueList mTemplates; TechniqueList mQueuedTemplates; + TechniqueList mInternalTechniques; std::unordered_map mTechniqueFileMap; @@ -258,6 +263,7 @@ namespace MWRender osg::ref_ptr mPingPongCull; std::array, 2> mCanvases; osg::ref_ptr mTransparentDepthPostPass; + osg::ref_ptr mDistortionCallback; fx::DispatchArray mTemplateData; }; diff --git a/apps/openmw/mwrender/renderbin.hpp b/apps/openmw/mwrender/renderbin.hpp index c14f611426..6f4ae0819b 100644 --- a/apps/openmw/mwrender/renderbin.hpp +++ b/apps/openmw/mwrender/renderbin.hpp @@ -13,7 +13,8 @@ namespace MWRender RenderBin_DepthSorted = 10, // osg::StateSet::TRANSPARENT_BIN RenderBin_OcclusionQuery = 11, RenderBin_FirstPerson = 12, - RenderBin_SunGlare = 13 + RenderBin_SunGlare = 13, + RenderBin_Distortion = 14, }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c175817fa8..7df5671eec 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -502,6 +502,7 @@ namespace MWRender sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); mFog = std::make_unique(); diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 76b54d55a5..cf50d20fe2 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -91,6 +91,7 @@ uniform @builtinSampler omw_SamplerLastShader; uniform @builtinSampler omw_SamplerLastPass; uniform @builtinSampler omw_SamplerDepth; uniform @builtinSampler omw_SamplerNormals; +uniform @builtinSampler omw_SamplerDistortion; uniform vec4 omw_PointLights[@pointLightCount]; uniform int omw_PointLightsCount; diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index ed356e0a37..0d17128e56 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -175,6 +175,9 @@ namespace fx void setLocked(bool locked) { mLocked = locked; } bool getLocked() const { return mLocked; } + void setInternal(bool internal) { mInternal = internal; } + bool getInternal() const { return mInternal; } + private: [[noreturn]] void error(const std::string& msg); @@ -295,6 +298,7 @@ namespace fx bool mDynamic = false; bool mLocked = false; + bool mInternal = false; }; template <> diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 7d420f1650..fbc7e8294c 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -108,6 +108,8 @@ namespace Nif enum BSShaderFlags1 { BSSFlag1_Specular = 0x00000001, + BSSFlag1_Refraction = 0x00008000, + BSSFlag1_FireRefraction = 0x00010000, BSSFlag1_Decal = 0x04000000, BSSFlag1_DepthTest = 0x80000000, }; @@ -148,6 +150,8 @@ namespace Nif bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } bool depthTest() const { return mShaderFlags1 & BSSFlag1_DepthTest; } bool depthWrite() const { return mShaderFlags2 & BSSFlag2_DepthWrite; } + bool refraction() const { return mShaderFlags1 & BSSFlag1_Refraction; } + bool fireRefraction() const { return mShaderFlags1 & BSSFlag1_FireRefraction; } }; struct BSShaderLightingProperty : BSShaderProperty diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index c30add7f77..ea4c16e402 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2381,6 +2381,8 @@ namespace NifOsg textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + if (texprop->refraction()) + SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); break; } case Nif::RC_BSShaderNoLightingProperty: @@ -2438,6 +2440,8 @@ namespace NifOsg if (texprop->treeAnim()) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); + if (texprop->refraction()) + SceneUtil::setupDistortion(*node, texprop->mRefractionStrength); break; } case Nif::RC_BSEffectShaderProperty: diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b3c9eee5e7..25abcfd0d8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -649,6 +649,7 @@ namespace Resource 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("useFalloff", false)); + node->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); } node->setUserValue(Misc::OsgUserValues::sFileHash, diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index 720e032a61..bd82e9abba 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -29,6 +29,19 @@ namespace SceneUtil node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); } + void setupDistortion(osg::Node& node, float distortionStrength) + { + static const osg::ref_ptr 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) { if (!mSceneMgr->getSoftParticles()) @@ -54,6 +67,12 @@ namespace SceneUtil setupSoftEffect(node, size, falloff, falloffDepth); } + else if (key == "distortion") + { + auto strength = it.second["strength"].as(0.1f); + + setupDistortion(node, strength); + } } node.setUserValue(Misc::OsgUserValues::sExtraData, std::string{}); diff --git a/components/sceneutil/extradata.hpp b/components/sceneutil/extradata.hpp index 9b1563b78a..7054ac91b3 100644 --- a/components/sceneutil/extradata.hpp +++ b/components/sceneutil/extradata.hpp @@ -16,6 +16,7 @@ namespace osg namespace SceneUtil { void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth); + void setupDistortion(osg::Node& node, float distortionStrength); class ProcessExtraDataVisitor : public osg::NodeVisitor { diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 5ea711953d..0bd1fbec85 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -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, + } + } + } diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index dbf86cc44d..4e3354807c 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -96,6 +96,7 @@ set(BUILTIN_DATA_FILES shaders/adjustments.omwfx shaders/bloomlinear.omwfx shaders/debug.omwfx + shaders/internal_distortion.omwfx mygui/core.skin mygui/core.xml diff --git a/files/data/shaders/internal_distortion.omwfx b/files/data/shaders/internal_distortion.omwfx new file mode 100644 index 0000000000..b641bb6711 --- /dev/null +++ b/files/data/shaders/internal_distortion.omwfx @@ -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; +} diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 6ead738477..ca0c264ade 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -16,6 +16,7 @@ set(SHADER_FILES lib/particle/occlusion.glsl lib/util/quickstep.glsl lib/util/coordinates.glsl + lib/util/distortion.glsl lib/core/fragment.glsl lib/core/fragment.h.glsl lib/core/fragment_multiview.glsl diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 70d9ef7ba7..ec5f5c8978 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -1,5 +1,5 @@ #version 120 -#pragma import_defines(FORCE_OPAQUE) +#pragma import_defines(FORCE_OPAQUE, DISTORTION) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -26,6 +26,8 @@ uniform sampler2D normalMap; varying vec2 normalMapUV; #endif +uniform sampler2D opaqueDepthTex; + varying float euclideanDepth; varying float linearDepth; @@ -38,9 +40,11 @@ uniform float alphaRef; uniform float emissiveMult; uniform float specStrength; uniform bool useTreeAnim; +uniform float distortionStrength; #include "lib/light/lighting.glsl" #include "lib/material/alpha.glsl" +#include "lib/util/distortion.glsl" #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_fragment.glsl" @@ -51,6 +55,15 @@ void main() { #if @diffuseMap 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); #else gl_FragData[0] = vec4(1.0); diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 80de6b0e9d..043aa266d8 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -1,5 +1,5 @@ #version 120 -#pragma import_defines(FORCE_OPAQUE) +#pragma import_defines(FORCE_OPAQUE, DISTORTION) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -66,6 +66,7 @@ uniform vec2 screenRes; uniform float near; uniform float far; uniform float alphaRef; +uniform float distortionStrength; #define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) @@ -91,6 +92,7 @@ varying vec4 passTangent; #include "lib/light/lighting.glsl" #include "lib/material/parallax.glsl" #include "lib/material/alpha.glsl" +#include "lib/util/distortion.glsl" #include "fog.glsl" #include "vertexcolors.glsl" @@ -100,7 +102,6 @@ varying vec4 passTangent; #if @softParticles #include "lib/particle/soft.glsl" -uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; uniform float softFalloffDepth; @@ -112,6 +113,8 @@ uniform sampler2D orthoDepthMap; varying vec3 orthoDepthMapCoord; #endif +uniform sampler2D opaqueDepthTex; + void main() { #if @particleOcclusion @@ -133,8 +136,17 @@ void main() offset = getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), height, flipY); #endif +vec2 screenCoords = gl_FragCoord.xy / screenRes; + #if @diffuseMap 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 gl_FragData[0].a = 1.0; #else @@ -234,7 +246,6 @@ void main() gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); - vec2 screenCoords = gl_FragCoord.xy / screenRes; #if !defined(FORCE_OPAQUE) && @softParticles gl_FragData[0].a *= calcSoftParticleFade( viewVec, diff --git a/files/shaders/lib/util/distortion.glsl b/files/shaders/lib/util/distortion.glsl new file mode 100644 index 0000000000..e0ccf6f2ec --- /dev/null +++ b/files/shaders/lib/util/distortion.glsl @@ -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 diff --git a/files/shaders/lib/util/quickstep.glsl b/files/shaders/lib/util/quickstep.glsl index 2baa0a7430..e505886337 100644 --- a/files/shaders/lib/util/quickstep.glsl +++ b/files/shaders/lib/util/quickstep.glsl @@ -4,8 +4,8 @@ float quickstep(float x) { 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; }