230430 자체 엔진 개발 : Camera system
참고 : http://egloos.zum.com/mataeoh/v/7295230
World , View Matrix 행렬 계산
void OrthographicCamera::RecalculateViewMatrix()
{
glm::mat4 transform = glm::translate(glm::mat4(1.f), m_Position);
// rotate z axis;
transform *= glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation), glm::vec3(0, 0, 1));
// inverse
m_ViewMatrix = glm::inverse(transform);
m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}
위 코드에서 trasform 이라는 행렬이 World Matrix 에 해당하는 matrix 이다.
일반적으로 World Matrix 는 Scale * Rotation * Traslation 형태로 구한다.
그래서 위 코드도 Translation 을 구하고 난 이후, 그 다음 Rotaion 을 곱하는 것을 알 수 있다.
즉, Rotation * Translation 형태로 World Matrix 를 구하는 것이다.
( 여기서 Scale 은 생략되었다)
그리고 ViewMatrix 는 World Matrix 의 역행렬을 취해주고 있다.
일반적으로 ViewMatrix 는 Camera 의 World Matrix 의 역행렬을 통해 구해주게 된다.
자세한 것은 컴퓨터 그래픽스 관련 기초를 더 공부하면 알 수 있다.
최종 행렬 계산
Local Space * World Tranform * View Transform * Projection Transform * ScreenSpace Transform
이 과정을 통해 실제 Object 가 Scene 에 Render 된다.
이때 마지막 Screen Space 는 래스터라이저 단계에서 진행해주기 때문에 실제 프로그래머가 신경써야 할 부분은
Local Space * World Tranform * View Transform * Projection Transform 이다.
이때 대표적으로 DX11 과 OpenGL 에서 해당 행렬들을 곱하는 "순서"가 다르다.
왜냐하면 2개의 Graphic API 들이 "row-major", "column-major" 를 사용하느냐. 가 다르기 때문이다.
OpenGL 의 경우 column major 를 사용한다. 쉽게 말하면 "행렬 * 벡터" 형태의 곱을 사용한다는 것이다.
그리고 추가적으로 행렬을 곱해주고자 한다면 "앞" 쪽에 행렬을 배치하면 된다.
따라서 실제 코드상으로도
m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
이와 같이 Projection * View 를 통해 VP Matrix 를 구하고
void main()
{
gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
}
VP Matrix * WorldMatrix 와 같이 WorldMatrix "앞" 쪽에 VP Matrix 를 곱해주는 것을 알 수 있다.
Orthographic Camera
현재 2D 게임을 위한 카메라를 제작하는 단계이다.
따라서 원근법이 적용되지 않은 orthographic 카메라를 적용하는 것을 알 수 있다.
// 2d camera
class OrthographicCamera
{
public :
// 가로, 세로 폭을 지정하는 것이다.
// ex) left, right : -1.f, 1.f --> -2.f, 2.f 가 된다는 것은 카메라가 보는 범위가 -2.f ~ 2.f 가 된다는 것
// 즉, 기존의 보이던 물체의 크기가 반으로 줄어든다는 것을 의미한다.
OrthographicCamera(float left, float right, float bottom, float top);
float GetRotation() { return m_Rotation; }
const glm::vec3& GetPosition() const { return m_Position; }
void SetPosition(const glm::vec3& position);
void SetRotation(float Rot);
const glm::mat4& GetProjectionMatrix() { return m_ProjectionMatrix; }
const glm::mat4& GetViewMatrix() { return m_ViewMatrix; }
const glm::mat4& GetViewProjectionMatrix() { return m_ViewProjectionMatrix; }
private :
void RecalculateViewMatrix();
private :
// inverse of transformation matrix of camera
glm::mat4 m_ViewMatrix;
glm::mat4 m_ProjectionMatrix;
glm::mat4 m_ViewProjectionMatrix;
glm::vec3 m_Position = {0.0f, 0.0f, 0.0f};
float m_Rotation = 0.f;
};
BeginScene 에 카메라 관련 정보 넘기기
Camera Class 를 통해 구한 VP Matrix 는 모든 Object 들에 대해서 동일하게 적용되므로
매 프레임마다
1) 계산도 한번씩
2) GPU 에 넘기는 것도 한번씩 진행해주면 된다.
void Renderer::BeginScene(OrthographicCamera& camera)
{
m_SceneData->ViewProjectionMatrix = camera.GetViewProjectionMatrix();
}
while (m_Running)
{
RenderCommand::SetClearColor({ 0.1f, 0.1f, 0.1f, 1.f});
RenderCommand::Clear();
m_Camera.SetPosition({ 0.5f, 0.5f, 0.0f });
m_Camera.SetRotation(45.f);
Renderer::BeginScene();
// 실제 draw 하기 전에 bind
m_BlueShader->Bind();
Renderer::Submit(m_SquareArray);
Renderer::BeginScene(m_Camera);
m_Shader->Bind();
Renderer::Submit(m_VertexArray);
Renderer::Submit(m_SquareArray, m_BlueShader);
Renderer::Submit(m_VertexArray, m_Shader);
Renderer::EndScene();
....
}
따라서 Renderer::BeginScene() 이라는 함수 안에서 프레임마다 한번씩만 세팅해주는 것을 알 수 있다.
반면에 각 Object 들의 고유한 transform 정보. 즉, WorldMatrix * Local Space 의 결과값을 Object 들을 그릴 때마다 계산하고 넘겨주어야 하므로 위와 같이 진행해준다.
glm::mat4 transform = glm::translate(glm::mat4(1.0f), m_SquarePos);
Hazel::Renderer::Submit(m_SquareArray, m_BlueShader, transform);
->
void Renderer::Submit(const std::shared_ptr<VertexArray>& vertexArray,
const std::shared_ptr<Shader>& shader)
const std::shared_ptr<Shader>& shader,
const glm::mat4& transform)
{
// 실제 draw 하기 전에 bind
shader->Bind();
// 각 object 마다 해당 함수를 호출해줘야 한다.
shader->UploadUniformMat4("u_Transform", transform);
...
...
}