1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2024-12-29 03:19:44 +00:00

Implement shader-based water ripples (feature 3537)

This commit is contained in:
Andrei Kortunov 2023-04-01 09:09:45 +04:00
parent 2493e79daa
commit e17281ac67
18 changed files with 784 additions and 147 deletions

View File

@ -51,6 +51,7 @@
Bug #7229: Error marker loading failure is not handled
Bug #7243: Get Skyrim.esm loading
Bug #7298: Water ripples from projectiles sometimes are not spawned
Feature #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics
Feature #6447: Add LOD support to Object Paging
Feature #6726: Lua API for creating new objects

View File

@ -23,7 +23,7 @@ add_openmw_dir (mwrender
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover
postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode precipitationocclusion
postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode precipitationocclusion ripples
)
add_openmw_dir (mwinput

View File

@ -154,12 +154,11 @@ namespace MWRender
class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater
{
public:
SharedUniformStateUpdater(bool usePlayerUniforms)
SharedUniformStateUpdater()
: mNear(0.f)
, mFar(0.f)
, mWindSpeed(0.f)
, mSkyBlendingStartCoef(Settings::Manager::getFloat("sky blending start", "Fog"))
, mUsePlayerUniforms(usePlayerUniforms)
{
}
@ -170,12 +169,8 @@ namespace MWRender
stateset->addUniform(new osg::Uniform("skyBlendingStart", 0.f));
stateset->addUniform(new osg::Uniform("screenRes", osg::Vec2f{}));
stateset->addUniform(new osg::Uniform("isReflection", false));
if (mUsePlayerUniforms)
{
stateset->addUniform(new osg::Uniform("windSpeed", 0.0f));
stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)));
}
stateset->addUniform(new osg::Uniform("windSpeed", 0.0f));
stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)));
}
void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override
@ -184,12 +179,8 @@ namespace MWRender
stateset->getUniform("far")->set(mFar);
stateset->getUniform("skyBlendingStart")->set(mFar * mSkyBlendingStartCoef);
stateset->getUniform("screenRes")->set(mScreenRes);
if (mUsePlayerUniforms)
{
stateset->getUniform("windSpeed")->set(mWindSpeed);
stateset->getUniform("playerPos")->set(mPlayerPos);
}
stateset->getUniform("windSpeed")->set(mWindSpeed);
stateset->getUniform("playerPos")->set(mPlayerPos);
}
void setNear(float near) { mNear = near; }
@ -207,7 +198,6 @@ namespace MWRender
float mFar;
float mWindSpeed;
float mSkyBlendingStartCoef;
bool mUsePlayerUniforms;
osg::Vec3f mPlayerPos;
osg::Vec2f mScreenRes;
};
@ -512,7 +502,7 @@ namespace MWRender
mStateUpdater = new StateUpdater;
sceneRoot->addUpdateCallback(mStateUpdater);
mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover);
mSharedUniformStateUpdater = new SharedUniformStateUpdater();
rootNode->addUpdateCallback(mSharedUniformStateUpdater);
mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(mResourceSystem->getSceneManager());
@ -891,11 +881,11 @@ namespace MWRender
float rainIntensity = mSky->getPrecipitationAlpha();
mWater->setRainIntensity(rainIntensity);
mWater->update(dt, paused);
if (!paused)
{
mEffectManager->update(dt);
mSky->update(dt);
mWater->update(dt);
const MWWorld::Ptr& player = mPlayerAnimation->getPtr();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());

View File

