4

ここここ で説明されている 2D 水面のアルゴリズムを実装したいと考えています。

しかし、2 つの int 配列を使用して CPU で計算する代わりに、SFML sf::RenderTexture(基本的には FBO) と GLSL シェーダーを使用してすべてを GPU で実行したいと考えています。私は SFML を使いたいと思っています。SFML はとてもシンプルで、以前に使用したことがあるからです。

これまでのところ、私はいくつかの良い進歩を遂げてきました。sf::RenderTextures私はそれらの間で3とピンポンを正しく設定することができました(int配列以外sf::RenderTextureでは同時に読み書きできないため)。-32.767 から 32.767 の範囲にある高さフィールド作成フォームのアルゴリズムを 0 から 1 の範囲 (より正確には -0.5 から 0.5 の計算) に適合させることもできました。また、新しい波紋を追加すると、ある程度機能します。したがって、この時点まで、実際に少し波が進行しているのを見ることができます。

ここで私の問題が発生します。波は非常に速く消え、まだダンピングも適用していません。アルゴリズムによると、減衰が適用されていない場合、リップルは停止しません。それは逆です。「増幅」を適用すると、波は予想されるものに近くなります (ただし、減衰を適用しなくても波は消えます)。私が最初に考えたのは、整数の代わりに 0 ~ 1 の範囲の float を使用しているためであるということでしたが、これは乗算を使用した場合にのみ問題になり、加算と減算のみを使用しました。

これが私の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;

}

これはすべて高さフィールドの作成に関するものであることを覚えておいてください。水の影はまだありません。

波が減衰せずに自然に消える理由を知っていますか?

4

2 に答える 2

2

コードを正しく読んでいる場合は、前のフレームでテクスチャの色/高さをサンプリングし、隣接する4つのピクセル/テクセルを使用して現在のピクセルの色/高さを決定します。

これらのネイバーを計算(スケーリング)しているときに、探している色/高さを含むテクセルが欠落している可能性があります。それは最も重いテクセルではないかもしれません、ちょうどその隣に少し低く、予期しない減衰を引き起こします。

これは、加算と減算を使用するだけではない場所です。

const float pixelSize = 1.0 / textureSize;

この値を使用すると、探しているテクセルを見逃す可能性があります。

編集

また、サンプルを平均化しているため、結果は常にサンプルの最大値よりも小さくなります。したがって、平均化する代わりに、最大値を選択できます。それは奇妙な結果をもたらすかもしれませんが、追加の洞察も与えるかもしれません。

于 2013-01-29T10:11:31.810 に答える
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
于 2013-03-09T06:42:48.630 に答える