대표적인 OpenGL의 예제들을 살펴보면 아래와 같은 형태의 데이터를 Vertex Shader에 전달해준다.

std::vector<glm::vec2>	texCoord = {
	1.0f, 1.0f,
	1.0f, 0.0f,
	0.0f, 0.0f,
	0.0f, 1.0f
}

단순하게 생각해보면 Texture를 가져온 데이터를 좌표값을 나타냈을 때,  아래처럼 생겼기에 화면에 맞추는 작업이구나 하고 넘어갈 수 있다.

left  top(0.0f, 1.0f)
right top(1.0f, 1.0f)
left  bottom(0.0f, 0.0f)
right bottom(1.0f, 0.0f)

 

하지만, 이렇게 맞춰서 변환하는 과정은 생각보다 길고, 복잡할 수 있다.


Tangent Plane

구로 예시를 들어서 말하는 것으로 하겠습니다.

구에서 임의의 한 점과 맞닿는 평면, 이 평면이 Tangent Plane이다.
또한, 이런 평면을 좌표계 기준으로 잡는 공간을 Tangent Space라고 한다.


OpenGL에서 Tangent Plane이 이 어떻게 쓰일까?

OpenGL에서는 3개의 Vertex로 이루어진 삼각형을 하나의 Tangent Space에 존재한다고 생각한다.
그래서 주어진 Vertices와 Indices를 기준으로 Tangent Space를 계산한다.

주어진 Vertices와 Indices에 따라 2개의 벡터를 생성[$E_{1}, E_{2}$]하고,
주어진 Vertices에 대한 TexCoord에 따라 u와 v를 2개씩 생성[$\begin {bmatrix} u_1 & v_1 \\ u_2 & v_2 \end {bmatrix}$]한다.

여기서 우리는 Tangent, Bitangent Vector를 구할 수 있다.

$\begin {bmatrix} E_{1x} & E_{1y} & E_{1z} \\ E_{2x} & E_{2y} & E_{2z} \end {bmatrix} = \begin {bmatrix} u_1 & v_1 \\ u_2 & v_2 \end {bmatrix} \begin {bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end {bmatrix}$

사실 이렇게 보면 잘 모르겠어서 여러 가지로 생각해봤다.

  1. 이 변환 과정은 한 번에 진행되는 것이 아니다.
  2. 여러 가지 좌표계의 변환을 통해 생긴 것.
  3. 단순히 변환 행렬을 구한 것이지만 여러 개의 혼합된 버전일 것.
  4. 해당 좌표계로 이동, 좌표계 방향에 맞춰 회전, 해당 크기로 축소/확대의 혼합
  5. 3차원에서 2차원으로 변환하는 것이기에 이렇게 구하는 것.
  6. 한 번에 변환하는 행렬을 구한 것일 듯?

결국 구하고 싶은 행렬을 우린 TB Matrix라고 생각했을 때, 생성된 uv Matrix의 역행렬과 2개의 Vector로 이뤄진 Matrix를 곱하면 구할 수 있다.

$\begin {bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end {bmatrix} = \begin {bmatrix} u_1 & v_1 \\ u_2 & v_2 \end {bmatrix} ^{-1} \begin {bmatrix} E_{1x} & E_{1y} & E_{1z} \\ E_{2x} & E_{2y} & E_{2z} \end {bmatrix}$


하지만 이와 같은 과정에서 TBN의 Space에서 서로가 직교하지 않을 수 있다.
그렇기 때문에 그람-슈미트 직교화를 통해 각각의 벡터가 직교하도록 만든다.

위의 방법처럼 한 번에 구하는 것이 아니라, Tangent만 구해서 Shader로 넘겨준다.
이후 Shader에서 Normal과 Tangent를 Cross Product 하면 Bitangent의 값이 나온다.

'OpenGL' 카테고리의 다른 글

Normal Vector의 변환 행렬  (0) 2022.11.14
OpenGL[Lighting]  (0) 2022.10.18
OpenGL[Coordinate System]  (0) 2022.10.18
OpenGL[02](First Triangle)  (1) 2022.10.12
OpenGL[01](Shader)  (1) 2022.10.12

Plane Equation

한 평면 위의 임의의 Vector와 평면의 법선 벡터(Normal Vector)는 서로 수직 한다.
즉, $ V_{평면 위의 임의의 Vec} \cdot N_{평면의 법선 벡터} = 0$ 를 만족한다.


Vertex Position의 변환 행렬[M]은 평면 위의 임의의 점을 변환시켜주는 변환 행렬이다.

변환 전 : $N \cdot V = 0$
변환 후 : $N' \cdot V' = 0$

여기서 법 선 벡터[N]와 평면 위의 임의의 Vector 사이에 $M^{-1} M$를 삽입한다.($M^{-1} M = I$)
$NM^{-1} MV = 0 \rightarrow \  NM^{-1} V' = 0 = N'V' \rightarrow \ NM^{-1} = N'$
$(M^{-1})^{T} N = N'$


결론

법선 벡터의 변환 행렬은 Vertex Position의 변환 행렬의 역행렬을 전치시킨 행렬이다.
[$(M^{-1})^{T}$]

 

'OpenGL' 카테고리의 다른 글

Texture Coordinate  (0) 2022.11.14
OpenGL[Lighting]  (0) 2022.10.18
OpenGL[Coordinate System]  (0) 2022.10.18
OpenGL[02](First Triangle)  (1) 2022.10.12
OpenGL[01](Shader)  (1) 2022.10.12

이번 내용은 LearnOpenGL에서의 내용과, 이미지를 사용하여 적어보겠습니다.

실생활의 조명은 매우 복잡하지만, OpenGL에서의 조명은 처리되기 쉽고 실제 사물과 비슷하게 보이는 모델을 사용하여 실제에 대한 근사치를 기반으로 이뤄져 있다. 사실 빛이 반사되는 모든 경우를 계산해서 정점의 색을 결정한다면 표현할 수 있겠지만, 그럴 수 없기 때문이다.

Flat Shading

  • 기본적인 Shading으로 하나의 Primitive를 하나의 색으로 채우는 방식이다.
  • Primitive마다의 색이 다르기 때문에 색이 있는 폴리곤을 보는 느낌이 들 수 있다.

Gouraud Shading

  • Primitive의 정점에 대해 색상을 부여하여 색상을 보간하여 채우는 방식이다.
  • Flat Shading보다는 자연스럽게 표현되지만 어딘지 모르게 부자연스러운 느낌이다.

Phong Shading

  • Primitive의 채워지는 Pixel에 대해 색상을 계산하여 채운다.
  • Pixel마다의 빛을 고려했기 때문에 자연스럽게 계산된다.


나는 Phong Lighting Model에 대해 적어보려 한다.(앞으로 이야기할 색의 범위는 [0~1]이다.)

Phong Lighting Model

  1. Ambient Lighting(주변광)
  2. Diffuse Lighting(분산광)
  3. Specular Lighting(반사광)

Ambient Lighting

아무리 어두운 곳에 있더라도 어디선가는 빛이 있다.
빛은 여러 방향으로 산란하여 퍼지기 때문에 어디 있을지 모르는 물체에 간접적인 영향을 준다.
하지만, 얼마나 영향을 받을지는 모르기에 사용자가 정한다.

결과적으로, Ambient 상수, 빛의 색, 물체의 색을 곱한 값이 된다.


Diffuse Lighting

빛과 물체의 관계에 따른 물체의 기본적인 색을 결정하는 데 사용된다.

광원에서 정점까지의 빛을 법선 벡터와 정점의 평면벡터로 나누게 되면 해당 값이 나오는데 이를 내적으로 계산한다.
즉, 빛의 세기가 1이라고 했을 때 법선 벡터 방향의 빛의 세기라고 볼 수 있다.

광원에서 정점까지의 벡터를 L, 정점의 법선 벡터라 했을 때, 내적 $(-L) \cdot N = |L||N|cos\theta = cos\theta$
여기서 만약 내적 값이 0보다 작다면 앞면이 아니기 때문에 그리지 않아도 된다.


Specular Lighting

빛과 물체 그리고 카메라의 관계에 따른 빛에 의해서 강조되는 부분을 표현하는 데 사용한다.

광원에서 정점까지의 빛을 정말 반사했을 때의 벡터, 카메라의 위치에서 정점까지의 벡터를 내적 한 값을 n제곱을 하면 된다.

위에서 Diffuse에서처럼 내적 값이 0보다 작다면 앞면이 아니기 때문에 그리지 않아도 된다. (0 처리하면 됨)

여기서 n제곱을 하는 마지막 과정이 있는데, 이는 우리가 반사된 빛을 얼마나 똑바로 보고 있는가를 판단하는데,
해당 값의 범위가 [0~1] 사이인데, 이 값을 n 제곱하면서 1과 가까운 값만 유효하게 쓸 수 있도록 해준다.
여기서 n의 값이 크면 클수록 유효한 범위가 작아지는 것을 알 수 있다.

'OpenGL' 카테고리의 다른 글

Texture Coordinate  (0) 2022.11.14
Normal Vector의 변환 행렬  (0) 2022.11.14
OpenGL[Coordinate System]  (0) 2022.10.18
OpenGL[02](First Triangle)  (1) 2022.10.12
OpenGL[01](Shader)  (1) 2022.10.12

Local Space

OpenGL에서 우리가 물체를 그리기 위해 정점의 위치를 정할 때 기준을 정하여 기준에 맞춘 위치를 명시한다.
예를 들면 육면체는 육면체의 중심, 원기둥은 아래 뚜껑의 중앙, 원뿔은 바닥의 중앙 등의 기준을 갖는다.
이런 기준을 Local Space에서의 원점이고 말 그대로 물체를 기준으로 본 좌표계에서의 원점이다.

하지만, 우리가 그릴 물체는 여러 개일 수도 있고, 움직임이 존재할 수 도 있고, 축소/확대될 수도 있다.
그러기 위해 우리는 Local Space에 존재하는 물체를 모든 물체에 기준이 되는 좌표계에 그려주어야 한다.
이를 World Space라고 한다.


World Space

World Space에서의 원점은 우리가 그릴 모든 물체에 기준이 되는 좌표계이다.
즉, 물체의 위치, 기울임, 크기 등을 조절해서 여러 개의 물체들을 World Space에 동시에 존재하도록 할 수 있다.
우리는 OpenGL에서 glm을 이용하여 Local Space에서 World Space로 변환 행렬을 만들고,
해당하는 정점의 위치, 법선 벡터에 적용시키게 된다.

이때, 정점의 위치는 단순한 위치를 표현하지만 법선 벡터는 방향을 갖기 때문에 변환 행렬을 수정해야 한다.

더보기

법선 변환 행렬의 증명과정은 아래와 같다.

 

T : 법선에 수직하고, 해당하는 정점과 임의의 한 점까지의 벡터
N : 법선
T' : 변환된 T
N' : 변환된 N
M : 정점의 변환 행렬
G : 법선의 변환 행렬

T와 N은 수직 하기 때문에 내적 했을 때 0이 나온다.
$N \cdot T = 0,\ N' \cdot T' = 0$

$N'\cdot T' = (GN)\cdot(MT)=0$
$N'\cdot T' = (GN)^{T}\cdot(MT)=0$
$N'\cdot T' = N^{T} G^{T} M T)=0$

위 상황에서 우리는 $N^{T} T = 0$인 것을 알고 있다.
그렇다면 $G^{T} M = I$가 되어 $G = (M^{-1})^{T}를 만족하게 된다.

법선 변환 행렬은 변환 행렬의 역행렬의 전치 행렬이다. => $(M^{-1})^{T}$이다.
이렇게 정점의 정보들을 World Space로 변환하면 사전작업이 완료됐다 볼 수 있다.

여기서 우리가 그려야 할 장면은 어떤 위치에서 어떤 방향으로 존재하며, 어떻게 해야 할지는 다음 과정에서 알 수 있다.


View Space

우리가 보는 장면, 카메라가 보는 장면이라 해서 Camera Space, Eye Space라고 불리기도 한다.
조금 어렵게 보일 수 있지만, 장면을 그리기 위해서 World Space의 앞/뒤, 위/아래, 위치를 변환하는 과정이다.

이 과정에서 카메라의 위치, 카메라가 보는 방향, 그리고 카메라의 정수리 방향이 어딘지 정하고,
회전, 이동 행렬들을 조합한 변환 행렬을 만들어 기존의 World Space 정보에 적용시키는 과정이다.

이렇게 적용되었을 때, 우리가 정한 너비, 크기에 해당하게 물체를 그릴지 판단하는 공간이 Clip Space이다.


Clip Space

우리가 보는 시야는 제한되어있기 때문에, 인간은 기계도 인간과 같이 표현하길 원한다.
표현한다면 기계는 수치적 연산을 통해 뒤까지 볼 수 있는 잠자리의 시야각을 표현할 수 있지만, 인간은 불편하다 느낀다.
그렇기에 인간이 느끼는 것처럼 제한된 시야를 보게 하기 위해 x, y의 좌표를 [-1~1]까지로 제한하여 정규화시킨다.
이러한 작업을 Clip이라 표현하여 그리지 않을 정보를 취급하지 않게 한다.

이렇게 만들어진 Viewing Box는 절두체(Frustum)이라고 불리고 절두체 내부의 물체들은 사용자의 화면에 나타난다.
이런 절두체를 화면에 표시하는 방법은 Orthographic Projection, Perspective Projection으로 2가지 정도 있다.

Orthographic Projection

이 방법은 너비, 높이를 그대로 찍는 방식으로 시야각을 고려하지 않고 주물을 찍어내듯 찍는 방식이다.

Orthographic Projection

 

Perspective Projection

원근감을 표현하기 위해 멀어질수록 작게 보이게 하기 위한 방법이다.

Prospective Projection

 

둘의 가장 큰 차이점은 w값을 조작하는가?이다.
우리가 가져온 물체들은 3차원의 정보들이었지만, 우리는 4차원의 정보를 다뤄야 원근감을 표현할 수 있다.|
이 4차원의 좌표계는 Homogeneous Coordinate라 한다.

 

Homogeneous Coordinate

기본적으로 우리는 3차원의 정보를 가져와 $vec4(Position, 1.0)$로 표현한다.
즉, $(2.0, 3.0, 4.0) -> (2.0, 3.0, 4.0, 1.0)$로 만들어주는데,
도대체 왜? 이게 원근법과 관련이 있나?라는 의문이 계속될 것이다.

계속해서 $(x, y, z) -> (x, y, z, 1.0)$으로 변환의 역변환은 $(x, y, z, 1.0) -> (x/1.0, y/1.0, z/1.0)$이다.
즉, 1.0으로 넣은 이유는 결국 x, y, z에 1.0을 곱한 변환인 것이다.
다시 말해서 $(x, y, z) -> (wx, wy, wz, w)$으로 변환의 역변환은 $(wx, wy, wz, w) -> (x, y, z))$이다.

