2013-03-25 20 views
5

Tôi đang phát triển trò chơi 2D dựa trên sprite đơn giản trong C++ sử dụng OpenGL cho hiển thị tăng tốc phần cứng và SDL cho quản lý cửa sổ và người dùng xử lý đầu vào. Vì đó là một game 2D, tôi sẽ chỉ cần vẽ quad, nhưng vì số lượng sprites là động, tôi không bao giờ có thể dựa vào đó là một số không đổi của quad. Do đó, tôi cần phải rebuffer tất cả các dữ liệu đỉnh thông qua VBO của tôi mỗi khung (vì có thể có nhiều hơn hoặc ít hơn quads hơn có trong khung cuối cùng, và do đó bộ đệm có thể là một kích thước khác nhau).Có hiệu quả hơn khi sử dụng GL_TRIANGLE_STRIP hoặc GL_TRIANGLES được lập chỉ mục để vẽ một số động của Quads

Chương trình mẫu thử nghiệm cho đến nay tạo cửa sổ và cho phép người dùng thêm và xóa quad trong hàng chéo bằng cách sử dụng các phím mũi tên lên và xuống. Ngay bây giờ các quads tôi vẽ là hình vuông màu trắng đơn giản, không được kết cấu. Đây là mã Tôi đang làm việc với (biên dịch và làm việc một cách chính xác theo OS X 10.6.8 và Ubuntu 12.04 với 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; 
} 

Tại thời điểm tôi đang sử dụng GL_TRIANGLE_STRIPS với glMultiDrawArrays() để render của tôi quads. Điều này làm việc, và có vẻ là khá phong nha về hiệu suất, nhưng tôi phải tự hỏi liệu sử dụng GL_TRIANGLES kết hợp với một IBO để tránh các đỉnh trùng lặp sẽ là một cách hiệu quả hơn để render? Tôi đã thực hiện một số nghiên cứu và một số người cho rằng chỉ số GL_TRIANGLES thường vượt trội hơn GL_TRIANGLE_STRIPS, nhưng chúng dường như giả định rằng số lượng quad sẽ vẫn không đổi, và do đó kích thước của VBO và IBO sẽ không phải được rebuffered mỗi khung . Đó là sự do dự lớn nhất của tôi với GL_TRIANGLES được lập chỉ mục: nếu tôi đã thực hiện GL_TRIANGLES được lập chỉ mục, tôi sẽ phải xóa toàn bộ bộ đệm chỉ mục mỗi khung ngoài việc tái tạo toàn bộ VBO mỗi khung hình, một lần nữa vì số lượng động của quad. Vì vậy, về cơ bản, câu hỏi của tôi là: Cho rằng tôi phải rebuffer tất cả các dữ liệu đỉnh của tôi để GPU mỗi khung do số động của quads, nó sẽ là hiệu quả hơn để chuyển sang chỉ số GL_TRIANGLES để vẽ quads , hoặc tôi nên gắn bó với việc thực hiện GL_TRIANGLE_STRIP hiện tại của tôi?

+1

tôi nghĩ trước khi bạn phải lo lắng về GL_TRIANGLES so với GL_TRIANGLE_STRIP, bạn nên giảm thiểu các cuộc gọi glBufferData() của mình.Tối ưu hóa dễ nhất: giữ một lá cờ bẩn để lưu trữ nếu createQuad/removeQuad được gọi kể từ lần gọi glBufferData() cuối cùng và chỉ tạo lại bộ đệm khi cờ được đặt. – Dirk

+0

Đó là một gợi ý tuyệt vời, cảm ơn! Tôi chắc chắn sẽ thực hiện nó. – artisticdude

Trả lời

3

Có thể bạn sẽ ổn khi sử dụng số không được lập chỉ mục GL_QUADS/GL_TRIANGLES và cuộc gọi glDrawArrays().


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

Don't do that:

Bề mặt trở lại được giải phóng bởi SDL_Quitkhông được giải thoát bởi người gọi. Quy tắc này cũng bao gồm các cuộc gọi liên tiếp đến SDL_SetVideoMode (tức là thay đổi kích thước hoặc thay đổi độ phân giải) bởi vì bề mặt hiện tại sẽ được phát hành tự động.


EDIT: Simple mảng đỉnh demo:

// 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; 
} 

Bạn có thể có được hiệu suất đàng hoàng với mảng chỉ đỉnh.

Lý tưởng nhất là tất cả/dt s của bạn phải là < = 16 mili giây.

+0

Cảm ơn lời khuyên SDL, điều cần biết! Tôi muốn tránh GL_QUADS nếu có thể, vì nó không còn được sử dụng trong các triển khai OpenGL hiện đại. Vì vậy, nếu tôi đi với GL_TRIANGLES không được lập chỉ mục, việc thêm 2 đỉnh cho mỗi quad có thể sẽ không có bất kỳ tác động đáng kể nào đến hiệu năng? – artisticdude

+0

Có thể, trừ khi bạn đang tìm cách di chuyển và vẽ> 20.000 quad/tam giác-cặp. – genpfault

+0

Gimme một vài và tôi có thể whip lên một bản demo bạn có thể thử trên phần cứng của bạn. – genpfault

Các vấn đề liên quan