/* vbo.c * * Kamil Kaminski * kkaminsk.com * * Example of using VBO * * */ #include #include #include #include #include #include #include #include #define BUFFER_OFFSET(i) ((char *)NULL + (i)) /* for simplicity we pack */ /* this structure represents a point with attributes */ #pragma pack(1) typedef struct gltpoint { GLfloat normal[3]; GLfloat texcoord[2]; GLubyte color[3]; } gltpoint_t; #pragma pack() /* globals */ int program_running = 1; SDL_Surface *screen; GLuint buffer_objects[3]; GLuint buffer_objects2[3]; /* few light arrays */ const GLfloat fNoLight[] = { 0.0f, 0.0f, 0.0f, 1.0f }; const GLfloat fLowLight[] = { 0.25f, 0.25f, 0.25f, 1.0f }; const GLfloat fMedLight[] = { 0.50f, 0.50f, 0.50f, 1.0f }; const GLfloat fShinyLight[] = { 0.70f, 0.70f, 0.70f, 1.0f }; const GLfloat fBrightLight[] = { 1.0f, 1.0f, 1.0f, 1.0f }; /* light values and coordinates */ const GLfloat lightPos[] = { -10.f, 5.0f, 5.0f, 1.0f }; /* function prototypes */ static void gltErrorCheck(void); static void resize(int, int); static void keys(SDL_keysym *, unsigned int *); static void process_events(void); static void render(void); static void setup_sdl(void); static void setup_glew(void); static void setup_opengl(void); /* we have a separate buffer objects for vertex data, attributes, and indices * interleaved arrays improve cache performance, the key is locality * for simplicity, vertex is composed of 3 components and texcoord of 2 components */ static int gltGenVBOInterleaved(GLuint *bufferobjects, float *vert_arr, float *texcoord_arr, float *norm_arr, unsigned char *color_arr, unsigned short *indices_arr, GLsizeiptr points_n, GLsizeiptr indices_n) { if (!bufferobjects || !vert_arr || !texcoord_arr || !norm_arr || !color_arr || !indices_arr || !points_n || !indices_n) return -1; /* create an interleaved data structure, is it worth the cpu time? */ GLsizeiptr attrib_buff_sz = ((points_n * (3+2) * sizeof(GLfloat)) + (points_n * 3 * sizeof(GLubyte))); if ((attrib_buff_sz % sizeof(gltpoint_t)) != 0) { fprintf(stderr, "%s: fatal, gltpoint_t appears to be padded!\n", __FUNCTION__); return -1; } /* I call point a vbo slice */ size_t slices_n = attrib_buff_sz / sizeof(gltpoint_t); gltpoint_t *gltpoints_arr = (gltpoint_t *) malloc(attrib_buff_sz); if (!gltpoints_arr) { fprintf(stderr, "%s failed, %s:%d\n", __FUNCTION__, __FILE__, __LINE__); perror("malloc"); return -1; } /* build interleaved data structure, seems cache friendly */ int i; for (i = 0; i < slices_n; i++) { gltpoints_arr[i].normal[0] = norm_arr[i * 3 + 0]; gltpoints_arr[i].normal[1] = norm_arr[i * 3 + 1]; gltpoints_arr[i].normal[2] = norm_arr[i * 3 + 2]; gltpoints_arr[i].texcoord[0] = texcoord_arr[i * 2 + 0]; gltpoints_arr[i].texcoord[1] = texcoord_arr[i * 2 + 1]; gltpoints_arr[i].color[0] = color_arr[i * 3 + 0]; gltpoints_arr[i].color[1] = color_arr[i * 3 + 1]; gltpoints_arr[i].color[2] = color_arr[i * 3 + 2]; } glGenBuffers(3, bufferobjects); /* vertex data */ glBindBuffer(GL_ARRAY_BUFFER, bufferobjects[0]); glBufferData(GL_ARRAY_BUFFER, points_n * 3 * sizeof(float), (const GLvoid *) vert_arr, GL_STATIC_DRAW); /* attribute data, normals, texcoords, and color */ glBindBuffer(GL_ARRAY_BUFFER, bufferobjects[1]); glBufferData(GL_ARRAY_BUFFER, attrib_buff_sz, (const GLvoid *) gltpoints_arr, GL_STATIC_DRAW); /* indices data */ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferobjects[2]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * indices_n, (const GLvoid *) indices_arr, GL_STATIC_DRAW); free(gltpoints_arr); return 0; } static int gltDrawVBOInterleaved(GLuint *bufferobjects, size_t indices_n, GLenum mode) { if (!bufferobjects || !indices_n) return -1; glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); /* vertex data */ glBindBuffer(GL_ARRAY_BUFFER, bufferobjects[0]); glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); /* attribute data, normals, texcoords, and color */ glBindBuffer(GL_ARRAY_BUFFER, bufferobjects[1]); glNormalPointer(GL_FLOAT, sizeof(gltpoint_t), BUFFER_OFFSET(0)); glClientActiveTexture(GL_TEXTURE0); glTexCoordPointer(2, GL_FLOAT, sizeof(gltpoint_t), BUFFER_OFFSET(sizeof(GLfloat) * 3)); glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(gltpoint_t), BUFFER_OFFSET(sizeof(GLfloat) * 5)); /* indices data */ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferobjects[2]); glDrawElements(mode, indices_n, GL_UNSIGNED_SHORT, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); return 0; } static void gltErrorCheck(void) { GLenum err_code; const GLubyte *err_str; while ((err_code = glGetError()) != GL_NO_ERROR) { err_str = gluErrorString(err_code); fprintf(stderr, "OpenGL error: %s\n", err_str); } } static void resize(int w, int h) { printf("window: resizing to %dx%d\n", w, h); GLfloat fAspect = (GLfloat) w / (GLfloat) h; if (h == 0) h = 1; glViewport(0, 0, w, h); /* reset coordinate system */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); /* produce the perspective projection */ /* void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar) */ gluPerspective(40.0f, fAspect, 1.0, 40.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* this needs to be ran again, glut does it for you I suppose */ SDL_SetVideoMode(w, h, 32, SDL_OPENGL | SDL_RESIZABLE); } static void setup_opengl(void) { /* setup fog */ glEnable(GL_FOG); glFogfv(GL_FOG_COLOR, fLowLight); /* set fog color to match background */ glFogf(GL_FOG_START, 4.0f); glFogf(GL_FOG_END, 20.0f); glFogi(GL_FOG_MODE, GL_LINEAR); /* fog equation */ glEnable(GL_DEPTH_TEST); /* hidden surface removal, aka Z-buffer */ glFrontFace(GL_CCW); /* counter clock-wise polygons face out */ glEnable(GL_CULL_FACE); /* do not calculate inside of a pyramid */ /* enable lighting, primitives now need to define color properties */ glEnable(GL_LIGHTING); /* global illumination, ambient RGBA intensity of the entire scene */ glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fLowLight); /* setup and enable light 0 */ glLightfv(GL_LIGHT0, GL_AMBIENT, fMedLight); glLightfv(GL_LIGHT0, GL_DIFFUSE, fShinyLight); glLightfv(GL_LIGHT0, GL_SPECULAR, fBrightLight); glLightfv(GL_LIGHT0, GL_POSITION, lightPos); glEnable(GL_LIGHT0); /* set material properties to follow all glColor values */ /* all primitives specified after the glMaterial call are affected by the * last values set, until another call to glMaterial is made */ glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); /* enable color tracking, should be called after glColorMaterial */ glEnable(GL_COLOR_MATERIAL); /* set globally, sets specular reflection to a white color, * this call follows all subsequent primitives */ glMaterialfv(GL_FRONT, GL_SPECULAR, fBrightLight); /* default (0), by increasing this value you reduce the size and increase * the focus of the specular highlight, causing a shiny spot to appear */ glMateriali(GL_FRONT, GL_SHININESS, 64); /* multisampling for polygons, conflicts with *polygon* anti-aliasing */ /* enabled by default, that's what man page says, hmmm! */ glEnable(GL_MULTISAMPLE); /* AA is disabled, but we will keep the blending function and hints though */ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); /* anisotropic filtering */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8); /* how OpenGL combines the colors from texels with the color of the underlying * geometry is controlled by the texture environment mode */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); /* once texture is loaded and enabled, it will applied to every primitive * that specifies coordinates, ORLY? */ glEnable(GL_TEXTURE_2D); /* specular highlights for textured items */ glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); /* gray background */ glClearColor(0.5f, 0.5f, 0.5f, 1.0f); } static void keys(SDL_keysym *keysym, unsigned int *keys_held) { switch (keysym->sym) { case SDLK_ESCAPE: program_running = 0; break; default: break; } } /* process SDL events */ static void process_events(void) { SDL_Event event; unsigned static int keys_held[323]; SDLKey sym; while (SDL_PollEvent(&event)) { sym = event.key.keysym.sym; switch (event.type) { case SDL_KEYUP: { /* reset the key to 0 */ keys_held[sym] = 0; break; } case SDL_KEYDOWN: { keys_held[sym] = 1; keys(&event.key.keysym, keys_held); break; } case SDL_VIDEORESIZE: { resize(event.resize.w, event.resize.h); break; } case SDL_QUIT: { program_running = 0; break; } default: break; } } } static void render(void) { /* clear the window with current clearing color */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* save the matrix state */ glPushMatrix(); { /* draw ground using immiediate mode */ glDisable(GL_MULTISAMPLE); glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); GLfloat fExtent = 20.0f; GLfloat fStep = 0.5f; GLfloat y = -0.4f; GLfloat iLine; glLineWidth(1.0f); glBegin(GL_LINES); for (iLine = -fExtent; iLine <= fExtent; iLine += fStep) { glVertex3f(iLine, y, fExtent); glVertex3f(iLine, y, -fExtent); glVertex3f(fExtent, y, iLine); glVertex3f(-fExtent, y, iLine); } glEnd(); glDisable(GL_LINE_SMOOTH); glDisable(GL_BLEND); glEnable(GL_MULTISAMPLE); /* end of ground */ glPushMatrix(); /* draw vbo */ glTranslatef(0.0f, 0.0f, -4.0f); gltDrawVBOInterleaved(buffer_objects, 3, GL_TRIANGLES); gltDrawVBOInterleaved(buffer_objects2, 4, GL_QUADS); glPopMatrix(); } /* restore the matrix state */ glPopMatrix(); /* buffer swap */ SDL_GL_SwapBuffers(); } static void setup_sdl(void) { if (SDL_Init(SDL_INIT_VIDEO) < 0 ) { fprintf(stderr, "SDL: unable to init, %s\n", SDL_GetError()); exit(-1); } atexit(SDL_Quit); SDL_WM_SetCaption("VBO", NULL); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); if ((screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL | SDL_RESIZABLE)) == NULL) { fprintf(stderr, "SDL: unable to set video mode: %s\n", SDL_GetError()); exit(-1); } SDL_EnableUNICODE(1); /* SDL doesn't trigger off a ResizeEvent at startup, but as we need this * for OpenGL, we do this ourselves */ SDL_Event resizeEvent; resizeEvent.type = SDL_VIDEORESIZE; resizeEvent.resize.w = 640; resizeEvent.resize.h = 480; SDL_PushEvent(&resizeEvent); } static void setup_glew(void) { /* initalize glew */ GLenum glewerr = glewInit(); if (GLEW_OK != glewerr) { fprintf(stderr, "GLEW error: %s\n", glewGetErrorString(glewerr)); exit(-1); } else fprintf(stdout, "GLEW: using version %s\n", glewGetString(GLEW_VERSION)); /* display OpenGL version */ GLint major; GLint minor; glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor); fprintf(stdout, "GLEW: initialized OpenGL %d.%d\n", major, minor); } int main(int argc, char **argv) { setup_sdl(); setup_glew(); setup_opengl(); gltErrorCheck(); /* setup a triangle */ float vert_arr[9] = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.83f, 0.0f }; float norm_arr[9] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }; float texcoord_arr[6] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f }; unsigned char color_arr[9] = { 255, 0, 0, 0, 255, 0, 0, 0, 255 }; unsigned short indices_arr[3] = { 0, 1, 2 }; gltGenVBOInterleaved(buffer_objects, vert_arr, texcoord_arr, norm_arr, color_arr, indices_arr, 3, 3); gltErrorCheck(); /* setup a rectangle, notice the ccw winding */ float vert_arr2[12] = { -0.5f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f }; float norm_arr2[12] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }; float texcoord_arr2[8] = { 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f }; unsigned char color_arr2[12] = { 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255 }; unsigned short indices_arr2[4] = { 0, 1, 2, 3 }; gltGenVBOInterleaved(buffer_objects2, vert_arr2, texcoord_arr2, norm_arr2, color_arr2, indices_arr2, 4, 4); gltErrorCheck(); while (program_running) { process_events(); render(); } gltErrorCheck(); glDeleteBuffers(3, buffer_objects); if (screen) SDL_FreeSurface(screen); puts("bye!"); return 0; }