[Graphics] OpenGL로 삼각형 그려보기

8 분 소요

그래픽스 기초

0. 시작하기전에

해당 포스팅은 출처에 기재되어 있는 링크에서 대부분의 내용을 참조하여 번역 및 추가 설명을 한 것임을 먼저 말씀드립니다.

준비물 : GLSL 설치, OPENGL 설치

OpenGL은 원하는 3D 화면을 띄워주는 랜더링 라이브러리입니다.

일반적으로는 원하는 3D 모델에서 어떠한 방법으로 정보를 얻어오면(파싱) 그 정보를 가지고 OpenGL에서 3D로 랜더링하는 형식을 취하고

파싱하는 대표적인 라이브러리로는 Assimp, FBX SDK 등이 있고

랜더링하는 라이브러리로는 OpenGL, DirectX 등이 있습니다.

즉 3D 모델 -> 정보 파싱 -> 랜더링의 과정을 따릅니다.

처음부터 3D 모델을 바로 랜더링 해볼 수도 있겠지만, 그에 앞서서 간단한 예제로 시작을 해볼까 합니다.

간단한 예제로써 이번 포스팅에서 해볼 건 Triangle rendering이고, 개인적으로는 이걸 토대로 OpenGL을 처음 배웠었기에 시작으로

삼아보고자 합니다.

시작해보겠습니다. ***

1. 프로젝트 목표

삼각형 띄워보기 (2D)


2. 개념 설명

2-1. OpenGL에게 위치 정보 가르쳐주기

앞서 설명한 것 처럼 랜더링을 하기에 앞서, 랜더링을 할 정보를 받아야합니다.

받아야 할 정보중 가장 중요한 것은 바로 입니다.

(Vertex)들이 모여서 하나의 (Polygon)을 이루고 그 들이 모여서 하나의 3D ObJect가 완성됩니다.

이 점이 가지는 정보는 점의 좌표, 그리고 색깔으로 구성됩니다. 그렇기에 저희는 OpenGL쪽에 이러한 정보들을 넘겨줘야합니다.

우선 색깔은 제쳐두고, 점의 좌표만으로 색 없이 삼각형을 랜더링해보겠습니다.

우리가 랜더링하고자 하는 것은 3D Object이기 때문에, 점의 좌표 역시 x, y, z. 총 3축으로 구성되어 있습니다.

바로 실제 코드에서 점의 정보를 나타내는 방법을 보겠습니다.

float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

한 행이 x y z를 이루고 있습니다.(첫번째 줄을 예시로 들자면 x=-0.5f, y=-0.5f, z=0.0f).

3D 랜더링 라이브러리이기 때문에 3축의 정보를 받아왔지만, 이번 포스팅에서는 2D 삼각형의 랜더링이 목적이므로, Z는 모두 0입니다.

해당 Vertex들을 2D 평면에 나타내면, 아래와 같은 삼각형이 됩니다.

Alt text

그냥 저렇게 다차원배열을 만드는 것 만으로도 바로 삼각형이 랜더링 되면 좋겠지만, 그럴 수 없기에 우리는 받아온 정보를 OpenGL이 알아볼 수 있게 어떠한 특정한 공간에 저장해야 될 필요성이 있습니다.

그 공간을 Buffer라고 하고 특히 이와 같은 Vertex를 저장하는 버퍼를 VBO(Vertex Buffer Objects)라고 합니다.

VBO는 저희가 버퍼에 저장한 Vertex들을 GPU상에 올려주는 역할을 하고, 많은 점들을 한번에 보낼 수 있기 때문에 매우 효율적입니다.

이렇게 GPU에 올려둔 Vertex들은 랜더링할 때마다 다시 전송해야 할 필요없이, 한번 올려두는 것만으로도 계속해서 꺼내서 다시 쓸수있어서

속도 측면으로 매우 이점이 많습니다.

이제 실제로 VBO를 만들어보겠습니다.

Alt text

