2013-03-25 2 views
5

Я разрабатываю простую 2D-игру на основе спрайтов на C++, которая использует OpenGL для аппаратно-ускоренного рендеринга, а SDL для управления окнами и пользователя обработка ввода. Поскольку это 2D-игра, мне только понадобится рисовать квадроциклы, но поскольку количество спрайтов динамично, я никогда не могу полагаться на постоянное количество квадов. Следовательно, мне нужно откорректировать все данные вершин через мой VBO каждый кадр (поскольку может быть больше или меньше квадов, чем в последнем кадре, и, следовательно, буфер может быть другого размера).Является ли это более эффективным для использования GL_TRIANGLE_STRIP или Indexed GL_TRIANGLES для рисования динамического числа квадов

Прототип программы, которую я создал до сих пор, позволяет пользователю добавлять и удалять квадраты в диагональной строке с помощью клавиш со стрелками вверх и вниз. Прямо сейчас квадраты, которые я рисую, - простые, нетекстурированные белые квадраты. Вот код, я работаю с (компилируется и работает правильно под OS X 10.6.8 и Ubuntu 12.04 с 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; 
} 

На данный момент я использую GL_TRIANGLE_STRIPS с glMultiDrawArrays(), чтобы сделать мой квадрациклов. Это работает и, кажется, довольно прилично с точки зрения производительности, но мне нужно задаться вопросом, будет ли более эффективным способом рендеринга использование GL_TRIANGLES в сочетании с IBO, чтобы избежать дублирования вершин? Я провел некоторое исследование, и некоторые люди предположили, что индексированные GL_TRIANGLES, как правило, превосходят GL_TRIANGLE_STRIPS, но они также, по-видимому, предполагают, что количество квад будет оставаться постоянным, и, следовательно, размер VBO и IBO не нужно будет отказывать в каждом кадре , Это моя самая большая нерешительность с индексированными GL_TRIANGLES: если бы я реализовал индексированные GL_TRIANGLES, мне пришлось бы откорректировать весь индексный буфер каждый кадр в дополнение к отказу всего VBO каждого кадра, опять же из-за динамического числа квадов.

В основном, мой вопрос заключается в следующем: учитывая, что я должен перекомпонировать все мои данные вершин на GPU каждый кадр из-за динамического числа квадов, было бы более эффективным переключиться на индексированные GL_TRIANGLES, чтобы нарисовать квадратики , или я должен придерживаться моей текущей реализации GL_TRIANGLE_STRIP?

+1

Я думаю, прежде чем вам придется беспокоиться о GL_TRIANGLES против GL_TRIANGLE_STRIP, вы должны свести к минимуму ваши вызовы glBufferData().Простая оптимизация: держите грязный флаг, который хранится, если createQuad/removeQuad был вызван с момента последнего вызова glBufferData() и только воссоздает буфер при установке флага. – Dirk

+0

Это отличное предложение, спасибо! Я обязательно его осуществлю. – artisticdude

ответ

3

Вы, вероятно, будете в порядке, используя неиндексированные GL_QUADS/GL_TRIANGLES и вызов glDrawArrays().


SDL_Surface *screen; 
... 
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 0, SDL_OPENGL); 
... 
SDL_FreeSurface(screen); 

Don't do that:

Возвращенный поверхность освобождается от SDL_Quit и не должны быть освобождены от вызывающей. Это правило также включает последовательные вызовы в SDL_SetVideoMode (то есть изменение размера или разрешение), поскольку существующая поверхность будет выпущена автоматически.


EDIT: Простой массив вершин демо:

// g++ main.cpp -lglut -lGL 
#include <GL/glut.h> 
#include <vector> 
using namespace std; 

// OpenGL Mathematics (GLM): http://glm.g-truc.net/ 
#include <glm/glm.hpp> 
#include <glm/gtc/random.hpp> 
using namespace glm; 

struct SpriteWrangler 
{ 
    SpriteWrangler(unsigned int aSpriteCount) 
    { 
     verts.resize(aSpriteCount * 6); 
     states.resize(aSpriteCount); 

     for(size_t i = 0; i < states.size(); ++i) 
     { 
      states[i].pos = linearRand(vec2(-400, -400), vec2(400, 400)); 
      states[i].vel = linearRand(vec2(-30, -30), vec2(30, 30)); 

      Vertex vert; 
      vert.r = (unsigned char)linearRand(64.0f, 255.0f); 
      vert.g = (unsigned char)linearRand(64.0f, 255.0f); 
      vert.b = (unsigned char)linearRand(64.0f, 255.0f); 
      vert.a = 255; 
      verts[i*6 + 0] = verts[i*6 + 1] = verts[i*6 + 2] = 
      verts[i*6 + 3] = verts[i*6 + 4] = verts[i*6 + 5] = vert; 
     } 
    } 