위에서 Prospective Projection에서만 w값을 조작하는 이유가 여기 있다.
만일 $w = z$라고 한다면? $(x, y, z, z) -> (x/z, y/z, 1)$로 간단하게 확인할 수 있다.
또한, 여기서 Depth Value의 그래프가 $\frac {1}{z}$인 이유를 알 수 있다.

그리고 w값이 0이 된다면 Zero Division Error가 발생할 테니 z의 값은 0보다는 큰 값이 될 것이다.
즉, z의 범위는 [0~1]까지가 될 것이다.


정리

우리가 가져온 정보는 Local Space에 존재하기에 물체의 정보에 따른 변환 행렬을 통해 World Space로 변환하는 과정이 필요하다.
World Space에서는 우리가 그리고 싶은 장면을 표현하기 위해 View Space로의 변환이 필요한데,
이 과정에서 앞/뒤, 위/아래, 원점의 변환이 필요하다.
View Space에서 필요한 정보만 얻기 위해 Clip Space로 변환하고, Projection 하여 우리가 보는 장면이 탄생한다.

여기까지 OpenGL의 좌표계 변환 과정이다.

'OpenGL' 카테고리의 다른 글

Normal Vector의 변환 행렬  (0) 2022.11.14
OpenGL[Lighting]  (0) 2022.10.18
OpenGL[02](First Triangle)  (1) 2022.10.12
OpenGL[01](Shader)  (1) 2022.10.12
OpenGL[00](First Window)  (0) 2022.10.12

