mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-06 06:45:35 +00:00
soft particles
This commit is contained in:
parent
a78dee740a
commit
8c3b00164e
13 changed files with 175 additions and 11 deletions
|
@ -93,6 +93,41 @@ namespace
|
|||
|
||||
MWRender::PostProcessor* mPostProcessor;
|
||||
};
|
||||
|
||||
// Copies the currently bound depth attachment to a new texture so drawables in transparent renderbin can safely sample from depth.
|
||||
class OpaqueDepthCopyCallback : public osgUtil::RenderBin::DrawCallback
|
||||
{
|
||||
public:
|
||||
OpaqueDepthCopyCallback(osg::ref_ptr<osg::Texture2D> opaqueDepthTex, osg::ref_ptr<osg::FrameBufferObject> sourceFbo)
|
||||
: mOpaqueDepthFbo(new osg::FrameBufferObject)
|
||||
, mSourceFbo(sourceFbo)
|
||||
, mOpaqueDepthTex(opaqueDepthTex)
|
||||
{
|
||||
mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER, osg::FrameBufferAttachment(opaqueDepthTex));
|
||||
}
|
||||
|
||||
void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override
|
||||
{
|
||||
if (bin->getStage()->getFrameBufferObject() == mSourceFbo)
|
||||
{
|
||||
osg::State& state = *renderInfo.getState();
|
||||
osg::GLExtensions* ext = state.get<osg::GLExtensions>();
|
||||
|
||||
mSourceFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER);
|
||||
mOpaqueDepthFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
|
||||
|
||||
ext->glBlitFramebuffer(0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), 0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
|
||||
mSourceFbo->apply(state);
|
||||
}
|
||||
|
||||
bin->drawImplementation(renderInfo, previous);
|
||||
}
|
||||
private:
|
||||
osg::ref_ptr<osg::FrameBufferObject> mOpaqueDepthFbo;
|
||||
osg::ref_ptr<osg::FrameBufferObject> mSourceFbo;
|
||||
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex;
|
||||
};
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
|
@ -103,7 +138,9 @@ namespace MWRender
|
|||
, mDepthFormat(GL_DEPTH_COMPONENT24)
|
||||
, mRendering(rendering)
|
||||
{
|
||||
if (!SceneUtil::getReverseZ())
|
||||
bool softParticles = Settings::Manager::getBool("soft particles", "Shaders");
|
||||
|
||||
if (!SceneUtil::getReverseZ() && !softParticles)
|
||||
return;
|
||||
|
||||
osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext();
|
||||
|
@ -124,17 +161,22 @@ namespace MWRender
|
|||
return;
|
||||
}
|
||||
|
||||
if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float"))
|
||||
mDepthFormat = GL_DEPTH_COMPONENT32F;
|
||||
else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float"))
|
||||
mDepthFormat = GL_DEPTH_COMPONENT32F_NV;
|
||||
else
|
||||
if (SceneUtil::getReverseZ())
|
||||
{
|
||||
// TODO: Once we have post-processing implemented we want to skip this return and continue with setup.
|
||||
// Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no
|
||||
// benefits if no floating point depth formats are supported.
|
||||
Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported.";
|
||||
return;
|
||||
if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float"))
|
||||
mDepthFormat = GL_DEPTH_COMPONENT32F;
|
||||
else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float"))
|
||||
mDepthFormat = GL_DEPTH_COMPONENT32F_NV;
|
||||
else
|
||||
{
|
||||
// TODO: Once we have post-processing implemented we want to skip this return and continue with setup.
|
||||
// Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no
|
||||
// benefits if no floating point depth formats are supported.
|
||||
Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported.";
|
||||
|
||||
if (!softParticles)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int width = viewer->getCamera()->getViewport()->width();
|
||||
|
@ -165,6 +207,12 @@ namespace MWRender
|
|||
mDepthTex->dirtyTextureObject();
|
||||
mSceneTex->dirtyTextureObject();
|
||||
|
||||
if (mOpaqueDepthTex)
|
||||
{
|
||||
mOpaqueDepthTex->setTextureSize(width, height);
|
||||
mOpaqueDepthTex->dirtyTextureObject();
|
||||
}
|
||||
|
||||
int samples = Settings::Manager::getInt("antialiasing", "Video");
|
||||
|
||||
mFbo = new osg::FrameBufferObject;
|
||||
|
@ -186,6 +234,9 @@ namespace MWRender
|
|||
if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY"))
|
||||
mFirstPersonDepthRBProxy = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples);
|
||||
|
||||
if (Settings::Manager::getBool("soft particles", "Shaders"))
|
||||
osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(new OpaqueDepthCopyCallback(mOpaqueDepthTex, mMsaaFbo ? mMsaaFbo : mFbo));
|
||||
|
||||
mViewer->getCamera()->resize(width, height);
|
||||
mHUDCamera->resize(width, height);
|
||||
mRendering.updateProjectionMatrix();
|
||||
|
@ -204,6 +255,12 @@ namespace MWRender
|
|||
mDepthTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
mDepthTex->setResizeNonPowerOfTwoHint(false);
|
||||
|
||||
if (Settings::Manager::getBool("soft particles", "Shaders"))
|
||||
{
|
||||
mOpaqueDepthTex = new osg::Texture2D(*mDepthTex);
|
||||
mOpaqueDepthTex->setName("opaqueTexMap");
|
||||
}
|
||||
|
||||
mSceneTex = new osg::Texture2D;
|
||||
mSceneTex->setTextureSize(width, height);
|
||||
mSceneTex->setSourceFormat(GL_RGB);
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace MWRender
|
|||
auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; }
|
||||
|
||||
int getDepthFormat() { return mDepthFormat; }
|
||||
osg::ref_ptr<osg::Texture2D> getOpaqueDepthTex() { return mOpaqueDepthTex; }
|
||||
|
||||
void resize(int width, int height);
|
||||
|
||||
|
@ -42,6 +43,7 @@ namespace MWRender
|
|||
|
||||
osg::ref_ptr<osg::Texture2D> mSceneTex;
|
||||
osg::ref_ptr<osg::Texture2D> mDepthTex;
|
||||
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex;
|
||||
|
||||
int mDepthFormat;
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ namespace MWRender
|
|||
stateset->addUniform(new osg::Uniform("linearFac", 0.f));
|
||||
stateset->addUniform(new osg::Uniform("near", 0.f));
|
||||
stateset->addUniform(new osg::Uniform("far", 0.f));
|
||||
stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{}));
|
||||
if (mUsePlayerUniforms)
|
||||
{
|
||||
stateset->addUniform(new osg::Uniform("windSpeed", 0.0f));
|
||||
|
@ -116,6 +117,10 @@ namespace MWRender
|
|||
if (uFar)
|
||||
uFar->set(mFar);
|
||||
|
||||
auto* uScreenRes = stateset->getUniform("screenRes");
|
||||
if (uScreenRes)
|
||||
uScreenRes->set(mScreenRes);
|
||||
|
||||
if (mUsePlayerUniforms)
|
||||
{
|
||||
auto* windSpeed = stateset->getUniform("windSpeed");
|
||||
|
@ -148,6 +153,11 @@ namespace MWRender
|
|||
mFar = far;
|
||||
}
|
||||
|
||||
void setScreenRes(float width, float height)
|
||||
{
|
||||
mScreenRes = osg::Vec2f(width, height);
|
||||
}
|
||||
|
||||
void setWindSpeed(float windSpeed)
|
||||
{
|
||||
mWindSpeed = windSpeed;
|
||||
|
@ -167,6 +177,7 @@ namespace MWRender
|
|||
bool mUsePlayerUniforms;
|
||||
float mWindSpeed;
|
||||
osg::Vec3f mPlayerPos;
|
||||
osg::Vec2f mScreenRes;
|
||||
};
|
||||
|
||||
class StateUpdater : public SceneUtil::StateSetUpdater
|
||||
|
@ -453,6 +464,7 @@ namespace MWRender
|
|||
|
||||
mPostProcessor = new PostProcessor(*this, viewer, mRootNode);
|
||||
resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat());
|
||||
resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex());
|
||||
|
||||
if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(mPostProcessor->getDepthFormat()))
|
||||
Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it.";
|
||||
|
@ -1166,6 +1178,7 @@ namespace MWRender
|
|||
|
||||
mSharedUniformStateUpdater->setNear(mNearClip);
|
||||
mSharedUniformStateUpdater->setFar(mViewDistance);
|
||||
mSharedUniformStateUpdater->setScreenRes(mViewer->getCamera()->getViewport()->width(), mViewer->getCamera()->getViewport()->height());
|
||||
|
||||
// Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear.
|
||||
// Limit FOV here just for sure, otherwise viewing distance can be too high.
|
||||
|
|
|
@ -1045,6 +1045,7 @@ namespace NifOsg
|
|||
void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags)
|
||||
{
|
||||
osg::ref_ptr<ParticleSystem> partsys (new ParticleSystem);
|
||||
partsys->getOrCreateStateSet();
|
||||
partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT);
|
||||
|
||||
const Nif::NiParticleSystemController* partctrl = nullptr;
|
||||
|
|
|
@ -429,6 +429,11 @@ namespace Resource
|
|||
mConvertAlphaTestToAlphaToCoverage = convert;
|
||||
}
|
||||
|
||||
void SceneManager::setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture)
|
||||
{
|
||||
mOpaqueDepthTex = texture;
|
||||
}
|
||||
|
||||
SceneManager::~SceneManager()
|
||||
{
|
||||
// this has to be defined in the .cpp file as we can't delete incomplete types
|
||||
|
@ -891,6 +896,7 @@ namespace Resource
|
|||
shaderVisitor->setSpecularMapPattern(mSpecularMapPattern);
|
||||
shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps);
|
||||
shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage);
|
||||
shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex);
|
||||
return shaderVisitor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <osg/ref_ptr>
|
||||
#include <osg/Node>
|
||||
#include <osg/Texture>
|
||||
#include <osg/Texture2D>
|
||||
|
||||
#include "resourcemanager.hpp"
|
||||
|
||||
|
@ -111,6 +112,8 @@ namespace Resource
|
|||
void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported);
|
||||
bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const;
|
||||
|
||||
void setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture);
|
||||
|
||||
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
|
||||
|
@ -209,6 +212,7 @@ namespace Resource
|
|||
SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods;
|
||||
bool mConvertAlphaTestToAlphaToCoverage;
|
||||
GLenum mDepthFormat;
|
||||
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex;
|
||||
|
||||
osg::ref_ptr<Resource::SharedStateManager> mSharedStateManager;
|
||||
mutable std::mutex mSharedStateMutex;
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <osg/Texture>
|
||||
#include <osg/ValueObject>
|
||||
|
||||
#include <osgParticle/ParticleSystem>
|
||||
|
||||
#include <osgUtil/TangentSpaceGenerator>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
@ -19,6 +21,7 @@
|
|||
#include <components/vfs/manager.hpp>
|
||||
#include <components/sceneutil/riggeometry.hpp>
|
||||
#include <components/sceneutil/morphgeometry.hpp>
|
||||
#include <components/sceneutil/util.hpp>
|
||||
|
||||
#include "removedalphafunc.hpp"
|
||||
#include "shadermanager.hpp"
|
||||
|
@ -554,6 +557,18 @@ namespace Shader
|
|||
updateAddedState(*writableUserData, addedState);
|
||||
}
|
||||
|
||||
if (auto partsys = dynamic_cast<osgParticle::ParticleSystem*>(&node))
|
||||
{
|
||||
writableStateSet->setDefine("SOFT_PARTICLES", "1", osg::StateAttribute::ON);
|
||||
|
||||
auto depth = SceneUtil::createDepth();
|
||||
depth->setWriteMask(false);
|
||||
writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||
writableStateSet->addUniform(new osg::Uniform("particleSize", partsys->getDefaultParticleTemplate().getSizeRange().maximum));
|
||||
writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2));
|
||||
writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON);
|
||||
}
|
||||
|
||||
std::string shaderPrefix;
|
||||
if (!node.getUserValue("shaderPrefix", shaderPrefix))
|
||||
shaderPrefix = mDefaultShaderPrefix;
|
||||
|
@ -769,6 +784,11 @@ namespace Shader
|
|||
mConvertAlphaTestToAlphaToCoverage = convert;
|
||||
}
|
||||
|
||||
void ShaderVisitor::setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture)
|
||||
{
|
||||
mOpaqueDepthTex = texture;
|
||||
}
|
||||
|
||||
ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets)
|
||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
, mAllowedToModifyStateSets(allowedToModifyStateSets)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <osg/NodeVisitor>
|
||||
#include <osg/Program>
|
||||
#include <osg/Texture2D>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
@ -45,6 +46,8 @@ namespace Shader
|
|||
|
||||
void setConvertAlphaTestToAlphaToCoverage(bool convert);
|
||||
|
||||
void setOpaqueDepthTex(osg::ref_ptr<osg::Texture2D> texture);
|
||||
|
||||
void apply(osg::Node& node) override;
|
||||
|
||||
void apply(osg::Drawable& drawable) override;
|
||||
|
@ -110,6 +113,7 @@ namespace Shader
|
|||
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);
|
||||
|
||||
osg::ref_ptr<const osg::Program> mProgramTemplate;
|
||||
osg::ref_ptr<osg::Texture2D> mOpaqueDepthTex;
|
||||
};
|
||||
|
||||
class ReinstateRemovedStateVisitor : public osg::NodeVisitor
|
||||
|
|
|
@ -269,3 +269,16 @@ antialias alpha test
|
|||
Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage when :ref:`antialiasing` is on.
|
||||
This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation.
|
||||
When MSAA is off, this setting will have no visible effect, but might have a performance cost.
|
||||
|
||||
soft particles
|
||||
------------------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: False
|
||||
|
||||
Enables soft particles for almost all particle effects, excluding precipitation.
|
||||
This technique softens the intersection between individual particles and other
|
||||
opaque geometry by blending between them. Note, this relies on overriding
|
||||
specific properties of particle systems that potentially differ from the source
|
||||
content, this setting may change the look of some particle systems.
|
||||
|
|
|
@ -494,6 +494,8 @@ minimum interior brightness = 0.08
|
|||
# When MSAA is off, this setting will have no visible effect, but might have a performance cost.
|
||||
antialias alpha test = false
|
||||
|
||||
soft particles = true
|
||||
|
||||
[Input]
|
||||
|
||||
# Capture control of the cursor prevent movement outside the window.
|
||||
|
|
|
@ -39,6 +39,7 @@ set(SHADER_FILES
|
|||
sky_vertex.glsl
|
||||
sky_fragment.glsl
|
||||
skypasses.glsl
|
||||
softparticles.glsl
|
||||
)
|
||||
|
||||
copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#version 120
|
||||
#pragma import_defines(FORCE_OPAQUE)
|
||||
#pragma import_defines(SOFT_PARTICLES)
|
||||
|
||||
#if @useUBO
|
||||
#extension GL_ARB_uniform_buffer_object : require
|
||||
|
@ -80,6 +81,10 @@ varying vec3 passNormal;
|
|||
#include "parallax.glsl"
|
||||
#include "alpha.glsl"
|
||||
|
||||
#if defined(SOFT_PARTICLES) && SOFT_PARTICLES
|
||||
#include "softparticles.glsl"
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
#if @diffuseMap
|
||||
|
@ -220,6 +225,10 @@ void main()
|
|||
#endif
|
||||
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
|
||||
|
||||
#if defined(SOFT_PARTICLES) && SOFT_PARTICLES
|
||||
gl_FragData[0].a *= calcSoftParticleFade();
|
||||
#endif
|
||||
|
||||
#if defined(FORCE_OPAQUE) && FORCE_OPAQUE
|
||||
// having testing & blending isn't enough - we need to write an opaque pixel to be opaque
|
||||
gl_FragData[0].a = 1.0;
|
||||
|
|
32
files/shaders/softparticles.glsl
Normal file
32
files/shaders/softparticles.glsl
Normal file
|
@ -0,0 +1,32 @@
|
|||
uniform float near;
|
||||
uniform float far;
|
||||
uniform sampler2D opaqueDepthTex;
|
||||
uniform vec2 screenRes;
|
||||
uniform float particleSize;
|
||||
|
||||
float viewDepth(float depth)
|
||||
{
|
||||
#if @reverseZ
|
||||
depth = 1.0 - depth;
|
||||
#endif
|
||||
return (near * far) / ((far - near) * depth - far);
|
||||
}
|
||||
|
||||
float calcSoftParticleFade()
|
||||
{
|
||||
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 falloff = particleSize * falloffMultiplier;
|
||||
float delta = particleDepth - sceneDepth;
|
||||
|
||||
if (delta < 0.0)
|
||||
discard;
|
||||
|
||||
const float shift = 0.845;
|
||||
|
||||
return shift * pow(clamp(delta/falloff, 0.0, 1.0), contrast);
|
||||
}
|
Loading…
Reference in a new issue