그래픽스 프로그래밍에서 수학은 빼놓을 수 없다.
이번 포스트에서는 glm이라는 오픈소스 header-only 수학 라이브러리를 사용해 간단한 Projection Matrix 행렬 연산을 코드에 추가해보겠다.




Application.cpp
// Include GLEW
#include <GL/glew.h>
// Include GLFW
#include <GLFW/glfw3.h>
#include <iostream>
#include "VertexBuffer.h"
#include "VertexArray.h"
#include "IndexBuffer.h"
#include "Shader.h"
#include "Renderer.h"
#include "Texture.h"
#include "glm/glm.hpp" // 라이브러리 사용
#include "glm/gtc/matrix_transform.hpp" // 라이브러리 사용
GLFWwindow* InitWindow()
{
// Initialise GLFW
if( !glfwInit() )
{
fprintf( stderr, "Failed to initialize GLFW\n" );
getchar();
return nullptr;
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
GLFWwindow* window = glfwCreateWindow( 1024, 768, "Tutorial 02 - Red triangle", NULL, NULL);
if( window == NULL ){
fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
getchar();
glfwTerminate();
return nullptr;
}
glfwMakeContextCurrent(window);
// Initialize GLEW
glewExperimental = true; // Needed for core profile
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
getchar();
glfwTerminate();
return nullptr;
}
std::cout << "Using GL Version: " << glGetString(GL_VERSION) << std::endl;
// Ensure we can capture the escape key being pressed below
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
return window;
}
int main( void )
{
GLFWwindow* window = InitWindow();
if (!window)
return -1;
float positions[] = {
-0.5f, -0.5f, 0.0f, 0.0f, // 0
0.5f, -0.5f, 1.0f, 0.0f, // 1
0.5f, 0.5f, 1.0f, 1.0f, // 2
-0.5f, 0.5f, 0.0f, 1.0f // 3
};
unsigned int indices[] = {
0, 1, 2,
2, 3, 0
};
GLCall( glEnable(GL_BLEND) );
GLCall( glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) );
{
VertexArray va;
VertexBuffer vb(positions, 4 * 4 * sizeof(float));
IndexBuffer ib(indices, 6);
glm::mat4 proj = glm::ortho(-2.0f, 2.0f, -1.5f, 1.5f, -1.0f, 1.0f);
/*
4x4 행렬을 만든다. glm::ortho는 orthographic matrix를 의미하는 것으로,
이는 그래픽스에서 사용되는 특수한 형태의 행렬로(아래 사진 참고), parallel projection
혹은 orthographic projection에 사용되는 행렬이다.
parallel projection이란 3d 물체를 2d 평면에 나타낼 때 원근을 무시하고 나타내는 방식이다.
(unity 뷰의 orthographic 모드 생각하면 편하다)
-2(left), 2(right), -1.5(bottom), 1.5(top) 을 의미하며 이는 곧 4:3 평면을 나타낸다.
-1, 1은 각각 near, far을 의미한다.
해당 행렬의 구조나 각 수치의 구체적인 의미에 대해서는 orthographic matrix 참고.
참고: https://zamezzz.tistory.com/98
이렇게 만든 행렬에 우리는 vertexBuffer의 positions 좌표들을 곱해주는 것으로
비율을 조정할 수 있다. 이는 cpu side에서 해도 되지만, 속도를 위해 셰이더로
이 행렬을 전달해 gpu에서 수학연산을 하겠다.
(아래 Basic.shader 코드 참고)
projection matrix에 대한 자세한 내용은 다음 포스트에서 다루겠다
*/
VertexBufferLayout layout;
layout.AddFloat(2);
layout.AddFloat(2);
va.AddBuffer(vb, layout);
Shader shader("res/shaders/Basic.shader");
shader.Bind();
shader.SetUniform4f("u_Color", 0.0f, 0.3f, 0.8f, 1.0f);
shader.SetUniformMat4f("u_MVP", proj); // 셰이더 uniform에 전달
//주의: 전달하기 위해 SetUniformMat4f 함수를 Shader.h랑 Shader.cpp에 구현해두어야 함.
//아래 코드 참조.
Texture texture("res/textures/phone.png");
texture.Bind();
shader.SetUniform1i("u_Texture", 0);
float red = 0.0f;
float step = 0.05f;
Renderer renderer;
do {
renderer.Clear();
shader.Bind();
shader.SetUniform4f("u_Color", red, 0.3, 0.8, 1.0);
renderer.Draw(va, ib, shader);
// Swap buffers
glfwSwapBuffers(window);
glfwPollEvents();
// increment red
if (red < 0.0f || red > 1.0f)
step *= -1.0;
red += step;
} // Check if the ESC key was pressed or the window was closed
while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0 );
}
// Close OpenGL window and terminate GLFW
glfwTerminate();
return 0;
}

Basic.shader
#shader vertex
#version 330 core
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texCoord;
out vec2 v_TexCoord;
uniform mat4 u_MVP; // Model View Projection Matrix
/*
현재로써는 일단 Projection Matrix만 사용중이다.
*/
void main()
{
gl_Position = u_MVP * position; // vec4 position에 mat4 MVP(사실 현재는 Projection Matrix)를 곱해준다.
v_TexCoord = texCoord;
}
#shader fragment
#version 330 core
layout(location = 0) out vec4 color;
in vec2 v_TexCoord;
uniform vec4 u_Color;
uniform sampler2D u_Texture;
void main()
{
vec4 texColor = texture(u_Texture, v_TexCoord);
color = texColor;
}
Shader.h
#pragma once
#include <string>
#include <unordered_map>
#include "glm/glm.hpp"
struct ShaderProgramSource
{
std::string VertexSource;
std::string FragmentSource;
};
class Shader
{
private:
unsigned int m_RendererID;
std::string m_FilePath;
std::unordered_map<std::string, int> m_UniformLocationCache;
public:
Shader(const std::string& filepath);
~Shader();
void Bind() const;
void Unbind() const;
// Set uniforms
void SetUniform1i(const std::string& name, int value);
void SetUniform1f(const std::string& name, float value);
void SetUniform4f(const std::string& name, float f0, float f1, float f2, float f3);
void SetUniformMat4f(const std::string& name, const glm::mat4& matrix); // 행렬 추가 함수 제작
private:
int GetUniformLocation(const std::string& name);
struct ShaderProgramSource ParseShader(const std::string& filepath);
unsigned int CompileShader(unsigned int type, const std::string& source);
unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader);
};
Shader.cpp
...
void Shader::SetUniformMat4f(const std::string& name, const glm::mat4& matrix)
{
GLCall( glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &matrix[0][0]) );
// glUniformMatrix4fv(uniform, count(갯수), Row-major인지(true) Column-major인지(false), 행렬)
}
...


Projection Matrix 및 Model Matrix, View Matrix에 대해서는 다음 포스트에서 다루도록 하겠다.
'computer graphics > OpenGL' 카테고리의 다른 글
[OpenGL] 12. Model View Projection Matrices (0) | 2022.05.03 |
---|---|
[OpenGL] 10. Blending (0) | 2022.04.16 |
[OpenGL] 9. Textures (0) | 2022.04.09 |
[OpenGL] 8. 추상화(Abstraction)와 렌더러 (0) | 2022.04.02 |
[OpenGL] 7. Vertex Array Object (VAO) (0) | 2022.03.01 |