SFML slow when drawing more than 500 shapes - c++

I am new to SFML and I would like to implement a fluid boids simulation. However, I realized that when more than 500 shapes are drawn at the same time, the fps drop quickly.
How can I improve the code below to make it much faster to run?
#include <SFML/Graphics.hpp>
#include <vector>
#include <iostream>
sf::ConvexShape newShape() {
sf::ConvexShape shape(3);
shape.setPoint(0, sf::Vector2f(0, 0));
shape.setPoint(1, sf::Vector2f(-7, 20));
shape.setPoint(2, sf::Vector2f(7, 20));
shape.setOrigin(0, 10);
shape.setFillColor(sf::Color(49, 102, 156, 150));
shape.setOutlineColor(sf::Color(125, 164, 202, 150));
shape.setOutlineThickness(1);
shape.setPosition(rand() % 800, rand() % 600);
shape.setRotation(rand() % 360);
return shape;
}
int main() {
sf::Clock dtClock, fpsTimer;
sf::RenderWindow window(sf::VideoMode(800, 600), "Too Slow");
std::vector<sf::ConvexShape> shapes;
for (int i = 0; i < 1000; i++) shapes.push_back(newShape());
while (window.isOpen()) {
window.clear(sf::Color(50, 50, 50));
for (auto &shape : shapes) { shape.rotate(0.5); window.draw(shape); }
window.display();
float dt = dtClock.restart().asSeconds();
if (fpsTimer.getElapsedTime().asSeconds() > 1) {
fpsTimer.restart();
std::cout << ((1.0 / dt > 60) ? 60 : (1.0 / dt)) << std::endl;
}
}
}
I have the following performance:
Shapes FPS
10 60
100 60
500 60
600 60
700 55
800 50
900 45
1000 21
My goal is to have about 5k boids on the screen.
EDIT
I am building the project on Windows 11 under WSL2 with vGPU enabled. When testing natively on Windows 11 with Visual Studio I get much better performance (I can run 5k boids at 60 FPS)

The problems are a lot of draw calls. That is slow part of this program. In order to fix this, we can put all triangles into single vertex array and call draw upon only that array. That way we will speed up program. Problem with it is that you must implement your own rotate method. I implemented the method below and edited so the function returns triangles in single vertex array.
#include <SFML/Graphics.hpp>
#include <iostream>
//This function returns vertex array by given number of triangles
sf::VertexArray newShape(int numberOfTriangles) {
sf::VertexArray shape(sf::Triangles);
//we are going trough every point in each triangle
for (int i=0;i<3*numberOfTriangles;i++){
//creating points of triangles as vertexes 1, 2 and 3
sf::Vertex v1(sf::Vector2f(rand() % 800, rand() % 600));
sf::Vertex v2(sf::Vector2f(v1.position.x - 7, v1.position.y - 20));
sf::Vertex v3(sf::Vector2f(v1.position.x + 7, v1.position.y - 20));
//setting color
v1.color = v2.color = v3.color = sf::Color(49, 102, 156, 150);
//rotating for random angle
sf::Transform transform;
transform.rotate(rand()%90, (v2.position.x + v3.position.x) / 2,v1.position.y - 10);
v1.position = transform.transformPoint(v1.position);
v2.position = transform.transformPoint(v2.position);
v3.position = transform.transformPoint(v3.position);
//appending them into vertex array
shape.append(v1);
shape.append(v2);
shape.append(v3);
}
return shape;
}
//rotate function finds the middle of 3 vertexes and rotates them
void rotate(sf::VertexArray& array, double angle){
for (int i=0;i<array.getVertexCount();i+=3){
sf::Transform transform;
transform.rotate(angle, (array[i+1].position.x + array[i+2].position.x) / 2, (array[i].position.y + array[i+1].position.y)/2);
array[i].position = transform.transformPoint(array[i].position);
array[i+1].position = transform.transformPoint(array[i+1].position);
array[i+2].position = transform.transformPoint(array[i+2].position);
}
}
int main() {
sf::Clock dtClock, fpsTimer;
sf::RenderWindow window(sf::VideoMode(800, 600), "Too Slow");
//creating new array with 30000 triangles
sf::VertexArray shapes = newShape(30000);
window.setFramerateLimit(60);
while (window.isOpen()) {
//event to close window on close button
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear(sf::Color(50, 50, 50));
//no need for for now, as you can rotate them all in function and draw them together
rotate(shapes,5);
window.draw(shapes);
window.display();
float dt = dtClock.restart().asSeconds();
if (fpsTimer.getElapsedTime().asSeconds() > 1) {
fpsTimer.restart();
std::cout << ((1.0 / dt > 60) ? 60 : (1.0 / dt)) << std::endl;
}
}
}
The bottleneck now is not drawing but rotating, i tested this code with 100000 triangles and im getting around 45 fps. With 1000000 i am getting bad framerate because of rotation.

