Ich entwickle ein einfaches Sprite-basiertes 2D-Spiel in C++, das OpenGL für hardwarebeschleunigtes Rendering und SDL für Fensterverwaltung und Benutzer verwendet Eingabehandhabung. Da es sich um ein 2D-Spiel handelt, muss ich nur Quads zeichnen, aber da die Anzahl der Sprites dynamisch ist, kann ich mich nie darauf verlassen, dass es eine konstante Anzahl an Quads gibt. Folglich muss ich alle Vertex-Daten über meine VBO für jeden Frame neu puffern (da es mehr oder weniger Quadrate geben kann als im letzten Frame und daher kann der Puffer eine andere Größe haben).Ist es effizienter, GL_TRIANGLE_STRIP oder indizierte GL_TRIANGLES zu verwenden, um eine dynamische Anzahl von Quads zu zeichnen?
Das Prototyp-Programm Ich habe bisher ein Fenster erstellt und ermöglicht es dem Benutzer, Quads in einer diagonalen Reihe mit den Pfeiltasten nach oben und nach unten hinzufügen und entfernen. Im Moment sind die Quads, die ich zeichne, einfache, untexturierte weiße Quadrate. Hier ist der Code, den ich mit gerade arbeite (kompiliert und funktioniert einwandfrei unter OS X 10.6.8 und Ubuntu 12.04 mit OpenGL 2.1):
#if defined(__APPLE__)
#include <OpenGL/OpenGL.h>
#endif
#if defined(__linux__)
#define GL_GLEXT_PROTOTYPES
#include <GL/glx.h>
#endif
#include <GL/gl.h>
#include <SDL.h>
#include <iostream>
#include <vector>
#include <string>
struct Vertex
{
//vertex coordinates
GLint x;
GLint y;
};
//Constants
const int SCREEN_WIDTH = 1024;
const int SCREEN_HEIGHT = 768;
const int FPS = 60; //our framerate
//Globals
SDL_Surface *screen; //the screen
std::vector<Vertex> vertices; //the actual vertices for the quads
std::vector<GLint> startingElements; //the index where the 4 vertices of each quad begin in the 'vertices' vector
std::vector<GLint> counts; //the number of vertices for each quad
GLuint VBO = 0; //the handle to the vertex buffer
void createVertex(GLint x, GLint y)
{
Vertex vertex;
vertex.x = x;
vertex.y = y;
vertices.push_back(vertex);
}
//creates a quad at position x,y, with a width of w and a height of h (in pixels)
void createQuad(GLint x, GLint y, GLint w, GLint h)
{
//Since we're drawing the quads using GL_TRIANGLE_STRIP, the vertex drawing
//order is from top to bottom, left to right, like so:
//
// 1-----3
// | |
// | |
// 2-----4
createVertex(x, y); //top-left vertex
createVertex(x, y+h); //bottom-left vertex
createVertex(x+w, y); //top-right vertex
createVertex(x+w, y+h); //bottom-right vertex
counts.push_back(4); //each quad will always have exactly 4 vertices
startingElements.push_back(startingElements.size()*4);
std::cout << "Number of Quads: " << counts.size() << std::endl; //print out the current number of quads
}
//removes the most recently created quad
void removeQuad()
{
if (counts.size() > 0) //we don't want to remove a quad if there aren't any to remove
{
for (int i=0; i<4; i++)
{
vertices.pop_back();
}
startingElements.pop_back();
counts.pop_back();
std::cout << "Number of Quads: " << counts.size() << std::endl;
}
else
{
std::cout << "Sorry, you can't remove a quad if there are no quads to remove!" << std::endl;
}
}
void init()
{
//initialize SDL
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 0, SDL_OPENGL);
#if defined(__APPLE__)
//Enable vsync so that we don't get tearing when rendering
GLint swapInterval = 1;
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &swapInterval);
#endif
//Disable depth testing, lighting, and dithering, since we're going to be doing 2D rendering only
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glDisable(GL_DITHER);
glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT);
//Set the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1.0, 1.0);
//Set the modelview matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//Create VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
}
void gameLoop()
{
int frameDuration = 1000/FPS; //the set duration (in milliseconds) of a single frame
int currentTicks;
int pastTicks = SDL_GetTicks();
bool done = false;
SDL_Event event;
while(!done)
{
//handle user input
while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_KEYDOWN:
switch (event.key.keysym.sym)
{
case SDLK_UP: //create a new quad every time the up arrow key is pressed
createQuad(64*counts.size(), 64*counts.size(), 64, 64);
break;
case SDLK_DOWN: //remove the most recently created quad every time the down arrow key is pressed
removeQuad();
break;
default:
break;
}
break;
case SDL_QUIT:
done = true;
break;
default:
break;
}
}
//Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//replace the current contents of the VBO with a completely new set of data (possibly including either more or fewer quads)
glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(Vertex), &vertices.front(), GL_DYNAMIC_DRAW);
glEnableClientState(GL_VERTEX_ARRAY);
//Set vertex data
glVertexPointer(2, GL_INT, sizeof(Vertex), 0);
//Draw the quads
glMultiDrawArrays(GL_TRIANGLE_STRIP, &startingElements.front(), &counts.front(), counts.size());
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//Check to see if we need to delay the duration of the current frame to match the set framerate
currentTicks = SDL_GetTicks();
int currentDuration = (currentTicks - pastTicks); //the duration of the frame so far
if (currentDuration < frameDuration)
{
SDL_Delay(frameDuration - currentDuration);
}
pastTicks = SDL_GetTicks();
// flip the buffers
SDL_GL_SwapBuffers();
}
}
void cleanUp()
{
glDeleteBuffers(1, &VBO);
SDL_FreeSurface(screen);
SDL_Quit();
}
int main(int argc, char *argv[])
{
std::cout << "To create a quad, press the up arrow. To remove the most recently created quad, press the down arrow." << std::endl;
init();
gameLoop();
cleanUp();
return 0;
}
Im Moment bin GL_TRIANGLE_STRIPS mit glMultiDrawArrays bin mit() zu machen, meine Quads. Das funktioniert und scheint in Bezug auf die Leistung recht ordentlich zu sein, aber ich muss mich fragen, ob die Verwendung von GL_TRIANGLES in Verbindung mit einem IBO, um doppelte Scheitelpunkte zu vermeiden, eine effizientere Methode zum Rendern wäre? Ich habe einige Nachforschungen angestellt, und einige Leute behaupten, dass indexierte GL_TRIANGLES GL_TRIANGLE_STRIPS generell übertreffen, aber sie scheinen auch anzunehmen, dass die Anzahl der Quads konstant bleiben würde und somit die Größe des VBO und IBO nicht für jeden Frame neu gepuffert werden müsste . Das ist mein größtes Zögern mit indizierten GL_TRIANGLES: Wenn ich indizierte GL_TRIANGLES implementieren würde, müsste ich den gesamten Indexpuffer jeden Frame zusätzlich puffern, zusätzlich dazu, die gesamte VBO jedes Frames erneut zu puffern, wiederum wegen der dynamischen Anzahl von Quads.
Also meine Frage ist das: Da ich alle meine Vertex-Daten auf jeden Frame aufgrund der dynamischen Anzahl der Quads auf die GPU rebuffer muss, wäre es effizienter, zum indizierten GL_TRIANGLES zu wechseln, um die Quads zu zeichnen oder sollte ich bei meiner aktuellen GL_TRIANGLE_STRIP-Implementierung bleiben?
Ich denke, bevor Sie sich Sorgen machen müssen über GL_TRIANGLES gegen GL_TRIANGLE_STRIP sollten Sie Ihre glBufferData() Aufrufe minimieren.Einfachste Optimierung: Ein schmutziges Flag behalten, das speichert, wenn createQuad/removeQuad seit dem letzten Aufruf von glBufferData() aufgerufen wurde, und den Puffer nur neu erstellen, wenn das Flag gesetzt ist. – Dirk
Das ist ein großartiger Vorschlag, danke! Ich werde es sicherlich umsetzen. – artisticdude