본문 바로가기

ComputerScience/Computer Graphics

OpenGL - 5 Hello Triangle (2)

728x90

* always thanks to https://learnopengl.com/

 

Learn OpenGL, extensive tutorial resource for learning Modern OpenGL

Welcome to OpenGL Welcome to the online book for learning OpenGL! Whether you are trying to learn OpenGL for academic purposes, to pursue a career or simply looking for a hobby, this book will teach you the basics, the intermediate, and all the advanced kn

learnopengl.com

5.4 Fragment shader

- 이번에는 최소한의 구현 조건 두번째, fragment Shader를 정의한다.

- fragment shader는 pixel의 color output을 계산한다. 구현을 쉽게 하기 위해 우리가 만들 shader는 orange-ish color만 내보낸다.

- computer graphics는 color를 4개의 원소(0.0 ~ 1.0 사이의 값)를 가진 배열로 표현한다. RGBA(red/ green/ blue/ alpha(opacity) component)

#version 330 core
out vec4 FragColor;
void main() {
	FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

- fragment shader는 오직 하나의 output variable만을 요구한다. size 4 vector로 최종 output의 color를 정의한다.

- "out" 키워드로 선언하고 이름은 FragColor라고 하자. 그리고 FragColor를 orange-ish color로 set한다.

- 컴파일 과정은 이전 vertex shader와 같다. 단 GL_FRAGMENT_SHADER로 shader type을 바꿔주자.

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

5.4.1 Shader program

shader program object는 여러 shader를 combine한 최종 linked version이다.

- 가장 최근에 컴파일된 shader를 사용하기 위해서 하나의 shader progrm object로 link하고 rendering할 때 activate해야한다.

- 각각의 shader로부터의 output을 다음 shader의 input으로 linking 해야한다.

- program object를 하나 만든다.

unsigned int shaderProgram;
shaderProgram = glCreateProgram();

- glCreateProgram은 프로그램 객체를 만들고 ID reference를 반환한다.

- 이전에 컴파일했던 shader들을 program object에 attach, link 한다. (glAttachShader, glLinkProgram)

glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

- attach, link의 fail에 대비해서 check하는 코드도 작성한다.

glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
	glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
	... 
}

- 이렇게 만든 program object를 glUseProgram으로 activate한다.

glUseProgram(shaderProgram);

- glUseProgram 호출 이후의 shader, rendering call은 이 program object를 쓰게 될 것이다.

- program object 와 shader objects link가 끝나고 나면  더이상 shader objects가 필요없기 때문에 delete 해준다.

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

- 이렇게 input vertex data를 GPU에게 전달했다. 그리고 GPU에게 어떻게 vertex data를 process할 것인지 fragment shader로 알려주었다.

- 하지만 아직 OpenGL이 모르는 게 한가지 있다. 메모리에 있는 vertex data를 어떻게게 해석할 것인가? vertex data를 shader's attributes와 어떻게 연결할 것인지?를 알려주어야한다.

5.5 Linking Vertex Attributes

- rendering 전에 OpenGL에게 vertex shader와 VBO에 있는 vertex data를 연결해주어야한다.

- vertex buffer data formatted 

https://learnopengl.com/

  • position data : 32-bit(4byte) floating point values.
  • 각 position은 세개의 value들로 구성
  • array의 세 values사이에 빈 공간은 없음
  • 첫번째 value는 buffer의 첫번째에 위치

- 위 정보들과 glVertexAttribPointer를 가지고 OpenGL에게 어떻게 VBO의 vertices data를 접근해서 좌표들을 해당하는 attributes로 해석하라고 할지 알려줄 수 있다.

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

첫번째 인자는 configure하고자 하는 vertex attribute를 가리킨다. vertex shader에서 vertex attribute의 위치를 0으로 하였던것을 기억해보자. (layout location = 0). 따라서 0을 넘겨줌으로써 data를 vertex attribute에게 넘겨줄 수 있게 된다.

- 즉 OpenGL이 vertex data를 vertex attribute마다 어떻게 해석해야 하는지 알 수 있게 된다.

- 두번째 인자는 vertex attribute의 크기이다. vec3는 세개의 value로 구성되어있기 때문에 3을 전달한다.

- 세번째 인자는 data의 type이다. GL_FLOAT

- 네번째 인자는 data normalization과 관계가 있다. 만약 input data가 int나 byte로 되어있다면 normalize해서 -1~1 사이의 float으로 convert해줘야 한다. 이럴때 GL_TRUE를 넣어준다. 반면 input data가 -1~1 사이의 float으로 normalize되었다면 GL_FALSE를 넣어주면 된다.

