5 분 소요

Triangle

그래픽에서 점(vertex)의 의미는 수학에서의 점과 개념이 살짝 다르다. 그래픽에서 점은 위치데이터뿐만 아니라, 색깔, 텍스쳐같은 데이터까지 포함한다..

Graphic pipeline

  1. Vertex shader

    모든 점들의 위치를 가져온다.

  2. Shape assembler

    점들을 primitive를 통해 잇는다.

  3. Geometry shader

    점을 추가할 수 있고, 이미 존재하는 primitive가 아닌 새로운 primitive를 만들 수 있다.

  4. Rasterization

    모든 완성된 기하학적 모델들은 픽셀로 변환된다. 이전에 삼각형이었던 것이 한 묶음의 픽셀로 표현된다.

  5. Fragment shader

    무색이었던 픽셀에 색깔을 채워넣는다. 조명, 텍스쳐, 그림자 등 여러 요인에 따라 달라진다.

  6. 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

실행결과

triangle

삼각형 하나 그리는게 이렇게 어렵다니.. 이해될때까지 씹고 뜯고 맛봐야겠다.

댓글남기기