OpenGL/공부

[OpenGL] Polygon Rasterization (Bilinear Interpolation)

ciel45 2024. 5. 28. 18:03

프로그래머가 GPU로 넘겨주는 것은 버텍스에 관한 정보이지만, GPU가 만들어내는 최종 결과물은 각 픽셀에 대한 정보이다.

 

따라서 GPU의 임무는 서로 떨어져있는 버텍스들을 이어 도형(폴리곤)들을 만든 뒤, 그 폴리곤을 픽셀의 집합으로 만드는 것이다.

픽셀의 집합으로 만든다는 것은, 곧 폴리곤을 픽셀로 채운다는 이야기이다.

 

이렇게 폴리곤을 픽셀의 집합으로 바꾸는 과정Polygon Rasterization이라고 한다. 

 

여기에 사용되는 메커니즘이 바로 Bilinear Interpolation이다.

한국어로 하면 이중 선형 보간인데, 말 그대로 선형 보간을 이중으로 하는 것이다.

 

 

위 이미지가 버텍스 3개로 이루어진 폴리곤이라 가정하고, vA, vB, vC 버텍스의 컬러가 각각 빨강, 초록, 파랑이라고 하자.

우선 vA, vB, vC 버텍스가 놓인 픽셀의 색은 각각 빨강, 초록, 파랑으로 정할 수 있다.

이렇게 삼각형의 꼭짓점에 해당하는 픽셀의 색을 결정한다.

 

삼각형의 모서리가 픽셀로 그려지려면, 우선 선분 vAvC가 지나는 픽셀들의 색깔이 정해져야한다.

그리고 그 픽셀들의 색깔은 선형 보간으로 구할 수 있다. 

 

뭉뚱그려 말하자면 vA에 가까울 수록 빨간색, vC에 가까울 수록 파란색이 되는 것이며,

다음과 같은 선형 보간 식을 통해 수학적으로 계산이 가능하다.

t가 0에 가까워질 수록 a가 되고, 1에 가까워질 수록 b가 된다.

 

선분 vAvC 위의 색을 모두 정했다면, 선분 vBvC, 선분 vAvB 위의 픽셀들도 같은 방식으로 색을 정할 수 있다.

 

이제 사각형 내부를 채울 차례인데, 선분 vBvC 위의 픽셀 vD, 선분 vAvC위의 픽셀 vE를 잡아보자.

픽셀 vD와 vE가 색이 정해져있으므로, 선분 vDvE 위의 픽셀들의 색도 같은 방식으로 선형보간하여 색을 정할 수 있다.

 

이러한 방식으로 선분  vBvC 위, 선분 vAvC 위, 선분 vAvB 위의 픽셀을 계속하여 잡아가면서 그 사이를 선형보간으로 색을 칠하면, 삼각형 내부 모든 픽셀의 색을 모두 칠할 수 있게 된다.

 

이렇게 선형보간을 이중으로 하기 때문에 Bilinear Interpolation이라는 이름이 붙은 것이다.

 

참고로, 선분 EF와 같이 폴리곤 내부를 채우기 위한 선을 scan line이라고 한다.

 

Bilinear Interpolation이 바로 버텍스의 정보가 픽셀의 정보로 바뀌는 핵심 중의 핵심 과정이라고 할 수 있다.

 

 

이제 이를 이용해서 삼각형 내부에 색을 칠해볼 것이다.

컬러 값을 새로 만들기 귀찮으니, 각 버텍스의 컬러 값은 위치 값을 그대로 사용할 것이다.

 

바뀐 쉐이더 코드는 다음과 같다.

// Vertex Shader
static const char* vShader = "        \n\
#version 330                          \n\
                                      \n\
layout (location = 0) in vec3 pos;    \n\
                                      \n\
out vec4 vertexColor;                 \n\
                                      \n\
uniform mat4 model;                   \n\
                                      \n\
void main()                           \n\
{                                     \n\
    gl_Position = model * vec4(pos, 1.f);           \n\
    vertexColor = vec4(clamp(pos, 0.f, 1.f), 1.f);  \n\
}";

// Fragment Shader
static const char* fShader = "        \n\
#version 330                          \n\
                                      \n\
in vec4 vertexColor;                  \n\
                                      \n\
out vec4 color;                       \n\
                                      \n\
void main()                           \n\
{                                     \n\
    color = vertexColor;              \n\
}";

 

버텍스 쉐이더에서 out 변수 vertexColor를 하나 만들었다. 

버텍스 쉐이더에서의 out 변수라 함은, 뒤의 쉐이더로 값을 넘겨주겠다는 의미이다. 여기서는 프래그먼트 쉐이더로 넘겨주게 된다.

clamp를 사용한 이유는, 위치 값에 음수가 섞여있기 때문에 0과 1 사이로 밀어넣어준 것이다.

 

그런데, 프래그먼트 쉐이더에서는 똑같은 vertexColor 이름의 변수가 in으로 존재한다.

또한, 프래그먼트 쉐이더에서 최종 픽셀의 컬러로 그 vertexColor를 사용하고 있다.

여기서의 vertexColor는 per-vertex 정보에서 per-fragment 정보로 바뀌어있는 상태이다.

 

버텍스 쉐이더가 뒤로 넘긴 vertexColor가, 렌더링 파이프라인 중간에 rasterizer를 거치면서 bilinear Interpolation을 통해 자료의 형태가 바뀐 것이다.

프래그먼트 쉐이더는 그 바뀐 자료를 받아 자기 마음대로 사용할 수 있는 것이다.

 

 