@ -0,0 +1,306 @@
#include "ripples.hpp"
#include <osg/Geometry>
#include <osg/Texture2D>
#include <osgUtil/CullVisitor>
#include <components/debug/debuglog.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/color.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/shader/shadermanager.hpp>
#include "../mwworld/ptr.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "vismask.hpp"
namespace MWRender
{
RipplesSurface::RipplesSurface(Resource::ResourceSystem* resourceSystem)
: osg::Geometry()
, mResourceSystem(resourceSystem)
{
setUseDisplayList(false);
setUseVertexBufferObjects(true);
osg::ref_ptr<osg::Vec3Array> verts = new osg::Vec3Array;
verts->push_back(osg::Vec3f(-1, -1, 0));
verts->push_back(osg::Vec3f(-1, 3, 0));
verts->push_back(osg::Vec3f(3, -1, 0));
setVertexArray(verts);
setCullingActive(false);
addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3));
#ifdef __APPLE__
// we can not trust Apple :)
mUseCompute = false;
#else
constexpr float minimumGLVersionRequiredForCompute = 4.4;
osg::GLExtensions* exts = osg::GLExtensions::Get(0, false);
mUseCompute = exts->glVersion >= minimumGLVersionRequiredForCompute
&& exts->glslLanguageVersion >= minimumGLVersionRequiredForCompute;
#endif
if (mUseCompute)
Log(Debug::Info) << "Initialized compute shader pipeline for water ripples";
else
Log(Debug::Info) << "Initialized fallback fragment shader pipeline for water ripples";
for (size_t i = 0; i < mState.size(); ++i)
{
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
// bindings are set in the compute shader
if (!mUseCompute)
stateset->addUniform(new osg::Uniform("imageIn", 0));
stateset->addUniform(new osg::Uniform("offset", osg::Vec2f()));
stateset->addUniform(new osg::Uniform("positionCount", 0));
stateset->addUniform(new osg::Uniform(osg::Uniform::Type::FLOAT_VEC3, "positions", 100));
stateset->setAttributeAndModes(new osg::Viewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize));
mState[i].mStateset = stateset;
}
for (size_t i = 0; i < mTextures.size(); ++i)
{
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setSourceFormat(GL_RGBA);
texture->setSourceType(GL_FLOAT);
texture->setInternalFormat(GL_RGBA16F);
texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture::LINEAR);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER);
texture->setBorderColor(osg::Vec4(0, 0, 0, 0));
texture->setTextureSize(mRTTSize, mRTTSize);
mTextures[i] = texture;
mFBOs[i] = new osg::FrameBufferObject;
mFBOs[i]->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mTextures[i]));
}
if (mUseCompute)
setupComputePipeline();
else
setupFragmentPipeline();
setCullCallback(new osg::NodeCallback);
setUpdateCallback(new osg::NodeCallback);
}
void RipplesSurface::setupFragmentPipeline()
{
auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager();
Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(mRTTSize) + ".0" } };
osg::ref_ptr<osg::Shader> vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX);
mProgramBlobber = shaderManager.getProgram(
vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT));
mProgramSimulation = shaderManager.getProgram(
vertex, shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT));
}
void RipplesSurface::setupComputePipeline()
{
auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager();
mProgramBlobber = shaderManager.getProgram(
nullptr, shaderManager.getShader("core/ripples_blobber.comp", {}, osg::Shader::COMPUTE));
mProgramSimulation = shaderManager.getProgram(
nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE));
}
void RipplesSurface::traverse(osg::NodeVisitor& nv)
{
if (!nv.getFrameStamp())
return;
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
{
size_t frameId = nv.getFrameStamp()->getFrameNumber() % 2;
const ESM::Position& player = MWMechanics::getPlayer().getRefData().getPosition();
mCurrentPlayerPos = osg::Vec2f(
std::floor(player.pos[0] / mWorldScaleFactor), std::floor(player.pos[1] / mWorldScaleFactor));
osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos;
mLastPlayerPos = mCurrentPlayerPos;
mState[frameId].mPaused = mPaused;
mState[frameId].mOffset = offset;
mState[frameId].mStateset->getUniform("positionCount")->set(static_cast<int>(mPositionCount));
mState[frameId].mStateset->getUniform("offset")->set(offset);
auto* positions = mState[frameId].mStateset->getUniform("positions");
for (size_t i = 0; i < mPositionCount; ++i)
{
osg::Vec3f pos = mPositions[i]
- osg::Vec3f(
mCurrentPlayerPos.x() * mWorldScaleFactor, mCurrentPlayerPos.y() * mWorldScaleFactor, 0.0)
+ osg::Vec3f(mRTTSize * mWorldScaleFactor / 2, mRTTSize * mWorldScaleFactor / 2, 0.0);
pos /= mWorldScaleFactor;
positions->setElement(i, pos);
}
positions->dirty();
mPositionCount = 0;
}
osg::Geometry::traverse(nv);
}
void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const
{
osg::State& state = *renderInfo.getState();
osg::GLExtensions& ext = *state.get<osg::GLExtensions>();
size_t contextID = state.getContextID();
size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2;
const State& frameState = mState[currentFrame];
if (frameState.mPaused)
{
return;
}
auto bindImage = [contextID, &state, &ext](osg::Texture2D* texture, GLuint index, GLenum access) {
osg::Texture::TextureObject* to = texture->getTextureObject(contextID);
if (!to || texture->isDirty(contextID))
{
state.applyTextureAttribute(index, texture);
to = texture->getTextureObject(contextID);
}
ext.glBindImageTexture(index, to->id(), 0, GL_FALSE, 0, access, GL_RGBA16F);
};
// Run simulation at a fixed rate independent on current FPS
// FIXME: when we skip frames we need to preserve positions. this doesn't work now
size_t ticks = 1;
// float referenceTime = state.getFrameStamp()->getReferenceTime();
// float frameTime = (mLastFrameTime != 0.0) ? referenceTime - mLastFrameTime : 0.0;
// frameTime = std::min(frameTime, 0.5f);
// mLastFrameTime = referenceTime;
// constexpr float rate = 60.0;
// constexpr float waveStep = 1.0 / rate;
// mRemainingWaveTime += frameTime;
// ticks = mRemainingWaveTime / waveStep;
// mRemainingWaveTime -= ticks * waveStep;
// PASS: Blot in all ripple spawners
mProgramBlobber->apply(state);
state.apply(frameState.mStateset);
for (size_t i = 0; i < ticks; i++)
{
if (mUseCompute)
{
bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB);
bindImage(mTextures[0], 1, GL_READ_ONLY_ARB);
ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1);
ext.glMemoryBarrier(GL_ALL_BARRIER_BITS);
}
else
{
mFBOs[1]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
state.applyTextureAttribute(0, mTextures[0]);
osg::Geometry::drawImplementation(renderInfo);
}
}
// PASS: Wave simulation
mProgramSimulation->apply(state);
state.apply(frameState.mStateset);
for (size_t i = 0; i < ticks; i++)
{
if (mUseCompute)
{
bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB);
bindImage(mTextures[1], 1, GL_READ_ONLY_ARB);
ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1);
ext.glMemoryBarrier(GL_ALL_BARRIER_BITS);
}
else
{
mFBOs[0]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
state.applyTextureAttribute(0, mTextures[1]);
osg::Geometry::drawImplementation(renderInfo);
}
}
}
osg::Texture* RipplesSurface::getColorTexture() const
{
return mTextures[0];
}
void RipplesSurface::emit(const osg::Vec3f pos, float sizeInCellUnits)
{
// Emitted positions are reset every frame, don't bother wrapping around when out of buffer space
if (mPositionCount >= mPositions.size())
{
return;
}
mPositions[mPositionCount] = osg::Vec3f(pos.x(), pos.y(), sizeInCellUnits);
mPositionCount++;
}
void RipplesSurface::releaseGLObjects(osg::State* state) const
{
for (const auto& tex : mTextures)
tex->releaseGLObjects(state);
for (const auto& fbo : mFBOs)
fbo->releaseGLObjects(state);
if (mProgramBlobber)
mProgramBlobber->releaseGLObjects(state);
if (mProgramSimulation)
mProgramSimulation->releaseGLObjects(state);
}
Ripples::Ripples(Resource::ResourceSystem* resourceSystem)
: osg::Camera()
, mRipples(new RipplesSurface(resourceSystem))
{
getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
setRenderOrder(osg::Camera::PRE_RENDER);
setReferenceFrame(osg::Camera::ABSOLUTE_RF);
setNodeMask(Mask_RenderToTexture);
setClearMask(GL_NONE);
setViewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize);
addChild(mRipples);
setCullingActive(false);
setImplicitBufferAttachmentMask(0, 0);
}
osg::Texture* Ripples::getColorTexture() const
{
return mRipples->getColorTexture();
}
void Ripples::emit(const osg::Vec3f pos, float sizeInCellUnits)
{
mRipples->emit(pos, sizeInCellUnits);
}
void Ripples::setPaused(bool paused)
{
mRipples->setPaused(paused);
}
}

