SFML 中的 2D 水着色器

2D water shader in SFML

提问人:Foaly 提问时间:1/29/2013 最后编辑:BЈовићFoaly 更新时间:3/9/2013 访问量:5781

问:

我想实现此处和此处描述的 2D 水面的算法。

但是,与其使用两个 int 数组并在 CPU 上进行计算,不如使用 SFML(基本上是 FBO)和 GLSL 着色器在 GPU 上运行所有内容。我想使用 SFML,因为它非常简单,而且我以前使用过它,所以我对它有一点了解。sf::RenderTexture

到目前为止,我已经取得了一些不错的进展。我能够正确地在它们之间设置 3 和乒乓球(因为除了 int 数组之外,您不能同时读取和写入相同的数组)。我还能够将 -32.767 到 32.767 范围内的高度字段创建表单的算法调整为 0 到 1 的范围(或者更准确地说,计算范围为 -0.5 到 0.5)。此外,添加新的涟漪在一定程度上也有效。所以到现在为止,你实际上可以看到一些波浪正在发生。sf::RenderTexturessf::RenderTexture

现在我的问题来了:波浪消失得非常非常快,我什至还没有应用任何阻尼。根据该算法,如果不施加阻尼,涟漪就不会停止。甚至反过来。如果我应用“放大”,波浪看起来接近你所期望的样子(但它们仍然消失,没有施加任何阻尼)。我的第一个想法是,这是因为我在 0 - 1 范围内使用浮点数而不是整数,但我只在使用乘法时才看到这是一个问题,但我只使用加法和减法。

这是我的 SFML C++ 代码:

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

这是我的GLSL着色器代码:

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;

}

请记住,这只是关于高度字段的创建。目前还没有遮阳水。

你知道为什么波浪会自行消失而没有阻尼吗?

C++ glsl SFML

评论

1赞 Foaly 1/29/2013
对不起,算法在这两页上进行了解释:gamedev.net/page/resources/_/technical/...... freespace.virgin.net/hugo.elias/graphics/x_water.htm(我无法发布两个以上的链接,因为我是新手)
0赞 Prizoff 1/29/2014
你找到解决方案了吗?

答:

2赞 Emond 1/29/2013 #1

如果我正确阅读了代码,您将对纹理的颜色/高度的前一帧进行采样,并使用四个相邻的像素/纹素来确定当前像素的颜色/高度。

在计算(缩放)这些相邻元素时,可能会丢失包含所需颜色/高度的纹素。它可能不是最重的纹素,只是旁边的纹素稍微低一点,导致意外的阻尼。

在这里,您不仅使用加法和减法:

const float pixelSize = 1.0 / textureSize;

使用此值,您可能会错过要查找的纹素。

编辑

另外:您正在对样本进行平均,因此结果将始终小于样本的最大值。因此,您可以选择最大值,而不是平均。这可能会带来奇怪的结果,但也会带来额外的见解。

评论

0赞 Foaly 1/29/2013
哇,首先感谢你们疯狂的快速响应!这其实很有道理!那么,如何确保我访问邻居纹素呢?
0赞 Emond 1/29/2013
您可以制作一个测试纹理(这样您就会知道运行单个帧后的结果应该是什么),运行单个帧并将帧渲染为位图,以便您可以检查帧/位图值。通过重复此操作,您应该可以看到发生了什么。这是我能想象到的唯一方法。可能提供一些见解的解决方案可能是添加额外的样本。另外:您正在对样本进行平均,因此结果将始终小于样本的最大值。因此,您可以选择最大值,而不是平均。这可能会带来奇怪的结果,但也会带来额外的见解。
0赞 Foaly 1/30/2013
谢谢你的提示,我做到了。我在这里上传了结果: imgur.com/KN2sUTh(它被缩放了两个,没有插值)第 3 帧和第 5 帧对我来说确实看起来很奇怪,所以你可能是对的。那么在GLSL中获取邻居纹素的正确方法是什么呢?
0赞 Emond 1/30/2013
您应该做一件事:使用位图手动计算几个像素,看看结果是否符合您对算法的期望。如果他们这样做了,算法就错了。如果他们不这样做,则算法的实现可能是错误的。
0赞 Foaly 1/30/2013
我在网格纸上手工计算。它们看起来与我发布的位图上的位图略有不同,所以我想实现是错误的。我仍然认为你的第一个想法可能是对的,有时步进没有得到邻居纹素,从而产生意想不到的阻尼。但我不知道其他方法,也没有找到其他方法......你能告诉我这是如何正确完成的吗?1.0 / textureSize
2赞 Hugo 3/9/2013 #2

这里有一些“处理”代码,它们实现了你上面发布的相同算法,它的阻尼是正确的,我希望你能从中得到一些要点:

// codes begin

int Width = 800;
int Height = 600;
int FullSize = 0;
//int Spacing = 10;

int[] source, dest;
PImage bg;

void setup()
{
  // if you want to run these codes by "Processing"
  // please make a picture named "HelloWorld.png"
  bg = loadImage("HelloWorld.png");
  Width = bg.width;
  Height = bg.height;
  FullSize = Width * Height;
  size(Width, Height);
  source = new int[FullSize];
  dest = new int[FullSize];
  for (int i=0; i< FullSize; i++)
    source[i] = dest[i] = 0;
}

void draw()
{
  for (int i=Width; i< FullSize-Width; i++)
  {
    // check for bounds
    int xi = i % Width;
    if ((xi==0) || (xi==Width-1)) continue;

    dest[i] = (
    ((source[i-1]+
      source[i+1]+
      source[i-Width]+
      source[i+Width]) >>1) ) -dest[i];

    int dampFactor = 1000;
    dest[i] -= (dest[i] >> dampFactor); // Damping - Quick divde by 32 (5 bits)
  }

  //image(bg, 0, 0);
  loadPixels();
  for (int i=Width; i< FullSize-Width; i++)
  {
    // check for bounds
    int xi = i % Width;
    if ((xi==0) || (xi==Width-1)) continue;

    int xoffset = dest[i-1] - dest[i+1];
    int yoffset = dest[i-Width] - dest[i+Width];

    int offset = i+xoffset+yoffset*Width;
    if (offset>0 && offset<FullSize)
    {
      // TODO: make better map
      pixels[i] = bg.pixels[offset];
    }
  }
  //bg.updatePixels();
  updatePixels();

  //swap
  int[] temp = source;
  source = dest;
  dest = temp;
}

void mouseDragged() 
{
    if (mouseX > 0 && mouseX < Width && mouseY > 0 && mouseY < Height)
      source[mouseY*Width+mouseX] = (int)random(50, 100); 
}

void mousePressed()
{
   // TODO: make a area pulse value, like a radius circle
   if (mouseX > 0 && mouseX < Width && mouseY > 0 && mouseY < Height)
      source[mouseY*Width+mouseX] = (int)random(50, 100); 
}

// codes end