위와 같이 int형 변수를 하나 선언하고(변수명은 뭐가 됐든 상관없습니다. 혼란을 피하기 위해서 여기서는 VBO로 사용하겠습니다.)

glGenBuffer 함수를 통해서 이 변수를 VBO 버퍼로 만들어줍니다.

VBO 버퍼를 만들었으면, 이 버퍼에 데이터를 전송해주기 위해 바인딩이라는 과정을 해야합니다.

바인딩이란 특정 버퍼를 활성화 시켜주는 과정이라고 생각하시면 됩니다.

즉, 버퍼가 여러 개 있을 때 특정 버퍼를 사용하거나, 정보를 넣고 싶을 때 바인딩을 항상 하고 진행한다고 생각하시면 편하실 듯 합니다.

Alt text

위와 같이 glBindBuffer 함수로 우리가 만든 VBO를 GL_ARRAY_BUFFER에 관해서 바인딩해주면, 그 이후로 GL_ARRAY_BUFFER에 정보를 넣을 때

우리가 만든 VBO에 정보를 저장하게 됩니다.

바인딩이 끝났으면, Vertex 정보를 VBO 내에 넣습니다.

Alt text

glBufferData 함수는 사용자가 넣고 싶은 데이터를 지정된 버퍼에 복사해주는 역할을 합니다.

glBufferData(선택 버퍼, 넣을 데이터의 사이즈, 실제 넣을 데이터 배열, 데이터 종류)

나머지는 보시면 아실듯하고, 마지막 데이터 종류의 경우에는 총 3가지로 나뉩니다.

  • GL_STATIC_DRAW : 거의 변하지 않는 데이터
  • GL_DYNAMIC_DRAW : 자주 변하는 데이터
  • GL_STREAM_DRAW : 그려질 때마다 변하는 데이터

이렇게 데이터의 종류를 나눠두는 이유는, 데이터를 저장할 공간을 구분하기 위해서입니다.

거의 변하지 않는 데이터의 경우에는 데이터를 쓰는 속도가 느린 메모리에, 자주 변하는 데이터일수록 쓰는 속도가 빠른 메모리에 저장하게 됩니다.

물론 이렇게 하는 것은, 쓰는 속도가 빠른 메모리의 경우에는 전체 용량이 한정적이고, 느릴 수록 많은 용량이 확보되어 있기 때문에

효율적인 동작을 위해 이렇게 구성합니다.

아무튼, 길었지만 이렇게 우리는 성공적으로 VBO의 우리의 삼각형 데이터를 저장했습니다.

2-2. OpenGL에게 정보 표현 도구 만들어주기

Shader

일반적인 Shader의 정의는 아래와 같습니다.

화면에 출력할 픽셀의 위치와 색상을 계산하는 함수

네 맞습니다. Shader는 우리가 처음 프로그래밍을 배울 때 배웠던 그 함수입니다.

단지, 입력과 출력의 형태가 그래픽스에서 사용되는 요소들이라는 것만 다를 뿐입니다.

Shader는 C언어와 비슷한 GLSL이라는 언어의 문법을 따르며 종류는 많지만 기본적으로 우리가 사용할 Shader는 두 가지입니다.

  • Vertex shader : 점의 위치 정보를 처리하는 Shader
  • Fragment shader : 점의 색깔 정보를 처리하는 Shader

Vertex shader

Vertex shader는 정의에 적혀있는 것과 같이 점의 위치 정보를 처리하는 Shader입니다.

VBO로부터 점의 정보를 받아서, vertex shader로 처리를 하고, 그 output을 Fragment shader로 넘겨주는 식으로 작동이 됩니다.

Alt text

위 코드가 바로 Vertex shader 코드입니다.

코드는 버전부터 선언하며 시작하는데, 이때 보이는 버전이 OpenGL의 버전과 동일해야 작동합니다.

(예를 들어 GLSL 330 Version은 OpenGL 3.3버전과 같아야합니다.)

