SFML community forums

Help => Graphics => Topic started by: Foaly on January 18, 2013, 02:42:23 pm

Title: 2D Water
Post by: Foaly on January 18, 2013, 02:42:23 pm
Greetings!
I know that this forum is not for general programming issues, but for SFML specific stuff. I'm stuck with a problem that I can't solve and I'm not sure if it's something general I'm doing wrong or if it's something SFML specific I'm doing wrong, so I just thought I ask :)
I am trying to implement the 2D water effect described here: http://freespace.virgin.net/hugo.elias/graphics/x_water.htm
I want to implement it using shaders and rendertextures, because it seems more efficient to me, than using arrays of pixels and then upload those to the GPU each frame. Right now I trying to implement the creation of the Heightmap.
Here is what I'm doing: I have 3 rendertextures (3 because you can't read and write to a rendertexture at the same time) firstBuffer contains the Heightmap 2 frames ago, secondBuffer contains the heightmap form the last frame and finalBuffer is where the heightmap is drawn to. Releasing space puts a new "ripple" at the mouse position.
Here is my code:
#include <SFML/Graphics.hpp>
#include <iostream>

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

    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];

    sf::Shader waterHeightmapShader;
    waterHeightmapShader.loadFromFile("waterHeightmapShader.glsl", sf::Shader::Fragment);

    sf::Sprite sprite;
    sprite.setPosition(50, 50);
    sprite.setTexture(finalBuffer->getTexture());

    while (window.isOpen())
    {
        waterHeightmapShader.setParameter("mousePosition", sf::Vector2f(-1, -1));

        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();

            // when the spacebar is pressed, add a new ripple at the mouse position
            if(event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Space)
            {
                sf::Vector2i mousePosition = sf::Mouse::getPosition(window);
                if(mousePosition.x > 50 && mousePosition.y > 50 && mousePosition.x < 600 - 50 && mousePosition.y < 600 - 50)
                {
                    // this is an ugly hack
                    mousePosition.x -= 50;
                    mousePosition.y -= 50;

                    sf::Vector2f mouse(mousePosition);

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


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

                    waterHeightmapShader.setParameter("mousePosition", mouse);
                }
            }

        }

        waterHeightmapShader.setParameter("textureTwoFramesAgo", firstBuffer->getTexture());
        waterHeightmapShader.setParameter("textureOneFrameAgo", secondBuffer->getTexture());

        // create the heightmap
        finalBuffer->clear(sf::Color::Transparent);
        finalBuffer->draw(sf::Sprite(secondBuffer->getTexture()), &waterHeightmapShader);
        finalBuffer->display();


        sprite.setTexture(finalBuffer->getTexture());

        window.clear();
        window.draw(sprite);
        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 the shader:
// Scene buffer
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(textureTwoFramesAgo, vec2(position.x - pixelSize, position.y)) +
                       texture2D(textureTwoFramesAgo, vec2(position.x + pixelSize, position.y)) +
                       texture2D(textureTwoFramesAgo, vec2(position.x, position.y + pixelSize)) +
                       texture2D(textureTwoFramesAgo, vec2(position.x, position.y - pixelSize))   ) / 2.0 -
                       texture2D(textureOneFrameAgo, position);

    // damping
//    finalColor *= 0.5;

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

    gl_FragColor = finalColor;
}

If you give the code a try you'll see my problem. The problem is that whenever I create a new "ripple" it appears, but the next frame it appears to be flipped around the x-axis and the frame after that the rendertexture seems to be empty... Also the circle spreads, but it doesn't do the up and down/ripple/wavy effect.
I know the explanation is shitty, but I don't know how to describe it any better. I tried to make a video of it, but my computer refuses to do so (really I tried everything)

Sorry again for abusing this forum, but there is a lot of smart people here and I thought maybe someone is able to help me or point me in the right direction.
Thanks in advance
Foaly
Title: Re: 2D Water
Post by: eXpl0it3r on January 18, 2013, 03:10:36 pm
I guess you should first try to narrow down the problem on your own, because for someone else to find the problem means, the he/she will have to understand what the algorithm does, verify it's implemented correctly and also understand everything you do in the code.