View File

@ -0,0 +1,103 @@
#ifndef OPENMW_MWRENDER_RIPPLES_H
#define OPENMW_MWRENDER_RIPPLES_H
#include <array>
#include <osg/Camera>
#include <osg/Geometry>
#include <components/sceneutil/rtt.hpp>
#include <components/sceneutil/statesetupdater.hpp>
namespace Resource
{
class ResourceSystem;
}
namespace osg
{
class Camera;
class Geometry;
class Program;
class Texture;
class StateSet;
class NodeVisitor;
class Texture;
class Texture2D;
class FrameBufferObject;
}
namespace MWRender
{
class RipplesSurface : public osg::Geometry
{
public:
RipplesSurface(Resource::ResourceSystem* resourceSystem);
osg::Texture* getColorTexture() const;
void emit(const osg::Vec3f pos, float sizeInCellUnits);
void drawImplementation(osg::RenderInfo& renderInfo) const override;
void setPaused(bool paused) { mPaused = paused; }
void traverse(osg::NodeVisitor& nv) override;
void releaseGLObjects(osg::State* state) const override;
static constexpr size_t mRTTSize = 1024;
// e.g. texel to cell unit ratio
static constexpr float mWorldScaleFactor = 2.5;
Resource::ResourceSystem* mResourceSystem;
struct State
{
osg::Vec2f mOffset;
osg::ref_ptr<osg::StateSet> mStateset;
bool mPaused = true;
};
size_t mPositionCount = 0;
std::array<osg::Vec3f, 100> mPositions;
std::array<State, 2> mState;
private:
void setupFragmentPipeline();
void setupComputePipeline();
osg::Vec2f mCurrentPlayerPos;
osg::Vec2f mLastPlayerPos;
std::array<osg::ref_ptr<osg::Texture2D>, 2> mTextures;
std::array<osg::ref_ptr<osg::FrameBufferObject>, 2> mFBOs;
osg::ref_ptr<osg::Program> mProgramBlobber;
osg::ref_ptr<osg::Program> mProgramSimulation;
bool mPaused = false;
bool mUseCompute = false;
// Read/written in draw thread only
mutable float mRemainingWaveTime = 0;
mutable double mLastFrameTime = 0;
};
class Ripples : public osg::Camera
{
public:
Ripples(Resource::ResourceSystem* resourceSystem);
osg::Texture* getColorTexture() const;
void emit(const osg::Vec3f pos, float sizeInCellUnits);
void setPaused(bool paused);
osg::ref_ptr<RipplesSurface> mRipples;
};
}
#endif

View File

