From b09411d396fe095b75bb424a537292b5f6aeb6d2 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 29 Jun 2022 18:15:12 -0700 Subject: [PATCH] allow soft particles on meshes and add extra data extensions --- apps/openmw/mwrender/renderingmanager.cpp | 13 ++- apps/openmw/mwrender/ripplesimulation.cpp | 2 +- components/CMakeLists.txt | 2 +- components/misc/osguservalues.cpp | 2 + components/misc/osguservalues.hpp | 2 + components/nifosg/nifloader.cpp | 11 ++- components/resource/scenemanager.cpp | 14 ++- components/resource/scenemanager.hpp | 4 +- components/sceneutil/extradata.cpp | 66 ++++++++++++++ components/sceneutil/extradata.hpp | 35 ++++++++ components/shader/shadermanager.cpp | 19 ++-- components/shader/shadermanager.hpp | 11 ++- components/shader/shadervisitor.cpp | 88 ++----------------- components/shader/shadervisitor.hpp | 4 - .../modding/custom-shader-effects.rst | 54 ++++++++++++ docs/source/reference/modding/index.rst | 1 + files/shaders/lighting_util.glsl | 2 - files/shaders/objects_fragment.glsl | 6 +- files/shaders/softparticles.glsl | 22 +++-- 19 files changed, 243 insertions(+), 115 deletions(-) create mode 100644 components/sceneutil/extradata.cpp create mode 100644 components/sceneutil/extradata.hpp create mode 100644 docs/source/reference/modding/custom-shader-effects.rst diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 449217fa68..55b50c9d7c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -87,8 +87,10 @@ namespace MWRender class PerViewUniformStateUpdater final : public SceneUtil::StateSetUpdater { public: - PerViewUniformStateUpdater() + PerViewUniformStateUpdater(Resource::SceneManager* sceneManager) + : mSceneManager(sceneManager) { + mOpaqueTextureUnit = mSceneManager->getShaderManager().reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); } void setDefaults(osg::StateSet* stateset) override @@ -108,6 +110,8 @@ namespace MWRender osg::Texture* skyTexture = mSkyRTT->getColorTexture(static_cast(nv)); stateset->setTextureAttribute(mSkyTextureUnit, skyTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); } + + stateset->setTextureAttribute(mOpaqueTextureUnit, mSceneManager->getOpaqueDepthTex(nv->getTraversalNumber()), osg::StateAttribute::ON); } void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override @@ -144,6 +148,9 @@ namespace MWRender osg::Matrixf mProjectionMatrix; int mSkyTextureUnit = -1; SceneUtil::RTTNode* mSkyRTT = nullptr; + + Resource::SceneManager* mSceneManager; + int mOpaqueTextureUnit = -1; }; class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater @@ -536,7 +543,7 @@ namespace MWRender mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover); rootNode->addUpdateCallback(mSharedUniformStateUpdater); - mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(); + mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(mResourceSystem->getSceneManager()); rootNode->addCullCallback(mPerViewUniformStateUpdater); mPostProcessor = new PostProcessor(*this, viewer, mRootNode, resourceSystem->getVFS()); @@ -581,7 +588,7 @@ namespace MWRender mSky->setCamera(mViewer->getCamera()); if (mSkyBlending) { - int skyTextureUnit = mResourceSystem->getSceneManager()->getShaderManager().reserveGlobalTextureUnits(1); + int skyTextureUnit = mResourceSystem->getSceneManager()->getShaderManager().reserveGlobalTextureUnits(Shader::ShaderManager::Slot::SkyTexture); Log(Debug::Info) << "Reserving texture unit for sky RTT: " << skyTextureUnit; mPerViewUniformStateUpdater->enableSkyRTT(skyTextureUnit, mSky->getSkyRTT()); } diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index d1a8ea1ecd..5aa7c29154 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -109,7 +109,7 @@ RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* createWaterRippleStateSet(resourceSystem, mParticleNode); - resourceSystem->getSceneManager()->recreateShaders(mParticleNode, "objects", false, nullptr, true); + resourceSystem->getSceneManager()->recreateShaders(mParticleNode); mParent->addChild(mParticleNode); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2d3fc01e3b..a4e1d32e99 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -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 color riggeometryosgaextension + screencapture depth color riggeometryosgaextension extradata ) add_component_dir (nif diff --git a/components/misc/osguservalues.cpp b/components/misc/osguservalues.cpp index 3bdd0d1848..b70647a63e 100644 --- a/components/misc/osguservalues.cpp +++ b/components/misc/osguservalues.cpp @@ -3,4 +3,6 @@ namespace Misc { const std::string OsgUserValues::sFileHash = "fileHash"; + const std::string OsgUserValues::sExtraData = "xData"; + const std::string OsgUserValues::sXSoftEffect = "xSoftEffect"; } diff --git a/components/misc/osguservalues.hpp b/components/misc/osguservalues.hpp index 022e81764f..443e6132e1 100644 --- a/components/misc/osguservalues.hpp +++ b/components/misc/osguservalues.hpp @@ -8,6 +8,8 @@ namespace Misc struct OsgUserValues { static const std::string sFileHash; + static const std::string sExtraData; + static const std::string sXSoftEffect; }; } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8f7b02eaa4..9b2e8db8d6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -614,17 +614,24 @@ namespace NifOsg else if(e->recType == Nif::RC_NiStringExtraData) { const Nif::NiStringExtraData *sd = static_cast(e.getPtr()); + + constexpr std::string_view extraDataIdentifer = "omw:data"; + // String markers may contain important information // affecting the entire subtree of this obj - if(sd->string == "MRK" && !Loader::getShowMarkers()) + if (sd->string == "MRK" && !Loader::getShowMarkers()) { // Marker objects. These meshes are only visible in the editor. hasMarkers = true; } - else if(sd->string == "BONE") + else if (sd->string == "BONE") { node->getOrCreateUserDataContainer()->addDescription("CustomBone"); } + else if (sd->string.rfind(extraDataIdentifer, 0) == 0) + { + node->setUserValue(Misc::OsgUserValues::sExtraData, sd->string.substr(extraDataIdentifer.length())); + } } } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 31b8a6319f..094220938b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -366,15 +367,13 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate, bool disableSoftParticles) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix)); shaderVisitor->setAllowedToModifyStateSets(false); shaderVisitor->setProgramTemplate(programTemplate); if (forceShadersForNode) shaderVisitor->setForceShaders(true); - if (disableSoftParticles) - shaderVisitor->setOpaqueDepthTex(nullptr, nullptr); node->accept(*shaderVisitor); } @@ -461,6 +460,11 @@ namespace Resource mOpaqueDepthTex = { texturePing, texturePong }; } + osg::ref_ptr SceneManager::getOpaqueDepthTex(size_t frame) + { + return mOpaqueDepthTex[frame % 2]; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -737,6 +741,9 @@ namespace Resource try { loaded = load(normalized, mVFS, mImageManager, mNifFileManager); + + SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this); + loaded->accept(extraDataVisitor); } catch (const std::exception& e) { @@ -990,7 +997,6 @@ namespace Resource shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); - shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex[0], mOpaqueDepthTex[1]); shaderVisitor->setSupportsNormalsRT(mSupportsNormalsRT); return shaderVisitor; } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 82adef9dc9..669b6df758 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -77,7 +77,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr, bool disableSoftParticles = false); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); /// Applying shaders to a node may replace some fixed-function state. /// This restores it. @@ -111,6 +111,8 @@ namespace Resource void setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong); + osg::ref_ptr getOpaqueDepthTex(size_t frame); + enum class UBOBinding { // If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp new file mode 100644 index 0000000000..7b50e281c7 --- /dev/null +++ b/components/sceneutil/extradata.cpp @@ -0,0 +1,66 @@ +#include "extradata.hpp" + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace SceneUtil +{ + void ProcessExtraDataVisitor::setupSoftEffect(osg::Node& node, float size, bool falloff) + { + const int unitSoftEffect = mSceneMgr->getShaderManager().reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); + static const osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LESS, 0, 1, false); + + osg::StateSet* stateset = node.getOrCreateStateSet(); + + stateset->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); + stateset->addUniform(new osg::Uniform("particleSize", size)); + stateset->addUniform(new osg::Uniform("particleFade", falloff)); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); + } + + void ProcessExtraDataVisitor::apply(osg::Node& node) + { + std::string source; + + if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) + { + YAML::Node root = YAML::Load(source); + + for (const auto& it : root["shader"]) + { + std::string key = it.first.as(); + + if (key == "soft_effect") + { + auto size = it.second["size"].as(45.f); + auto falloff = it.second["falloff"].as(false); + + setupSoftEffect(node, size, falloff); + } + } + + node.setUserValue(Misc::OsgUserValues::sExtraData, std::string{}); + } + else if (osgParticle::ParticleSystem* partsys = dynamic_cast(&node)) + { + setupSoftEffect(node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false); + } + + traverse(node); + } +} \ No newline at end of file diff --git a/components/sceneutil/extradata.hpp b/components/sceneutil/extradata.hpp new file mode 100644 index 0000000000..b460bd04d0 --- /dev/null +++ b/components/sceneutil/extradata.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_EXTRADATA_H +#define OPENMW_COMPONENTS_RESOURCE_EXTRADATA_H + +#include + +#include +#include + +namespace Resource +{ + class SceneManager; +} + +namespace osg +{ + class Node; +} + +namespace SceneUtil +{ + class ProcessExtraDataVisitor : public osg::NodeVisitor + { + public: + ProcessExtraDataVisitor(Resource::SceneManager* sceneMgr) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mSceneMgr(sceneMgr) {} + + void apply(osg::Node& node) override; + + private: + void setupSoftEffect(osg::Node& node, float size, bool falloff); + + Resource::SceneManager* mSceneMgr; + }; +} + +#endif diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 0ac96ce4dc..97162a8986 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -510,22 +510,31 @@ namespace Shader program->addShader(linkedShader); } - int ShaderManager::reserveGlobalTextureUnits(int count) + int ShaderManager::reserveGlobalTextureUnits(Slot slot) { + int unit = mReservedTextureUnitsBySlot[static_cast(slot)]; + if (unit >= 0) + return unit; + { // Texture units from `8 - numberOfShadowMaps` to `8` are used for shadows, so we skip them here. // TODO: Maybe instead of fixed texture units use `reserveGlobalTextureUnits` for shadows as well. static const int numberOfShadowMaps = Settings::Manager::getBool("enable shadows", "Shadows") ? std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8) : 0; - if (getAvailableTextureUnits() >= 8 && getAvailableTextureUnits() - count < 8) + if (getAvailableTextureUnits() >= 8 && getAvailableTextureUnits() - 1 < 8) mReservedTextureUnits = mMaxTextureUnits - (8 - numberOfShadowMaps); } - if (getAvailableTextureUnits() < count + 1) + if (getAvailableTextureUnits() < 2) throw std::runtime_error("Can't reserve texture unit; no available units"); - mReservedTextureUnits += count; - return mMaxTextureUnits - mReservedTextureUnits; + mReservedTextureUnits++; + + unit = mMaxTextureUnits - mReservedTextureUnits; + + mReservedTextureUnitsBySlot[static_cast(slot)] = unit; + + return unit; } } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 05c1b17bb4..4d3cc9937a 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -58,7 +59,13 @@ namespace Shader int getMaxTextureUnits() const { return mMaxTextureUnits; } int getAvailableTextureUnits() const { return mMaxTextureUnits - mReservedTextureUnits; } - int reserveGlobalTextureUnits(int count); + enum class Slot + { + OpaqueDepthTexture, + SkyTexture, + }; + + int reserveGlobalTextureUnits(Slot slot); private: void getLinkedShaders(osg::ref_ptr shader, const std::vector& linkedShaderNames, const DefineMap& defines); @@ -89,6 +96,8 @@ namespace Shader int mMaxTextureUnits = 0; int mReservedTextureUnits = 0; + + std::array mReservedTextureUnitsBySlot = {-1, -1}; }; bool parseForeachDirective(std::string& source, const std::string& templateName, size_t foundPos); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index b1b657daa1..6769da0ee2 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -30,50 +31,6 @@ #include "removedalphafunc.hpp" #include "shadermanager.hpp" -namespace -{ - class OpaqueDepthAttribute : public osg::StateAttribute - { - public: - OpaqueDepthAttribute() = default; - - OpaqueDepthAttribute(const OpaqueDepthAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) - : osg::StateAttribute(copy, copyop), mTextures(copy.mTextures), mUnit(copy.mUnit) {} - - void setTexturesAndUnit(const std::array, 2>& textures, int unit) - { - mTextures = textures; - mUnit = unit; - } - - META_StateAttribute(Shader, OpaqueDepthAttribute, osg::StateAttribute::TEXTURE) - - int compare(const StateAttribute& sa) const override - { - COMPARE_StateAttribute_Types(OpaqueDepthAttribute, sa); - - COMPARE_StateAttribute_Parameter(mTextures); - - return 0; - } - - void apply(osg::State& state) const override - { - auto index = state.getFrameStamp()->getFrameNumber() % 2; - - if (!mTextures[index]) - return; - - state.setActiveTextureUnit(mUnit); - state.applyTextureAttribute(mUnit, mTextures[index]); - } - - private: - mutable std::array, 2> mTextures; - int mUnit; - }; -} - namespace Shader { /** @@ -198,7 +155,6 @@ namespace Shader , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) - , mSoftParticleSize(0.f) , mNode(nullptr) { } @@ -308,6 +264,10 @@ namespace Shader if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) mRequirements.back().mShaderRequired = true; + bool softEffect = false; + if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) + mRequirements.back().mSoftParticles = true; + // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); @@ -681,28 +641,7 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } - if (reqs.mSoftParticles && mOpaqueDepthTex.front()) - { - osg::ref_ptr depth = new SceneUtil::AutoDepth; - depth->setWriteMask(false); - writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - addedState->setAttributeAndModes(depth); - - writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); - addedState->addUniform("particleSize"); - - constexpr int unit = 2; - - writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unit)); - addedState->addUniform("opaqueDepthTex"); - - osg::ref_ptr opaqueDepthAttr = new OpaqueDepthAttribute; - opaqueDepthAttr->setTexturesAndUnit(mOpaqueDepthTex, unit); - writableStateSet->setAttributeAndModes(opaqueDepthAttr, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - addedState->setAttributeAndModes(opaqueDepthAttr); - } - - defineMap["softParticles"] = reqs.mSoftParticles && mOpaqueDepthTex.front() ? "1" : "0"; + defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; Stereo::Manager::instance().shaderStereoDefines(defineMap); @@ -888,20 +827,12 @@ namespace Shader void ShaderVisitor::apply(osg::Drawable& drawable) { - auto partsys = dynamic_cast(&drawable); - - bool needPop = drawable.getStateSet() || partsys; + bool needPop = drawable.getStateSet(); if (needPop) { pushRequirements(drawable); - if (partsys) - { - mRequirements.back().mSoftParticles = true; - mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum; - } - if (drawable.getStateSet()) applyStateSet(drawable.getStateSet(), drawable); } @@ -982,11 +913,6 @@ namespace Shader mConvertAlphaTestToAlphaToCoverage = convert; } - void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong) - { - mOpaqueDepthTex = { texturePing, texturePong }; - } - ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 3ddcd9815f..dca97df7b6 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -48,8 +48,6 @@ namespace Shader void setConvertAlphaTestToAlphaToCoverage(bool convert); - void setOpaqueDepthTex(osg::ref_ptr texturePing, osg::ref_ptr texturePong); - void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } void apply(osg::Node& node) override; @@ -108,7 +106,6 @@ namespace Shader int mTexStageRequiringTangents; bool mSoftParticles; - float mSoftParticleSize; // the Node that requested these requirements osg::Node* mNode; @@ -122,7 +119,6 @@ namespace Shader bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); osg::ref_ptr mProgramTemplate; - std::array, 2> mOpaqueDepthTex; }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst new file mode 100644 index 0000000000..a21afcc7bd --- /dev/null +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -0,0 +1,54 @@ +Custom Shader Effects +##################### + +OpenMW leverages the `NiStringExtraData` node to inject special shader flags and effects. +This nodes must have the prefix `omw:data` and have a valid JSON object that follows. + +.. note:: + + This is a new feature to inject OpenMW specific effects. Only a single + effect is currently supported. By default, the shader effects will propogate + to all a nodes children. Other propogation modes and effects will come with + future releases. + +See below to see the currently supported effects. + +Soft Effect +----------- + +This effect softens the intersection of alpha-blended planes with other opaque +geometry. This effect is automatically applied to all particle systems, but can +be applied to any mesh or node. This is useful when layering many alpha-blended +planes for various effects like steam over a hotspring or low hanging fog for +dungeons. + +To use this feature the :ref:`soft particles` setting must be enabled. +This settings can either be activated in the OpenMW launcher or changed in `settings.cfg`: + +:: + + [Shaders] + soft particles = true + +Variables. + ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| Name | Description | Type | Default | ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| size | Scaling ratio. Larger values will make a softer fade effect. Larger geometry requires higher values. | integer | 45 | ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| falloff | Fades away geometry as camera gets closer. Geometry full fades when parallel to camera. | boolean | false | ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ + +Example usage. + +:: + + omw:data { + "shader" : { + "soft_effect" : { + "size": 250, + "falloff" : false, + } + } + } diff --git a/docs/source/reference/modding/index.rst b/docs/source/reference/modding/index.rst index 1e8560930a..558600b562 100644 --- a/docs/source/reference/modding/index.rst +++ b/docs/source/reference/modding/index.rst @@ -26,6 +26,7 @@ about creating new content for OpenMW, please refer to custom-models/index font sky-system + custom-shader-effects extended paths localisation diff --git a/files/shaders/lighting_util.glsl b/files/shaders/lighting_util.glsl index 30e3dbf63a..e27f1b04ae 100644 --- a/files/shaders/lighting_util.glsl +++ b/files/shaders/lighting_util.glsl @@ -1,4 +1,3 @@ -#if !@lightingMethodFFP float quickstep(float x) { x = clamp(x, 0.0, 1.0); @@ -6,7 +5,6 @@ float quickstep(float x) x = 1.0 - x*x; return x; } -#endif #if @lightingMethodUBO diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 6ebc7cbd62..265ed1513f 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -95,6 +95,7 @@ void main() #endif vec3 worldNormal = normalize(passNormal); + vec3 viewVec = normalize(passViewPos.xyz); #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); @@ -164,7 +165,6 @@ void main() #if @normalMap // if using normal map + env map, take advantage of per-pixel normals for envTexCoordGen - vec3 viewVec = normalize(passViewPos.xyz); vec3 r = reflect( viewVec, viewNormal ); float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); envTexCoordGen = vec2(r.x/m + 0.5, r.y/m + 0.5); @@ -226,13 +226,13 @@ void main() #if (!@normalMap && !@parallax && !@forcePPL) vec3 viewNormal = gl_NormalMatrix * worldNormal; #endif - gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), viewVec, shininess, matSpec) * shadowing; } gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos); #if !defined(FORCE_OPAQUE) && @softParticles - gl_FragData[0].a *= calcSoftParticleFade(); + gl_FragData[0].a *= calcSoftParticleFade(viewVec, viewNormal, passViewPos); #endif #if defined(FORCE_OPAQUE) && FORCE_OPAQUE diff --git a/files/shaders/softparticles.glsl b/files/shaders/softparticles.glsl index f2ed36a8ad..9a04c1f45e 100644 --- a/files/shaders/softparticles.glsl +++ b/files/shaders/softparticles.glsl @@ -1,6 +1,7 @@ uniform float near; uniform sampler2D opaqueDepthTex; uniform float particleSize; +uniform bool particleFade; float viewDepth(float depth) { @@ -10,21 +11,28 @@ float viewDepth(float depth) return (near * far) / ((far - near) * depth - far); } -float calcSoftParticleFade() +float calcSoftParticleFade(in vec3 viewDir, in vec3 viewNormal, in vec3 viewPos) { + float euclidianDepth = length(viewPos); + const float falloffMultiplier = 0.33; const float contrast = 1.30; vec2 screenCoords = gl_FragCoord.xy / screenRes; - float sceneDepth = viewDepth(texture2D(opaqueDepthTex, screenCoords).x); - float particleDepth = viewDepth(gl_FragCoord.z); + + float depth = texture2D(opaqueDepthTex, screenCoords).x; + + float sceneDepth = viewDepth(depth); + float particleDepth = passViewPos.z; float falloff = particleSize * falloffMultiplier; float delta = particleDepth - sceneDepth; - if (delta < 0.0) - discard; + const float nearMult = 300.0; + float viewBias = 1.0; + + if (particleFade) + viewBias = abs(dot(-viewDir, viewNormal) * quickstep(euclidianDepth / nearMult)); const float shift = 0.845; - - return shift * pow(clamp(delta/falloff, 0.0, 1.0), contrast); + return shift * pow(clamp(delta/falloff, 0.0, 1.0), contrast) * viewBias; }