My first guess would be to extensively test, if the algorithm actually does what I want/if it's implemented correctly. If that seems okay, then I'd try to use a non-shader version to check if I did something wrong there.
I ran it but I only got some extreme flickering... :-\
Title: Re: 2D Water
Post by: Foaly on January 18, 2013, 07:47:47 pm
Ok so I just spend some time inspecting the code and the studying the algorithm further. I noticed there are A LOT of things wrong with this code. I need some time to investigate this further. Sorry for posting so quick. I might come back with some more specific questions.
Title: Re: 2D Water
Post by: Foaly on January 19, 2013, 05:52:23 pm
Ok here is a question that is semi-related (that's why I didn't want to open another thread).
I want to draw a circle on a rendertexture with a shader. The code for the circle itself seems fine. I took it from here and modified it: http://morgan.leborgne.free.fr/GLSL-Shaders.html
Everything works perfectly fine if I only use one rendertexture. But if I use two (or more) and swap the drawing between them the texture sometimes seems to be flipped along the x-axis (this appears randomly, sometimes there is a pattern, sometimes there's not...) Does anybody has a explanation for this and maybe a way that I can avoid it?
Here is my code:
#include <SFML/Graphics.hpp>
#include <iostream>

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

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

    sf::Shader shader;
    shader.loadFromFile("Shader.glsl", sf::Shader::Fragment);

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

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

    while (window.isOpen())
    {
        shader.setParameter("mousePosition", sf::Vector2f(-1.f, -1.f));

        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();

            // when the spacebar is pressed, add a new circle at the mouse position
            if(event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Space)
            {
                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;

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

                    shader.setParameter("mousePosition", mouse);
                }
            }
        }

        // create circle
        firstBuffer->clear();
        firstBuffer->draw(sf::Sprite(secondBuffer->getTexture()), &shader);
        firstBuffer->display();


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

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

        // swap the buffers around
        sf::RenderTexture* swapper = firstBuffer;
        firstBuffer = secondBuffer;
        secondBuffer = swapper;
    }

    return 0;
}
 
Shader:
// Scene buffer
uniform sampler2D texture;
uniform vec2 mousePosition;

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


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

    // add new circle
    if(mousePosition.x > 0.0)
    {
        if(distance(position, mousePosition) < pixelSize * 10)
        {
            gl_FragColor = vec4(1, 0, 0, 1);
        }
        else
            gl_FragColor = vec4(0, 0, 0, 1);
    }
    else
        gl_FragColor = vec4(0, 0, 0, 1);

}

I hope I could make my problem clear. Just give the program a try. Hover the mouse over the left 500x500 and press space a couple times. The program is running at 1 fps so you can see whats happening otherwise you only see a flickering.
Thanks ,
Foaly
Title: Re: 2D Water
Post by: cire on January 19, 2013, 08:25:19 pm
Everything works perfectly fine if I only use one rendertexture. But if I use two (or more) and swap the drawing between them the texture sometimes seems to be flipped along the x-axis (this appears randomly, sometimes there is a pattern, sometimes there's not...) Does anybody has a explanation for this and maybe a way that I can avoid it?

You need to display the textures before they're drawn.  IOW, secondBuffer->display must be called before you draw it on first->Buffer.  You also need to invert the y-coordinate when you're calculating the value for mouse in your event loop.

Title: Re: 2D Water
Post by: Foaly on January 20, 2013, 10:44:51 pm
Wow thank you very much! That did the trick! I don't really understand why though. Would you mind explaining that to me? Because I don't actually touch the renderTexture secondBuffer (ie I never draw() to it) I always use firstBuffer, call display() on that one and then swap them.
You where also right about inverting the mouse y-coordinate. Why is that? I always though in texture coordinates the top left corner of a texture is (0, 0) is that wrong?
Thanks again
Foaly
Title: Re: 2D Water
Post by: cire on January 21, 2013, 12:48:04 am
Wow thank you very much! That did the trick! I don't really understand why though. Would you mind explaining that to me? Because I don't actually touch the renderTexture secondBuffer (ie I never draw() to it) I always use firstBuffer, call display() on that one and then swap them.
You where also right about inverting the mouse y-coordinate. Why is that? I always though in texture coordinates the top left corner of a texture is (0, 0) is that wrong?

A look into the source will show that a call to display can change the way SFML interprets the coordinates in the texture, so not calling display before drawing it to something may result in the y-coordinates being inverted.

OpenGL treats the lower left corner of a screen (or texture) as the origin by default.  SFML doesn't.  So one must keep track of whether one is talking to SFML or OpenGL (for instance, with a shader.)
Title: Re: 2D Water
Post by: Laurent on January 21, 2013, 08:45:29 am
Nothing of what cire said should be true. If it is, then there's clearly a bug in SFML ;)

You shouldn't have to call display() if you didn't draw anything. So your code is correct. Calling display() incorrectly may also explain why you have to invert Y coordinates.
Title: Re: 2D Water
Post by: Foaly on January 21, 2013, 10:57:00 am
Hahaha well thats hilarious. Because inverting the y-coordinate and calling display() on the secondBuffer fixes the problem I got (that sometimes the textures appears to be flipped)
Did you get a chance to give my code a try? Does the flipping also happen for you?
So both SFML and OpenGL treat in the lower left corner as 0, 0?
Title: Re: 2D Water
Post by: Laurent on January 21, 2013, 12:19:57 pm
Quote
Did you get a chance to give my code a try?
Hmm... your code produces runtime errors on my PC, I'm not sure why :-\

Quote
Because inverting the y-coordinate and calling display() on the secondBuffer fixes the problem I got
It's probably a side-effect, what you need is most likely a glFlush() (that's what display() does). And you won't have to invert Y coordinates anymore.