Related

How to remove green pixels from character green screen

I am a beginner with C++, so please forgive me, but I am trying to bring two images together. One of them is a character in a green screen and a real life location. I was able to successfully mix the two images together on Visual Studio, however, I'm trying to remove the green pixels from the character and replace them with the background image. How can I use the green pixels. Any form of help would be much appreciated. Here is my code:
#include <iostream>
#include <SFML/Graphics.hpp>
using namespace sf;
using namespace std;
int main()
{
Texture tex;
if (!tex.loadFromFile("yoda.png")) {
cout << "Couldn’t Load Yoda Image";
exit(1);
}
Texture tex2;
if (!tex2.loadFromFile("prague.png")) {
cout << "Couldn’t Load Prague Image";
exit(1);
}
Image yodaImage;
yodaImage = tex.copyToImage();
Image pragueImage;
pragueImage = tex2.copyToImage();
RenderWindow window(VideoMode(1024, 800), "Assignment 3");
Sprite sprite;
sprite.setTexture(tex);
Vector2u textureSize = tex.getSize();
sprite.setPosition((1024 - textureSize.x) / 2,
(800 - textureSize.y) / 2);
Vector2u sz = yodaImage.getSize();
for (int y = 0; y < sz.y; y++) {
for (int x = 0; x < sz.x; x++) {
Color pragueC = pragueImage.getPixel(x,y);
Color yodaC = yodaImage.getPixel(x,y);
Color mixedC(
pragueC.r / 2 + yodaC.r / 2,
pragueC.g / 2 + yodaC.g / 2,
pragueC.b / 2 + yodaC.b / 2 );
yodaImage.setPixel(0, 0, mixedC);
}
}
tex.loadFromImage(yodaImage);
window.clear();
window.draw(sprite);
window.display();
while (true);
}

How to prevent objects from intersecting in C++?

