본문 바로가기
전공/컴퓨터 그래픽스

버퍼 오브젝트 (Buffer Objects) (2)

by 임 낭 만 2023. 5. 29.

Buffer Objects

Stride and Start Address

  • Often different vertex attributes (such as position, color, etc.) are not stored separately, but in a common data structure that is created for each vertex, for example:
struct Vertex {
    float position[3];
    float color[4];
    float texCoord[2];
    float normal[3];
};
std::vector<Vertex> vertexData;
  • If vertexData is created as one large VBO, memory access to the individual components is defined by specifying the stride, which is the number of bytes between consecutive elements of the same type, and the start address offset:
int stride = sizeof(Vertex);
char* offset = (char*)NULL * (3 + 4 + 2) * sizeof(float);
glNormalPointer(GL_FLOAT, stride, offset);

Example : Stride and Start Address

// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.
#include <GL/glew.h>
#include <GL/freeglut.h> // we use glut here as window manager
#define _USE_MATH_DEFINES
#include <math.h>
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
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
        float vertexData[] = {
          0.0f, 0.0f, 2.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f, 0.0000f,-0.9701f, 0.2425f,
          -0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0000f,-0.9701f, 0.2425f,
          0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0000f,-0.9701f, 0.2425f,
          0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.5f, 1.0f, 0.9701f, 0.0000f, 0.2425f,
          0.5f,-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.9701f, 0.0000f, 0.2425f,
          0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.9701f, 0.0000f, 0.2425f,
          0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, 1.0f, 0.0000f, 0.9701f, 0.2425f,
          0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0000f, 0.9701f, 0.2425f,
          -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0000f, 0.9701f, 0.2425f,
          0.0f, 0.0f, 2.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.5f, 1.0f,-0.9701f, 0.0000f, 0.2425f,
          -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,-0.9701f, 0.0000f, 0.2425f,
          -0.5f,-0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f,-0.9701f, 0.0000f, 0.2425f
        };
        vertNo = 12;
        // generating vertex VBO
        glGenBuffers(1, &vertBufID);
        glBindBuffer(GL_ARRAY_BUFFER, vertBufID);
        glBufferData(GL_ARRAY_BUFFER, vertNo * sizeof(Vertex), vertexData, 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, 2.0, 10.0);
    }
    void display() {
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        // set camera
        gluLookAt(3.0, -1.0, 4.5, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0);
        // draw scene
        glRotatef(t, 0.0f, 0.0f, 1.0f);
        // 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<unsigned char> 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<unsigned char>& 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;
    }
};
//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();
}


Vertex Array Objects

Vertex Array Objects

  • When examining the display( ) function in the previous example, it is noticeable that in addition to the actual render call with glDrawArrays(…) several additional function calls to glBindBuffer(…), glXXXPointer(…) and glEnableClientState(…) are needed
  • A Vertex Array Object(VAO) encapsulated these calls by storing the states of these additional functions and later restoring them automatically when binding the VAO again
  • A VAO contains only the states, but no vertex data. The vertex data is still stored in the VBOs

Creating Vertex Array Objects

  • The creation of VAOs follows the usual pattern:
    • Creating a unique ID with
glGenVertexArrays(1, &vaoID);
  • Creating a VAO with glBindVertexArray(…) and setting of the VBO states, they are stored to the currently bound VAO
glBindVertexArray(vaoID);
glBindBuffer(GL_ARRAY_BUFFER, bufID);
glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
glVertexPointer(3, GL_FLOAT, stride, offset);
glEnableClientState(GL_VERTEX_ARRAY);
//...

Using Vertex Array Objects

  • The actual rendering is then carried out with only two function calls:
glBindVertexArray(vaoID);
glDrawArrays(GL_TRIANGLES, 0, numOfTris);
  • Deleting a VAO with:
glDeleteVertexArrays(1, &vaoID);
  • When deleting the VAO its associated VBOs are not deleted. If desired, they must be released separately

Example : Vertex Array Objects

// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.
#include <GL/glew.h>
#include <GL/freeglut.h> // we use glut here as window manager
#define _USE_MATH_DEFINES
#include <math.h>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
class Renderer {
private:
    struct Vertex
    {
        float position[3];
        float color[4];
    };
public:
    float t;
private:
    enum { Pyramid, Cube, numVAOs };
    enum { PyramidAll, CubePos, CubeCol, CubeIndices, numVBOs };
    GLuint vaoID[numVAOs];
    GLuint bufID[numVBOs];
    int pyramidVertNo;
    int cubeIndicesNo;
public:
    // constructor
    Renderer() : t(0.0), pyramidVertNo(0), cubeIndicesNo(0) {}
    //destructor
    ~Renderer() {
        glDeleteVertexArrays(numVAOs, vaoID);
        glDeleteBuffers(numVBOs, bufID);
    }
public:
    void init() {
        glEnable(GL_DEPTH_TEST);
        // now we create 2 Vertex Array Objects (VAO):
        // one for a pyramid and one for a cube.
        // The pyramid uses one interleaved VBO
        // which is later drawn with "glDrawArrays",
        // while the cube uses 3 separate VBOs
        // which are later drawn with "glDrawElements"

        // create the Vertex Array Objects
        glGenVertexArrays(numVAOs, vaoID);
        // generating Vertex Buffer Objects (VBO)
        glGenBuffers(numVBOs, bufID);
        // specifying the pyramid VAO
        glBindVertexArray(vaoID[Pyramid]);

        float pyramidVertexData[] = {
          0.0f, 0.0f, 2.0f, 1.0f, 0.0f, 0.0f, 0.0f,
          -0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
          0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
          0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 0.0f,
          0.5f,-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
          0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
          0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 1.0f, 0.0f,
          0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
          -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
          0.0f, 0.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f,
          -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
          -0.5f,-0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
        };
        pyramidVertNo = 12;
        glBindBuffer(GL_ARRAY_BUFFER, bufID[PyramidAll]);
        glBufferData(GL_ARRAY_BUFFER, pyramidVertNo * sizeof(Vertex), pyramidVertexData, GL_STATIC_DRAW);

        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);
        // specifying the cube VAO
        glBindVertexArray(vaoID[Cube]);
        float cubePosData[] = {
          -1.0f,  1.0f,  1.0f,
          -1.0f, -1.0f,  1.0f,
          1.0f, -1.0f,  1.0f,
          1.0f,  1.0f,  1.0f,
          -1.0f,  1.0f, -1.0f,
          -1.0f, -1.0f, -1.0f,
          1.0f, -1.0f, -1.0f,
          1.0f,  1.0f, -1.0f
        };
        float cubeColorData[] = {
          1.0f, 1.0f, 0.0f,
          1.0f, 0.0f, 0.0f,
          0.0f, 1.0f, 0.0f,
          0.0f, 0.0f, 1.0f,
          1.0f, 1.0f, 0.0f,
          1.0f, 0.0f, 0.0f,
          0.0f, 1.0f, 0.0f,
          0.0f, 0.0f, 1.0f,
        };
        int cubeIndicesData[] =
        { 0, 1, 2, 3,
        0, 4, 5, 1,
        4, 7, 6, 5,
        2, 6, 7, 3,
        1, 5, 6, 2,
        3, 7, 4, 0
        };
        cubeIndicesNo = 24;

        // position
        glBindBuffer(GL_ARRAY_BUFFER, bufID[CubePos]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(cubePosData), cubePosData, GL_STATIC_DRAW);
        glVertexPointer(3, GL_FLOAT, 0, NULL);
        glEnableClientState(GL_VERTEX_ARRAY);
        // color
        glBindBuffer(GL_ARRAY_BUFFER, bufID[CubeCol]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(cubeColorData), cubeColorData, GL_STATIC_DRAW);
        glColorPointer(3, GL_FLOAT, 0, NULL);
        glEnableClientState(GL_COLOR_ARRAY);
        // indices
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufID[CubeIndices]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cubeIndicesData), cubeIndicesData, GL_STATIC_DRAW);
    }
    void resize(int w, int h) {
        glViewport(0, 0, w, h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(60.0, (float)w / (float)h, 0.1, 50.0);
    }
    void display() {
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        // set camera
        gluLookAt(3.0, -1.0, 4.5, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0);
        // draw scene
        glRotatef(t, 0.0f, 0.0f, 1.0f);
        // bind cube VAO
        glBindVertexArray(vaoID[Cube]);
        // render data
        glDrawElements(GL_QUADS, cubeIndicesNo, GL_UNSIGNED_INT, NULL);

        glTranslatef(0.0f, 0.0f, 1.0f);
        // bind pyramid VAO
        glBindVertexArray(vaoID[Pyramid]);
        // render data
        glDrawArrays(GL_TRIANGLES, 0, pyramidVertNo);
    }
};
//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);
}
int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(320, 320);
    glutCreateWindow("Vertex Array Object (VAO) Demo");
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "Glew error: %s\n", glewGetErrorString(err));
    }
    glutDisplayFunc(glutDisplay);
    //glutIdleFunc(glutDisplay);
    glutReshapeFunc(glutResize);

    renderer = new Renderer;
    renderer->init();

    glutTimerFunc(unsigned(20), timer, 0);
    glutMainLoop();
}

댓글