Quote
So both SFML and OpenGL treat in the lower left corner as 0, 0?
OpenGL doesn't treat lower-left corner as (0, 0). It treats it like you tell him to treat it: it depends on many states that you set (projection matrix, texture coordinates, ... it also depends how you load your texture).
SFML generally treats the upper-left corner as (0, 0).
Title: Re: 2D Water
Post by: Foaly on January 21, 2013, 12:32:09 pm
Runtime error?! That's weird... What did your debugger say?

what you need is most likely a glFlush()
Hmm glFlush() doesn't fix the problem... it still occures.

SFML generally treats the upper-left corner as (0, 0).
Ok thanks. It's really not that important for me, but I saw different tutorials say different thing, so I was wondering... But it makes sense now, if it depends on a number of things.
Title: Re: 2D Water
Post by: cire on January 21, 2013, 08:19:42 pm

Quote
So both SFML and OpenGL treat in the lower left corner as 0, 0?
OpenGL doesn't treat lower-left corner as (0, 0). It treats it like you tell him to treat it: it depends on many states that you set (projection matrix, texture coordinates, ... it also depends how you load your texture).
SFML generally treats the upper-left corner as (0, 0).

My OpenGL experience is limited.  I keep meaning to spend more time delving into it, but the time is hard to come by. 

It is my understanding that, yes, you can specify to OpenGL what it treats as the origin but, by default, it treats the lower left as origin.  (That can't be done with FBOs though, can it?)


Quote
Because inverting the y-coordinate and calling display() on the secondBuffer fixes the problem I got
It's probably a side-effect, what you need is most likely a glFlush() (that's what display() does). And you won't have to invert Y coordinates anymore.

I'm a little confused by this.  What is the line m_texture.m_pixelsFlipped = true; intended to do if it doesn't affect the way SFML interprets the Y coordinate?

After rooting around in the code a bit, I didn't find anywhere pixelsFlipped would be set to false, and in the above code, putting a secondBuffer->display() call above the event loop worked fine.  Perhaps this is is something that should be set in the default constructor instead of in display?

Relevant thread:
http://en.sfml-dev.org/forums/index.php?topic=1273.msg8106#msg8106