우리는 삼각형을 그릴 텐데 Shader를 불러오고, VAO, VBO, EBO를 연결해준 뒤 무한루프에서 그릴 수 있게 해 주면 된다.

// shader.vs

#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

out vec3 ourColor;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
}

// shader.fs

#version 460 core
out vec4 FragColor;

in vec3 ourColor;

void main()
{
    FragColor = vec4(ourColor, 1.0f);
}

GLSL로 작성된 Vertex Shader, Fragment Shader를 읽어와서 컴파일한 후, 프로그램에 붙여주어야 한다.

GLuint	vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vertexShaderCode, NULL);
glCompileShader(vertex);

GLuint	fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fragmentShaderCode, NULL);
glCompileShader(fragment);
  1. 해당하는 Shader Type에 따라 생성해주어야 한다.
  2. ShaderCode는 해당 파일에서 const char*로 읽어온다.
  3. 해당하는 Shader에 코드를 넣어준다.
  4. 컴파일해준다.

프로그램도 쉐이더를 불러오는 방식과 비슷하다.

GLuint	ID = glCreateProgram();

glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);

glDeleteShader(vertex);
glDeleteShader(fragment);
  1. 프로그램 생성
  2. Vertex Shader, Fragment Shader를 프로그램에 붙여준다.
  3. 프로그램을 링크해준다.
  4. 사용한 vertex, Fragment는 지워준다.

