1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-03-06 01:49:42 +00:00

Merge branch 'leave_butter_to_soften' into 'master'

Soft Particles (#6128)

See merge request OpenMW/openmw!980
This commit is contained in:
psi29a 2021-11-09 14:16:47 +00:00
commit 5836d0225f
16 changed files with 198 additions and 12 deletions

View file

@ -85,6 +85,7 @@
Feature #6017: Separate persistent and temporary cell references when saving
Feature #6032: Reverse-z depth buffer
Feature #6078: First person should not clear depth buffer
Feature #6128: Soft Particles
Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly
Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
Feature #6199: Support FBO Rendering

View file

@ -117,6 +117,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders");
loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
loadSettingBool(radialFogCheckBox, "radial fog", "Shaders");
loadSettingBool(softParticlesCheckBox, "soft particles", "Shaders");
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
@ -270,6 +271,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(autoUseTerrainSpecularMapsCheckBox, "auto use terrain specular maps", "Shaders");
saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
saveSettingBool(radialFogCheckBox, "radial fog", "Shaders");
saveSettingBool(softParticlesCheckBox, "soft particles", "Shaders");
saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");

View file

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

View file

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

View file

@ -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
@ -452,6 +463,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.";
@ -1156,6 +1168,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.

View file

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

View file

@ -430,6 +430,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
@ -892,6 +897,7 @@ namespace Resource
shaderVisitor->setSpecularMapPattern(mSpecularMapPattern);
shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps);
shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage);
shaderVisitor->setOpaqueDepthTex(mOpaqueDepthTex);
return shaderVisitor;
}
}

View file

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

View file

@ -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,27 @@ namespace Shader
updateAddedState(*writableUserData, addedState);
}
bool softParticles = false;
if (mOpaqueDepthTex)
{
auto partsys = dynamic_cast<osgParticle::ParticleSystem*>(&node);
if (partsys)
{
softParticles = true;
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);
}
}
defineMap["softParticles"] = softParticles ? "1" : "0";
std::string shaderPrefix;
if (!node.getUserValue("shaderPrefix", shaderPrefix))
shaderPrefix = mDefaultShaderPrefix;
@ -769,6 +793,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)

View file

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

View file

@ -260,7 +260,7 @@ aforementioned changes in visuals.
This setting has no effect if :ref:`lighting method` is 'legacy'.
antialias alpha test
---------------------------------------
--------------------
:Type: boolean
:Range: True/False
@ -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 particle effects. 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.

View file

@ -494,6 +494,9 @@ 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
# Soften intersection of blended particle systems with opaque geometry
soft particles = false
[Input]
# Capture control of the cursor prevent movement outside the window.

View file

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

View file

@ -80,6 +80,10 @@ varying vec3 passNormal;
#include "parallax.glsl"
#include "alpha.glsl"
#if @softParticles
#include "softparticles.glsl"
#endif
void main()
{
#if @diffuseMap
@ -220,6 +224,10 @@ void main()
#endif
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
#if @softParticles
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;

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

View file

@ -444,6 +444,16 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="softParticlesCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Soft Particles</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>