Title: Re: 2D Water
Post by: Laurent on January 22, 2013, 08:54:33 am
I' on vacation this week so I can't investigate more, you'll have to wait a little bit.
Title: Re: 2D Water
Post by: Foaly on January 22, 2013, 12:35:47 pm
If this means that I might found a bug in SFML than I'm willing to wait for as long as it may take  :P
Title: Re: 2D Water
Post by: Foaly on January 28, 2013, 11:47:22 pm
Ok I opened another thread (http://en.sfml-dev.org/forums/index.php?topic=10442.0) for this problem, so I can ask another question related to the water.

Alright! So I was able to make good progress. I got rid of all the weird flickering and I adapted the algorithm form being in the range -32.767 to 32.767 to the range 0 to 1 (more precise -0.5 to 0.5 for the calculation). So you can actually see a little bit of waves going on. But here is my problem: the waves disappear really really fast and I don't even have any damping applied yet. It's even the opposite way around. If I apply some "amplification" the waves look close to what you would expect them to look like (but they still disappear without damping). If I understand the original algorithm correctly, the waves are not supposed to stop if no damping is applied. First I though it might be because I am using floats in range 0-1 instead of integers, but that would only be a problem if I use multiplication, which is not the case. I only use addition and subtraction... Maybe I'm missing something obvious, but could somebody explain to me way that is?

Please remember there is no shading yet, this is all about the height field generation.
The 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::Shader waterHeightmapShader;
    waterHeightmapShader.loadFromFile("waterHeightmapShader.glsl", sf::Shader::Fragment);

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


        waterHeightmapShader.setParameter("mousePosition", sf::Vector2f(-1.f, -1.f));
        // 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;

                waterHeightmapShader.setParameter("mousePosition", mouse);
            }
        }


        waterHeightmapShader.setParameter("textureTwoFramesAgo", firstBuffer->getTexture());
        waterHeightmapShader.setParameter("textureOneFrameAgo", secondBuffer->getTexture());

        // create the heightmap
        secondBuffer->display();
        finalBuffer->clear(sf::Color(128, 128, 128));
        finalBuffer->draw(sf::Sprite(secondBuffer->getTexture()), &waterHeightmapShader);
        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;
}
Shader:
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;

    // add new ripples
    if(mousePosition.x > 0.0)
    {
        if(distance(position, mousePosition) < pixelSize * 5)
        {
            finalColor = vec4(0.9, 0.9, 0.9, 1.0);
        }
    }

    gl_FragColor = finalColor;

}
Hopefully somebody can point me in the right direction.
Thanks, Foaly
Title: Re: 2D Water
Post by: Foaly on February 07, 2013, 09:43:37 am
I had the chance of playing around with a CPU implementation of the algorithm and it turns out that my assumption was right, that the waves keep going for ever if no damping is applied. That means that something in my code is wrong...
I wasn't able to find out was is wrong in the meantime though. I'm getting really desperate, because I don't know what else I can try or change. If anybody has a hint or a idea what to do I would be really really greatful!
Foaly
Title: Re: 2D Water
Post by: FRex on February 07, 2013, 01:49:35 pm
Show video, cpu code or whatever so I know what effect you're going for exactly. I got ripples that fly around and bounce off the walls but they dissapear after traveling some distance too.
Title: Re: 2D Water
Post by: daemon on February 07, 2013, 02:44:46 pm
I was toying around with your code a little bit and I came up with this:
(https://dl.dropbox.com/u/32050233/water.jpg)
Is this the expected behaviour? I'm not really sure what you're looking for.
Title: Re: 2D Water
Post by: Foaly on February 07, 2013, 06:14:20 pm
First of all thanks for giving my code a try and playing with it!

Alright so this is what the CPU implementation looks like: http://youtube.com/watch?v=jDhVwx7bBS4 I can't post the download link to the source, because I didn't write it myself. I'll aks the author if it's ok though. But basically the code works like discribed in this post: http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/the-water-effect-explained-r915

FRex: The disappearing ripples are exactly my problem... They shouldn't disappear if I don't apply any damping, which I don't.

daemon: Could you explain the picture you posted a little bit more to me? What is the red in the top left corner? The blue part looks a little bit like what I'm trying to acchive. Also could you please post the code you changed?

Please remember that this is all just about the heightfield generation. There is no shading yet (second paragraph in the article).
Title: Re: 2D Water
Post by: FRex on February 07, 2013, 06:42:37 pm
The red is probably his raw map that he applies to image.
I meant I got waves just like in that video, but I don't apply dampening and they still disappear, probably because of using 0..1 of glsl shader normalized float color and not (-1* 2^15)..(2^15-1) of short. It might be harder than I thought to convert back and forth because I failed in doing it so far. And your second code(last post on 1st page) just causes a dot to appear for me.

Press left mouse and move around.
https://docs.google.com/file/d/0B8dEkQw1a4WvRmoxM0xzWExkbk0/edit?usp=sharing
Title: Re: 2D Water
Post by: daemon on February 07, 2013, 07:55:03 pm
Alright. Here's some code:

package main

import (
        sf "bitbucket.org/krepa098/gosfml2"
        "fmt"
)

const size = 500

func main() {
        fmt.Println("Hello World")

        wnd := sf.NewRenderWindow(sf.VideoMode{size, size, 32}, "Water", sf.Style_DefaultStyle, nil)
        wnd.SetFramerateLimit(60)

        firstBuffer := sf.NewRenderTexture(size, size, false)
        secondBuffer := sf.NewRenderTexture(size, size, false)
        finalBuffer := sf.NewRenderTexture(size, size, false)

        //load shaders
        waterShaderRaw, _ := sf.NewShaderFromFile("", "water_raw.glsl")
        waterShaderRaw.SetFloatParameter("textureSize", size, size)
        waterShaderHeightfield, _ := sf.NewShaderFromFile("", "water_heightfield.glsl")

        secondSprite := sf.NewSprite(secondBuffer.GetTexture())
        finalSprite := sf.NewSprite(finalBuffer.GetTexture())

        //debug output
        firstSpriteDbg := sf.NewSprite(firstBuffer.GetTexture())
        firstSpriteDbg.SetScale(sf.Vector2f{0.25, 0.25})

        //render states
        rsRaw := sf.MakeRenderStates(sf.Blend_None, sf.TransformIdentity(), nil, waterShaderRaw)
        rsHeightfield := sf.MakeRenderStates(sf.Blend_None, sf.TransformIdentity(), nil, waterShaderHeightfield)

        for wnd.IsOpen() {

                for ev := wnd.PollEvent(); ev != nil; ev = wnd.PollEvent() {
                        switch ev.(type) {
                        case sf.EventClosed:
                                wnd.Close()
                        }
                }

                //mouse input
                waterShaderRaw.SetFloatParameter("mousePosition", -1, -1)
                if sf.IsMouseButtonPressed(sf.MouseLeft) {
                        mousePos := sf.MouseGetPosition(wnd)
                        if mousePos.X < size && mousePos.Y < size {
                                mx := float32(mousePos.X) / size
                                my := 1.0 - float32(mousePos.Y)/size

                                waterShaderRaw.SetFloatParameter("mousePosition", mx, my)
                        }
                }

                waterShaderRaw.SetTextureParameter("textureTwoFramesAgo", firstBuffer.GetTexture())
                waterShaderRaw.SetTextureParameter("textureOneFrameAgo", secondBuffer.GetTexture())

                secondBuffer.Display()
                finalBuffer.Clear(sf.ColorBlack())

                finalBuffer.Draw(secondSprite, &rsRaw)
                finalBuffer.Display()

                secondSprite.SetTexture(secondBuffer.GetTexture(), false)
                finalSprite.SetTexture(finalBuffer.GetTexture(), false)

                wnd.Clear(sf.ColorBlack())
                wnd.Draw(finalSprite, &rsHeightfield)
                wnd.Draw(firstSpriteDbg, nil) // raw output
                wnd.Display()

                //swap buffers
                firstBuffer, secondBuffer, finalBuffer = secondBuffer, finalBuffer, firstBuffer
        }
}
 
Yeah it's written in Go. I'm sorry.  :P
I didn't change much. Your rendering code is correct.

water_raw.glsl:
uniform sampler2D textureTwoFramesAgo;
uniform sampler2D textureOneFrameAgo;
uniform vec2 mousePosition;
uniform vec2 textureSize;

const float damping = 0.999;

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

    vec4 smoothed =   (texture2D(textureOneFrameAgo, vec2(position.x - 1.0/textureSize.x, position.y)) +
                       texture2D(textureOneFrameAgo, vec2(position.x + 1.0/textureSize.x, position.y)) +
                       texture2D(textureOneFrameAgo, vec2(position.x, position.y + 1.0/textureSize.y)) +
                       texture2D(textureOneFrameAgo, vec2(position.x, position.y - 1.0/textureSize.y))) / 4.0;
                       
               
    float velocity = texture2D(textureTwoFramesAgo, position).g - texture2D(textureTwoFramesAgo, position).r; // <-- @FRex

    smoothed.r = smoothed.r*2.0 + velocity;
    smoothed.g = -smoothed.r;
    smoothed.g *= damping;
    smoothed.r *= damping;

    // add new ripples
    if(mousePosition.x > 0.0)
        if(distance(position, mousePosition) < 1.0/textureSize.x * 5.0)
            smoothed.r = 0.9;

           
    gl_FragColor = vec4(smoothed.r, smoothed.g, 0.0, 1.0);
}
 