이제 쉐이더 끝!


해당하는 정점과, 정점의 인덱스를 선언하여 VAO를 바인드 하여 VBO, EBO에 정보를 전달해준다.

float vertices[] = {
    0.5f, 0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    -0.5f, 0.5f, 0.0f
};
uint32_t indices[] = {
    0, 1, 3,
    1, 2, 3
};

해당 정점, 순서를 지정한다.

GLuint	VAO, VBO, EBO;

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

glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
  1. VAO 생성해서 바인드 한다.
  2. VBO 생성해서 바인드 하면 GL_ARRAY_BUFFER를 타깃으로 하는 모든 버퍼는 VBO를 사용하게 된다.
  3. glVertexAttribPointer()로 Vertex Shader에서 사용할 정점 속성에 대해 알려준다.
    • 첫 번째 파라미터가 Vertex Shader에서의 layout의 위치다.
    • 두 번째 파라미터는 해당 정점의 속성 값의 크기이다.
    • 세 번째 파라미터는 해당하는 데이터의 타입이다.
    • 네 번째 파라미터는 데이터를 정규화할 것인지 정하는 것인데, GL_TRUE로 설정하면 [0~1], 부호가 있다면 [-1~1]의 범위를 갖게 한다.
    • 다섯 번째 파라미터는 데이터가 시작하는 위치의 offset이다.
  4. glEnableertexAttribArray()로 해당하는 Vertex Shader에서의 layout의 위치를 사용할 수 있게 한다.
  5. EBO 또한 VBO와 같은 방식이지만, GL_ELEMENT_ARRAY_BUFFER를 바인딩한다.

