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:
parent
2493e79daa
commit
e17281ac67
@ -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
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
306
apps/openmw/mwrender/ripples.cpp
Normal file
306
apps/openmw/mwrender/ripples.cpp
Normal 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);
|
||||
}
|
||||
}
|
103
apps/openmw/mwrender/ripples.hpp
Normal file
103
apps/openmw/mwrender/ripples.hpp
Normal 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
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
)
|
||||
|
27
files/shaders/compatibility/ripples_blobber.frag
Normal file
27
files/shaders/compatibility/ripples_blobber.frag
Normal 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;
|
||||
}
|
34
files/shaders/compatibility/ripples_simulate.frag
Normal file
34
files/shaders/compatibility/ripples_simulate.frag
Normal 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);
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
30
files/shaders/core/ripples_blobber.comp
Normal file
30
files/shaders/core/ripples_blobber.comp
Normal 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);
|
||||
}
|
31
files/shaders/core/ripples_simulate.comp
Normal file
31
files/shaders/core/ripples_simulate.comp
Normal 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));
|
||||
}
|
126
files/shaders/lib/water/rain_ripples.glsl
Normal file
126
files/shaders/lib/water/rain_ripples.glsl
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user