@ -142,7 +142,16 @@ namespace MWRender
|| world->isWalkingOnWater(ptr);
if (!shouldEmit)
{
emitter.mTimer = 0.f;
}
else if (mRipples)
{
// Ripple simulation needs to continously apply impulses to keep simulation alive.
// Adding a timer delay will introduce many smaller ripples around actor instead of a smooth wake
currentPos.z() = mParticleNode->getPosition().z();
emitRipple(currentPos);
}
else if (emitter.mTimer <= 0.f || (currentPos - emitter.mLastEmitPosition).length() > 10)
{
emitter.mLastEmitPosition = currentPos;
@ -210,10 +219,18 @@ namespace MWRender
{
if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20)
{
osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex());
osgParticle::Particle* p = mParticleSystem->createParticle(nullptr);
p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f));
p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI));
if (mRipples)
{
constexpr float particleRippleSizeInUnits = 12.f;
mRipples->emit(osg::Vec3f(pos.x(), pos.y(), 0.f), particleRippleSizeInUnits);
}
else
{
osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex());
osgParticle::Particle* p = mParticleSystem->createParticle(nullptr);
p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f));
p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI));
}
}
}

View File

@ -5,6 +5,8 @@
#include "../mwworld/ptr.hpp"
#include "ripples.hpp"
namespace osg
{
class Group;
@ -61,6 +63,8 @@ namespace MWRender
/// Remove all active ripples
void clear();
void setRipples(Ripples* ripples) { mRipples = ripples; }
private:
osg::ref_ptr<osg::Group> mParent;
@ -68,6 +72,8 @@ namespace MWRender
osg::ref_ptr<osg::PositionAttitudeTransform> mParticleNode;
std::vector<Emitter> mEmitters;
Ripples* mRipples = nullptr;
};
}

View File

@ -38,6 +38,7 @@
#include "../mwworld/cellstore.hpp"
#include "renderbin.hpp"
#include "ripples.hpp"
#include "ripplesimulation.hpp"
#include "vismask.hpp"
@ -510,6 +511,12 @@ namespace MWRender
mParent->removeChild(mRefraction);
mRefraction = nullptr;
}
if (mRipples)
{
mParent->removeChild(mRipples);
mRipples = nullptr;
mSimulation->setRipples(nullptr);
}
mWaterNode->setStateSet(nullptr);
mWaterGeom->setStateSet(nullptr);
@ -536,9 +543,13 @@ namespace MWRender
mParent->addChild(mRefraction);
}
mRipples = new Ripples(mResourceSystem);
mSimulation->setRipples(mRipples);
mParent->addChild(mRipples);
showWorld(mShowWorld);
createShaderWaterStateSet(mWaterNode, mReflection, mRefraction);
createShaderWaterStateSet(mWaterNode);
}
else
createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha"));
@ -608,11 +619,12 @@ namespace MWRender
class ShaderWaterStateSetUpdater : public SceneUtil::StateSetUpdater
{
public:
ShaderWaterStateSetUpdater(Water* water, Reflection* reflection, Refraction* refraction,
ShaderWaterStateSetUpdater(Water* water, Reflection* reflection, Refraction* refraction, Ripples* ripples,
osg::ref_ptr<osg::Program> program, osg::ref_ptr<osg::Texture2D> normalMap)
: mWater(water)
, mReflection(reflection)
, mRefraction(refraction)
, mRipples(ripples)
, mProgram(program)
, mNormalMap(normalMap)
{
@ -640,6 +652,10 @@ namespace MWRender
depth->setWriteMask(false);
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
}
if (mRipples)
{
stateset->addUniform(new osg::Uniform("rippleMap", 4));
}
stateset->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWater->getPosition())));
}
@ -653,6 +669,10 @@ namespace MWRender
stateset->setTextureAttributeAndModes(2, mRefraction->getColorTexture(cv), osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(3, mRefraction->getDepthTexture(cv), osg::StateAttribute::ON);
}
if (mRipples)
{
stateset->setTextureAttributeAndModes(4, mRipples->getColorTexture(), osg::StateAttribute::ON);
}
stateset->getUniform("nodePosition")->set(osg::Vec3f(mWater->getPosition()));
}
@ -660,17 +680,20 @@ namespace MWRender
Water* mWater;
Reflection* mReflection;
Refraction* mRefraction;
Ripples* mRipples;
osg::ref_ptr<osg::Program> mProgram;
osg::ref_ptr<osg::Texture2D> mNormalMap;
};
void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction)
void Water::createShaderWaterStateSet(osg::Node* node)
{
// use a define map to conditionally compile the shader
std::map<std::string, std::string> defineMap;
defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0");
const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2);
defineMap["rain_ripple_detail"] = std::to_string(rippleDetail);
defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::mWorldScaleFactor);
defineMap["ripple_map_size"] = std::to_string(RipplesSurface::mRTTSize) + ".0";
Stereo::Manager::instance().shaderStereoDefines(defineMap);
@ -691,7 +714,7 @@ namespace MWRender
node->setUpdateCallback(mRainIntensityUpdater);
mShaderWaterStateSetUpdater
= new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, program, normalMap);
= new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, mRipples, program, normalMap);
node->addCullCallback(mShaderWaterStateSetUpdater);
}
@ -714,6 +737,12 @@ namespace MWRender
mParent->removeChild(mRefraction);
mRefraction = nullptr;
}
if (mRipples)
{
mParent->removeChild(mRipples);
mRipples = nullptr;
mSimulation->setRipples(nullptr);
}
}
void Water::listAssetsToPreload(std::vector<std::string>& textures)
@ -775,9 +804,17 @@ namespace MWRender
mRainIntensityUpdater->setRainIntensity(rainIntensity);
}
void Water::update(float dt)
void Water::update(float dt, bool paused)
{
mSimulation->update(dt);
if (!paused)
{
mSimulation->update(dt);
}
if (mRipples)
{
mRipples->setPaused(paused);
}
}
void Water::updateVisible()
@ -788,6 +825,8 @@ namespace MWRender
mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0u);
if (mReflection)
mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0u);
if (mRipples)
mRipples->setNodeMask(visible ? Mask_RenderToTexture : 0u);
}
bool Water::toggle()