그 후 vec3 자료형을 가진 aPos 변수를 선언함과 동시에 location 또한 지정하는데, 이는 shader 외부에서 이 변수에 접근할 때

이 location에 접근하여 정보를 전달해야 이 변수에 정보가 대입되기 때문입니다.

또한 vec3 자료형이란 vector3의 줄임말으로, (x,y,z) 3축 좌표를 넣을 수 있는 vector라고 생각하시면 될 듯 합니다.

vector에 관한 설명은 따로 적지 않겠습니다.

그 후 출력 값을 내보내주기 위해서 gl_Position 이라는 변수에 받아온 aPos의 데이터를 전달해줍니다.

(이 gl_Position은 미리 선언된 변수로써 다음 쉐이더와 연결해주는 다리 역할을 하며 현재 shader의 output이 됩니다.)

위와 같이 shader의 소스 코드를 전부 작성했으면, 일반 코딩시에 함수를 전부 만든 것과 같습니다.

함수를 만들었으면, 그걸 실제 코드에 반영해야겠죠?

Alt text

과정은 VBO를 선언할 때와 매우 유사합니다.

shader가 되길 원하는 변수명을 지으시고(여기서는 vertexShader라고 명명하였습니다.) 이를 int형 변수로 선언합니다.

그리고 선언된 변수가 shader임을 지정하기 위해서 glCreateShader 함수를 사용하고, vertexShader로 지정하길 원하므로

GL_VERTEX_SHADER를 넣습니다.

Alt text

그 후에는 Shader를 컴파일하기 위해서 우선 glShaderSource를 사용합니다. glShaderSource(shader 객체, 소스 코드 문자열의 개수, shader 소스 코드 주소, NULL)

그 후, glCompileShader함수를 사용해 shader를 컴파일합니다.

컴파일이 완료되었다면, vertex shader가 완성된 것입니다.

Fragment shader

Alt text

Fragment shader는 하나의 출력 변수만을 가질 수 있고, 이는 RGBA 값을 가지는 vec4 자료형을 사용합니다.

이 코드 상에서는 FragColor라는 변수로 이를 선언했고, 이 변수에 원하는 색깔을 RGBA를 고려하여 지정합니다.

(RGBA에서 A는 Alpha를 뜻하는 것으로, 투명도를 뜻합니다. 0이 완전 투명, 1이 불투명)

Alt text

Fragment shader의 컴파일은 Vertex shader의 컴파일과 매우 유사합니다.

똑같이 int형 변수를 원하는 이름으로 선언해주고, glCreateShader를 통해서 shader를 만들고

glShaderSource 함수를 사용해서 shader 내부를 완성해주고

glCompileShader를 통해서 shader를 컴파일 해줍니다기

## Shader Program

우리는 총 두가지의 shader를 만들었습니다.

그렇지만 각각의 shader는 단지 만들어지고 컴파일 되어서 코드상에서 사용될 준비를 끝마쳤을뿐이고, 이를 이어주는 작업이 필요합니다.

이와 같은 역할을 해주는게 바로 shaderProgram입니다.

shaderProgram은 간단하기 때문에, 한번에 코드를 보여드리도록 하겠습니다.

Alt text Alt text Alt text Alt text 앞의 VBO나 Shader를 만들 때와 같이, int 형으로 원하는 이름의 변수를 선언해주고

glCreateProgram으로 변수를 program으로 만들어주고

glAttachShader로 vertex shader와 fragment shader을 program에 붙여주고

glLinkProgram으로 속한 shader들을 link(연결)시켜줍니다.

또 glUseProgram을 사용해서 이 프로그램을 활성화시키면, 더 이상 shader 객체 자체를 호출할 일은 없어지기 때문에

glDeleteShader로 만들었던 shader들을 지워줍니다.

2-3. OpenGL에게 정보 표현 도구 사용 방법 가르쳐주기

