Today, I will

[OpenGL] opengl graphics pipeline의 이해와 hello_triangle 속 VAO와 VBO의 쓰임 본문

Computer Science/그래픽스

[OpenGL] opengl graphics pipeline의 이해와 hello_triangle 속 VAO와 VBO의 쓰임

Lv.Forest 2023. 10. 11. 16:29

*컴퓨터 그래픽스 강의와 Learn Open GL Code를 참고하여 작성한 포스팅입니다.

 

opengl graphics pipeline

SI03-graphics pipeline (free.fr)

순서 

(1) Vertex shader : transform verices : 각각의 버텍스를 어떻게 transformation할 것인지.

 각각 버텍스에 적용(모델링, 뷰잉, 프로젝션, 뷰포트 transformation 등 행렬을 곱해주는 연산을 Vertex shader 에서 수행)

(2) shape assembly : form a primitive

(3) geometry shader : emit vertices to form new primitives

(4) pixelization : rasterization

(5) colorization : fragment shader: 컬러를 정하는 것. 각각의 픽셀에 대해서 픽셀의 컬러가 무엇인지 정해주는 일을 한다.

(6) Test and Blending 

Vertex shaderfragment shader는 반드시 따라야하는 것이다. 

getting_started__2.1.hello_triangle

색이 똑같은 삼각형을 그려보도록 한다.

Vertex shader fragment shader

Vertex shader fragment shader를 사용자가 만들어야 한다.

원래는 개별적인 코드 파일로 만든다. 

fs = fragment shader , vs =  Vertex shader

fs 예시

#version 330 core
out vec4 FragColor;

in vec2 TexCoord;

// texture samplers
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
	// linearly interpolate between both textures (80% container, 20% awesomeface)
	FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

vs 예시

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	gl_Position = projection * view * model * vec4(aPos, 1.0);
	TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

hello_triangle에서는 아래 코드와 같이 char 포인터 변수로 잡아두어 string으로 읽고 있다.

const char *vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" // aPos 인풋을 그대로 출력함
    "}\0"; // 즉, transformation이 없이 받은 인풋을 그대로 파이프라인에 흘려보낸다.
const char *fragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" // r 1, g 0.5, b 0.2, a 1로 통일한 아웃풋
    "}\n\0";

Vertex shader fragment shader 컴파일 및 링킹

open gl  host code가 있고 쉐이더 코드가 있으면, 쉐이더를 open gl 쪽으로 불러와서 컴파일 및 링킹하는 작업을 open gl에서 해주어야 한다.

렌더링 루프에서 쉐이더를 써야하므로, 그 전에 shader를 load하고 complie하는 코드가 있다.

    // build and compile our shader program
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); //glCreateShader
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //vertexShaderSource를 가져온다
    glCompileShader(vertexShader); //CompileShader
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success) // 컴파일한 것이 이상이 없는지 확인
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

출처&nbsp; SI03-graphics pipeline (free.fr) 만들고, 소스가져와서, 컴파일

쉐이더 프로그램에서 컴파일시킨 쉐이더들은 어떤 것을 링크하느냐에 따라 바꿔 갈아끼울 수 있도록 한다. 

프로그램에서 사용할 쉐이더들을 계속 바꿀 수도 있기 때문이다.

그러나 본 코드에세는 버텍스 쉐이더와 프래그먼트 쉐이더를 쓰기 위한 전체 쉐이더 프로그램을 하나 만들고(glCreateProgram), glAttachShader, glLinkProgram를 순서대로 call하여 링크하여 쉐이더 프로그램에 위에서 빌드하고 컴파일한 쉐이더를 링크한다.

    // link shaders
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader); // 링크했으므로 쓸 일이 없으므로 delete
    glDeleteShader(fragmentShader);// 링크했으므로 쓸 일이 없으므로 delete
    // 나중에 shaderProgram를 이용하면 Attach한 vertexShader와 fragmentShader가 들어있다.

 

전체 코드

int main()
{
    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    // link shaders
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // set up vertex data (and buffer(s)) and configure vertex attributes
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // left  
         0.5f, -0.5f, 0.0f, // right 
         0.0f,  0.5f, 0.0f  // top   
    }; 

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
    glBindBuffer(GL_ARRAY_BUFFER, 0); 

    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    glBindVertexArray(0); 

    // uncomment this call to draw in wireframe polygons.
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // render loop
    while (!glfwWindowShouldClose(window))
    {
        // input
        processInput(window);

        // render
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // draw our first triangle
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
        glDrawArrays(GL_TRIANGLES, 0, 3);
        // glBindVertexArray(0); // no need to unbind it every time 
 
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // glfw: terminate, clearing all previously allocated GLFW resources.
    glfwTerminate();
    return 0;
}