water_heightfield.glsl:
uniform sampler2D inputTex;

const vec4 baseColorL = vec4(0.0,0.0,0.0,1.0);
const vec4 baseColorH = vec4(1.0,1.0,1.0,1.0);

void main()
{
    vec4 color = mix(baseColorL,baseColorH,(texture2D(inputTex,(gl_TexCoord[0].xy)).r+1.0)/2.0);
    gl_FragColor = color;
}
 

This should generate a nice heightfield.

@FRex
Have a look at line 19. Your waves disappear because you're actually losing the negative part (correct me if I'm wrong) of the waves intensity which I store in the green channel.

@Foaly
Yes the output in red is the raw output. Though you can still apply whatever transformations you like.
Title: Re: 2D Water
Post by: FRex on February 07, 2013, 08:09:25 pm
Actually, I have no idea what the f**k is going on in there and gpu laughed at my futile attempts. I just swapped two frames ago and one frame ago and made sure all textures get one clear and display just after being created and it looked good enough but I'll analyze that code sooner or later.
Actually2, I think I know what the problem was, our two frames ago is its' own texture so reading from it isn't like reading from same buffer in 2 buffer cpu approach. Thanks for pointing that out.
Quote
Yeah it's written in Go. I'm sorry.  :P
;D
Title: Re: 2D Water
Post by: daemon on February 08, 2013, 11:35:49 am
Using code from the article above I managed to get this:

(https://dl.dropbox.com/u/32050233/water4.jpg)

Yep it still needs some tweaks but we are getting close  :)
(Looks far more impressive in motion!)

water_heightfield.glsl
uniform sampler2D inputTex;
uniform sampler2D backgroundTex;

uniform float teta;
uniform float hue;
const float rIndex = 2.0;

void main()
{
    vec4 output;
    vec2 pos = vec2(gl_TexCoord[0].x, 1.0-gl_TexCoord[0].y); // "unflip" the texture
   
    float xDiff = (texture2D(inputTex,pos + vec2(1.0/teta,0.0)).r - texture2D(inputTex,pos).r);
    float yDiff = (texture2D(inputTex,pos + vec2(0.0,1.0/teta)).r - texture2D(inputTex,pos).r);
   
    float xAngle = atan(xDiff);
    float xRefraction = asin(sin(xAngle) / rIndex);
    float xDisplace = tan(xRefraction) * xDiff;
   
    float yAngle = atan(yDiff);
    float yRefraction = asin(sin(yAngle) / rIndex);
    float yDisplace = tan(yRefraction) * yDiff;
   
    if(xDiff < 0.0) {
      if(yDiff < 0.0)
        output = texture2D(backgroundTex,vec2(pos.x-xDisplace,pos.y-yDisplace));
      else
        output = texture2D(backgroundTex,vec2(pos.x-xDisplace,pos.y+yDisplace));
    }
    else
    {
      if(yDiff < 0.0)
        output = texture2D(backgroundTex,vec2(pos.x+xDisplace,pos.y-yDisplace));
      else
        output = texture2D(backgroundTex,vec2(pos.x+xDisplace,pos.y+yDisplace));
    }
   
    gl_FragColor = output + max(xDiff,yDiff) * vec4(hue,hue,hue,0.0);
}
 