I have been working on creating a pong game in C++ and am facing issues with how my ball and paddle collide. Virtually, when the ball hits the paddle from the top, the ball bounces off. But if the ball hits the paddle from the side, the ball slides along the paddle like its caught and only bounces back off once it reaches the other end. I've tried messing around with the dimensions of the collision box for the paddle and using the clock method to try and count how long the ball has been colliding with the paddle, and neither have worked. Can anyone help out with this?
My main code:
#include "RealBat.h"
#include <sstream>
#include <string>
#include <cstdlib>
#include <SFML/Graphics.hpp>
#include <iostream>
using namespace sf;
int windowWidth = 1024;
int windowHeight = 868;
Bat bat(windowWidth / 2, windowHeight - 50);
sf::Vector2f ballUpdate(sf::Vector2f ballPosition, int windowWidth, int windowHeight, float *velocityX, float *velocityY){
sf::Vector2f ballPosition2 = sf::Vector2f(ballPosition.x, ballPosition.y);
ballPosition2.x += *velocityX;
ballPosition2.y += *velocityY;
if (ballPosition2.x >= windowWidth || ballPosition2.x <= 0) {
*velocityX *= -1.0f;
}
if (ballPosition2.y >= windowHeight || ballPosition2.y <= 0) {
*velocityY *= -1.0f;
}
int ballBottomLeft = ballPosition2.y + 9;
int ballRight = ballPosition2.x + 9;
int batRight = bat.getVector().x + 100;
int score = 0;
if (ballBottomLeft >= bat.getVector().y) {
if (ballPosition2.x >= bat.getVector().x && ballRight <= batRight) {
std::cout << "collisionTracker" << std::endl;
*velocityY *= -1.0f;
score = score + 1;
}
else {
score = 0;
}
return ballPosition2;
}
int main()
{
float velocityX = 0.05;
float velocityY = 0.05;
sf::Vector2f position = sf::Vector2f(10, 10);
RectangleShape ball = RectangleShape();
ball.setSize(sf::Vector2f (10, 10));
RenderWindow window(VideoMode(windowWidth, windowHeight), "ID PONG");
while (window.isOpen()) {
Event event;
while (window.pollEvent(event)) {
if (event.type == Event::Closed) {
window.close();
}
}
if (Keyboard::isKeyPressed(Keyboard::Left)) {
bat.moveLeft();
}
else if (Keyboard::isKeyPressed(Keyboard::Right)) {
bat.moveRight();
}
bat.update();
position = ballUpdate(position, windowWidth, windowHeight, &velocityX, &velocityY);
ball.setPosition(position);
window.clear(Color(148, 213, 0, 255));
window.draw(bat.getShape());
window.draw(ball);
window.display();
}
}
My Bat Header File:
#pragma once
#include <SFML/Graphics.hpp>
using namespace sf;
class Bat {
private:
Vector2f position;
RectangleShape batShape;
float batSpeed = .3f;
public:
Bat(float startX, float startY);
FloatRect getPosition();
RectangleShape getShape();
Vector2f getVector();
void moveLeft();
void moveRight();
void update();
};
My Bat cpp:
#include "RealBat.h"
Bat::Bat(float startX, float startY)
{
position.x = startX;
position.y = startY;
batShape.setSize(sf::Vector2f(100, 10));
batShape.setPosition(position);
}
FloatRect Bat::getPosition()
{
return batShape.getGlobalBounds();
}
RectangleShape Bat::getShape()
{
return batShape;
}
void Bat::moveLeft()
{
position.x -= batSpeed;
}
void Bat::moveRight()
{
position.x += batSpeed;
}
void Bat::update()
{
batShape.setPosition(position);
}
sf::Vector2f Bat::getVector () {
return position;
}
Most likely what is happening is that the ball is still within the boundary of the bat on the next time round the loop.
Picture the ball and bat like this terrible ascii representation (+ for bat, 0 for ball), ball has just hit bats right side, heading down and to the left
+--------------+
+ +
+ O
+ +
+--------------+
balls y velocity will be reversed and next iteration it will be
+--------------+
+ O+
+ +
+ +
+--------------+
This is still inside the ball's boundary, so its y velocity will be reversed again giving
+--------------+
+ +
+ O +
+ +
+--------------+
So the ball will bounce up and down inside the bat until it hits the left hand edge.
+--------------+
+ O O O O O O O+
+O O O O O O O O
+ +
+--------------+
One way to fix this would be to only reverse the Y velocity if the ball is heading down, that way the ball would just continue upwards even though it is still inside the bat. Possibly by adding:
if (*velocityY > 0.0) {
just above the collisionTracker cout call, so you don't score or bounce when heading upwards.
I had something of a similar issue when I made my pong game years ago. This is because we only check intersection and change the direction of velocity in x-axis. So when it hit from the sides the velocity is constantly inverting every frame. The way I solved it at the time was by checking the position of the center of my ball with respect to the bounds (sides) of the paddle. So, when the ball is hitting any of the sides I simply invert the direction of velocity in the y-axis. This way the ball hits the paddle and just moves away.
I'm pretty sure there's better ways to do it but I was only a beginner at the time and this was the best I could come up with.

Visual Studio 2017 Exception Unhandled

I'm a novice programmer trying to follow a tuturial on recreating a game on Steam called Timber(I believe). The program was working fine and I had almost completed the tutrial, but I ran into trouble when I added the code:
for (int i = 0 < NUM_BRANCHES; i++;)
{
branches[i].setTexture(textureBranch);
branches[i].setPosition(-2000, -2000);
// Set the sprite's origin to dead center
// We can then spin it around without changing its position
branches[i].setOrigin(220, 20);
}
Visual Studio says: branches[i].setPosition(-2000, -2000); Unhandled exception thrown: write access violation.
this was 0x59AF28. occurred
I'll also post the full code, apologies that it's a bit messy.
#include "stdafx.h"
#include <sstream>
#include <SFML\Graphics.hpp>
using namespace sf;
// Function declaration
void updateBranches(int seed);
const int NUM_BRANCHES = 6;
Sprite branches[NUM_BRANCHES];
// Where is the player/branch?
// Left or right
enum class side{ LEFT, RIGHT, NONE };
side branchPositions[NUM_BRANCHES];
int main()
{
// Creates a video mode object
VideoMode vm(1920, 1080);
// Creates and opens a window for the game
RenderWindow window(vm, "Timber!!!", Style::Fullscreen);
// Create a texture to hold a graphic on the GPU
Texture textureBackground;
// Load a graphic into the texture
textureBackground.loadFromFile("graphics/background.png");
// Create a sprite
Sprite spriteBackground;
// Attach the texture to the sprite
spriteBackground.setTexture(textureBackground);
// Set the spriteBackground to cover the screen
spriteBackground.setPosition(0, 0);
// Make a tree sprite
Texture textureTree;
textureTree.loadFromFile("graphics/tree.png");
Sprite spriteTree;
spriteTree.setTexture(textureTree);
spriteTree.setPosition(810, 0);
// Prepare the bee
Texture textureBee;
textureBee.loadFromFile("graphics/bee.png");
Sprite spriteBee;
spriteBee.setTexture(textureBee);
spriteBee.setPosition(0, 450);
// Is the be currently moving?
bool beeActive = false;
// How fast can the bee fly
float beeSpeed = 0.0f;
// Make 3 cloud sprites from 1 texture
Texture textureCloud;
// Load 1 new texture
textureCloud.loadFromFile("graphics/cloud.png");
// 3 new sprites with the same texture
Sprite spriteCloud1;
Sprite spriteCloud2;
Sprite spriteCloud3;
spriteCloud1.setTexture(textureCloud);
spriteCloud2.setTexture(textureCloud);
spriteCloud3.setTexture(textureCloud);
// Position the clouds off screen
spriteCloud1.setPosition(0, 0);
spriteCloud2.setPosition(0, -150);
spriteCloud3.setPosition(0, -300);
// Are the clouds currently on the screen?
bool cloud1Active = false;
bool cloud2Active = false;
bool cloud3Active = false;
// How fast is each cloud?
float cloud1Speed = 0.1f;
float cloud2Speed = 0.2f;
float cloud3Speed = 0.3f;
// Variables to control time itself
Clock clock;
// Time bar
RectangleShape timeBar;
float timeBarStartWidth = 400;
float timeBarHeight = 80;
timeBar.setSize(Vector2f(timeBarStartWidth, timeBarHeight));
timeBar.setFillColor(Color::Red);
timeBar.setPosition((1920 / 2) - timeBarStartWidth / 2, 980);
Time gameTimeTotal;
float timeRemaining = 6.0f;
float timeBarWidthPerSecond = timeBarStartWidth / timeRemaining;
// Track whether the game is running
bool paused = true;
// Draw some text
int score = 0;
sf::Text messageText;
sf::Text scoreText;
// Font
Font font;
font.loadFromFile("fonts/KOMIKAP_.ttf");
// Set the font of our message
messageText.setFont(font);
scoreText.setFont(font);
// Assign the actual message
messageText.setString("Press Enter to Start!");
scoreText.setString("score = 0");
// Make text really big
messageText.setCharacterSize(75);
scoreText.setCharacterSize(100);
// Choose a color
messageText.setFillColor(Color::White);
scoreText.setFillColor(Color::Black);
// Position the text
FloatRect textRect = messageText.getLocalBounds();
messageText.setOrigin(textRect.left +
textRect.width / 2.0f,
textRect.top +
textRect.height / 2.0f);
messageText.setPosition(1920 / 2.0f, 1080 / 2.0f);
scoreText.setPosition(20, 20);
// Prepare 6 branches
Texture textureBranch;
textureBranch.loadFromFile("graphics/branch.png");
// Set the texture for each branch sprite
for (int i = 0 < NUM_BRANCHES; i++;)
{
branches[i].setTexture(textureBranch);
branches[i].setPosition(-2000, -2000);
// Set the sprite's origin to dead center
// We can then spin it around without changing its position
branches[i].setOrigin(220, 20);
}
while (window.isOpen())
{
/*
**************
Handles the player input
**************
*/
if (Keyboard::isKeyPressed(Keyboard::Escape))
{
window.close();
}
// Start the game
if (Keyboard::isKeyPressed(Keyboard::Return))
{
paused = false;
// Reset the time and the score
score = 0;
timeRemaining = 5;
}
/*
**************
Update the scene
**************
*/
if (!paused)
{
// Measure time
Time dt = clock.restart();
// Subtract from the amount of time remaining
timeRemaining -= dt.asSeconds();
// size up the time bar
timeBar.setSize(Vector2f(timeBarWidthPerSecond *
timeRemaining, timeBarHeight));
if (timeRemaining <= 0.0f)
{
// Pause the game
paused = true;
// Change the message shown to the player
messageText.setString("Out of time");
// Reposition the text base on its new size
FloatRect textRect = messageText.getLocalBounds();
messageText.setOrigin(textRect.left +
textRect.width / 2.0f,
textRect.top +
textRect.height / 2.0f);
messageText.setPosition(1920 / 2.0f, 1080 / 2.0f);
}
// Setup the bee
if (!beeActive)
{
// How fast is the bee
srand((int)time(0) * 10);
beeSpeed = (rand() % 400) + 350;
// How high is the bee
srand((int)time(0) * 10);
float height = (rand() % 650) + 850;
spriteBee.setPosition(1921, height);
beeActive = true;
}
else
// Move the bee
{
spriteBee.setPosition(
spriteBee.getPosition().x -
(beeSpeed * dt.asSeconds()),
spriteBee.getPosition().y);
// Has the bee reached the right hand edge of the screen?
if (spriteBee.getPosition().x < -100)
{
// Set it up ready to be a whole new bee next frame
beeActive = false;
}
}
// Manage the clouds
// Cloud 1
if (!cloud1Active)
{
// How fast is the cloud
srand((int)time(0) * 10);
cloud1Speed = (rand() % 150);
// How high is the cloud
srand((int)time(0) * 10);
float height = (rand() % 200);
spriteCloud1.setPosition(-300, height);
cloud1Active = true;
}
else
{
spriteCloud1.setPosition(
spriteCloud1.getPosition().x +
(cloud1Speed * dt.asSeconds()),
spriteCloud1.getPosition().y);
// Has the cloud reached the right hand edge of the screen?
if (spriteCloud1.getPosition().x > 1920)
{
// Set it up ready to be a whole new cloud next frame
cloud1Active = false;
}
}
// Cloud 2
if (!cloud2Active)
{
// How fast is the cloud
srand((int)time(0) * 20);
cloud2Speed = (rand() % 200);
// How high is the cloud
srand((int)time(0) * 20);
float height = (rand() % 300);
spriteCloud2.setPosition(-200, height);
cloud2Active = true;
}
else
{
spriteCloud2.setPosition(
spriteCloud2.getPosition().x +
(cloud1Speed * dt.asSeconds()),
spriteCloud2.getPosition().y);
// Has the cloud reached the right hand edge of the screen?
if (spriteCloud2.getPosition().x > 1920)
{
// Set it up ready to be a whole new cloud next frame
cloud2Active = false;
}
}
// Cloud 3
if (!cloud3Active)
{
// How fast is the cloud
srand((int)time(0) * 30);
cloud3Speed = (rand() % 250);
// How high is the cloud
srand((int)time(0) * 30);
float height = (rand() % 150);
spriteCloud3.setPosition(-100, height);
cloud3Active = true;
}
else
{
spriteCloud3.setPosition(
spriteCloud3.getPosition().x +
(cloud1Speed * dt.asSeconds()),
spriteCloud3.getPosition().y);
// Has the cloud reached the right hand edge of the screen?
if (spriteCloud3.getPosition().x > 1920)
{
// Set it up ready to be a whole new cloud next frame
cloud3Active = false;
}
}
// Update the score text
std::stringstream ss;
ss << "Score = " << score;
scoreText.setString(ss.str());
// Update the branch sprites
for (int i = 0; i < NUM_BRANCHES; i++)
{
float height = i * 150;
if (branchPositions[i] == side::LEFT)
{
// Move the sprite to the left side
branches[i].setPosition(610, height);
// Flip the sprite around the other way
branches[i].setRotation(180);
}
else if (branchPositions[i] == side::RIGHT)
{
// Move the sprite to the right side
branches[i].setPosition(1330, height);
// Set the sprite rotation to normal
branches[i].setRotation(0);
}
else
{
// Hide the branch
branches[i].setPosition(3000, height);
}
}
} // End if(!paused)
/*
**************
Draw the scene
**************
*/
// Clear everything from the last frame
window.clear();
// Draw our game scene here
window.draw(spriteBackground);
// Draw the clouds
window.draw(spriteCloud1);
window.draw(spriteCloud2);
window.draw(spriteCloud3);
// Draw the branches
for (int i = 0; i < NUM_BRANCHES; i++)
{
window.draw(branches[i]);
}
// Draw the tree
window.draw(spriteTree);
// Draw the insect
window.draw(spriteBee);
// Draw the score
window.draw(scoreText);
//Draw the timebar
window.draw(timeBar);
if (paused)
{
// Draw our message
window.draw(messageText);
}
// Show everyting we just drew
window.display();
}
return 0;
}
// Function definition
void updateBranches(int seed)
{
// Move all the branches down on place
for (int j = NUM_BRANCHES - 1; j > 0; j--)
{
branchPositions[j] = branchPositions[j - 1];
}
// Spawn a new branch at postion 0
// LEFt, RIGHT, NONE
srand((int)time(0) + seed);
int r = (rand() % 5);
switch (r)
{
case 0:
branchPositions[0] = side::LEFT;
break;
case 1:
branchPositions[0] = side::RIGHT;
break;
default:
branchPositions[0] = side::NONE;
break;
}
}
setPosition() requires an sf::Vector2f. So you could fix your code by changing that line to:
branches[i].setPosition(sf::Vector2f(-2000, -2000))

How to make an increment followed by a decrement continuously?

I am trying to obtain an increment that goes up from 0 to n, then decreases from n-1 to 0, and repeats the cycle over and over.
In this example written in Processing, I would like the background to go from black(i=0) to white(i=255) incrementally then white to black incrementally and so forth. Now I only get it to go from black to white, and then it comes back to black suddenly.
int i = 0;
void setup(){
size(640, 360);
frameRate(60);
}
void draw(){
background(i);
i++;
if(i==256){i=0;}
}
Try -
int change = 1;
void draw(){
background(i);
i = i + change;
if(i==256){change = -1;}
if(i==0){change = 1;}
}
Another way to look at this question would be: "How could I draw a triangle wave?".
I like this way cause it does not need "ifs". Some thing like this would do.
triangleWave = maxNumber - abs(incrementedVar % (2*maxNumber) - maxNumber);
Coll, isn't it?
I have this old code using this, it's not drawing the wave, but using it for size and fill color. Also there is a sine wave for comparision. Check it out:
float zigZag, toIncrement, speed =1, maxNumber = 255;
float sine, x = 270, speed2 = 1;
void setup() {
size(800, 400);
background(255);
}
void draw() {
background(255);
//triangle wave
toIncrement+=speed;
zigZag = maxNumber - abs(toIncrement % (2*maxNumber) - maxNumber);
fill(zigZag);
noStroke();
ellipse( 150, height/2+100, 50, 50);
strokeWeight(zigZag);
stroke(0);
line(100, height/2-100, 200, height/2-100);
text("triangle = " + int(zigZag), 100, height-30);
println("triangle wave value = " + zigZag);
//sine wave
x+=speed2;
sine = (1+sin(radians(x)))*(maxNumber/2);
fill(sine);
noStroke();
ellipse( 650, height/2+100, 50, 50);
strokeWeight(sine);
stroke(0);
line(600, height/2-100, 700, height/2-100);
fill(80);
text("sine = " + int(sine), 600, height-30);
}

2D water shader in SFML

I want to implement the algorithm for a 2D water surface described here and here.
But instead of using two int arrays and calculating on the CPU I would like to use SFML's sf::RenderTexture's (FBO's basically) and a GLSL shader to run everything on the GPU. I want to use SFML, because it's so simple and I have worked with it before, so I know my way around it a little.
I've made some good progress so far. I was able to set up 3 sf::RenderTextures and ping-pong between them correctly (because other than int array you can't read and write to the same sf::RenderTexture at the same time). I was also able to adapt the algorithm for the height field creation form being in the range -32.767 to 32.767 to the range 0 to 1 (or to be more precise -0.5 to 0.5 for the calculation). Also adding new ripples works to some extend. So up to this point you can actually see a little of waves going on.
Here comes my problem now: The waves disappear really, really fast and I don't even apply any damping yet. According to the algorithm the ripples are not stopping if there is no damping applied. It's even the other way around. If I apply "amplification" the waves look close to what you would expect them to look like (but they still disappear without any damping applied to them). My first thought was that this is, because I use float's in range 0 - 1 instead of integers, but I only see this being a problem if multiplication is used, but I only use addition and subtraction.
Here is my SFML C++ 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;
}
And here is my GLSL shader code :
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;
}
Please remember that this is all just about the height field creation. There is no shading of the water yet.
Do you know why the waves disappear by them self without damping?
If I am reading the code correctly you sample the previous frame for the texture's colors/height and use four neighboring pixels/texels to determine the color/height of the current pixel.
As you are calculating (scaling) these neighbors you might run into missing the texel that contains the color/height you are looking for. It might not be the heighest texel, just one next to it a little bit lower causing the unexpected damping.
This is where you do not just use addition and subtraction:
const float pixelSize = 1.0 / textureSize;
By using this value you could just miss the texel you are looking for.
EDIT
Also: you are averaging the samples so the result will always be less than the maximum value of the samples. So instead of averaging you could select the maximum value. That might give weird results but also extra insight.
Here are some "Processing" codes which implements the same algorithm you've posted above, and its damping is correct, I hope you can get some points from it :
// 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