- 다섯번째 인자는 stride이다. 연속된 vertex attributes사이의 공간이다. 만약 위의 경우처럼 array가 tightly pack되어있다면 vertex attribute사이에 공간이 없기 때문에 0을 전달해도 된다. 하지만 다른 방법으로 다음 position data가 float크기 * 3 만큼 떨어져있기 때문에 이 값을 전달해도 된다. (위 그림 참조)

- 마지막인자는 offset인데 이는 buffer에서 position data의 시작 위치를 말한다. void* 타입으로 인자를 받는다.  data array의 시작위치에 position data가 있기때문에 0을 void* type으로 cast해서 넣어준다. 자세한 내용은 뒤에서 알아보자.

- 이제 OpenGL은 vertex data를 어떻게 interpret해야하는지 알고 있다. 다음으로 glEnableVertexAttribArray로 vertex attribute loacation을 인자로 주어야 한다. 이는 vertex attributes는 기본적으로 disabled되어있기 때문이다.

 

- 이제 모든 준비는 끝났다. 일련의 과정을 간략하게 정리해보자

  • vertex buffer object로 buffer에 vertex data를 초기화 한다.
  • vertex shader, fragment shader 정의
  • OpenGL에게 어떻게 vertex data를 vertex shader의 vertex attributes와 link시킬 건지 알려준다.
// 0. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. then set the vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. use our shader program when we want to render an object
glUseProgram(shaderProgram);
// 3. now draw the object
someOpenGLFunctionThatDrawsOurTriangle();

- 도형을 그릴때마다 위의 일련의 과정들을 계속해서 반복하게 될 것이다.

- vertex의 수가 많아질수록 buffer object를 binding하고 모든 vertex attribute을 configure하는 과정은 꽤나 버거운 일이 될 것이다. 그래서 이 과정에서 state configuration들을 저장할 수 있는, bind만 하면 바로 사용가능한 object가 존재한다. 

5.5.1 Vertex Array Object

- vertex array object (VAO)도 VBO처럼 bind될 수 있다. 모든 point의 vertex attribute call은 VAO에 저장된다.

- bind가 되고나면 함수 호출 한번만으로 모든 vertex attribute을 불러올 수 있다.

- vertex array object에 저장되는 내용들

  • Calls to glEnableVertexAttribArray or glDisableVertexAttribArray.
  • Vertex attribute configurations via glVertexAttribPointer.
  • Vertex buffer objects associated with vertex attributes by calls to glVertexAttribPointer.

https://learnopengl.com/

- core OpenGL은 VAO를 사용하기를 요구한다. 사용자가 vertex input으로 무엇을 하려는지 알기 위해서다. VAO bind가 실패하면 OpenGL은 그리기를 거부한다.

- VAO를 만드는 과정은 VBO와 유사하다.

unsigned int VAO;
glGenVertexArrays(1, &VAO);

- glGenVertexArrays로 VAO를 bind한다.

- VBO(s)와 attribute pointer(s)를 bind/configure하고 다른 VAO의 나중사용을 위해 다시 unbind한다.

- 도형을 그리기 전에 VAO를 bind하기만 하면 된다.

// ..:: Initialization code (done once (unless your object frequently changes)) :: ..
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
// 2. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. then set our vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: Drawing code (in render loop) :: ..
// 4. draw the object
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();

- VBO가 사용할 vertex attribute configuration을 VAO가 저장하고 있다.

- 만약 여러 도형을 그리게 되는 경우, 모든 VAO를 generate/configure하고 추후 사용을 위해 저장하고 있으면 된다.

- 정리하면 지금 그리고 싶은 도형의 사용 전에 그 특정 VAO를 bind하고 그다음 도형을 그리고 unbind하면 된다.

5.5.2 The triangle we’ve all been waiting for

- 도형을 그릴때 OpenGL은 glDrawArrays를 제공한다. 이 함수를 통해 현재 active한 shader, 미리 전에 정의된 vertex attribute configuration 그리고 VBO's vertex data를 가지고 primitives를 그릴 수 있다.

glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);

- glDrawArrays의 첫번째 인자는 우리가 그리고자 하는 OpenGL pritimitive type이다. 삼각형을 그릴 것 이기 때문에 GL_TRIANGLES를 전달한다.

- 두번째 인자는 그리고자 하는 vertex array의 첫번째 index이다.

- 마지막 인자는 몇개의 vertex를 그리고 싶은지 이다.

- 전체 소스코드

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

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"
    "}\0";
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"
    "}\n\0";

int main() {
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }


    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // 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;
    }
    // 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);
    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;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}
728x90
반응형

'ComputerScience > Computer Graphics' 카테고리의 다른 글

OpenGL - 6 Shaders (1)  (0) 2021.07.06
OpenGL - 5 Hello Triangle (3)  (0) 2021.06.30
OpenGL - 5 Hello Triangle (1)  (0) 2021.06.29
OpenGL - 4 Hello Window  (0) 2021.06.25
OpenGL - 3 Creating a window  (0) 2021.06.22