- C++ 행렬 / 벡터 연산
- GLSL의 경우 내부적으로 행렬 및 벡터와 관련된 다양한 기능 및 내부 함수를 제공한다
- C++에는 기본적인 수학적 연산 외에 선형대수 관련기능을 제공하지 않는다.
-> 라이브러리를 활용하면 된다.
# glm
ExternalProject_Add(
dep_glm
GIT_REPOSITORY "https://github.com/g-truc/glm" // 라이브러리가 있는곳
GIT_TAG "0.9.9.8"
GIT_SHALLOW 1
UPDATE_COMMAND ""
PATCH_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
TEST_COMMAND ""
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory
${PROJECT_BINARY_DIR}/dep_glm-prefix/src/dep_glm/glm
${DEP_INSTALL_DIR}/include/glm
)
set(DEP_LIST ${DEP_LIST} dep_glm)
...
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// 위치 (1, 0, 0)의 점. 동차좌표계 사용
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
// 단위행렬 기준 (1, 1, 0)만큼 평행이동하는 행렬
auto trans = glm::translate(glm::mat4(1.0f), glm::vec3(1.0f, 1.0f, 0.0f));
// 단위행렬 기준 z축으로 90도만큼 회전하는 행렬
auto rot = glm::rotate(glm::mat4(1.0f),
glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
// 단위행렬 기준 모든 축에 대해 3배율 확대하는 행렬
auto scale = glm::scale(glm::mat4(1.0f), glm::vec3(3.0f));
// 확대 -> 회전 -> 평행이동 순으로 점에 선형 변환 적용
vec = trans * rot * scale * vec;
SPDLOG_INFO("transformed vec: [{}, {}, {}]", vec.x, vec.y, vec.z);
- Log : transformed vec: [0.9999999, 4, 0]
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
uniform mat4 transform; // 추가
out vec4 vertexColor;
out vec2 texCoord;
void main() {
gl_Position = transform * vec4(aPos, 1.0); // 추가
vertexColor = vec4(aColor, 1.0);
texCoord = aTexCoord;
}
// 0.5배 축소후 z축으로 90도 회전하는 행렬
auto transform = glm::rotate(glm::scale(glm::mat4(1.0f), glm::vec3(0.5f)), glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
auto transformLocation = glGetUniformLocation(m_program->Get(), "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
좌표계
- 어떤 정점의 위치를 기술하기 위한 기준
- 선형 변환은 한 좌표계로 기술된 벡터를 다른 좌표계에 대해 기술하는 변환으로 해석할 수 있다.
- 설명
- 기준이 되는 전역 좌표계 W
v = [5,4]
- 좌표계W와 [3,3]만큼 떨어져 있는 지역 좌표계 L
L을 기준으로 한 v = [2,1]
- [2,1]을 [3,3]만큼 평행이동 시킨다
-> L을 기준으로 [2,1]에 위치한 점은 W를 기준으로 [3,3]만큼 평행이동되어 있다.
-> 지역좌표계 L에 찍혀있는 점은 아래로 나타낼 수 있다.(전역좌표계 W기준 v의 위치) =
(전역좌표계 W기준 지역좌표계L의 원점의 위치) + (지역좌표계 기준 v의 위치)
- 좌표계 W와 [5,2]만큼 떨어져 있고 z축 방향으로 45도 회전한 지역좌표계 L
-> L을 기준으로 기술된 v의 위치 = [1.414... , 1.414...]
자주 사용되는 좌표계 용어
- World space : 항상 기준이 되는 좌표계 (전역 좌표계)
- Local (Object) space : 그 오브젝트를 기준으로한 좌표계
- View (Eye) space : 바라보고있는 는의 위치를 기준으로한 좌표계
- Screen space : 화면을 기준으로한 좌표계
OpenGL의 그림이 그려지는 공간은 Normalized된 공간이다.
- [-1,1]범위로 Normalized된 공간
- Canonical space
Object들은 Local space를 기준으로 기술
- Local space -> World space -> View space -> Canonical space의 순으로 변환
Transform Metrix
- Model Metrix : Local을 World로
- View Metrix : World를 Camera로
- Projection Metrix : Camera를 Canonical로
- Clip space에서 [-1,1]범위 밖으로 벗어난 면들은 Clipping해서 안나오게 함
* Local space -> World space -> View space 를 묶어서 Model View Transform이라고 한다.
* Model Metrix, View Metrix, Progection Metrix 이 세개를 묶어 MVP Metrix라고 한다.
직교 투영
- 원근감 없이 평행한 선이 계속 평행하도록 투영하는 방식
- 설계도면 등을 그려낼때 유용하다.
- 6개의 파라미터 : left, right, bottom, top, near, far
- z축에 -1 : Clip space 이후에는 오른손 좌표계에서 왼손 좌표계로 변경
- 오른손 좌표계 / 왼손 좌표계
- x축, y축을 화면의 오른 / 위 방향으로 했을때
- 오른손 좌표계 : z축이 화면에서부터 나오는 방향
- 왼손 좌표계 : z축이 화면으로 들어가는 방향
원근 투영
- 변환 이전에 평행한 선이 변환 후에 한점(소실점)에서 만남
- 멀리 있을수록 물체가 작아져 원근감이 발생
- 4개의 파라미터 : 종횡비(Aspect Ratio), 화각(Field Of View), near, far
위에 써놓았듯이 MVP (model-view-projection) matrix라고 한다.
// x축으로 -55도 회전
auto model = glm::rotate(glm::mat4(1.0f), glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
// 카메라는 원점으로부터 z축 방향으로 -3만큼 떨어짐
auto view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
// 종횡비 4:3, 세로화각 45도의 원근 투영
auto projection = glm::perspective(glm::radians(45.0f), (float)640 / (float)480, 0.01f, 10.0f);
auto transform = projection * view * model;
auto transformLocation = glGetUniformLocation(m_program->Get(), "transform");
- 수정
auto projection = glm::perspective(glm::radians(45.0f), (float)640 / (float)480, 0.01f, 10.0f);
윈도우의 크기를 지정해주어도 되지만 cMakeList.txt에 있는 이미 지정된 Window Size인
set(WINDOW_WIDTH XXX) set(WINDOW_HEIGHT XXX)
를 사용하여
auto projection = glm::perspective(glm::radians(45.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.01f, >10.0f);
로 수정해야지 제대로된 종횡비 값이 지정된다.
float vertices[] =
{
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
};
uint32_t indices[] =
{
0, 2, 1, 2, 0, 3,
4, 5, 6, 6, 7, 4,
8, 9, 10, 10, 11, 8,
12, 14, 13, 14, 12, 15,
16, 17, 18, 18, 19, 16,
20, 22, 21, 22, 20, 23,
};
...
m_vertexLayout = VertexLayout::Create();
m_vertexBuffer = Buffer::CreateWithData( GL_ARRAY_BUFFER, GL_STATIC_DRAW, vertices, sizeof(float) * 120);
m_vertexLayout->SetAttrib(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 5, 0);
m_vertexLayout->SetAttrib(2, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 5, sizeof(float) 3);
m_indexBuffer = Buffer::CreateWithData(GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, ndices, sizeof(uint32_t) * 36);
void Context::Render()
{
glClear(GL_COLOR_BUFFER_BIT);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}
깊이가 없다.
- Z Buffer라고도 한다.
- 각 픽셀의 컬러값을 저장하는 버퍼 외에, 해당 픽셀의 깊이값(z축 값)을 저장한다.
깊이 테스트(Depth test)
- 어떤 픽셀의 값을 업데이트 하기전, 현재 그리려는 픽셀의 Z값과 깊이 버퍼에 저장된 해당 위치의 Z값을 비교해본다. (같은 좌표에 있는 여러 픽셀의 깊이 값을 비교해본다.)
-> 비교결과 현재 그리려는 픽셀이 이전에 그려진 픽셀보다 뒤에 있을경우 픽셀을 그리지 않는다. (가장 가까운 픽셀만 그린다.)
OpenGL의 Depth Buffer 초기값은 1
- 1이 가장 뒤에 있고, 0이 가장 앞에 있다. (왼손좌표계)
- 사용 함수
- glEnable(GL_DEPTH_TEST) / glDisable(GL_DEPTH_TEST)로 깊이 테스트를 켜고 끌수 있다.
- glDepthFunc() : 깊이 테스트 통과조건을 변경할 수 있다. (기본값은 'GL_LESS')
void Context::Render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}
가장 가까운 픽셀만 그려졌다.
public :
...
void SetUniform(const std::string& name, int value) const;
void SetUniform(const std::string& name, const glm::mat4& value) const;
private : ...
...
void Program::SetUniform(const std::string& name, int value) const
{
auto loc = glGetUniformLocation(m_program, name.c_str());
glUniform1i(loc, value);
}
void Program::SetUniform(const std::string& name, const glm::mat4& value) const
{
auto loc = glGetUniformLocation(m_program, name.c_str());
glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(value));
}
m_program->Use();
m_program->SetUniform("tex", 0);
m_program->SetUniform("tex2", 1);
...
auto transform = projection * view * model;
m_program->SetUniform("transform", transform);
transform의 지정을 매 프레임마다 지정하기
코드 수정 , src / context.cpp / Render() 작성
void Context::Render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auto projection = glm::perspective(glm::radians(45.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.01f, 10.0f);
auto view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
auto model = glm::rotate(glm::mat4(1.0f), glm::radians((float)glfwGetTime() * 120.0f), glm::vec3(1.0f, 0.5f, 0.0f));
auto transform = projection * view * model;
m_program->SetUniform("transform", transform);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}
glfwGetTime() : Context :: 오브젝트가 생성된 이후로 얼마만큼 시간이 흘렀는지 초 단위로 기록
-> 이 코드에선 Context :: Init()이 실행된 이후로 얼마만큼 시간이 흘렀는지 초 단위로 기록
void Context::Render() {
std::vector<glm::vec3> cubePositions =
{
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3( 2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3( 1.3f, -2.0f, -2.5f),
glm::vec3( 1.5f, 2.0f, -2.5f),
glm::vec3( 1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f),
};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
auto projection = glm::perspective(glm::radians(45.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.01f, 20.0f);
auto view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
for (size_t i = 0; i < cubePositions.size(); i++)
{
auto& pos = cubePositions[i];
auto model = glm::translate(glm::mat4(1.0f), pos);
model = glm::rotate(model, glm::radians((float)glfwGetTime() * 120.0f + 20.0f * (float)i), glm::vec3(1.0f, 0.5f, 0.0f));
auto transform = projection * view * model;
m_program->SetUniform("transform", transform);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}
}