이후 무한 루프에서 그려주면 된다.

while (!glfwWindowShouldClose(window))
{
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	glUseProgram(m_program);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
	glfwSwapBuffers(window);
	glfwPollEvents();
}

해당 프로그램을 사용하도록 해준다.

glDrawElements()를 이용해서 그릴 프리미티브의 특성을 정해준다.

  • 첫 번째 파라미터는 프리미티브의 유형을 정해준다.
  • 두 번째 파라미터는 몇 개의 정점을 그릴지에 대해 정한다.
  • 세 번째 파라미터는 정점 인덱스의 자료형을 정한다.
  • 네 번째 파라미터는 정점 인덱스의 오프셋이다.

 

'OpenGL' 카테고리의 다른 글

OpenGL[Lighting]  (0) 2022.10.18
OpenGL[Coordinate System]  (0) 2022.10.18
OpenGL[01](Shader)  (1) 2022.10.12
OpenGL[00](First Window)  (0) 2022.10.12
vcpkg를 이용한 GLFW, GLM, ASSIMP, GLAD setup  (0) 2022.10.06

Shader

물체의 Shading을 할 때 GPU에서 돌리는 프로그램이다.
Shading은 가상의 3D 공간에서 물체의 표면, 색, 빛, 카메라, 재질 등을 표현하기 위해 설정하는 다양한 설정값들을 바탕으로 표현하는 과정을 의미한다.


OpenGL Shader Language(GLSL)

C언어를 기반으로 하며 OpenGL에서 사용하는 Shading 언어다.
Types는 기본적인 것들은 대부분 갖고 있으며 Vector, Matrices가 있다.

실제로 OpenGL을 이용해서 무언가를 그리기 위해선 Vertex Shader, Fragment Shader가 필요하다.
그리고 OpenGL은 실제로 점, 선, 삼각형밖에 그리질 못하고 이것을 우리는 프리미티브(Primitive)라고 한다.

3D 모델링을 위해 폴리곤이라는 것을 검색해보면 많은 수의 삼각형 또는 사각형 등으로 조각조각되어있다.
우리는 그 삼각형 또는 사각형 조각 하나를 프리미티브라고 부른다.
즉, 3D 모델은 수많은 프리미티브를 조합한 것이다.

프리미티브는 실제로 주어진 정점에 따라 도형을 그린다는 것에 초점을 둔다.
정점이 1개면 점, 2개면 선, 3개면 삼각형인 것이다.
그렇다면 우리는 정점들의 정보를 갖고 다양한 색을 표현하여 모델을 그린다는 것을 알 수 있다.


Vertex Shader

OpenGL은 Vertex Shader로 해당하는 정점의 정보를 어떤 정보인지 확인해서 넘겨준다. [layout]
하지만 정점 정보만 받는 것이 아니라, 받은 정점의 정보에 변동점(공간 데이터)을 주는 정보도 받는다. [Uniform]
이렇게 받은 두 종류의 정보를 이용하여 Vertex Shader는 정점의 정보를 건네준다. [out]

Rasterization

Vertex Shader에서 건네주는 정보들은 OpenGL에서 일련의 과정에서 프리미티브가 생성되어 픽셀당의 데이터로 표현한다.
이렇게 픽셀당 데이터로 표현하기 위해서 지정한 개수의 정점들을 보간하여 픽셀의 데이터를 정한다.

Fragment Shader

그렇게 프리미티브는 픽셀의 정보들로 변환되어 Fragment Shader에 보내진다.
그리고 픽셀들의 정보들과 OpenGL로부터 받은 빛, 텍스쳐의 정보를 이용하여 색을 적용시킨다.
결과적으로 픽셀의 값들을 Frame Buffer로 전달된다.

이후는 여러 가지 테스트들이 실행된다. 사실 위 세 가지 과정 사이에도 몇 가지 과정들이 숨어있는데 이후에 정리해야겠다.


