Merge branch 'ripples' into 'master'

Implement shader-based water ripples

See merge request OpenMW/openmw!2877
depth-refraction
psi29a 2 years ago
commit b0a129d6e4

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

@ -23,7 +23,7 @@ add_openmw_dir (mwrender
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover 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 add_openmw_dir (mwinput

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

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

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

@ -142,7 +142,16 @@ namespace MWRender
|| world->isWalkingOnWater(ptr); || world->isWalkingOnWater(ptr);
if (!shouldEmit) if (!shouldEmit)
{
emitter.mTimer = 0.f; 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) else if (emitter.mTimer <= 0.f || (currentPos - emitter.mLastEmitPosition).length() > 10)
{ {
emitter.mLastEmitPosition = currentPos; emitter.mLastEmitPosition = currentPos;
@ -210,10 +219,18 @@ namespace MWRender
{ {
if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20) if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20)
{ {
osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex()); if (mRipples)
osgParticle::Particle* p = mParticleSystem->createParticle(nullptr); {
p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); constexpr float particleRippleSizeInUnits = 12.f;
p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); 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));
}
} }
} }

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

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

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

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

@ -0,0 +1,28 @@
#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;
}

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

@ -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); 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 vec4 position;
varying float linearDepth; varying float linearDepth;
@ -71,7 +78,6 @@ uniform float osg_SimulationTime;
uniform float near; uniform float near;
uniform float far; uniform float far;
uniform vec3 nodePosition;
uniform float rainIntensity; uniform float rainIntensity;
@ -83,12 +89,11 @@ uniform vec2 screenRes;
#include "lib/light/lighting.glsl" #include "lib/light/lighting.glsl"
#include "fog.glsl" #include "fog.glsl"
#include "lib/water/fresnel.glsl" #include "lib/water/fresnel.glsl"
#include "lib/water/ripples.glsl" #include "lib/water/rain_ripples.glsl"
#include "lib/view/depth.glsl" #include "lib/view/depth.glsl"
void main(void) void main(void)
{ {
vec3 worldPos = position.xyz + nodePosition.xyz;
vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0;
UV.y *= -1.0; UV.y *= -1.0;
@ -114,6 +119,12 @@ void main(void)
vec3 rippleAdd = rainRipple.xyz * 10.0; 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);
float distortionLevel = 2.0;
rippleAdd += distortionLevel * vec3(texture2D(rippleMap, rippleMapUV).ba * blendFar * blendClose, 0.0);
vec2 bigWaves = vec2(BIG_WAVES_X,BIG_WAVES_Y); 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 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); vec2 smallWaves = mix(vec2(SMALL_WAVES_X,SMALL_WAVES_Y),vec2(SMALL_WAVES_RAIN_X,SMALL_WAVES_RAIN_Y),rainIntensity);

@ -8,12 +8,21 @@ varying float linearDepth;
#include "shadows_vertex.glsl" #include "shadows_vertex.glsl"
#include "lib/view/depth.glsl" #include "lib/view/depth.glsl"
uniform vec3 nodePosition;
uniform vec3 playerPos;
varying vec3 worldPos;
varying vec2 rippleMapUV;
void main(void) void main(void)
{ {
gl_Position = modelToClip(gl_Vertex); gl_Position = modelToClip(gl_Vertex);
position = 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); vec4 viewPos = modelToView(gl_Vertex);
linearDepth = getLinearDepth(gl_Position.z, viewPos.z); linearDepth = getLinearDepth(gl_Position.z, viewPos.z);

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

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

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

@ -1,126 +1,29 @@
#ifndef LIB_WATER_RIPPLES #ifndef LIB_WATER_RIPPLES
#define LIB_WATER_RIPPLES #define LIB_WATER_RIPPLES
#define RAIN_RIPPLE_DETAIL @rain_ripple_detail float getTemporalWaveSizeMultiplier(in float time)
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)); return 1.0 + 0.055 * sin(16.0 * time) + 0.065 * sin(12.87645 * time);
} }
float blip(float x) vec4 applySprings(in vec4 samplerData, in vec4 n, in vec4 n2)
{ {
x = max(0.0, 1.0-x*x); vec4 storage = vec4(0.0, samplerData.r, 0.0, 0.0);
return x*x*x;
}
float blipDerivative(float x) // Tweak to look most like water, not a physically accurate simulation
{ const float a = 0.14;
x = clamp(x, -1.0, 1.0); const float udamp = 0.02;
float n = x*x-1.0; const float vdamp = 0.02;
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; // Apply 2d wave equation with dampening
float d = length(toCenter); // Continous impulse needed to maintain simulation, otherwise ripples will fade
float ringfollower = (phase-d/r)/RAIN_RING_TIME_OFFSET-1.0; // -1.0 ~ +1.0 cover the breadth of the ripple's ring 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;
#if RAIN_RIPPLE_DETAIL > 0 // Calculate normal and store in blue-alpha channel
// normal mapped ripples storage.ba = 2.0 * (n.xy - n.zw) + 0.5 * (n2.xy - n2.zw);
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 return storage;
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 #endif

Loading…
Cancel
Save