Merge branch 'soften_me_up_like_one_of_your_french_meshes' into 'master'

Allow soft particle effect on any NIF

See merge request OpenMW/openmw!2015
precompile_headers
psi29a 2 years ago
commit 56187ad977

@ -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<osgUtil::CullVisitor*>(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());
}

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

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

@ -3,4 +3,6 @@
namespace Misc
{
const std::string OsgUserValues::sFileHash = "fileHash";
const std::string OsgUserValues::sExtraData = "xData";
const std::string OsgUserValues::sXSoftEffect = "xSoftEffect";
}

@ -8,6 +8,8 @@ namespace Misc
struct OsgUserValues
{
static const std::string sFileHash;
static const std::string sExtraData;
static const std::string sXSoftEffect;
};
}

@ -614,17 +614,24 @@ namespace NifOsg
else if(e->recType == Nif::RC_NiStringExtraData)
{
const Nif::NiStringExtraData *sd = static_cast<const Nif::NiStringExtraData*>(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()));
}
}
}

@ -39,6 +39,7 @@
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/riggeometryosgaextension.hpp>
#include <components/sceneutil/extradata.hpp>
#include <components/shader/shadervisitor.hpp>
#include <components/shader/shadermanager.hpp>
@ -366,15 +367,13 @@ namespace Resource
return mForceShaders;
}
void SceneManager::recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate, bool disableSoftParticles)
void SceneManager::recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate)
{
osg::ref_ptr<Shader::ShaderVisitor> 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<osg::Texture> 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;
}

@ -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<osg::Node> node, const std::string& shaderPrefix = "objects", bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr, bool disableSoftParticles = false);
void recreateShaders(osg::ref_ptr<osg::Node> 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<osg::Texture> texturePing, osg::ref_ptr<osg::Texture> texturePong);
osg::ref_ptr<osg::Texture> 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

@ -0,0 +1,66 @@
#include "extradata.hpp"
#include <unordered_set>
#include <osg/Node>
#include <osg/ValueObject>
#include <osgParticle/ParticleSystem>
#include <yaml-cpp/yaml.h>
#include <components/misc/osguservalues.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/shader/shadermanager.hpp>
#include <components/serialization/osgyaml.hpp>
#include <components/debug/debuglog.hpp>
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<SceneUtil::AutoDepth> 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<std::string>();
if (key == "soft_effect")
{
auto size = it.second["size"].as<float>(45.f);
auto falloff = it.second["falloff"].as<bool>(false);
setupSoftEffect(node, size, falloff);
}
}
node.setUserValue(Misc::OsgUserValues::sExtraData, std::string{});
}
else if (osgParticle::ParticleSystem* partsys = dynamic_cast<osgParticle::ParticleSystem*>(&node))
{
setupSoftEffect(node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false);
}
traverse(node);
}
}

@ -0,0 +1,35 @@
#ifndef OPENMW_COMPONENTS_RESOURCE_EXTRADATA_H
#define OPENMW_COMPONENTS_RESOURCE_EXTRADATA_H
#include <array>
#include <osg/NodeVisitor>
#include <osg/StateAttribute>
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

@ -510,22 +510,31 @@ namespace Shader
program->addShader(linkedShader);
}
int ShaderManager::reserveGlobalTextureUnits(int count)
int ShaderManager::reserveGlobalTextureUnits(Slot slot)
{
int unit = mReservedTextureUnitsBySlot[static_cast<int>(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<int>(slot)] = unit;
return unit;
}
}

@ -5,6 +5,7 @@
#include <map>
#include <mutex>
#include <vector>
#include <array>
#include <osg/ref_ptr>
@ -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<osg::Shader> shader, const std::vector<std::string>& linkedShaderNames, const DefineMap& defines);
@ -89,6 +96,8 @@ namespace Shader
int mMaxTextureUnits = 0;
int mReservedTextureUnits = 0;
std::array<int, 2> mReservedTextureUnitsBySlot = {-1, -1};
};
bool parseForeachDirective(std::string& source, const std::string& templateName, size_t foundPos);

@ -20,6 +20,7 @@
#include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/osguservalues.hpp>
#include <components/stereo/stereomanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/vfs/manager.hpp>
@ -31,50 +32,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<osg::ref_ptr<osg::Texture>, 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<osg::ref_ptr<osg::Texture>, 2> mTextures;
int mUnit;
};
}
namespace Shader
{
/**
@ -201,7 +158,6 @@ namespace Shader
, mNormalHeight(false)
, mTexStageRequiringTangents(-1)
, mSoftParticles(false)
, mSoftParticleSize(0.f)
, mNode(nullptr)
{
}
@ -311,6 +267,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> addedState = getAddedState(*stateset);
@ -698,28 +658,7 @@ namespace Shader
updateRemovedState(*writableUserData, removedState);
}
if (reqs.mSoftParticles && mOpaqueDepthTex.front())
{
osg::ref_ptr<osg::Depth> 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<OpaqueDepthAttribute> 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);
@ -905,20 +844,12 @@ namespace Shader
void ShaderVisitor::apply(osg::Drawable& drawable)
{
auto partsys = dynamic_cast<osgParticle::ParticleSystem*>(&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);
}
@ -999,11 +930,6 @@ namespace Shader
mConvertAlphaTestToAlphaToCoverage = convert;
}
void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr<osg::Texture> texturePing, osg::ref_ptr<osg::Texture> texturePong)
{
mOpaqueDepthTex = { texturePing, texturePong };
}
ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mAllowedToModifyStateSets(allowedToModifyStateSets)

@ -48,8 +48,6 @@ namespace Shader
void setConvertAlphaTestToAlphaToCoverage(bool convert);
void setOpaqueDepthTex(osg::ref_ptr<osg::Texture> texturePing, osg::ref_ptr<osg::Texture> texturePong);
void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; }
void apply(osg::Node& node) override;
@ -111,7 +109,6 @@ namespace Shader
int mTexStageRequiringTangents;
bool mSoftParticles;
float mSoftParticleSize;
// the Node that requested these requirements
osg::Node* mNode;
@ -125,7 +122,6 @@ namespace Shader
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);
osg::ref_ptr<const osg::Program> mProgramTemplate;
std::array<osg::ref_ptr<osg::Texture>, 2> mOpaqueDepthTex;
};
class ReinstateRemovedStateVisitor : public osg::NodeVisitor

@ -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,
}
}
}

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

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

@ -99,6 +99,7 @@ void main()
#endif
vec3 worldNormal = normalize(passNormal);
vec3 viewVec = normalize(passViewPos.xyz);
#if @normalMap
vec4 normalTex = texture2D(normalMap, normalMapUV);
@ -168,7 +169,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);
@ -230,13 +230,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

@ -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;
const float shift = 0.845;
if (particleFade)
viewBias = abs(dot(-viewDir, viewNormal) * quickstep(euclidianDepth / nearMult));
return shift * pow(clamp(delta/falloff, 0.0, 1.0), contrast);
const float shift = 0.845;
return shift * pow(clamp(delta/falloff, 0.0, 1.0), contrast) * viewBias;
}

Loading…
Cancel
Save