전체 소스 코드:

#include <stdio.h>
#include <string.h>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <GLM/glm.hpp>
#include <GLM/gtc/matrix_transform.hpp>
#include <GLM/gtc/type_ptr.hpp>

// 창 크기
const GLint WIDTH = 800, HEIGHT = 600;

GLuint VAO, VBO, shader, uniformModel;

GLfloat translateOffset = -1;
GLfloat rotateOffset = 0;
GLfloat scaleOffset = 0;

// Vertex Shader
static const char* vShader = "        \n\
#version 330                          \n\
                                      \n\
layout (location = 0) in vec3 pos;    \n\
                                      \n\
out vec4 vertexColor;                 \n\
                                      \n\
uniform mat4 model;                   \n\
                                      \n\
void main()                           \n\
{                                     \n\
    gl_Position = model * vec4(pos, 1.f);           \n\
    vertexColor = vec4(clamp(pos, 0.f, 1.f), 1.f);  \n\
}";

// Fragment Shader
static const char* fShader = "        \n\
#version 330                          \n\
                                      \n\
in vec4 vertexColor;                  \n\
                                      \n\
out vec4 color;                       \n\
                                      \n\
void main()                           \n\
{                                     \n\
    color = vertexColor;              \n\
}";

void CreateTriangle()
{
    GLfloat vertices[] =
    {
        //    x     y    z
            -1.f, -1.f, 1.f,
            1.f, -1.f, 0.f,
            0.f, 1.f, 0.f
    };

    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // static draw : 넣은 값을 바꾸지 않겠다

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);
}

void AddShader(GLuint theProgram, const char* shaderCode, GLenum shaderType)
{
    GLuint theShader = glCreateShader(shaderType); // 빈 쉐이더를 생성, ID를 얻어옴

    const GLchar* theCode[1];
    theCode[0] = shaderCode;

    GLint codeLength[1];
    codeLength[0] = strlen(shaderCode);

    glShaderSource(theShader, 1, theCode, codeLength); // 생성한 쉐이더에 코드를 집어넣음
    glCompileShader(theShader); //컴파일

    GLint result = 0;
    GLchar errorLog[1024] = { 0 };
    glGetShaderiv(theShader, GL_COMPILE_STATUS, &result);
    if (!result)
    {
        glGetShaderInfoLog(theShader, sizeof(errorLog), NULL, errorLog);
        printf("Error compiling the %d shader : %s\n", shaderType, errorLog);
    }

    glAttachShader(theProgram, theShader); // 프로그램에 attach
}

void CompileShaders()
{
    shader = glCreateProgram();

    if (!shader)
    {
        printf("Error creating program\n");
        return;
    }

    AddShader(shader, vShader, GL_VERTEX_SHADER);
    AddShader(shader, fShader, GL_FRAGMENT_SHADER);

    GLint result = 0;
    GLchar errorLog[1024] = { 0 };

    glLinkProgram(shader); // 각 쉐이더를 컴파일해서 프로그램에 attach했으므로, 링킹 수행
    glGetProgramiv(shader, GL_LINK_STATUS, &result);
    if (!result)
    {
        glGetProgramInfoLog(shader, sizeof(errorLog), NULL, errorLog);
        printf("Error linking program : %s\n", errorLog);
    }

    glValidateProgram(shader); // 현재 OpenGL context에 유효한지 확인
    glGetProgramiv(shader, GL_VALIDATE_STATUS, &result);
    if (!result)
    {
        glGetProgramInfoLog(shader, sizeof(errorLog), NULL, errorLog);
        printf("Error validating program : %s\n", errorLog);
    }

    uniformModel = glGetUniformLocation(shader, "model");
}

int main()
{
    // GLFW 초기화
    if (!glfwInit())
    {
        printf("GLFW 초기화 실패\n");
        glfwTerminate();
        return 1;
    }

    // GLFW 윈도우 속성 셋업
    // OpenGL 버전
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    // Core profile = 이전 버전 호환성 없음
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // 앞으로의 호환성을 허용
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    GLFWwindow* mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "Test", NULL, NULL);
    if (!mainWindow)
    {
        printf("GLFW 창 생성 실패\n");
        glfwTerminate();
        return 1;
    }

    // 프레임 버퍼 크기 정보를 가져온다
    int bufferWidth, bufferHeight;
    glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);

    // glew가 사용할 컨텍스트 설정
    glfwMakeContextCurrent(mainWindow);

    // 최신 확장 기능을 허용
    glewExperimental = GL_TRUE;

    if (glewInit() != GLEW_OK)
    {
        printf("GLEW 초기화 실패\n");
        glfwDestroyWindow(mainWindow);
        glfwTerminate();
        return 1;
    }

    // 뷰포트 생성
    glViewport(0, 0, bufferWidth, bufferHeight);

    CreateTriangle();
    CompileShaders();

    // 창이 닫힐 때까지 반복
    while (!glfwWindowShouldClose(mainWindow))
    {
        // 사용자 입력 이벤트 가져오고 처리
        glfwPollEvents();

        // 창 지우기
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(shader);

        glm::mat4 model(1.f);
        model = glm::scale(model, glm::vec3(0.5f));

        glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(model));

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

        glUseProgram(0);

        glfwSwapBuffers(mainWindow);
    }
    return 0;
};

 

 

 

실행 결과:

 

 

여기서는 컬러가 Bilinear Interpolation 되었는데, 실제로는 normal, texcoord 등 다양한 attribute들이 Bilinear Interpolation 될 수 있다.