View File

@ -47,6 +47,7 @@ namespace MWRender
class Reflection;
class RippleSimulation;
class RainIntensityUpdater;
class Ripples;
/// Water rendering
class Water
@ -64,6 +65,7 @@ namespace MWRender
osg::ref_ptr<Refraction> mRefraction;
osg::ref_ptr<Reflection> mReflection;
osg::ref_ptr<Ripples> mRipples;
bool mEnabled;
bool mToggled;
@ -79,9 +81,7 @@ namespace MWRender
void createSimpleWaterStateSet(osg::Node* node, float alpha);
/// @param reflection the reflection camera (required)
/// @param refraction the refraction camera (optional)
void createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction);
void createShaderWaterStateSet(osg::Node* node);
void updateWaterMaterial();
@ -114,7 +114,7 @@ namespace MWRender
void setHeight(const float height);
void setRainIntensity(const float rainIntensity);
void update(float dt);
void update(float dt, bool paused);
osg::Node* getReflectionNode();
osg::Node* getRefractionNode();

View File

@ -8,6 +8,7 @@ set(DDIRRELATIVE resources/shaders)
set(SHADER_FILES
lib/water/fresnel.glsl
lib/water/rain_ripples.glsl
lib/water/ripples.glsl
lib/view/depth.glsl
lib/luminance/constants.glsl
@ -26,6 +27,8 @@ set(SHADER_FILES
lib/sky/passes.glsl
lib/material/parallax.glsl
lib/material/alpha.glsl
compatibility/ripples_blobber.frag
compatibility/ripples_simulate.frag
compatibility/fog.glsl
compatibility/groundcover.vert
compatibility/groundcover.frag
@ -60,6 +63,8 @@ set(SHADER_FILES
compatibility/bs/nolighting.frag
compatibility/luminance/resolve.frag
compatibility/luminance/luminance.frag
core/ripples_blobber.comp
core/ripples_simulate.comp
core/gui.frag
core/gui.vert
)

View File

@ -0,0 +1,27 @@
#version 120
uniform sampler2D imageIn;
#define MAX_POSITIONS 100
uniform vec3 positions[MAX_POSITIONS];
uniform int positionCount;
uniform float osg_SimulationTime;
uniform vec2 offset;
#include "lib/water/ripples.glsl"
void main()
{
vec2 uv = (gl_FragCoord.xy + offset) / @ripple_map_size;
vec4 color = texture2D(imageIn, uv);
float wavesizeMultiplier = getTemporalWaveSizeMultiplier(osg_SimulationTime);
for (int i = 0; i < positionCount; ++i) {
float wavesize = wavesizeMultiplier * positions[i].z;
float displace = clamp(2.0 * abs(length((positions[i].xy + offset) - gl_FragCoord.xy) / wavesize - 1.0), 0.0, 1.0);
color.rg = mix(vec2(-1.0), color.rg, displace);
}
gl_FragColor = color;
}

View File

@ -0,0 +1,34 @@
#version 120
uniform sampler2D imageIn;
#include "lib/water/ripples.glsl"
void main()
{
vec2 uv = gl_FragCoord.xy / @ripple_map_size;
float pixelSize = 1.0 / @ripple_map_size;
float oneOffset = pixelSize;
float oneAndHalfOffset = 1.5 * pixelSize;
vec4 n = vec4(
texture2D(imageIn, uv + vec2(oneOffset, 0.0)).r,
texture2D(imageIn, uv + vec2(-oneOffset, 0.0)).r,
texture2D(imageIn, uv + vec2(0.0, oneOffset)).r,
texture2D(imageIn, uv + vec2(0.0, -oneOffset)).r
);
vec4 n2 = vec4(
texture2D(imageIn, uv + vec2(oneAndHalfOffset, 0.0)).r,
texture2D(imageIn, uv + vec2(-oneAndHalfOffset, 0.0)).r,
texture2D(imageIn, uv + vec2(0.0, oneAndHalfOffset)).r,
texture2D(imageIn, uv + vec2(0.0, -oneAndHalfOffset)).r
);
vec4 color = texture2D(imageIn, uv);
gl_FragColor = applySprings(color, n, n2);
}

View File

@ -62,6 +62,13 @@ vec2 normalCoords(vec2 uv, float scale, float speed, float time, float timer1, f
return uv * (WAVE_SCALE * scale) + WIND_DIR * time * (WIND_SPEED * speed) -(previousNormal.xy/previousNormal.zz) * WAVE_CHOPPYNESS + vec2(time * timer1,time * timer2);
}
uniform sampler2D rippleMap;
uniform vec3 playerPos;
varying vec3 worldPos;
varying vec2 rippleMapUV;
varying vec4 position;
varying float linearDepth;
@ -71,7 +78,6 @@ uniform float osg_SimulationTime;
uniform float near;
uniform float far;
uniform vec3 nodePosition;
uniform float rainIntensity;
@ -83,12 +89,11 @@ uniform vec2 screenRes;
#include "lib/light/lighting.glsl"
#include "fog.glsl"
#include "lib/water/fresnel.glsl"
#include "lib/water/ripples.glsl"
#include "lib/water/rain_ripples.glsl"
#include "lib/view/depth.glsl"
void main(void)
{
vec3 worldPos = position.xyz + nodePosition.xyz;
vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0;
UV.y *= -1.0;
@ -114,6 +119,11 @@ void main(void)
vec3 rippleAdd = rainRipple.xyz * 10.0;
float distToCenter = length(rippleMapUV - vec2(0.5));
float blendClose = smoothstep(0.001, 0.02, distToCenter);
float blendFar = 1.0 - smoothstep(0.3, 0.4, distToCenter);
rippleAdd += vec3(texture2D(rippleMap, rippleMapUV).ba * blendFar * blendClose, 0.0);
vec2 bigWaves = vec2(BIG_WAVES_X,BIG_WAVES_Y);
vec2 midWaves = mix(vec2(MID_WAVES_X,MID_WAVES_Y),vec2(MID_WAVES_RAIN_X,MID_WAVES_RAIN_Y),rainIntensity);
vec2 smallWaves = mix(vec2(SMALL_WAVES_X,SMALL_WAVES_Y),vec2(SMALL_WAVES_RAIN_X,SMALL_WAVES_RAIN_Y),rainIntensity);

View File

@ -8,12 +8,21 @@ varying float linearDepth;
#include "shadows_vertex.glsl"
#include "lib/view/depth.glsl"
uniform vec3 nodePosition;
uniform vec3 playerPos;
varying vec3 worldPos;
varying vec2 rippleMapUV;
void main(void)
{
gl_Position = modelToClip(gl_Vertex);
position = gl_Vertex;
worldPos = position.xyz + nodePosition.xyz;
rippleMapUV = (worldPos.xy - playerPos.xy + (@ripple_map_size * @ripple_map_world_scale / 2.0)) / @ripple_map_size / @ripple_map_world_scale;
vec4 viewPos = modelToView(gl_Vertex);
linearDepth = getLinearDepth(gl_Position.z, viewPos.z);

View File

@ -0,0 +1,30 @@
#version 440 core
layout (binding = 0, rgba16f) restrict writeonly uniform image2D imageOut;
layout (binding = 1, rgba16f) restrict readonly uniform image2D imageIn;
layout (local_size_x=16, local_size_y=16) in;
#define MAX_POSITIONS 100
uniform vec3 positions[MAX_POSITIONS];
uniform int positionCount;
uniform float osg_SimulationTime;
uniform vec2 offset;
#include "lib/water/ripples.glsl"
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy + offset);
vec4 color = imageLoad(imageIn, texel);
float wavesizeMultiplier = getTemporalWaveSizeMultiplier(osg_SimulationTime);
for (int i = 0; i < positionCount; ++i) {
float wavesize = wavesizeMultiplier * positions[i].z;
float displace = clamp(2.0 * abs(length((positions[i].xy + offset) - vec2(gl_GlobalInvocationID.xy)) / wavesize - 1.0), 0.0, 1.0);
color.rg = mix(vec2(-1.0), color.rg, displace);
}
imageStore(imageOut, ivec2(gl_GlobalInvocationID.xy), color);
}

View File

@ -0,0 +1,31 @@
#version 440 core
layout (binding = 0, rgba16f) restrict writeonly uniform image2D imageOut;
layout (binding = 1, rgba16f) restrict readonly uniform image2D imageIn;
layout (local_size_x=16, local_size_y=16) in;
#include "lib/water/ripples.glsl"
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec4 n = vec4(
imageLoad(imageIn, texel + ivec2(1, 0)).r,
imageLoad(imageIn, texel + ivec2(-1, 0)).r,
imageLoad(imageIn, texel + ivec2(0, 1)).r,
imageLoad(imageIn, texel + ivec2(0, -1)).r
);
vec4 n2 = vec4(
imageLoad(imageIn, texel + ivec2(2, 0)).r,
imageLoad(imageIn, texel + ivec2(-2, 0)).r,
imageLoad(imageIn, texel + ivec2(0, 2)).r,
imageLoad(imageIn, texel + ivec2(0, -2)).r
);
vec4 color = imageLoad(imageIn, texel);
imageStore(imageOut, texel, applySprings(color, n, n2));
}

View File

@ -0,0 +1,126 @@
#ifndef LIB_WATER_RIPPLES
#define LIB_WATER_RIPPLES
#define RAIN_RIPPLE_DETAIL @rain_ripple_detail
const float RAIN_RIPPLE_GAPS = 10.0;
const float RAIN_RIPPLE_RADIUS = 0.2;
float scramble(float x, float z)
{
return fract(pow(fract(x)*3.0+1.0, z));
}
vec2 randOffset(vec2 c, float time)
{
time = fract(time/1000.0);
c = vec2(c.x * c.y / 8.0 + c.y * 0.3 + c.x * 0.2,
c.x * c.y / 14.0 + c.y * 0.5 + c.x * 0.7);
c.x *= scramble(scramble(time + c.x/1000.0, 4.0), 3.0) + 1.0;
c.y *= scramble(scramble(time + c.y/1000.0, 3.5), 3.0) + 1.0;
return fract(c);
}
float randPhase(vec2 c)
{
return fract((c.x * c.y) / (c.x + c.y + 0.1));
}
float blip(float x)
{
x = max(0.0, 1.0-x*x);
return x*x*x;
}
float blipDerivative(float x)
{
x = clamp(x, -1.0, 1.0);
float n = x*x-1.0;
return -6.0*x*n*n;
}
const float RAIN_RING_TIME_OFFSET = 1.0/6.0;
vec4 circle(vec2 coords, vec2 corner, float adjusted_time)
{
vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(corner, floor(adjusted_time)) - 1.0);
float phase = fract(adjusted_time);
vec2 toCenter = coords - center;
float r = RAIN_RIPPLE_RADIUS;
float d = length(toCenter);
float ringfollower = (phase-d/r)/RAIN_RING_TIME_OFFSET-1.0; // -1.0 ~ +1.0 cover the breadth of the ripple's ring
#if RAIN_RIPPLE_DETAIL > 0
// normal mapped ripples
if(ringfollower < -1.0 || ringfollower > 1.0)
return vec4(0.0);
if(d > 1.0) // normalize center direction vector, but not for near-center ripples
toCenter /= d;
float height = blip(ringfollower*2.0+0.5); // brighten up outer edge of ring; for fake specularity
float range_limit = blip(min(0.0, ringfollower));
float energy = 1.0-phase;
vec2 normal2d = -toCenter*blipDerivative(ringfollower)*5.0;
vec3 normal = vec3(normal2d, 0.5);
vec4 ret = vec4(normal, height);
ret.xyw *= energy*energy;
// do energy adjustment here rather than later, so that we can use the w component for fake specularity
ret.xyz = normalize(ret.xyz) * energy*range_limit;
ret.z *= range_limit;
return ret;
#else
// ring-only ripples
if(ringfollower < -1.0 || ringfollower > 0.5)
return vec4(0.0);
float energy = 1.0-phase;
float height = blip(ringfollower*2.0+0.5)*energy*energy; // fake specularity
return vec4(0.0, 0.0, 0.0, height);
#endif
}
vec4 rain(vec2 uv, float time)
{
uv *= RAIN_RIPPLE_GAPS;
vec2 f_part = fract(uv);
vec2 i_part = floor(uv);
float adjusted_time = time * 1.2 + randPhase(i_part);
#if RAIN_RIPPLE_DETAIL > 0
vec4 a = circle(f_part, i_part, adjusted_time);
vec4 b = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET);
vec4 c = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*2.0);
vec4 d = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*3.0);
vec4 ret;
ret.xy = a.xy - b.xy/2.0 + c.xy/4.0 - d.xy/8.0;
// z should always point up
ret.z = a.z + b.z /2.0 + c.z /4.0 + d.z /8.0;
//ret.xyz *= 1.5;
// fake specularity looks weird if we use every single ring, also if the inner rings are too bright
ret.w = (a.w + c.w /8.0)*1.5;
return ret;
#else
return circle(f_part, i_part, adjusted_time) * 1.5;
#endif
}
vec2 complex_mult(vec2 a, vec2 b)
{
return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x);
}
vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake specularity in w
{
return
rain(uv, time)
+ rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time)
#if RAIN_RIPPLE_DETAIL == 2
+ rain(uv * 0.75 + vec2( 3.7,18.9),time)
+ rain(uv * 0.9 + vec2( 5.7,30.1),time)
+ rain(uv * 1.0 + vec2(10.5 ,5.7),time)
#endif
;
}
#endif