    void wrap(const float minVal, float& val, const float maxVal) 
    { 
     if(val < minVal) 
      val = maxVal - fmod(maxVal - val, maxVal - minVal); 
     else 
      val = minVal + fmod(val - minVal, maxVal - minVal); 
    } 

    void Update(float dt) 
    { 
     for(size_t i = 0; i < states.size(); ++i) 
     { 
      states[i].pos += states[i].vel * dt; 
      wrap(-400.0f, states[i].pos.x, 400.0f); 
      wrap(-400.0f, states[i].pos.y, 400.0f); 

      float size = 20.0f; 
      verts[i*6 + 0].pos = states[i].pos + vec2(-size, -size); 
      verts[i*6 + 1].pos = states[i].pos + vec2( size, -size); 
      verts[i*6 + 2].pos = states[i].pos + vec2( size, size); 
      verts[i*6 + 3].pos = states[i].pos + vec2( size, size); 
      verts[i*6 + 4].pos = states[i].pos + vec2(-size, size); 
      verts[i*6 + 5].pos = states[i].pos + vec2(-size, -size); 
     } 
    } 

    struct Vertex 
    { 
     vec2 pos; 
     unsigned char r, g, b, a; 
    }; 

    struct State 
    { 
     vec2 pos; 
     vec2 vel;  // units per second 
    }; 

    vector<Vertex> verts; 
    vector<State> states; 
}; 

void display() 
{ 
    // timekeeping 
    static int prvTime = glutGet(GLUT_ELAPSED_TIME); 
    const int curTime = glutGet(GLUT_ELAPSED_TIME); 
    const float dt = (curTime - prvTime)/1000.0f; 
    prvTime = curTime; 

    // sprite updates 
    static SpriteWrangler wrangler(2000); 
    wrangler.Update(dt); 
    vector<SpriteWrangler::Vertex>& verts = wrangler.verts; 

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

    // set up projection and camera 
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity(); 
    double w = glutGet(GLUT_WINDOW_WIDTH); 
    double h = glutGet(GLUT_WINDOW_HEIGHT); 
    double ar = w/h; 
    glOrtho(-400 * ar, 400 * ar, -400, 400, -1, 1); 

    glMatrixMode(GL_MODELVIEW); 
    glLoadIdentity(); 

    glEnableClientState(GL_VERTEX_ARRAY); 
    glEnableClientState(GL_COLOR_ARRAY); 

    glVertexPointer(2, GL_FLOAT, sizeof(SpriteWrangler::Vertex), &verts[0].pos.x); 
    glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(SpriteWrangler::Vertex), &verts[0].r); 
    glDrawArrays(GL_TRIANGLES, 0, verts.size()); 

    glDisableClientState(GL_VERTEX_ARRAY); 
    glDisableClientState(GL_COLOR_ARRAY); 

    glutSwapBuffers(); 
} 

// run display() every 16ms or so 
void timer(int extra) 
{ 
    glutTimerFunc(16, timer, 0); 
    glutPostRedisplay(); 
} 

int main(int argc, char **argv) 
{ 
    glutInit(&argc, argv); 
    glutInitWindowSize(600, 600); 
    glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE); 
    glutCreateWindow("Sprites"); 

    glutDisplayFunc(display); 
    glutTimerFunc(0, timer, 0); 
    glutMainLoop(); 
    return 0; 
} 

Вы можете получить достойную производительность с только вершинных массивов.

В идеале большинство/всех ваших dt s должно быть < = 16 миллисекунд.

+0

Спасибо за подсказку SDL, приятно знать! Я предпочел бы избегать GL_QUADS, если это возможно, поскольку он устарел в современных реализациях OpenGL. Итак, если я перейду с неиндексированными GL_TRIANGLES, добавление дополнительных 2 вершин на квадрат, вероятно, не окажет существенного влияния на производительность? – artisticdude

+0

Возможно, если вы не хотите двигаться и рисовать> 20 000 квадроциклов/треугольников. – genpfault

+0

Дайте немного, и я могу взломать демоверсию, которую вы можете попробовать на своем оборудовании. – genpfault