OpenGL 2. Triangle
Triangle
그래픽에서 점(vertex)의 의미는 수학에서의 점과 개념이 살짝 다르다. 그래픽에서 점은 위치데이터뿐만 아니라, 색깔, 텍스쳐같은 데이터까지 포함한다..
Graphic pipeline
-
Vertex shader
모든 점들의 위치를 가져온다.
-
Shape assembler
점들을 primitive를 통해 잇는다.
-
Geometry shader
점을 추가할 수 있고, 이미 존재하는 primitive가 아닌 새로운 primitive를 만들 수 있다.
-
Rasterization
모든 완성된 기하학적 모델들은 픽셀로 변환된다. 이전에 삼각형이었던 것이 한 묶음의 픽셀로 표현된다.
-
Fragment shader
무색이었던 픽셀에 색깔을 채워넣는다. 조명, 텍스쳐, 그림자 등 여러 요인에 따라 달라진다.
-
Test and Blending
이 시점에서 여러 개체가 겹치는데, 이 때문에 하나의 픽셀에 대해 다양한 색깔이 있을 수 있다. 이 단계에서 이미지가 결정되고 출력된다.
code
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
// 삼각형의 정점을 그리기 위한 vertex shader 코드 (GLSL로 작성)
const char* vertexShaderSource = "#version 330 core\n" // GLSL 버전 330을 사용한다고 선언. core은 코어 프로파일을 사용한다는 의미, 모던 OpenGL에서만 사용가능
"layout (location = 8) in vec3 aPos;\n" // 입력으로 사용되는 vertex 속성인 aPos 정의. layout (location = 8)은 해당 속성이 버텍스 데이터에서 8번 인덱스에 위치한다는 것을 의미한다. vec3는 3차원 벡터 타입을 의미하며 정점의 위치 좌표를 나타내는 변수이다.
"void main()\n" // vertex shader의 진입점을 나타낸다. main()함수는 모든 vertex에 대해 실행된다.
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" // 이 코드는 vertex의 위치를 설정한다. gl_Position은 내장된 변수로, vertex의 위치를 나타내는 4차원 벡터이다. vec4()는 aPos 변수의 x, y, z 좌표값을 사용하여 4차원벡터를 생성하고, 이를 gl_Position에 할당한다. vertex의 좌표값은 정규화된 디바이스 좌표로 표현되며, 범위는 -1 ~ 1이다.
"}\0";
// 삼각형의 색상을 결정하기 위한 fragment shader 코드
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n" // 출력으로 사용되는 fragment 속성인 FragColor를 정의한다. vec4는 4차원 벡터 타입을 나타내며, 프래그먼트의 색상을 나타내는 변수이다.
"void main()\n"
"{\n"
" FragColor = vec4(0.8f, 0.3f, 0.02f, 1.0f);\n" // fragment의 색상을 설정하는 부분이다. 각각 r, g, b, 투명도를 의미
"}n\0";
int main(void)
{
// GLFW 라이브러리를 초기화
glfwInit();
// GLFW 창 생성 전에 창 힌트(창의 동작과 모양을 제어)를 설정한다. 창의 OpenGL 컨텍스트의 버전 및 프로파일 등을 지정한다.
// OpenGL 3.3 사용
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 코어 프로파일을 사용함을 알린다
// modern function만을 가짐을 의미한다
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 삼각형의 정점 좌표를 포함하는 배열. 정점의 x, y, z좌표를 저장한다.
GLfloat vertices[] =
{
-0.5f, -0.5f * float(sqrt(3)) / 3, 0.0f,
0.5f, -0.5f * float(sqrt(3)) / 3, 0.0f,
0.0f, 0.5f * float(sqrt(3)) * 2 / 3, 0.0f
}
// 800 x 800 픽셀의 GLFWwindow를 생성하고 이름을 Youtube OpenGL이라 붙였다.
GLFWwindow* window = glfwCreateWindow(800, 800, "YoutubeOpenGL", NULL, NULL);
// 윈도우 생성을 제대로 하는지 체크
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
// 현재 OpenGL 컨텍스트로 사용할 창을 지정한다.
glfwMakeContextCurrent(window);
// glad를 사용하여 OpenGL 함수 포인터를 로드한다.
// OpenGL 함수는 런타임에 로드되어야 한다. 이때문에 다양한 플랫폼, 운영체제에서는 각각의 OpenGL 확장을 로드하는 다른 방법을 제공한다.
// 이 함수는 현재 활성화된 OpenGL 컨텍스트에 대해 필요한 모든 OpenGL 함수 포인터를 로드한다.
gladLoadGL();
// 뷰포트를 설정하여 출력 창의 크기를 지정한다.
// (0, 0)에서 (800, 800)
glViewport(0, 0, 800, 800);
// vertex shader를 생성한다.
// GL_VERTEX_SHADER : vertex shader를 나타내는 상수이다. vertex shader은 정점의 위치, 색상, 텍스쳐 좌표 등과 같은 속성을 수정하거나 새로운 속성을 생성할 수 있다.
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); // OpenGL버전 unsigned int
// shader에 소스를 연결한다.
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
// vertex shader를 기계어로 컴파일한다.
glCompileShader(vertexShader);
// fragment shader를 생성한다.
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
// shader에 소스를 연결한다.
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
// vertex shader를 기계어로 컴파일한다.
glCompileShader(fragmentShader);
// 프로그램 객체를 생성한다. 프로그램 객체는 shader 객체를 연결하고 링크하여 최종적으로 실행가능한 프로그램을 생성하는 데 사용된다.
GLuint shaderProgram = glCreateProgram();
// 생성한 프로그램 객체에 vertex shader를 연결한다. shaderProgram은 프로그램 객체의 ID이고, vertexShader은 shader의 ID이다.
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
// 프로그램 객체에 연결된 shader들을 링크하여 최종적으로 실행가능한 프로그램을 생성한다. 프로그램 링크 후에는 개별 shader 객체가 더이상 필요하지 않으므로 삭제할 수 있다.
glLinkProgram(shaderProgram);
// shader객체를 삭재한다. 프로그램이 이미 링크돼있으므로 shader객체는 더이상 필요하지 않다. shader객체를 삭제하여 메모리 해제.
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 정점 배열 객체와 버퍼 객체의 ID를 저장하기 위한 변수
GLuint VAO, VBO;
// VAO를 생성. ID가 VAO에 저장된다.
glGenVertexArrays(1, &VAO); // 반드시 VBO 이전에 생성할것!!(중요!!)
// VBO를 생성, ID가 VBO에 저장된다.
glGenBuffers(1, &VBO);
// 이후의 VAO관련 함수 호출은 VAO에 바인딩된 vAO를 대상으로 수행된다. VAO를 현재 컨텍스트에 바인딩한다.
glBindVertexArray(VAO);
// 이후의 GL_ARRAY_BUFFER 관련 함수 호출은 VBO에 바인딩된 버퍼를 대상으로 수행된다. VBO를 현재 컨텍스트에 바인딩한다.
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 현재 바인딩된 버퍼(VBO)에 데이터를 복사한다. sizeof(vertices)는 정점 데이터의 크기를 바이트 단위로 나타낸다. vertices는 복사할 데이터의 포인터이다. GL_STATIC_DRAW는 버퍼의 사용 방식을 나타내며, 정적으로 변경되지 않는 데이터를 의미한다.
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 현재 바인딩된 VBO의 데이터를 해석하는 방법을 지정한다. 첫번째 인자는 vertex shader의 attribute 위치를 나타낸다. 두번째 인자는 각 정점의 좌표 구성요소 개수이다. 세번째 인자는 좌표의 데이터 형식을 나타낸다. 네번째 인자는 좌표 데이터를 정규화하지 않음을 나타낸다. 다섯번째 인자는 각 정점의 데이터 크기이다. 여섯번째 인자는 데이터의 시작 위치를 나타낸다.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
// 현재 바인딩된 VBO의 vertex attribute 배열을 활성화 한다. 이렇게 하면 해당 attribute를 사용하여 정점 데이터를 그래픽 파이프라인으로 전달할 수 있다.
glEnableVertexAttribArray(0);
// 화면을 지울 때 사용할 배경색을 설정한다.
glClearColor(0.07f, 0.13f, 0.17f, 1.0f); // 순서대로 R, G, B, 투명도를 의미한다.
// 현재 설정된 색상으로 화면을 지운다.
glClear(GL_COLOR_BUFFER_BIT);
// Front buffer와 Back buffer swqp
glfwSwapBuffers(window);
// 창이 닫힐 때까지 이벤트를 처리하는 루프. 이 루프에서는 사용자의 입력 및 이벤트 처리를 수행한다.
while (!glfwWindowShouldClose(window))
{
glClearColor(0.07f, 0.13f, 0.17f, 1.0f);
// 현재 색 버퍼를 설정된 색상으로 지운다. GL_COLOR_BUFFER_BIT은 색 버퍼를 나타내는 플래그이다.
glClear(GL_COLOR_BUFFER_BIT);
// 실행할 shader 프로그램을 선택한다. shaderProgram은 생성된 프로그램 객체의 ID이다.
glUseProgram(shaderProgram);
// 실행할 정점 배열 객체(VAO)를 선택한다. 이렇게 함으로써 VAO에 설정된 정점 속성들이 활성화된다.
glBindVertexArray(VAO);
// 현재 바인딩된 VAO의 정점 데이터를 사용하여 삼각형을 그린다. GL_TRIANGLES은 삼각형을 그리는 모드를 나타내며, 0은 정점 배열의 시작 인덱스, 3은 그릴 정점의 개수
glDrawArrays(GL_TRIANGLES, 0, 3);
// 전면, 후면 버퍼를 스왑
glfwSwapBuffers(window);
// 현재 발생한 이벤트를 처리하는 데 사용되는 GLFW 이벤트 처리 함수이다. 이 함수는 창의 이벤트 큐에서 이벤트를 가져와 해당 이벤트를 처리한다.
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
// 프로그램 종료 전에 윈도우 종료
glfwDestroyWindow(window);
// 프로그램 종료 전에 GLFW 종료
glfwTerminate();
return 0;
}
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Stream : vertices will be modified once and used a few times
// static : vertices will be modified once and used many many times
// dynamic : vertices will be modified multiple times and used many many times
실행결과
삼각형 하나 그리는게 이렇게 어렵다니.. 이해될때까지 씹고 뜯고 맛봐야겠다.
댓글남기기