c – 使用GL_TRIANGLE_STRIP或索引GL_TRIANGLES绘制动态数量的四
我在C中开发了一个简单的基于精灵的2D游戏,它使用OpenGL进行硬件加速渲染,使用SDL进行窗口管理和用户输入处理.由于它是2D游戏,我只需要绘制四边形,但由于精灵的数量是动态的,我永远不能依赖于有恒定数量的四边形.因此,我需要每帧通过我的VBO重新缓冲所有顶点数据(因为可能有比最后一帧更多或更少的四边形,因此缓冲区可能是不同的大小).
到目前为止我的原型程序创建了一个窗口,允许用户使用向上和向下箭头键在对角线中添加和删除四边形.现在我正在绘制的四边形是简单,无纹理的白色方块.这是我正在使用的代码(在OS X 10.6.8下编译并正常工作,在OpenGL 2.1下使用Ubuntu 12.04): #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,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,-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实施? 解决方法
使用未编入索引的GL_QUADS / GL_TRIANGLES和glDrawArrays()调用可能会很好.
SDL_Surface *screen; ... screen = SDL_SetVideoMode(SCREEN_WIDTH,SDL_OPENGL); ... SDL_FreeSurface(screen); Don’t do that:
编辑:简单的顶点数组演示: // 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,&verts[0].r ); glDrawArrays( GL_TRIANGLES,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,0 ); glutMainLoop(); return 0; } 只使用顶点数组就可以获得不错的性能. 理想情况下,你的大部分/全部dts应该<= 16毫秒. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |