2013-03-25 50 views
5

我開發的C++一個簡單的基於精靈的2D遊戲,使用OpenGL硬件加速渲染的動態數字,以及SDL的窗口管理和用戶輸入處理。由於它是2D遊戲,我只需要繪製四邊形,但因爲精靈的數量是動態的,所以我永遠不能依賴於四邊形的數量不變。因此,我需要通過VBO的每一幀重新緩衝所有頂點數據(因爲可能有更多或更少的四邊形比最後一幀中的四邊形,因此緩衝區可能是不同的大小)。是它更有效地使用GL_TRIANGLE_STRIP或索引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()調用。最簡單的優化:保留一個髒標誌,該標誌在自上次glBufferData()調用後調用createQuad/removeQuad並僅在標誌設置時重新創建緩衝區時存儲。 – Dirk 2013-03-25 15:14:35

+0

這是一個很好的建議,謝謝!我一定會執行它。 – artisticdude 2013-03-25 17:45:52

回答

3

使用未索引的GL_QUADS/GL_TRIANGLESglDrawArrays()調用可能會很好。


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

Don't do that

返回的表面由SDL_Quit釋放一定不能被呼叫者被釋放。此規則還包括連續調用SDL_SetVideoMode(即調整大小或更改分辨率),因爲現有曲面將自動釋放。


編輯:簡單頂點數組演示:

// 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 2013-03-25 17:44:31

+0

也許,除非你想移動並繪製> 20,000個四邊形/三角形對。 – genpfault 2013-03-25 17:50:04

+0

給我幾個,我可以掀起一個演示,你可以試試你的硬件。 – genpfault 2013-03-25 17:51:21