Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

sw1

Frischling

  • »sw1« ist der Autor dieses Themas
  • Private Nachricht senden

1

07.04.2016, 17:37

(C++ SFML) Particle System, maximale Anzahl an Partikeln

Hallo liebes spieleprogrammierer.de Forum,

ich schreibe zur Zeit eine Klasse für ein Partikel System mit SFML. Es funktioniert soweit auch alles gut.
Meine Frage ist nun, wieviele Partikel so ein System maximal aufnehmen können sollte.
Bei mir falle ich ab ca. 3000 Partikel unter 60 FPS, ab ca. 10000 Partikel bin ich unter 15 FPS. Ich habe schon versucht die Update-Funktionen (Berechnung der Lebenszeit der Partikel und den sf::VertexArray Container neu bestücken) in mehreren Threads laufen zu lassen, d.h. ich habe mehrere Objekte der Klasse particleSystem erstellt (von denen jedes Objekt z.b. 1000 Partikel aufnehmen konnte) und hier die Update-Funktionen der einzelnen Objekte separat in Threads laufen lassen. Dies brachte aber keine verbesserung, es verschlechterte die Lage ab ca. 10 Threads eher.

Hier mal meine Klasse:

Header Datei:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct particle
{
    enum Type
    {
        fire,
        typeCount
    };

    sf::Vector2f position;
    sf::Time leftLifetime;
};


class particleSystem : public sf::Drawable
{
public:
    particleSystem(particle::Type mType);

    void update(sf::Time timePerFrame);
    void addParticle(sf::Vector2f position);
    

private:
    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
    void updateVertices() const;
    void addVertex(sf::Vector2f worldPos, sf::Vector2f texCoords, float alphaValue) const;


    particle::Type mType;
    sf::Time particleLifetime;
    const sf::Texture* mTexture;

    std::deque<particle> particles;
    mutable sf::VertexArray vertices;
};


.cpp Datei:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
particleSystem::particleSystem(particle::Type mType)
    :
    mType(mType),
    mTexture(nullptr),
    vertices(sf::PrimitiveType::Quads)
{
    switch (mType)
    {
    case particle::fire:
    {
        mTexture = &Resources::get()->getResource(Holder::particles, Texture::fire);
        particleLifetime = sf::seconds(1.25f);
        break;
    }
    }
}


void particleSystem::update(sf::Time timePerFrame)
{
    for (auto& particle : particles)
    {
        particle.leftLifetime -= timePerFrame;
    }
    while (!particles.empty() && particles.front().leftLifetime <= sf::Time::Zero) particles.pop_front();
}


void particleSystem::addParticle(sf::Vector2f position)
{
    particle newParticle;
    newParticle.position = position;
    switch (mType)
    {
    case particle::fire: newParticle.leftLifetime = particleLifetime; break;
    }

    particles.push_back(newParticle);
}


void particleSystem::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
    updateVertices();
    states.texture = mTexture;
    target.draw(vertices, states);
}


void particleSystem::updateVertices() const
{
    sf::Vector2f textureSize(mTexture->getSize());
    sf::Vector2f halfTextureSize(textureSize / 2.f);

    vertices.clear();

    for (auto& particle : particles)
    {
        sf::Vector2f pos = particle.position;
        float alphaValue = va::max(0.f, 255.f * particle.leftLifetime.asSeconds() / particleLifetime.asSeconds());

        addVertex(sf::Vector2f(pos.x - halfTextureSize.x, pos.y - halfTextureSize.y), sf::Vector2f(0, 0), alphaValue);
        addVertex(sf::Vector2f(pos.x + halfTextureSize.x, pos.y - halfTextureSize.y), sf::Vector2f(textureSize.x, 0), alphaValue);
        addVertex(sf::Vector2f(pos.x + halfTextureSize.x, pos.y + halfTextureSize.y), sf::Vector2f(textureSize.x, textureSize.y), alphaValue);
        addVertex(sf::Vector2f(pos.x - halfTextureSize.x, pos.y + halfTextureSize.y), sf::Vector2f(0, textureSize.y), alphaValue);
    }
}