렌더링 루프

렌더링 루프에서 이전 코드와 차이나는 부분

        // draw our first triangle
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
        glDrawArrays(GL_TRIANGLES, 0, 3);
        // glBindVertexArray(0); // no need to unbind it every time

(1) shaderProgram를 사용한다고 선택 glUseProgram

 - 여기까지 쉐이더를 만들어서 붙인 쉐이더 프로그램을 사용한다고 명시하였으므로 쉐이더를 써서 렌더링할 준비가 됨

(2) glBindVertexArray(VAO)를 콜하여 VAO를 쓰기 위해서는 그 전에,

어떤 버텍스데이타를 쓸지 넘겨주기 : 그래픽스 파이프라인에 버텍스 데이타를 넣어주어야 하기 때문

 - 삼각형을 그리기 위하여 꼭지점 3개 데이터를 전달해주어야 한다.

 

 - VBO(vertex buffer object)에 삼각형 꼭지점 데이터를 넣는데, 지금은 넣는 데이터가 단순하지만 포지션, 컬러, 텍스쳐코디네이트 등 다양하고 복잡한 형태로 데이터가 구성될 수도 있다.

VBO는 데이터가 저장되어 있는 버퍼이다.

사진 출처 : VOB-CSDN 블로그에서 VBO, VAO, EBO의 역할 이해 OPenGL_opengl

VAOVBO에 어떤 식의 데이터가 들어있는지를 알고 있어야 한다.

VAO(Vertex Array Object) : VBO에 저장되어 있는 것들이 무엇을 의미하고 어떤 형태로 저장되어있는지 가지고 있다.

 

실제로 렌더링을 하기 위해 데이터를 넘겨줄 때 저장은 vbo, 렌더링을 위해 사용하는 것은 vao 인 것이다.

Prepare Vertex Data

수학적 xy 2차원 그래프와 똑같다.

좌표의 표현 : (x,y,z,w)

 - 2차원 좌표는 z = 0, w = 1로 설정

 - 가령 ( -0.5, 0.5 ) => ( -0.5, 0.5 , 0,1)

 

    // set up vertex data (and buffer(s)) and configure vertex attributes
    float vertices[] = { // 지금은 단순하게 포지션만 있는 케이스
        -0.5f, -0.5f, 0.0f, // left  
         0.5f, -0.5f, 0.0f, // right 
         0.0f,  0.5f, 0.0f  // top   
    }; 

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
   
   
    glBindVertexArray(VAO);               // 바인딩 VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);   // 바인딩 VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // VBO에 버텍스 데이터를 카피
    //c의 memcopy와 비슷, 크기와 vertices의 포인터

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//VBO에 저장되어 있는 데이터가 어떤 형태라고 선언!
    // Attrib이 하나 뿐이기 때문에 첫번째 인자 0(어떤 경우에는 POSITION, COLOR, TEXTCOORDS까지 있다)
    // 3은 컴포넌트 개수(position이므로 x,y,z)
    // GL_FLOAT는 데이터 타입
    // GL_FALSE는 normalize 여부
    // 3 * sizeof(float)는 stride: 첫번째 버텍스 시작으로부터 두번째 버텍스가기 위해 얼마나 넘어가야 하는지
    // (void*)0는 첫번째 버텍스 시작점을 알려줌
    glEnableVertexAttribArray(0);

    // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
    glBindBuffer(GL_ARRAY_BUFFER, 0); 

    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    glBindVertexArray(0);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//VBO에 저장되어 있는 데이터가 어떤 형태라고 선언!

Attrib이 하나 뿐이기 때문에 첫번째 인자 0

(아래 경우에는 POSITION, COLOR, TEXTCOORDS까지 있다)

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (GLvoid*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (GLvoid*)(2*sizeof(GLfloat)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (GLvoid*)(5*sizeof(GLfloat));

컴포넌트는 데이터타입에 따라 달라진다(3차원 데이터는 3, 2차원 데이터는 2인 식으로)

 

 (3) 마지막 드로잉(렌더링) 

 - glDrawArrays(GL_TRIANGLES, 0, 3) // 여기서 GL_TRIANGLES는 shape assembly(open gl 파이프라인 두번째)

 - (2)에서 vao를 통해 vbo에 저장된 데이터를 알기 때문에 이 버텍스 데이터들로 삼각형을 만들고 싶다는 것을 알려줌

 - 0은 처음 컴포넌트를 뜻함(0부터 사용)

 - 3은 버텍스 데이터가 총 3개라는 뜻