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

Author Topic: 2D Water  (Read 18255 times)

0 Members and 1 Guest are viewing this topic.

Foaly

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

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: 2D Water
« Reply #16 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.
Back to C++ gamedev with SFML in May 2023

daemon

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: 2D Water
« Reply #17 on: February 07, 2013, 02:44:46 pm »
I was toying around with your code a little bit and I came up with this:

Is this the expected behaviour? I'm not really sure what you're looking for.

Foaly

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

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: 2D Water
« Reply #19 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
« Last Edit: February 07, 2013, 07:01:35 pm by FRex »
Back to C++ gamedev with SFML in May 2023

daemon

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: 2D Water
« Reply #20 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.

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: 2D Water
« Reply #21 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
« Last Edit: February 07, 2013, 08:15:31 pm by FRex »
Back to C++ gamedev with SFML in May 2023

daemon

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: 2D Water
« Reply #22 on: February 08, 2013, 11:35:49 am »
Using code from the article above I managed to get this:



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

Foaly

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

FRex

  • Hero Member
  • *****
  • Posts: 1845
  • Back to C++ gamedev with SFML in May 2023
    • View Profile
    • Email
Re: 2D Water
« Reply #24 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
Back to C++ gamedev with SFML in May 2023

Foaly

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

daemon

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: 2D Water
« Reply #26 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.
« Last Edit: February 09, 2013, 03:07:43 pm by daemon »

Foaly

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

daemon

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: 2D Water
« Reply #28 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?
« Last Edit: February 12, 2013, 07:32:01 pm by daemon »

Foaly

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