void particleSystem::addVertex(sf::Vector2f worldPos, sf::Vector2f texCoords, float alphaValue) const
{
    sf::Vertex newVertex;

    newVertex.color = sf::Color(255, 255, 255, alphaValue);
    newVertex.position = worldPos;
    newVertex.texCoords = texCoords;

    vertices.append(newVertex);
}



Ist diese Anzahl an maximalen Partikeln so normal oder stimmt bei mir etwas nicht?

TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

2

07.04.2016, 18:59

Du hast ein übliches Problem mit Partikelsystemen gefunden. Ist halt so, dass der Aufwand mit Anzahl der Partikel steigt. Ich denke, hiermit fährst du ganz gut:

1) Gib eine maximale Anzahl an Partikeln an. Wenn man mehr als einige 1000 hat, dann bezweifle ich, dass die bei nicht kompletter Verteilung auf den Bildschirm einen großen Unterschied machen. Der User merkt es garantiert nicht. Die ältesten sterben halt und werden durch die neuen angeforderten ersetzt, wenn du an der Grenze der maximalen Anzahl bist.
2) Mehrere Threads finde ich nicht so toll, wenn dann sollten alle Partikelsystem-Instanzen in einem Hintergrundthread verarbeitet werden, du hast ja in deinem Spiel noch mehr zu tun als nur Partikel zu rendern.
3) Lagere die Berechnungen auf die GPU aus. Da habe ich allerdings wenig Erfahrung.
4) Optimiere deine Partikelsystem auf Cachefreundlichkeit.

Ansonsten naja: Jedes Partikelsystem was ich kenne, gibt als Optimierung an: Weniger Partikel rendern :P

3

07.04.2016, 19:24

Dein Befüllen des Vectors in updateVertices ist sehr ineffizient. Dieser muss sehr viele mal realloziieren, daher rate ich zu einem reserve bzw rezize davor.

TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

4

07.04.2016, 19:26

Das würde aber nur kurz ein hickup geben, und nicht permanent die Framerate niedrig halten oder? Unabhängig davon hast du natürlich recht ;)

5

07.04.2016, 19:27

Ich vermutete, updateVerticed würde pro Frame aufgerufen.

6

07.04.2016, 19:28

was du meiner Meinung nach noch verbessern könntest, wäre die update Methode.
Statt die absolute Zeit der Partikel zu errechnen und im update jeden Einzelnen zu verringern, würde ich die Differenz zum letzt erstellten speichern und somit nur die ersten x Partikel updaten, deren Zeit < FrameTime ist. Das würde sicherlich auch einen ordentlichen boost bringen ;)

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »anti-freak« (07.04.2016, 19:37)


TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

7

07.04.2016, 20:13

Ich vermutete, updateVerticed würde pro Frame aufgerufen.


Dann gebe ich dir aber recht, stimmt (habs übersehen!). Spricht ja aber für meinen Punkt 1), die Anzahl könnte man vorresevieren.

sw1

Frischling

  • »sw1« ist der Autor dieses Themas
  • Private Nachricht senden

8

07.04.2016, 22:02

Danke für die Antworten.

Zitat

Dein Befüllen des Vectors in updateVertices ist sehr ineffizient. Dieser muss sehr viele mal realloziieren, daher rate ich zu einem reserve bzw rezize davor.


Laut der SFML Seite wird der Speicher durch sf::VertexArray::clear() nicht freigegeben, entsprechent sollte der Speicher immer noch vom vorherigen Durchlauf zur Verfügung stehen. Ich habe es gerade trotzdem mal mit

C-/C++-Quelltext

1
vertices.resize(4 * particles.size());

versucht, ändert das Ergebnis aber nicht.

Zitat

4) Optimiere deine Partikelsystem auf Cachefreundlichkeit.


Was genau bedeutet Cachefreundlichkeit?

Zitat

3) Lagere die Berechnungen auf die GPU aus. Da habe ich allerdings wenig Erfahrung.


Habe ich auch schonmal was von gelesen, bisher aber nichts weiteres zu gefunden.

