# 2D water shader in SFML

I want to implement the algorithm for a 2D water surface described here and here.

Before somebody finds out, beats me up and deletes this, I'm gonna say it myself. I posted this question over at stackoverflow too. I know cross posting is a terrible thing, but I just found this Game Development Q&A site. My question is closely connected to game development, because I want to use the effect in a game. Also I use SFML, which is a library mostly used for developing games. I think there are more GLSL and graphics experts here, so maybe somebody is able to help me. Again please excuse this.

So some background information: I am using SFML and C++. I want to recreate the effect described in the articles above. But instead of using two int arrays and calculating on the CPU I would like to use SFML's sf::RenderTexture's (FBO's basically) and a GLSL shader to run everything on the GPU.

I've made some good progress so far. I was able to set up 3 sf::RenderTextures and ping-pong between them correctly (because other than int array you can't read and write to the same sf::RenderTexture at the same time). I was also able to adapt the algorithm for the height field creation form being in the range -32.767 to 32.767 to the range 0 to 1 (or to be more precise -0.5 to 0.5 for the calculation). Also adding new ripples works to some extend. So up to this point you can actually see a little of waves going on.

Here comes my problem now: The waves disappear really, really fast and I don't even apply any damping yet. According to the algorithm the ripples are not stopping if there is no damping applied. It's even the other way around. If I apply "amplification" the waves look close to what you would expect them to look like (but they still disappear without any damping applied to them). My first thought was that this is, because I use float's in range 0 - 1 instead of integers, but I only see this being a problem if multiplication is used, but I only use addition and subtraction.

Another idea that a user at stackoverflow suggested was, that I was sampling the texels edges and not their centre, giving me interpolated values and thus creating the unexpected damping, but this turned out to be false as well.

Here is my SFML C++ code :

#include <SFML/Graphics.hpp>
#include <iostream>

int main()
{
sf::RenderWindow window(sf::VideoMode(1000, 1000), "SFML works!");
window.setFramerateLimit(12);

sf::RenderTexture buffers[3];
buffers[0].create(500, 500);
buffers[1].create(500, 500);
buffers[2].create(500, 500);
sf::RenderTexture* firstBuffer = buffers;
sf::RenderTexture* secondBuffer = &buffers[1];
sf::RenderTexture* finalBuffer = &buffers[2];

firstBuffer->clear(sf::Color(128, 128, 128));
secondBuffer->clear(sf::Color(128, 128, 128));
finalBuffer->clear(sf::Color(128, 128, 128));

sf::Sprite spritefirst;
spritefirst.setPosition(0, 0);
spritefirst.setTexture(firstBuffer->getTexture());

sf::Sprite spritesecond;
spritesecond.setPosition(500, 0);
spritesecond.setTexture(secondBuffer->getTexture());

sf::Sprite spritefinal;
spritefinal.setPosition(0, 500);
spritefinal.setTexture(finalBuffer->getTexture());

while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if(event.type == sf::Event::Closed)
window.close();

if(event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Escape)
window.close();
}

// if mouse button is pressed add new ripples
if(sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
sf::Vector2i mousePosition = sf::Mouse::getPosition(window);
if(mousePosition.x < 500 && mousePosition.y < 500)
{
sf::Vector2f mouse(mousePosition);

mouse.x /= 500.f;
mouse.y /= 500.f;

mouse.y = 1 - mouse.y;

std::cout << mouse.x << " " << mouse.y << std::endl;

}
}

// create the heightmap
secondBuffer->display();
finalBuffer->clear(sf::Color(128, 128, 128));
finalBuffer->display();

spritefirst.setTexture(firstBuffer->getTexture());
spritesecond.setTexture(secondBuffer->getTexture());
spritefinal.setTexture(finalBuffer->getTexture());

window.clear();
window.draw(spritefirst);
window.draw(spritesecond);
window.draw(spritefinal);
window.display();

// swap the buffers around, first becomes second, second becomes third and third becomes first
sf::RenderTexture* swapper = firstBuffer;
firstBuffer = secondBuffer;
secondBuffer = finalBuffer;
finalBuffer = swapper;
}

return 0;
}


And here is my GLSL shader code :

uniform sampler2D textureTwoFramesAgo;
uniform sampler2D textureOneFrameAgo;
uniform vec2 mousePosition;

const float textureSize = 500.0;
const float pixelSize = 1.0 / textureSize;

void main()
{
// pixels position
vec2 position = gl_TexCoord[0].st;

vec4 finalColor = ((texture2D(textureOneFrameAgo, vec2(position.x - pixelSize, position.y)) +
texture2D(textureOneFrameAgo, vec2(position.x + pixelSize, position.y)) +
texture2D(textureOneFrameAgo, vec2(position.x, position.y + pixelSize)) +
texture2D(textureOneFrameAgo, vec2(position.x, position.y - pixelSize)) - 2.0) / 2) -
(texture2D(textureTwoFramesAgo, position) - 0.5);

// damping
//    finalColor.rgb *= 1.9;  // <---- uncomment this for the "amplifiction" ie. to see the waves better
finalColor.rgb += 0.5;

if(mousePosition.x > 0.0)
{
if(distance(position, mousePosition) < pixelSize * 5.0)
{
finalColor = vec4(0.9, 0.9, 0.9, 1.0);
}
}

gl_FragColor = finalColor;

}


Please remember that this is all just about the height field creation. There is no shading of the water or a background texture yet.

I have done a lot of research, but I can't find anything that helps me, I am really stuck now! So does anybody know why the waves disappear by them self without damping?

• Why would you expect the waves to not disappear? Unless you apply boundary conditions that reflect the waves, the initial disturbance in the system will average out over the course of the simulation. – user830 Feb 13 '13 at 16:24
• Also your shader doesn't quite look right to me. This is the correct algorithm as I know it. pastebin.com/wxJJLbM2 – user830 Feb 13 '13 at 16:46