View File

@ -1,126 +1,29 @@
#ifndef LIB_WATER_RIPPLES
#define LIB_WATER_RIPPLES
#define RAIN_RIPPLE_DETAIL @rain_ripple_detail
const float RAIN_RIPPLE_GAPS = 10.0;
const float RAIN_RIPPLE_RADIUS = 0.2;
float scramble(float x, float z)
float getTemporalWaveSizeMultiplier(in float time)
{
return fract(pow(fract(x)*3.0+1.0, z));
return 1.0 + 0.055 * sin(16.0 * time) + 0.065 * sin(12.87645 * time);
}
vec2 randOffset(vec2 c, float time)
vec4 applySprings(in vec4 samplerData, in vec4 n, in vec4 n2)
{
time = fract(time/1000.0);
c = vec2(c.x * c.y / 8.0 + c.y * 0.3 + c.x * 0.2,
c.x * c.y / 14.0 + c.y * 0.5 + c.x * 0.7);
c.x *= scramble(scramble(time + c.x/1000.0, 4.0), 3.0) + 1.0;
c.y *= scramble(scramble(time + c.y/1000.0, 3.5), 3.0) + 1.0;
return fract(c);
}
vec4 storage = vec4(0.0, samplerData.r, 0.0, 0.0);
float randPhase(vec2 c)
{
return fract((c.x * c.y) / (c.x + c.y + 0.1));
}
// Tweak to look most like water, not a physically accurate simulation
const float a = 0.14;
const float udamp = 0.02;
const float vdamp = 0.02;
float blip(float x)
{
x = max(0.0, 1.0-x*x);
return x*x*x;
}
// Apply 2d wave equation with dampening
// Continous impulse needed to maintain simulation, otherwise ripples will fade
float nsum = n.x + n.y + n.z + n.w;
storage.r = a * nsum + ((2.0 - udamp - vdamp) - 4.0 * a) * samplerData.r - (1.0 - vdamp) * samplerData.g;
float blipDerivative(float x)
{
x = clamp(x, -1.0, 1.0);
float n = x*x-1.0;
return -6.0*x*n*n;
}
// Calculate normal and store in blue-alpha channel
storage.ba = 2.0 * (n.xy - n.zw) + 0.5 * (n2.xy - n2.zw);
const float RAIN_RING_TIME_OFFSET = 1.0/6.0;
vec4 circle(vec2 coords, vec2 corner, float adjusted_time)
{
vec2 center = vec2(0.5,0.5) + (0.5 - RAIN_RIPPLE_RADIUS) * (2.0 * randOffset(corner, floor(adjusted_time)) - 1.0);
float phase = fract(adjusted_time);
vec2 toCenter = coords - center;
float r = RAIN_RIPPLE_RADIUS;
float d = length(toCenter);
float ringfollower = (phase-d/r)/RAIN_RING_TIME_OFFSET-1.0; // -1.0 ~ +1.0 cover the breadth of the ripple's ring
#if RAIN_RIPPLE_DETAIL > 0
// normal mapped ripples
if(ringfollower < -1.0 || ringfollower > 1.0)
return vec4(0.0);
if(d > 1.0) // normalize center direction vector, but not for near-center ripples
toCenter /= d;
float height = blip(ringfollower*2.0+0.5); // brighten up outer edge of ring; for fake specularity
float range_limit = blip(min(0.0, ringfollower));
float energy = 1.0-phase;
vec2 normal2d = -toCenter*blipDerivative(ringfollower)*5.0;
vec3 normal = vec3(normal2d, 0.5);
vec4 ret = vec4(normal, height);
ret.xyw *= energy*energy;
// do energy adjustment here rather than later, so that we can use the w component for fake specularity
ret.xyz = normalize(ret.xyz) * energy*range_limit;
ret.z *= range_limit;
return ret;
#else
// ring-only ripples
if(ringfollower < -1.0 || ringfollower > 0.5)
return vec4(0.0);
float energy = 1.0-phase;
float height = blip(ringfollower*2.0+0.5)*energy*energy; // fake specularity
return vec4(0.0, 0.0, 0.0, height);
#endif
}
vec4 rain(vec2 uv, float time)
{
uv *= RAIN_RIPPLE_GAPS;
vec2 f_part = fract(uv);
vec2 i_part = floor(uv);
float adjusted_time = time * 1.2 + randPhase(i_part);
#if RAIN_RIPPLE_DETAIL > 0
vec4 a = circle(f_part, i_part, adjusted_time);
vec4 b = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET);
vec4 c = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*2.0);
vec4 d = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*3.0);
vec4 ret;
ret.xy = a.xy - b.xy/2.0 + c.xy/4.0 - d.xy/8.0;
// z should always point up
ret.z = a.z + b.z /2.0 + c.z /4.0 + d.z /8.0;
//ret.xyz *= 1.5;
// fake specularity looks weird if we use every single ring, also if the inner rings are too bright
ret.w = (a.w + c.w /8.0)*1.5;
return ret;
#else
return circle(f_part, i_part, adjusted_time) * 1.5;
#endif
}
vec2 complex_mult(vec2 a, vec2 b)
{
return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x);
}
vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake specularity in w
{
return
rain(uv, time)
+ rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time)
#if RAIN_RIPPLE_DETAIL == 2
+ rain(uv * 0.75 + vec2( 3.7,18.9),time)
+ rain(uv * 0.9 + vec2( 5.7,30.1),time)
+ rain(uv * 1.0 + vec2(10.5 ,5.7),time)
#endif
;
return storage;
}
#endif