Hallo, Ich programmiere inzwischen schon seit über einem Jahr C++ (davor habe ich auch mal mit Delphi gespielt), bin aber relativ neu im Bereich OpenGL. Ich arbeite im Moment an einem Projekt, bei dem ich ziemlich viele Polygone (ca 3 bis 60 millionen) pro Frame (~60fps) anzeigen lassen möchte. Im Moment bin ich bei etwa 2 fps, wobei die CPU anscheinend die Grafikleistung erheblich vermindert (100% CPU Auslastung, wo hingegen Ein- und Ausschalten von Antialiasing und Backfaceculling keinen unterschied macht.)
Was gezeichnet werden soll: Ich extrahiere eine Map und ein Tileset aus einem Spiel. Die Map ist ungefähr 100x150 Tiles groß. Jedes Tile bestand aus ganz vielen kleinen Würfeln (max 16x16x128, waren früher einmal Voxel), wobei die Anzahl der Polygone so weit runterreduziert wurde, bis nur noch die Außenhaut des gesamten Tiles existierte (etwa 1000 bis 7000 Quads). Gezeichnet werden soll nun die gesamte Map (auf der die einzelnen Tiles logischerweise mehrmals an verschiedenen Stellen vorkommen). Es gibt insgesamt ~700 verschiedene Tiles. Außerdem sind die Tiles untexturiert bzw es werden ausschließlich Colorvalues pro Quad vergeben. Daher bestehen auch große ebene Flächen aus sehr vielen Polygonen. Die Polygonzahl zu verringern bringt einen hohen Performancegewinn, der aber durch die dann benötigten Texturen vermutlich wieder ausgeglichen wird. In dieser Richtung der Optimierung arbeite ich bereits.
Die einzelnen Tiles sind jeweils in einem eigenen VBO, allerdings muss ich um die gesamte Map anzuzeigen immerhin eine Schleife rund 15.000 mal durchgehen. Im Moment sieht die so aus:
Meine Frage ist nun, wie ich die Anzahl der Schleifendurchgänge reduzieren kann (am Besten auf einen). Im Wiki habe ich etwas von instancing gelesen. So könnte ich mit glDrawElementsInstanced die Anzahl der Schleifendurchgänge immerhin auf ~700 (für jedes Tile einen) reduzieren. Gibt es möglicherweise eine Funktion bzw Methode, die besser geeignet wäre (die ich z.B. nur einmal aufzurufen brauche)? Und lässt sich die Render-Funktion oben noch weiter optimieren bzw die Performance verbessern?
Instancing eignet sich nur wenn alle Ausgangsobjekte gleich sind. In dem Fall kann die Schleife auf die Grafikkarte verlegt werden, was einen kleinen Performance-Vorteil bringt. Aber ich glaube deine Tiles sind nicht gleich.
Bei deinem Code sehe ich folgende Optimierungsmöglichkeiten: 1. Verwende einen Interleaved-VBO, also einen Buffer wo Farbe und Position für jeden Vertex direkt hintereinander gespeichert werden. Dies kann den benötigten Speicherdurchsatz halbieren.
2. Führe ein Frustum-Culling aus. Wenn die Bounding-Box eines Tiles nicht sichtbar ist (z.B. weil hinter der Kamera) brauchst du diese auch nicht rendern.
3. Möglicherweise macht ein Occlusion-Culling Sinn, wenn sich die Tiles gegenseitig vollständig verdecken. Das geht relativ einfach mit Occlusion-Queries, da diese die Anzahl der gerenderten Pixel zählen. Bei jedem Tile des du renderst zählst du die Pixel. Wenn ein Tile im letzten Frame nicht sichtbar war renderst du nur die BoundingBox. Wenn mindestens ein Pixel der BoundingBox des Objektes sichtbar ist wird im nächsten Frame gerendert. Die "sichtbaren" Tiles renderst du natürlich zuerst und die BoundingBoxen dürfen später nicht sichtbar sein.
5. Ich nehme an die Position eines Tiles ist in jedem Frame gleich? Dann kannst du die Matrix vor berechnen und nur noch mit glLoadMatrixf laden statt diese ganze Berechnung zu machen:
Das glPushMatrix und glPopMatrix kann dann auch aus der Schleife raus.
6. glEnableClientState und glDisableClientState kannst du aus der Schleife raus ziehen.
7. Leg dir vielleicht ne Referenz auf objects3d.at(i).model an, also Model& model = objects3d.at(i).model; Dann musst du nicht immer im Array nach gucken.
Da du Instancing vorschlägst hast du wahrscheinlich eine recht aktuelle Grafikkarte. Hast du einen Geometrieshader? Möglicherweise ist ein Ansatz über Marchingcubes schneller. Also du speicherst die vielen kleinen Würfelchen in einer Volumentextur mit nur 1 bit pro Würfel, also gesetzt oder nicht. Die Geometrieshader generiert dann daraus mit dem Marchingcubes-Algorithmus zur Laufzeit ein Mesh. Hier ein Artikel dazu: http://http.developer.nvidia.com/GPUGem ... _ch01.html Allerdings versucht Marchingcubes das Mesh "rund" zu machen, also ggf. musst du das etwas modifizieren. (Dadurch wird es dann aber viel einfacher/schneller)
Registriert: Fr Jan 04, 2008 21:29 Beiträge: 419 Wohnort: Lübeck
wenn es sich um eine Map handelt, kann man auch Unmengen von Performance herrausholen, wenn man die Map unterteilt und nur die Boundingbox der einzelnen Unterteilungen testet. Wenn deine Map rechteckig/Quadratisch ist, kann man da super nen Quad oder Octtree implementieren. Sollten sich auch auf der Höhe (Y-Achse) der Map viele unterschiedliche Tiles befinden nimmt man eher den Octtree, ansonsten reicht der Quadtree. Ist deine Karte komplett unförmig, kann man auf einen BSP- oder KD-Tree ausweichen. Ziel des Ganzen ist, dass man das Frustum gegen die einzelnen unterteilten Bereiche prüft und dann alles zeichnet was in dem Bereich liegt, so kann man richtig viel Zeit sparen. Da deine Tiles ja nicht texturiert sind, sondern nur aus Vertices, Color und TriangleIndices bestehen kann man auch einfach alle Tiles hintereinanderweg in ein VBO speichern und sich lediglich merken, bei welchem Index ein Tile anfängt und bei welchem es aufhört, so muss man nicht ständig ein neues VBO binden. Das kostet ansonsten nähmlich ziemlich viel Zeit. Falls du das nicht möchtest, solltest du ggf. die Objekte nach VBO sortieren, damit du nicht bei jedem Schleifendurchlauf das VBO binden musst, obwohl die ganze Zeit Tiles vom selben Typ gerendert werden sollen. Ebenfalls könnte es von Vorteil sein, sich die Matrizen zum Ausrichten der Objekte vorzuberechnen. Ansich solltest du ja nicht so viele Möglichkeiten haben dein Tile zu drehen. Dann brauchst du, so wie Coolcat schon gesagt hat, die Matrix einfach nur laden und das Objekt an die entsprechende Stelle verschieben.
Das mit dem Marchingcubes ist natürlich auch noch ne super Idee!
Vielen Dank für die vielen tollen Ideen! Einiges habe ich inzwischen versucht, und bin nun zu dem Ergebnis gekommen, dass die hohe CPU Auslastung von der hohen Auslastung der GPU (die wohl 100% ist) verursacht wird. Das verwenden von glDrawArraysInstancedARB hat selbst ohne Schleife die gleiche Framerate gebracht. Anscheinend ist im Moment die Framerate antiproportional zur Vertexanzahl. Zwar könnte ich mit Frustum Culling die Framerate erhöhen, doch dann wäre ich nicht mehr in der Lage die gesamte Karte anzuzeigen :/ Und den großen Performanceschub brächte es trotzdem noch nicht. Die Marching Cubes Sache werde ich als nächstes ausprobieren. Ich würde aber gerne wissen, ob das nicht eher mehr Performance "verbraucht", weil da ja jedes einzelne Voxel berechnet werden müsste. Dahingegen berechne ich ja derzeit nur die Außenform...
Marching Cubes wird wahrscheinlich mehr Performance verbrauchen. Immerhin: Du hast die Würfeln --> Du benutzt diese für Marching Cubes --> Du bekommst wieder die Würfeln Oder ich bin einfach zu blöd, um den Sinn davon zu erkennen. Kann natürlich auch sein. (ähm... LOD vielleicht?)
Allerdings glaube ich, dass du mit Frustum Culling durchaus einiges an Performance rausholen könntest (es sei denn du siehst immer die ganze Karte).
Zitat:
Die Polygonzahl zu verringern bringt einen hohen Performancegewinn, der aber durch die dann benötigten Texturen vermutlich wieder ausgeglichen wird. In dieser Richtung der Optimierung arbeite ich bereits.
Eigentlich sind Texturen recht schnell. Das Problem ist dann eher, dass du Texturen (theoretisch) nur dann benutzen kannst, wenn wenn der zu texturierende Bereich halbwegs eben ist. Wenn aber bei dir jedes Tile aus maximal 16x16x128 Würfeln besteht, bräuchtest du für eine 3D textur 128 KByte macht bei 700 Tiles < 128 MByte. Sollte also auf aktuellen Karten kein Problem sein. Wenn du schon instancing benutzt, sollte der Texturwechsel auch kein Problem mehr sein, weil die dinger ja schon geordnet sind. Und die Texturkoordinaten könntest du auch gleich im shader erzeugen. Inwieweit das jetzt wirklich sehr viel schneller wird, weiß ich nicht. Anders als das Marching Cubes dürfts allerdings nicht ganz so schwer zum implementieren sein.
ok. hab das mit den Texturen noch mal überdacht. Deinen bisherigen beiträgen nach scheinst du nicht unbedingt die aktuellste Karte zu haben (Vertexlimitiert, und nicht füllratenlimitiert oder bandbreitenlimitiert. falls das mit den 3 millionen - 60 millionen nicht bloß eine ungenaue Schätzung ist... 3 Millionen bei 2 FPS schafft meine Karte ja fast im Immediate Mode... ok, leicht übertrieben ) Damit können die (nicht ganz) 128 MByte schon ein Problem werden...
Oder ich bin einfach zu blöd, um den Sinn davon zu erkennen. Kann natürlich auch sein. (ähm... LOD vielleicht?)
Der Grundgedanke ist das die Rechenleistung der GPU höher ist als der Speicherdurchsatz. Du musst nämlich pro Würfel nur noch 1 Bit speichern und nicht 8 Vertices (mit Indices) oder gar 24 Vertices (ohne Indices). Jeder Vertex hat ja schon mindestens 12 Byte. Und ja....von der Volumentextur kann man auch ein Mipmap-Volumen erzeugen und so LOD erreichen.
Des weiteren kommen bei Marching Cubes keine Würfel raus sondern es wird versucht das tatsächliche Volumen zu rekonstruieren. Der Algorithmus ist schon ziemlich alt, ursprünglich war der gedacht um aus CRT-Scans eines Schädels (etc.) ein renderbares Dreiecksmesh zu generieren.
Man kann die Idee aber so vereinfachen, dass einfach überall da ein Quad erzeugt wird wo ein 0 Bit an ein 1 Bit grenzt. (Dann hat es mit Marching Cubes natürlich nicht mehr viel zu tun...)
Mitglieder in diesem Forum: 0 Mitglieder und 2 Gäste
Du darfst keine neuen Themen in diesem Forum erstellen. Du darfst keine Antworten zu Themen in diesem Forum erstellen. Du darfst deine Beiträge in diesem Forum nicht ändern. Du darfst deine Beiträge in diesem Forum nicht löschen. Du darfst keine Dateianhänge in diesem Forum erstellen.