mirror of https://github.com/OpenMW/openmw.git
Merge branch 'ripples' into 'master'
Implement shader-based water ripples See merge request OpenMW/openmw!2877depth-refraction
commit
b0a129d6e4
@ -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
|
@ -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);
|
||||
}
|
@ -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
|
||||
#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)
|
||||
float getTemporalWaveSizeMultiplier(in float time)
|
||||
{
|
||||
x = clamp(x, -1.0, 1.0);
|
||||
float n = x*x-1.0;
|
||||
return -6.0*x*n*n;
|
||||
return 1.0 + 0.055 * sin(16.0 * time) + 0.065 * sin(12.87645 * time);
|
||||
}
|
||||
|
||||
const float RAIN_RING_TIME_OFFSET = 1.0/6.0;
|
||||
|
||||
vec4 circle(vec2 coords, vec2 corner, float adjusted_time)
|
||||
vec4 applySprings(in vec4 samplerData, in vec4 n, in vec4 n2)
|
||||
{
|
||||
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;
|
||||
vec4 storage = vec4(0.0, samplerData.r, 0.0, 0.0);
|
||||
|
||||
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;
|
||||
// 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;
|
||||
|
||||
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);
|
||||
// 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 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
|
||||
}
|
||||
// Calculate normal and store in blue-alpha channel
|
||||
storage.ba = 2.0 * (n.xy - n.zw) + 0.5 * (n2.xy - n2.zw);
|
||||
|
||||
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