Title: Re: 2D Water
Post by: Foaly on February 08, 2013, 08:03:57 pm
Daemon: wow thanks! Your results looks awesome. The picture pretty much looks like what I'm trying to acchive! Unfortunately I'm not at my computer, so I can't test your code right now. But I already have some questions :D this is the first time I see some Go code, so I'm only assuming and using my "programmer common sense" please correct me if I'm wrong! As far as I can tell you didn't change much and just converted my code to Go (exept for the debug sprite in the top left corner). The interesting changes are in the shader code! The calculation of the smooth value hasn't changed, but the calculation of the velocity has. You write that you use the green channel for the negativ part. I don't quiet get that. Could you explain to me what you are doing exactly?
Also, since I can't try it myself rightnow, if you don't apply any damping do the ripples disappear or do they keep on spreading?
I haven't tryed any shading code yet (combining the heightmap and a background texture) but your code looks great! I'll definitly test it! Also what tweeking are you speaking of? Just code cleaning or actuall algorithm improvments?
Thanks again for all the help!

Actually, I have no idea what the f**k is going on in there and gpu laughed at my futile attempts.
This is exactly what I felt like...
I'll have a look at your code once I get home, too!
Title: Re: 2D Water
Post by: FRex on February 08, 2013, 08:10:00 pm
There's no code, there's just exe and I didn't change much:
Quote
I just swapped two frames ago and one frame ago and made sure all textures get one clear and display just after being created and it looked good enough

Quote
You write that you use the green channel for the negativ part. I don't quiet get that. Could you explain to me what you are doing exactly?
That's something with the fact we use three buffers to fake two buffers, I think. I don't know how to put that into words. :P
Title: Re: 2D Water
Post by: Foaly on February 08, 2013, 11:07:02 pm
That's something with the fact we use three buffers to fake two buffers, I think. I don't know how to put that into words. :P
Hmm I don't know. The three buffers are neccessary, because on the GPU you can't read and write to the same texture at the same time. That's why I used a technique called ping-pong where you switch between two (in this case 3) textures. Or do you mean something else?
I think daemon uses one channel to represent the positiv numbers and one to represent the negative numbers, but I'm not sure. It's just a guess. And if it's right I don't quiet understand how he does it. That's why I asked for an explanation :)
Title: Re: 2D Water
Post by: daemon on February 09, 2013, 11:54:36 am
Uhh... so many questions and I'm not the opengl guy so my explanation could be plain wrong  :-[.

So about the green channel:
The algorithm spits out negative numbers which you can't simply store as pixel color as they get clamped between 0 and 1 using 8bit precision. So if you store lets say -0.5 and read it back next frame you get 0.  So I store the negative values in g an apply them back in the velocity calculation.

About the disappearing ripples:
Yes they also disappear. I tried hard to figure out why and I think its due to a lack of precision. SFML is using GL_RGBA8 textures so the precision is quite limited. I changed it to GL_RGBA16 for testing and the ripples disappear way slower.

About the 3 buffers:
Actually I managed to get the same result using 2 buffers.
func main() {
        ...
        firstBuffer := sf.NewRenderTexture(size, size, false)
        secondBuffer := sf.NewRenderTexture(size, size, false)

        firstBuffer.Clear(sf.ColorBlack())
        secondBuffer.Clear(sf.ColorBlack())

        renderSprite := sf.NewSprite(nil)

        //load shaders
        ...
       
        //render states
        ...

        for wnd.IsOpen() {
                //mouse input
                ...

                waterShaderRaw.SetTextureParameter("textureTwoFramesAgo", firstBuffer.GetTexture())
                waterShaderRaw.SetTextureParameter("textureOneFrameAgo", secondBuffer.GetTexture())

                secondBuffer.Display()

                renderSprite.SetTexture(secondBuffer.GetTexture(), false)

                firstBuffer.Draw(renderSprite, &rsRaw)

                wnd.Clear(sf.ColorBlack())
                wnd.Draw(renderSprite, nil)
                wnd.Display()

                //swap buffers
                firstBuffer, secondBuffer = secondBuffer, firstBuffer
        }
}
 