VAO & VBO & EBO

우리가 Vertex Shader에 정점의 정보를 전달할 때, OpenGL에서는 VAO(Vertex Array Object), VBO(Vertex Buffer Object), EBO(Element Buffer Object)를 이용해서 전달한다.

VBO는 정점의 정보를 담고 있는 배열의 객체이다. 해당 배열에는 정점의 정보를 넣는 순서대로 들어간다.
VAO는 여러 개의 VBO를 바인드 받아서 저장하고 있는 객체이다.
그리고 VBO에 지정된 순서대로 Vertex Shader로 들어간다.

여기서 EBO가 있는 이유는 중복 데이터 제거의 용도인데, 이는 예시를 들어 이해하는 것이 빠르다.

VBO

위의 같은 경우엔 6개의 정점을 선언했지만 따지고 보면 1,5와 2,3의 정점은 똑같은 정점이 된다.
즉, 선언은 6개, 사용은 4개인 것으로 2개의 정점의 자원이 낭비된다는 것이다.

 

EBO

해당 VBO는 4개의 정점을 선언하고, EBO에 정점의 순서를 지정한 것이 된다.
이렇게 진행했을 경우엔 낭비되는 자원이 없다.

'OpenGL' 카테고리의 다른 글

OpenGL[Lighting]  (0) 2022.10.18
OpenGL[Coordinate System]  (0) 2022.10.18
OpenGL[02](First Triangle)  (1) 2022.10.12
OpenGL[00](First Window)  (0) 2022.10.12
vcpkg를 이용한 GLFW, GLM, ASSIMP, GLAD setup  (0) 2022.10.06

앞으로 LearnOpenGL을 참고하여 OpenGL을 공부할 것이다.
OpenGL을 공부하는 이유는 컴퓨터 그래픽스의 파이프라인을 이해하는 것이다.

시작!


기본적인 세팅

// GLFW 초기 설정
    if (!glfwInit())
	{
		const char*	description = nullptr;
		glfwGetError(&description);
		return (putError("Failed to initialize glfw: " + std::string(description)));
	}
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

GLFW는 glfwinit()을 이용해 초기화된다.
기본적으로 하드웨어에서 호환되는 버전을 명시하여 사용해야 하고, OPENGL_PROFILE에 Core-profile을 사용하도록 명시한다.

glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

Mac OS에서는 위의 코드를 추가해야 성공적으로 작동한다.

// GLFW를 이용한 Window생성
	GLFWwindow*	window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_NAME, nullptr, nullptr);
	if (!window)
	{
		glfwTerminate();
		return (putError("Failed to create glfw window"));
	}
	glfwMakeContextCurrent(window);
	
// GLAD 초기 설정
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		glfwTerminate();
		return (putError("Failed to initialize glad"));
	}

glfwCreateWindow()를 이용하여 현재 윈도우의 가로, 세로를 픽셀로 나타내고 윈도우의 프로그램 제목을 나타낸다.
glfwMakeContextCurrent()를 이용하여 내가 현재 만든 윈도우를 현재 창으로 설정하는 것이다.

void	OnFramebufferSizeChange(GLFWwindow* window, int width, int height)
{
	std::cout << "FrameBuffer size changed: (" << width << ", " << height << ")" << std::endl;
	glViewport(0, 0, width, height);
};

void	OnKeyEvent(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	std::cout << "key: " << key << "\tscancode: " << scancode;
	std::cout << "\taction: " <<	(action == GLFW_PRESS ? "Pressed" : 
								(action == GLFW_RELEASE ? "RELEASE" :
								(action == GLFW_REPEAT ? "REPEAT" : "UNKNOWN")))
			<< "\tmods: " << (mods & GLFW_MOD_CONTROL ? "C" :
							(mods & GLFW_MOD_SHIFT ? "S" :
							(mods & GLFW_MOD_ALT ? "A" : "---"))) << std::endl;
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
};

