Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: 2D Water  (Read 21306 times)

0 Members and 1 Guest are viewing this topic.

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
2D Water
« 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

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11047
    • View Profile
    • development blog
    • Email
Re: 2D Water
« Reply #1 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... :-\
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: 2D Water
« Reply #2 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.

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: 2D Water
« Reply #3 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

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
Re: 2D Water
« Reply #4 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.


Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: 2D Water
« Reply #5 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

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
Re: 2D Water
« Reply #6 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.)

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: 2D Water
« Reply #7 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.
Laurent Gomila - SFML developer

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: 2D Water
« Reply #8 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?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: 2D Water
« Reply #9 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).
Laurent Gomila - SFML developer

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: 2D Water
« Reply #10 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.

cire

  • Full Member
  • ***
  • Posts: 138
    • View Profile
Re: 2D Water
« Reply #11 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


« Last Edit: January 21, 2013, 08:21:57 pm by cire »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: 2D Water
« Reply #12 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.
Laurent Gomila - SFML developer

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: 2D Water
« Reply #13 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

Foaly

  • Sr. Member
  • ****
  • Posts: 453
    • View Profile
Re: 2D Water
« Reply #14 on: January 28, 2013, 11:47:22 pm »
Ok I opened another thread 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