Zitat

Statt die absolute Zeit der Partikel zu errechnen und im update jeden Einzelnen zu verringern, würde ich die Differenz zum letzt erstellten speichern und somit nur die ersten x Partikel updaten, deren Zeit < FrameTime ist


Ich verstehe nicht was du genau meinst, kannst du das nochmal etwas ausführlicher aufschreiben? ?(

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »sw1« (07.04.2016, 22:08)


TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

9

07.04.2016, 22:11

Zitat

Was genau bedeutet Cachefreundlichkeit?


Daten / Programmcodee möglichst nacheinander im Speicher bei der Abarbeitung zu haben. Das macht sich bei vielen Operationen durchaus bemerkbar. Hierbei könnte das bedeuten, dass man an statt einer deque einen vector verwendet, um zu garantieren, dass die Daten nacheinander im Speicher liegen. Um sich beim "löschen" von daten kopieren spart, kann man beim löschen einfach das element am gelöschten index mit dem letzten aktiven element der liste tauschen. Muss man halt schauen, obs was bringt. Wichtig ist dann aber, dass im Vektor wirklich Objekte und keine Pointer auf Objekte gespeichert werden. außerdem sollten die Particle-Objekte nicht allzu groß sein, damit genügend Elemente in den Cache passen.

Zitat

Habe ich auch schonmal was von gelesen, bisher aber nichts zu gefunden.


Durch SFML bist du auch ein bisschen eingeschränkt. Gibt auch mehrere Wege, wie man das über die GPU beschleunigt: Geometry Shader (die sind in SFML aber glaube grad in der Entwicklung und nicht verfügbar), oder Compute Shader, damit habe ich aber auch nicht so die Erfahrung.

Ansonsten ist die Frage, ob du immer alle Daten in deinen Vertexdaten aktualisieren musst und vor allem wie SFML die Daten auf die Grafikkarte aktualisiert.

10

07.04.2016, 22:14

Zitat

Statt die absolute Zeit der Partikel zu errechnen und im update jeden Einzelnen zu verringern, würde ich die Differenz zum letzt erstellten speichern und somit nur die ersten x Partikel updaten, deren Zeit < FrameTime ist


Ich verstehe nicht was du genau meinst, kannst du das nochmal etwas ausführlicher aufschreiben? ?(


So wie ich das sehe, haben alle deine Partikel ein und die selbe Lebensdauer. D.h. du pushst den neuen Partikel wie jetzt auch hinten an deine Liste an. Aber statt die Lebensdauer auf z.B. 1000ms zu setzen, setzt du ihn auf die Differenz zur accumulierten Lebensdauer der bereits erstellten Partikel.
Solle heißen, du hast z.B. 3 Partikel mit den folgenden Lebensdauern: [100, 300, 100]
Das heißt, Partikel 1 lebt noch 100ms, 2 noch 400 und 3 noch 500.
Hoffe du kannst mir soweit folgen.
Jetzt möchtest du einen weiteren einfügen, der wie bereits oben angenommen, eine Lebensdauer von 1sec hat. Das sähe dann entsprechend so aus: [100, 300, 100, 500]

Nun kommen wir zu Schritt 2:
Statte jetzt im Update alle Partikel um die elapsed time zu reduzieren kannst du nun lediglich den 1. reduzieren.
Hast du z.B. eine elapsed time von 50 entsteht folgendes: [50, 300, 100, 500] -> Partikel1: 50; Partikel2: 350; Partikel3: 450; Partikel5: 950

Ist deine elapsed time größer als die verbleibende Lebenszeit des 1. Partikels ziehst du diese von der elapsed time ab, zerstörst Partikel 1 und überprüfst den nächsten.
Beispiel:
Elapsed time = 200
Wir betrachten die letzte Liste:
[50, 300, 100, 500] -> Partikel 1 hat eine geringere Lebenszeit, also subtrahieren wir die 50 von den 200 und erhalten 150ms für den Rest.
Am ende sieht die Liste dann so aus: [150, 100, 500]

Hoffe ich konnte helfen ;)

Werbeanzeige