우리는 위의 함수 두 개로 키가 눌리고 창의 크기가 변경되는 것을 콜백 함수로 둘 것이다.

	glfwSetFramebufferSizeCallback(window, OnFramebufferSizeChange);
	glfwSetKeyCallback(window, OnKeyEvent);

glfwSet~Callback() 함수들은 함수 포인터를 가져가서 콜백 함수로 등록해주는 역할이다.

	while (!glfwWindowShouldClose(window))
	{
		glfwSwapBuffers(window);
		glClearColor(0.0f, 0.1f, 0.2f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		glfwPollEvents();
	}
	glfwDestroyWindow(window);
	glfwTerminate();

glfwWindowShouldClose()는 해당 윈도우를 종료해야 하는가?를 물어보는 함수이다.
즉, 종료하기 전까지는 FALSE의 반환 값으로 실행된다.
GLFW의 윈도우는 기본적으로 이중 버퍼인데, 이전에 그렸던 것과 현재 그렸던 것을 바꾸는 과정을 glfwSwapBuffers()로 처리한다.
glfwPollEvents()는 윈도우와 관련된 다른 이벤트를 다루는데 예를 들면 마우스, 키보드의 입력을 말한다.
glClearColor()glClear() 직전에 호출되고 해당 윈도우의 Color Buffer가 지워질 때 해당 윈도우의 Color Buffer에 넣어질 값을 설정한다.
윈도우가 꺼지게 되면 무한 루프를 탈출하여 glfwDestroyWindow()와 glfwTerminate()로 모든 자원들이 정리될 수 있다.

'OpenGL' 카테고리의 다른 글

OpenGL[Lighting]  (0) 2022.10.18
OpenGL[Coordinate System]  (0) 2022.10.18
OpenGL[02](First Triangle)  (1) 2022.10.12
OpenGL[01](Shader)  (1) 2022.10.12
vcpkg를 이용한 GLFW, GLM, ASSIMP, GLAD setup  (0) 2022.10.06
  1. vcpkg 설치 [https://github.com/microsoft/vcpkg]
  2. 설치 후, vcpkg 폴더 가서 vcpkg install glfw3 (기본 32bit 버전임 [64bit가 필요하다면 ':x64-windows'를 추가])
  3. vcpkg 폴더 가서 vcpkg install glm(기본 32bit 버전임 [64bit가 필요하다면 ':x64-windows'를 추가])
  4. vcpkg 폴더 가서 vcpkg install assimp (기본 32bit 버전임 [64bit가 필요하다면 ':x64-windows'를 추가])
  5. glad는 https://glad.dav1d.de/ 에서 현재 GL버전을 선택하고 Profile은 'Core' 선택 후, 'ADD ALL'을 누른 뒤, 'GENERATE'
  6. 다운로드하면 'vcpkg/install/x(선택한 비트)-windows/include'위치에 glad, KHR 폴더를 넣고 'glad.c'는 본인 편한 곳에 풀어서 본인 설루션에 포함시킨다.
  7. 이렇게 되면 vcpkg에서 설치가 됐고 이제 Visual Studio와 연동하기 위해 'vcpkg integrate install'!
  8. 이제 Visual Studio에서 #include ~~~~ 로 사용 가능하다.
 

https://glad.dav1d.de/

Extensions...

glad.dav1d.de

 

GitHub - microsoft/vcpkg: C++ Library Manager for Windows, Linux, and MacOS

C++ Library Manager for Windows, Linux, and MacOS. Contribute to microsoft/vcpkg development by creating an account on GitHub.

github.com

'OpenGL' 카테고리의 다른 글

OpenGL[Lighting]  (0) 2022.10.18
OpenGL[Coordinate System]  (0) 2022.10.18
OpenGL[02](First Triangle)  (1) 2022.10.12
OpenGL[01](Shader)  (1) 2022.10.12
OpenGL[00](First Window)  (0) 2022.10.12

+ Recent posts