OpenGL은 주로 graphics/images를 조작하는 여러 함수들을 제공하는 API로 생각된다. 하지만 OpenGL 자체로는 API가 아니라 단지 규격/사양일 뿐이다.
따라서 OpenGL specification은 구체적인 구현을 제공하지 않으며, 버전에 따라 다르게 구현되어 있을 수 있다. 하지만 동일한 사양을 만족하므로 사용자 입장에서는 차이가 없다.
glGenObject: object 생성 및 reference를 id의 형태로 저장. 실제 object의 data는 감춰져 있다.
glBindObject : object를 context의 타겟(GL_WINDOW_TARGET) 위치에 bind.
glSetObjectOption : window option 세팅.
glBindObject(GL_WINDOW_TARGET, 0) : GL_WINDOW_TARGET에 bind되어있는 object를 unbind.
// create object
unsigned int objectId = 0;
glGenObject(1, &objectId);
// bind/assign object to context
glBindObject(GL_WINDOW_TARGET, objectId);
// set options of object currently bound to GL_WINDOW_TARGET
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// set context target back to default
glBindObject(GL_WINDOW_TARGET, 0);
// define the function’s prototype
typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);
// find the function and assign it to a function pointer
GL_GENBUFFERS glGenBuffers =
(GL_GENBUFFERS)wglGetProcAddress("glGenBuffers");
// function can now be called as normal
unsigned int buffer;
glGenBuffers(1, &buffer);
#include <glad/glad.h> // GLFW보다 먼저 와야함.
#include <GLFW/glfw3.h>
glViewport(x, y, w, h) : OpenGL window의 사이즈 정의. (x, y)는 window의 lower left corner 기준 위치. (w, h)는 window의 width, height.
OpenGL 내부에서glViewport에서 정의한 data를 이용해서 normalized 2D coordinate에서 스크린 상의 coordinate 변환을 수행한다. 예를 들어 (w=800,h=600)인 경우, (-0.5, 0.5)는 (200, 450)으로 매핑된다. 즉 (-1~1,-1~1)의 좌표가 (0~w,0~h)로 선형 매핑된다.
glfwSwapBuffers(window) : OpenGL에서는 double buffer를 사용해서 flickering 문제를 해결한다. front buffer에는 screen에 출력할 이미지를 담고 있고, back buffer에 rendering 함수를 이용해 그림을 그린 뒤 glfwSwapBuffers를 이용해 두 버퍼를 swap한다.
glfwTerminate() : 프로그램 종료 시 호출해 GLFW 관련 자원 반환.
glfwPollEvents() : 키보드, 마우스 이벤트 등이 발생했는 지 체크해서 state를 변경. 내가 정의한 callback method를 호출할 수 있다.
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS
: ESC 키를 누르고 있는 지 체크. input을 체크해서 원하는대로 state를 변경할 수 있다.
glClear(GL_*_BUFFER_BIT) : 이전 루프에서 그렸던 이미지를 초기화하는 코드. 인자로 어떤 buffer를 clear할 것인지 결정할 수 있다. (COLOR, DEPTH, STENCIL)
glClearColor(r, g, b, 1.0f) : glClear(GL_COLOR_BUFFER_BIT)을 이용해 색상을 초기화할 때 glClearColor를 이용해서 미리 세팅해둔 rgb 값으로 초기화됨.
OpenGL이 하는 일은 3D 좌표계에서 screen 상의 2D pixel로 변환하는 것이다. 이것은 OpenGL의 graphics pipeline에 의해 이뤄진다.
graphics pipeline : 크게 3D -> 2D 변환과 2D -> pixel 변환 두 가지로 나눌 수 있다. graphics pipeline은 여러 step으로 나눌 수 있고, 각 스텝들은 병렬적으로 처리될 수 있다.
shaders : graphics pipeline의 과정이 parallel하게 처리되므로 GPU 내부의 processing core가 파이프라인의 각 스텝을 작은 프로그램으로 나누어 실행하는데, 이 small program을 shaders라 한다.
shaders는 GLSL 언어로 작성된다.
pipeline의 input은 vertex data로, 3D 좌표의 배열이다. vertex data는 vertex attributes를 이용해 vertex data의 구성을 정의한다.(3D position + color + ...등등)
primitives : vertex data를 이용해서 점, 삼각형, 선분들 중 어떤 것을 그리고 싶은 지 OpenGL에게 힌트를 줘야한다. 이 힌트를 primitives라 하고, GL_POINTS, GL_TRIANGLES, GL_LINE_STRIP 등이 있다.
pipeline
vertex shader : pipeline의 첫번째 부분으로 1개의 vertex를 input으로 받는다. 3D 좌표를 다른 3D 좌표로(나중에 자세히 다룸) 변환함.
primitive assembly : vertex shader로부터 모든 vertices를 받아 모든 점들을 주어진 primitives에 따라 assemble한다.
geometry shader : 새로운 vertices를 추가해 새로운 primitives를 구성, 새로운 shape를 만들어낼 수 있음.
rasterization state : 결과 primitives를 screen 상의 pixel로 매핑. clipping을 구행해 window의 범위를 벗어나는 fragment들을 모두 버림.
fragment shader : 픽셀의 최종 색상을 계산. 주로 fragment shader에 3D scene에 대한 데이터(ligt, shadow, color of light...)를 포함하고 있으며 이것들을 이용해서 최종 색상을 계산한다.
6-1. alpha test / blending : fragment의 depth/stencil value를 체크해 결 fragment가 object의 앞/뒤에 있는 지 체크하고 버릴 지를 판단. alpha value(object의 opacity 투명도)를 검사해 blending 수행.
6개의 pipeline 중 우리가 작성해야하는 것은 vertex shader / fragment shader이다.
geometry shader는 필수적이지 않고, 주로 default shader를 그대로 사용.
이외에도 tessellation / transform feedback loop 가 있으나 나중에 다룸.
GL_STREAM_DRAW : data set은 최초 한 번. GPU에 의한 사용 빈도 적음.
GL_STATIC_DRAW : data set은 최초 한 번. GPU에 의한 사용 빈도 많음.
GL_DYNAMIC_DRAW : data가 자주 변함. GPU에 의한 사용 빈도 많음.
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
layout (location = #) in type name
: input data.#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
const char* vertexShaderSource
에 저장.const char *vertexShaderSource = "~~~";
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
out vec4 FragColor
: fragment shader의 output#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
const char *fragmentShaderSource = "~~~";
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
Shader Program object : 여러 셰이더가 합쳐진 오브젝트. 컴파일된 셰이더를 사용하기 위해서는 이것들을 shader program object에 "link"하고, object를 렌더링하는 순간에 활성화해야한다.
셰이더를 linking하게되면 앞선 shader의 output이 뒤 shader의 input과 연결된다. 변수명이 동일해야하므로 주의.
glCreateProgram() : program object 생성.
glAttatchShader() : shader를 program에 추가.
glLinkProgram() : shader들을 linking.
glUseProgram() : 프로그램 활성화. 해당 프로그램을 사용하려고 할 때 사용.
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
...
# when using Program
glUseProgram(shaderProgram);
glDeleteShader() : program object의 link가 완료되면 shader object는 더이상 필요하지 않기 때문에 제거해줌.
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
vertex data는 배열 타입으로, 사용자가 정의하는 대로 data가 나열되어 있다. 따라서 vertex data에 저장된 데이터의 어떤 부분이 vertex shader의 layout (location = #) 변수에 해당하는 지를 정해줘야한다.
현재 내가 정의한 vertex data는 위와 같은데,
position data는 4byte float 값이다.
각 position은 3개의 float으로 구성된다.
각 float 값 사이에 빈 공간이나 다른 value는 존재하지 않는다.(tightly packed)
data의 처음 값은 buffer의 처음 부분에 위치한다.
glVertexAttribPointer() : 바로 위에있는 정보들을 인자로 넣어준다. OpenGL이 vertex data를 해석할 수 있도록 정보 제공. 이 함수를 호출하기 전에 현재 내가 사용할 VBO를 bind 해주어야 한다.
들어갈 parameter는 아래와 같다.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
지금가지의 과정을 순서대로 코드로 작성하면 아래와 같다.
// VBO bind
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// bind한 버퍼에 data를 dumping
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// vertex attribute pointer 세팅
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
// vertex attribute enable
glEnableVertexAttribArray(0);
// program 활성화
glUseProgram(shaderProgram);
// TODO : Draw Triangle
위 과정을 object를 그릴 때마다 반복해야 하는데, 꽤나 번거롭다. 이것들을 VAO에 저장해서 그때그때 불러와서 사용할 수 있다.
VAO(Vertex Array Object) : vertex buffer object와 vertex attribute들을 bind할 수 있음. 그러면 vertex attribute pointer를 한번만 생성하고, object를 그릴 때마다 사용할 수 있다.
VAO가 저장하는 것들
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);
...
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
//TODO : Draw triangle
그려야 할 object가 여러개라면 우선 VAO를 모두 생성해둔 뒤, 이후에 VAO bind를 해가며 그릴 수 있다.
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 0);
만약 사각형을 그리고 싶다면 삼각형 2개를 이용하면 된다.
그렇다면 하나의 사각형을 위해 6개의 vertex를 사용해야 하는데, 겹치는 vertex가 존재하므로 최소 4개의 vertex만 있어도 그릴 수 있어야 한다.
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
EBO 또한 VAO에 함께 저장할 수 있다.
// initialization
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
...
// render loop
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0); //unbind