Although it's not highly tested. Just an experiment.
Title: Re: 2D Water
Post by: Foaly on February 10, 2013, 04:34:55 pm
Thanks for the explanation. I think know what the problem was. The code I posted did store the negative value, by subtracting 0.5 from the value before applying the algorithm and adding 0.5 afterwards. So the whole value was in one channel. Since SFML uses 8bit per channel that means the range was only from-128 to 127. When splitting up the value and storing the negative and the positive part of the value and store them in separate channels like you did the precision doubles and the value can now range from -255 to 255. A brilliant idea! Thank you very much. I would have never thought of that.
It makes sense, that ripples still disappear, because using float-point arithmetic (GPU implementation) instead of integer math (CPU implementation) applies some damping. Setting the texture type to GL_RGBA16 decreases the rounding errors.
The code only using two buffers surprises me. According to my understanding it shouldn't work :D I see if I find some time to investigate it.
I implemented the render shader and I tried out your code, because it is a pretty much exactly what the articles explains. It works great and it looks awesome, but I have some questions. I suppose teta is the texture size, right? I also don't understand the last line. Why is max(xDiff,yDiff) * vec4(hue,hue,hue,0.0) added to the output value? What does the hue do? And what values should it be set to? What does adding max(xDiff,yDiff) do?
Title: Re: 2D Water
Post by: daemon on February 12, 2013, 07:20:36 pm
You're welcome.  :)
Teta is the texture size but depending on the background texture using different values (e.g. 0.5*texturesize) yields better results. Actually I think the whole thing really depends on the quality of the background texture. Sadly I haven't had much time recently to dig any deeper.

I also don't understand the last line. Why is max(xDiff,yDiff) * vec4(hue,hue,hue,0.0) added to the output value? What does the hue do? And what values should it be set to? What does adding max(xDiff,yDiff) do?
Forget about that line :D. My intention was to create some sort of "shiny" appearance.
Do you also have this upside down problem? Missing an .update() call?
Title: Re: 2D Water
Post by: Foaly on February 13, 2013, 11:08:39 am
Actually I think the whole thing really depends on the quality of the background texture.
I also think that the background texture makes or breaks the effect! The better it is the more realistic the effect looks. I'll play around with the texture size a bit. Thanks for the hint.

About the last line again: If I just put gl_FragColor = output; the effect looks really soft and not good at all. Even though that way it's "physically correct" (i.e. like described in the article), right?. What would be a good way to make it stronger and look more wavy?
What do you mean by upside down problem? Do you mean that if I use vec2 pos = gl_FragCoords[0]; the texture is flipped? If so, yes that happens for me to. I don't know why that happens though. It may or may not be related to the weird behavior described earlier in this post (the need to call secondBuffer->display()). What do you mean with missing update() call?
Title: Re: 2D Water
Post by: daemon on February 13, 2013, 06:11:56 pm
I added some stuff from http://habibs.wordpress.com/lake to achieve a more glossy look. And this is what I got:
(https://dl.dropbox.com/u/32050233/water5.png)
Too bad you can't see it in motion ;D. The math is probably wrong but I'm pleased by the results.

Do you mean that if I use vec2 pos = gl_FragCoords[0]; the texture is flipped? If so, yes that happens for me to.
Yes. So I'll just pick an other background ;)
Title: Re: 2D Water
Post by: Foaly on February 14, 2013, 12:42:31 pm
Wow the picture looks very cool! Maybe could make a video of it or upload a .exe or something so people can see it in motion :D
I really like the look of your picture, because it look like it's reflecting light (like the sun) without any actual lights. Could you explain what you added from the article? And maybe show your code?
Yes. So I'll just pick an other background ;)
I don't understand. How is taking a different background fix the problem?
Title: Re: 2D Water
Post by: daemon on February 14, 2013, 07:06:50 pm
Unfortunately I can't provide windows binaries as I'm using linux but I put all the code an images into an archive (https://dl.dropbox.com/u/32050233/water.zip) so that you can compile it on your own. Feel free to provide an exe for other users. Oh, and don't ask me about the math. There is a lot of trial and error in it ;).

I don't understand. How is taking a different background fix the problem?
It doesn't. But using a symmetrical background is a workaround... sort of.