From e17281ac675f89f65e62b392d0d7bf74cfc6c58f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 1 Apr 2023 09:09:45 +0400 Subject: [PATCH] Implement shader-based water ripples (feature 3537) --- CHANGELOG.md | 1 + apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 24 +- apps/openmw/mwrender/ripples.cpp | 306 ++++++++++++++++++ apps/openmw/mwrender/ripples.hpp | 103 ++++++ apps/openmw/mwrender/ripplesimulation.cpp | 25 +- apps/openmw/mwrender/ripplesimulation.hpp | 6 + apps/openmw/mwrender/water.cpp | 51 ++- apps/openmw/mwrender/water.hpp | 8 +- files/shaders/CMakeLists.txt | 5 + .../compatibility/ripples_blobber.frag | 27 ++ .../compatibility/ripples_simulate.frag | 34 ++ files/shaders/compatibility/water.frag | 16 +- files/shaders/compatibility/water.vert | 9 + files/shaders/core/ripples_blobber.comp | 30 ++ files/shaders/core/ripples_simulate.comp | 31 ++ files/shaders/lib/water/rain_ripples.glsl | 126 ++++++++ files/shaders/lib/water/ripples.glsl | 127 +------- 18 files changed, 784 insertions(+), 147 deletions(-) create mode 100644 apps/openmw/mwrender/ripples.cpp create mode 100644 apps/openmw/mwrender/ripples.hpp create mode 100644 files/shaders/compatibility/ripples_blobber.frag create mode 100644 files/shaders/compatibility/ripples_simulate.frag create mode 100644 files/shaders/core/ripples_blobber.comp create mode 100644 files/shaders/core/ripples_simulate.comp create mode 100644 files/shaders/lib/water/rain_ripples.glsl diff --git a/CHANGELOG.md b/CHANGELOG.md index 732f5f341e..d20c45e688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7794500edf..82dfe1010c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -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 diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d09487bf2c..d2b4ee1bc2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -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()); diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp new file mode 100644 index 0000000000..b9da6e39af --- /dev/null +++ b/apps/openmw/mwrender/ripples.cpp @@ -0,0 +1,306 @@ +#include "ripples.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 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 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 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 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(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(); + 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); + } +} diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp new file mode 100644 index 0000000000..8e7f9f0c0a --- /dev/null +++ b/apps/openmw/mwrender/ripples.hpp @@ -0,0 +1,103 @@ +#ifndef OPENMW_MWRENDER_RIPPLES_H +#define OPENMW_MWRENDER_RIPPLES_H + +#include + +#include +#include + +#include +#include + +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 mStateset; + bool mPaused = true; + }; + + size_t mPositionCount = 0; + std::array mPositions; + + std::array mState; + + private: + void setupFragmentPipeline(); + void setupComputePipeline(); + + osg::Vec2f mCurrentPlayerPos; + osg::Vec2f mLastPlayerPos; + + std::array, 2> mTextures; + std::array, 2> mFBOs; + + osg::ref_ptr mProgramBlobber; + osg::ref_ptr 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 mRipples; + }; +} + +#endif diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 306cf7a34a..abfb7ba9cd 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -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)); + } } } diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index bc2eb91381..1f09797bf4 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -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 mParent; @@ -68,6 +72,8 @@ namespace MWRender osg::ref_ptr mParticleNode; std::vector mEmitters; + + Ripples* mRipples = nullptr; }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b082ad5185..5c57764c80 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -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 program, osg::ref_ptr 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 mProgram; osg::ref_ptr 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 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& 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() diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 2c5f416428..0204cb4303 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -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 mRefraction; osg::ref_ptr mReflection; + osg::ref_ptr 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(); diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 9059360980..1b73acf758 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -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 ) diff --git a/files/shaders/compatibility/ripples_blobber.frag b/files/shaders/compatibility/ripples_blobber.frag new file mode 100644 index 0000000000..270fd3b3b4 --- /dev/null +++ b/files/shaders/compatibility/ripples_blobber.frag @@ -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; +} diff --git a/files/shaders/compatibility/ripples_simulate.frag b/files/shaders/compatibility/ripples_simulate.frag new file mode 100644 index 0000000000..8b19c3386c --- /dev/null +++ b/files/shaders/compatibility/ripples_simulate.frag @@ -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); +} diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 8fe9c824fc..e8188fb5f6 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -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); diff --git a/files/shaders/compatibility/water.vert b/files/shaders/compatibility/water.vert index 843118f46d..93796a7b9c 100644 --- a/files/shaders/compatibility/water.vert +++ b/files/shaders/compatibility/water.vert @@ -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); diff --git a/files/shaders/core/ripples_blobber.comp b/files/shaders/core/ripples_blobber.comp new file mode 100644 index 0000000000..86816ff03d --- /dev/null +++ b/files/shaders/core/ripples_blobber.comp @@ -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); +} diff --git a/files/shaders/core/ripples_simulate.comp b/files/shaders/core/ripples_simulate.comp new file mode 100644 index 0000000000..7fa2c7e05a --- /dev/null +++ b/files/shaders/core/ripples_simulate.comp @@ -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)); +} diff --git a/files/shaders/lib/water/rain_ripples.glsl b/files/shaders/lib/water/rain_ripples.glsl new file mode 100644 index 0000000000..4e5f85017b --- /dev/null +++ b/files/shaders/lib/water/rain_ripples.glsl @@ -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 diff --git a/files/shaders/lib/water/ripples.glsl b/files/shaders/lib/water/ripples.glsl index 4e5f85017b..07856d7236 100644 --- a/files/shaders/lib/water/ripples.glsl +++ b/files/shaders/lib/water/ripples.glsl @@ -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