우리는 마침내 shader도 다 만들었고, 만든 shader 끼리 연결도 했습니다.

즉, 필요한 도구를 다 만든 샘입니다.

이제 그 도구를 어떻게 사용하는지를 알려줘야겠죠?

우리는 이미 buffer에 정보를 넘겨주었고, 이 정보들은 GPU에 모두 저장되어 있는 상태입니다.

이제 이 정보들을 shader로 넘겨주고, 처리를 통해서 우리가 원하는 정보를 바꾸어 내야겠죠?

Alt text

glVertexAttribPointer(vertex input 위치, vertex 크기, vertex 데이터 타입, 데이터 정규화 여부, stride, offset)

첫 번째 매개변수인 vertex input은 vertex를 받을 변수의 위치를 의미합니다. 저희가 shader를 만들 때 안에 들어 있던 input 변수의

location 파트가 기억나시나요? 그 때 정의했던 location에 vertex 정보들을 넘겨주게 됩니다.

두 번째 매개변수인 vertex 크기는, 말 그대로 vertex의 크기를 의미합니다. 저희는 3D vertex를 다룰거라서 vec3으로 정의했으니

이 크기 역시 3으로 지정해준 것 입니다.

세 번째 매개변수인 vertex 데이터 타입은 소수점 단위까지 사용이 필요하므로 float형을 사용하였고

네 번째 매개변수인 데이터 정규화 여부같은 경우에는 input으로 들어온 데이터들을 0~1 사이로 다시 정규화해 줄지를 묻는 변수입니다.

현재 저희는 따로 필요하지 않으므로 false를 주었습니다.

다섯 번째 매개변수인 stride같은 경우는 vertex 값들 사이의 공백을 의미합니다.

여섯 번째 매개변수인 offset같은 경우는 버퍼 내에서 데이터가 시작하는 위치를 의미합니다.

다섯 번째 매개변수와 여섯 번째 매개변수같은 경우에는 따로 신경쓰지마시고, 나중에 따로 언급하도록 하겠습니다. 쭉 따라와주세요.

Alt text

위 코드는 정점을 버퍼에 넣고, 버퍼에서 shader에 어떤 변수에 값을 넘겨줘야하고, shader를 사용하는 것까지를 나타내고 있습니다.

이제 마지막으로 그 정보를 그려주기만 하면 되지만, 만약에 우리가 랜더링하고 싶은 오브젝트들이 여러개이고, 이것들이 각각 서로 다른

Vertex 속성을 가져야한다고 생각하면 어떨까요? 할 때마다 저 코드를 불러와줘야하고, 너무나도 비 효율적일 것 같습니다.

이를 예방하기위해서, 삼각형을 그리기 전에 마지막으로 하나만 더 보겠습니다.

Vertex Array Object(VAO)

Alt text

VAO는 방금 전에 언급했던 오브젝트마다 vertex의 속성들이 달라진다면 다시 속성을 전부 정의해야 했던 문제점을 해결해줍니다.

위에 보이는 그림과 같이 VAO는 glVertexAttribPointer를 통해서 이루어진 vertex 속성 정의나 이와 연결된 VBO 그리고 vertex 배열을

사용하기 위해 필요한 glEnableVertexAttribArray과 같은 함수들의 호출에 관한 정보를 저장합니다.

다시 말하자면, 호출에 필요한 속성들을 배열의 요소마다 저장해둘 수 있어서 만약 object가 5개고, 랜더링에 필요한 vertex 속성의 개수도

5개라고 친다면, 단 5개의 요소만으로 간편하게 이 속성들을 저장할 수 있으며 랜더링시에는 단지 이 VAO에 접근하여 바인딩하는 것 만으로

랜더링이 가능한 것입니다.

Alt text

VAO를 생성하는 것은 VBO와 매우 유사하게 위와 같은 코드로 진행됩니다.

VAO를 성공적으로 생성하고 난 뒤에는, 각 요소에 Vertex 속성들을 모두 저장하기 위해서

