// This code example is created for educational purpose // by Thorsten Thormaehlen (contact: www.thormae.de). // It is distributed without any warranty. #include #include // we use glut here as window manager #define _USE_MATH_DEFINES #include #include #include #include #include #include using namespace std; class Renderer { private: struct Vertex { float position[3]; float color[4]; float texCoord[2]; float normal[3]; }; public: float t; int mode; private: GLuint vertBufID; GLuint texID; int vertNo; public: // constructor Renderer() : t(0.0), mode(0), vertBufID(0), texID(0), vertNo(0) {} //destructor ~Renderer() { if(vertBufID !=0) glDeleteBuffers( 1, &vertBufID); if(texID !=0) glDeleteTextures( 1, &texID); } public: void init() { glEnable(GL_DEPTH_TEST); // generating VBO input data std::vector data; int perVertexFloats = (3+4+2+3); loadVertexData(string("pushbike.vbo"), data, perVertexFloats); // The binary version of a float array is faster to read but less portable // becaue of endianness. The provided binary array // "pushbike_binary.vbo" was created on a machine with Little-Endian byte order. // Use this function to load the binary version: // loadVertexDataBinary(string("pushbike_binary.vbo"), data, perVertexFloats); vertNo = int(data.size()) / perVertexFloats; // generating vertex VBO glGenBuffers(1, &vertBufID); glBindBuffer(GL_ARRAY_BUFFER, vertBufID); glBufferData(GL_ARRAY_BUFFER, vertNo*sizeof(Vertex), &data[0], GL_STATIC_DRAW); std::string fileName("checkerboard.ppm"); texID = loadTexture(fileName); } void resize(int w, int h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (30.0, (float)w/(float)h, 1.0, 10.0); } void display() { glClearColor(0.3f, 0.3f, 0.3f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // camera orbits in the z=1.5 plane // and looks at origin double rad = M_PI / 180.0f * t; gluLookAt(1.5f*float(cos(rad)), 1.5f*float(sin(rad)), 1.5f, // eye 0.0f, 0.0f, 0.0f, // look at 0.0f, 0.0f, 1.0f); // up // activating VBO glBindBuffer(GL_ARRAY_BUFFER, vertBufID); int stride = sizeof(Vertex); char *offset = (char*)NULL; // position glVertexPointer(3, GL_FLOAT, stride, offset); glEnableClientState(GL_VERTEX_ARRAY); // color offset = (char*)NULL + 3*sizeof(float); glColorPointer(4, GL_FLOAT, stride, offset); glEnableClientState(GL_COLOR_ARRAY); // texture offset = (char*)NULL + (3+4)*sizeof(float); glTexCoordPointer(2, GL_FLOAT, stride, offset); glEnableClientState(GL_TEXTURE_COORD_ARRAY); // normals offset = (char*)NULL + (3+4+2)*sizeof(float); glNormalPointer(GL_FLOAT, stride, offset); glEnableClientState(GL_NORMAL_ARRAY); // bind texture if(mode == 0) { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texID); }else{ glDisable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } // render data glDrawArrays(GL_TRIANGLES, 0, vertNo); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_TEXTURE_2D); } private: // returns a valid textureID on success, otherwise 0 GLuint loadTexture(std::string &filename) { unsigned width; unsigned height; int level = 0; int border = 0; std::vector imgData; // load image data if(!loadPPMImageFlipped(filename, width, height, imgData)) return 0; // data is aligned in byte order glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //request textureID GLuint textureID; glGenTextures( 1, &textureID); // bind texture glBindTexture( GL_TEXTURE_2D, textureID); //define how to filter the texture (important but ignore for now) glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //texture colors should modulate the original color values glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // specify the 2D texture map glTexImage2D(GL_TEXTURE_2D, level, GL_RGB, width, height, border, GL_RGB, GL_UNSIGNED_BYTE, &imgData[0]); // return unique texture identifier return textureID; } bool loadPPMImageFlipped(std::string &filename, unsigned &width, unsigned &height, std::vector &imgData) { ifstream input(filename.c_str(), ifstream::in | ifstream::binary); if(!input) { // cast istream to bool to see if something went wrong cerr << "Can not find texture data file " << filename.c_str() << endl; return false; } input.unsetf(std::ios_base::skipws); string line; input >> line >> std::ws; if (line != "P6") { cerr << "File is not PPM P6 raw format" << endl; return false; } width = 0; height = 0; unsigned depth = 0; unsigned readItems = 0; unsigned char lastCharBeforeBinary; while (readItems < 3) { input >> std::ws; if(input.peek() != '#') { if (readItems == 0) input >> width; if (readItems == 1) input >> height; if (readItems == 2) input >> depth >> lastCharBeforeBinary; readItems++; }else{ // skip comments std::getline(input, line); } } if(depth >= 256) { cerr << "Only 8-bit PPM format is supported" << endl; return false; } unsigned byteCount = width * height * 3; imgData.resize(byteCount); input.read((char*)&imgData[0], byteCount*sizeof(unsigned char)); // vertically flip the image because the image origin // in OpenGL is the lower-left corner unsigned char tmpData; for(unsigned y=0; y < height / 2; y++) { int sourceIndex = y * width * 3; int targetIndex = (height-1-y) * width *3; for(unsigned x=0; x < width*3; x++) { tmpData = imgData[targetIndex]; imgData[targetIndex] = imgData[sourceIndex]; imgData[sourceIndex] = tmpData; sourceIndex++; targetIndex++; } } return true; } bool loadVertexData(std::string &filename, std::vector &data, unsigned perVertexFloats) { // read vertex data from vbo file in plain text format ifstream input(filename.c_str()); if(!input) { // cast istream to bool to see if something went wrong cerr << "Can not find vertex data file " << filename << endl; return false; } int numFloats; double vertData; if(input >> numFloats) { if(numFloats > 0) { data.resize(numFloats); int i = 0; while(input >> vertData && i < numFloats) { // store it in the vector data[i] = float(vertData); i++; } if(i != numFloats || numFloats % perVertexFloats) return false; } }else{ return false; } return true; } bool loadVertexDataBinary(std::string &filename, std::vector &data, unsigned perVertexFloats) { // read vertex data from vbo file in binary format ifstream input(filename.c_str(), ifstream::in | ifstream::binary); if(!input) { // cast istream to bool to see if something went wrong cerr << "Can not find vertex data file " << filename << endl; return false; } else { unsigned int numFloats = 0; input.read((char*)(&numFloats), sizeof(unsigned int)); if(!input) return false; data.resize(numFloats); input.read((char*)(&data[0]), numFloats * sizeof(float)); if(!input || numFloats % perVertexFloats) return false; } return true; } }; //this is a static pointer to a Renderer used in the glut callback functions static Renderer *renderer; //glut static callbacks start static void glutResize(int w, int h) { renderer->resize(w,h); } static void glutDisplay() { renderer->display(); glutSwapBuffers(); glutReportErrors(); } static void timer(int v) { float offset = 1.0f; renderer->t += offset; glutDisplay(); glutTimerFunc(unsigned(20), timer, ++v); } static void glutKeyboard(unsigned char key, int x, int y) { bool redraw = false; std::string modeStr; switch(key) { case '1': if(renderer->mode == 1) renderer->mode = 0; else renderer->mode = 1; redraw = true; break; } if(redraw) { glutDisplay(); } } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); glutInitWindowPosition(100,100); glutInitWindowSize(320, 320); glutCreateWindow("Interleaved Data VBO Demo"); GLenum err = glewInit(); if (GLEW_OK != err) { fprintf(stderr, "Glew error: %s\n", glewGetErrorString(err)); } glutDisplayFunc(glutDisplay); //glutIdleFunc(glutDisplay); glutReshapeFunc(glutResize); glutKeyboardFunc(glutKeyboard); renderer = new Renderer; renderer->init(); glutTimerFunc(unsigned(20), timer, 0); glutMainLoop(); }