게임엔진/크로스플랫폼 : HazelEngine

230430 자체 엔진 개발 : Camera system

mrawesome 2023. 5. 1. 15:02

참고 : http://egloos.zum.com/mataeoh/v/7295230

 

OpenGL vs DirectX

이문서도 꼭 읽어보자! MatricesTips.pdf 1. 좌표계 OpenGL : 오른손 좌표계 DirectX : 왼손 좌표계 2. 행렬 ( Matrix ) https://msdn.microsoft.com/en-us/library/windows/desktop/bb509634(v=vs.85).aspx row major인지 coulmn major

egloos.zum.com

 

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);
    ...
    ...
}