VAO를 바인딩 한 후에, 속성들을 정의하는 과정을 거쳐야합니다.

그 과정은 아래와 같습니다.

Alt text

위에 말한대로, 우선 VAO를 바인딩하고 버퍼를 바인딩하고 데이터를 GPU에 올린 후에 Vertex 속성 포인터를 설정하고

VertexAttribArray를 enable 시킵니다.

그 후에는 랜더링 루프 내부에서 program을 사용하고 VAO를 바인딩하여

Alt text

glDrawArrays를 사용해주는 것으로 랜더링 과정은 끝이납니다.

glDrawArrays(랜더링 polygon 종류, vertex 시작 인덱스, vertex 종류 개수)

OpenGL에서는 일반적으로는 삼각형으로 polygon을 랜더링합니다. 저희도 거기서 벗어날 필요는 없기에 삼각형으로 지정하고

시작 인덱스 역시 0으로 지정하고, vertex 종류 개수는 (x,y,z) 3개이므로 3을 줍니다.

드디어! 코드를 컴파일 해 볼 시간입니다.

컴파일을 마치면!

Alt text

짠! 드디어 삼각형을 랜더링했습니다.

Element Buffer Objects

우리는 삼각형을 성공적으로 랜더링했지만, 좀 더 space 절약을 위해선 Element buffer objects 라는 것이 있습니다.

Alt text

만약에 저희처럼 삼각형 하나만 랜더링하는게 아니라, 사각형을 랜더링 하고 싶다면?

일반적인 OpenGL에서는 랜더링하는 Polygon을 삼각형을 사용하기 때문에 그 삼각형의 개수는 하나가 아닐 것입니다.

일반적인 오브젝트 랜더링에서는 보통 2개 이상일 것이기 때문에, 위와 같이 모든 정점을 따로 지정하는 것은

Vertex 개수가 많아지면 많아질 수록 비효율적인 면을 띄게됩니다.

위의 코드처럼 6개가 아니라 만약 4개만으로 이게 가능하다면 얼마나 좋을까요?

그 기능을 도와주는 것이 바로 EBO입니다.

EBO는 VBO처럼 버퍼이며, 꼭 필요한 정점을 따로 정의해두고, 그 정점에 인덱스(번호)를 붙여 그 번호를 호출하여 랜더링을 진행합니다.

Alt text

위 코드를 보시면, 원래는 6개이던 정점이 4개로 줄어들었고, 그 대신 indices라는 index의 배열이 추가되었습니다.

Alt text Alt text

EBO의 생성 과정은 여타 버퍼와 매우 비슷합니다.

똑같이 버퍼를 만들고, 버퍼를 바인딩 한 후에, 데이터를 넣습니다.

단, vertex는 이미 VBO에 들어가 있을테니, EBO에는 index 정보들만 넣습니다.

그 후에, 랜더링시에는 오직 glDrawArrays 함수를 glDrawElements 함수로 대체하기만 하면 됩니다.

Alt text

이 EBO는 여타 vertex의 속성을 저장했을 때처럼 똑같이 VAO에 저장시킬 수 있고, 마침내 VAO는 아래와 같은 형태를 띄게 됩니다.

Alt text

랜더링 코드는 결국 아래와 같은 형태를 띄게 됩니다.

Alt text

이 코드를 통해 랜더링 한 사각형 영상은 아래와 같습니다.

Alt text

프로젝트를 끝내며

드디어 삼각형 만들기 프로젝트가 끝이 났습니다.

첫 긴 글이었는데, 생각보다는 시간이 걸렸네요.

단 한분이라도 도움이 되는 글이었으면 좋겠습니다.

개인적으로도 정리할 수 있어 도움이 많이 되는 시간이었던 것 같습니다.

더더욱 정진하겠습니다.

화이팅!


전체 소스 코드

Link : Source_code


출처

https://learnopengl.com

댓글남기기