[DirectX] 뷰 행렬 연산
in Study on WinAPI&DirectX
뷰(View) 행렬 코드
// ==============
// View 행렬 계산
// ==============
m_matView = XMMatrixIdentity();
// 카메라 좌표를 원점으로 이동
Vec3 vCamPos = Transform()->GetRelativePos();
Matrix matViewTrans = XMMatrixTranslation(-vCamPos.x, -vCamPos.y, -vCamPos.z);
// 카메라가 바라보는 방향을 Z 축과 평행하게 만드는 회전 행렬을 적용
Matrix matViewRot = XMMatrixIdentity();
Vec3 vR = Transform()->GetRelativeDir(DIR_TYPE::RIGHT);
Vec3 vU = Transform()->GetRelativeDir(DIR_TYPE::UP);
Vec3 vF = Transform()->GetRelativeDir(DIR_TYPE::FRONT);
matViewRot._11 = vR.x; matViewRot._12 = vU.x; matViewRot._13 = vF.x;
matViewRot._21 = vR.y; matViewRot._22 = vU.y; matViewRot._23 = vF.y;
matViewRot._31 = vR.z; matViewRot._32 = vU.z; matViewRot._33 = vF.z;
m_matView = matViewTrans * matViewRot;
뷰 행렬 = (이동 행렬 x 회전 행렬) 혹은 (회전 행렬 x 이동 행렬)을 곱한 형태이다.
이때 이동, 회전 순으로 곱하면 공전 형태로 취급하고,
회전, 이동 순으로 곱하면 자전 형태로 취급한다.
언리얼 엔진의 축에 따른 회전 명칭
- x축 회전 : pitch 회전
- y축 회전 : yaw 회전
- z축 회전 : roll 회전
CTransform 클래스의 변화
define.h에 enum class를 설정하고, 방향 벡터를 3개 만든다.
// define.h
enum class DIR_TYPE
{
RIGHT,
UP,
FRONT
}
// 헤더파일
#pragma once
#include "CComponent.h"
class CTransform :
public CComponent
{
private:
Vec3 m_vRelativePos;
Vec3 m_vRelativeScale;
Vec3 m_vRelativeRot;
Vec3 m_vRelativeDir[3];
Matrix m_matWorld; // 크기, 회전, 이동 정보를 합쳐놓음
public:
void SetRelativePos(Vec3 _vPos) { m_vRelativePos = _vPos; }
void SetRelativeScale(Vec3 _vScale) { m_vRelativeScale = _vScale; }
void SetRelativeRot(Vec3 _vRot) { m_vRelativeRot = _vRot; }
void SetRelativePos(float _x, float _y, float _z) { m_vRelativePos = Vec3(_x, _y, _z); }
void SetRelativeScale(float _x, float _y, float _z) { m_vRelativeScale = Vec3(_x, _y, _z); }
void SetRelativeRot(float _x, float _y, float _z) { m_vRelativeRot = Vec3(_x, _y, _z); }
Vec3 GetRelativePos() { return m_vRelativePos; }
Vec3 GetRelativeScale() { return m_vRelativeScale; }
Vec3 GetRelativeRot() { return m_vRelativeRot; }
Vec3 GetRelativeDir(DIR_TYPE _type) { return m_vRelativeDir[(UINT)_type]; }
public:
virtual void finaltick() override;
void UpdateData();
CLONE(CTransform);
public:
CTransform();
~CTransform();
};
방향 벡터를 추가해주고, 방향 타입을 넘기는 함수를 만든다.
// cpp파일
#include "pch.h"
#include "CTransform.h"
#include "CDevice.h"
#include "CConstBuffer.h"
CTransform::CTransform()
: CComponent(COMPONENT_TYPE::TRANSFORM)
, m_vRelativeScale(Vec3(1.f, 1.f, 1.f))
, m_vRelativeDir{
Vec3(1.f, 0.f, 0.f)
, Vec3(0.f, 1.f, 0.f)
, Vec3(0.f, 0.f, 1.f)}
{
}
CTransform::~CTransform()
{
}
void CTransform::finaltick()
{
Matrix matScale = XMMatrixIdentity();
matScale = XMMatrixScaling(m_vRelativeScale.x, m_vRelativeScale.y, m_vRelativeScale.z);
Matrix matRot = XMMatrixIdentity();
matRot = XMMatrixRotationX(m_vRelativeRot.x);
matRot *= XMMatrixRotationY(m_vRelativeRot.y);
matRot *= XMMatrixRotationZ(m_vRelativeRot.z);
Matrix matTranslation = XMMatrixTranslation(m_vRelativePos.x, m_vRelativePos.y, m_vRelativePos.z);
m_matWorld = matScale * matRot * matTranslation;
Vec3 vDefaultDir[3] = {
Vec3(1.f, 0.f, 0.f)
, Vec3(0.f, 1.f, 0.f)
, Vec3(0.f, 0.f, 1.f)
};
for (int i = 0; i < 3; ++i)
{
// XMVector3TransformCoord(vDefaultDir[i], matRot);
//m_vRelativeDir[i] = XMVector3TransformNormal(vDefaultDir[i], m_matWorld);
//m_vRelativeDir[i].Normalize();
m_vRelativeDir[i] = XMVector3TransformNormal(vDefaultDir[i], matRot);
}
}
void CTransform::UpdateData()
{
// 위치값을 상수버퍼에 전달 및 바인딩
CConstBuffer* pTransformBuffer = CDevice::GetInst()->GetConstBuffer(CB_TYPE::TRANSFORM);
g_transform.matWorld = m_matWorld;
pTransformBuffer->SetData(&g_transform);
pTransformBuffer->UpdateData();
}
여기서는 회전 행렬을 각 방향 벡터에 곱한다.
이때 사용되는 함수는 아래와 같다.
- XMVector3TransformCoord() : 동차 좌표를 1로 확장
- XMVector3TransformNormal() : 동차 좌표를 0으로 확장
그리고 정규화를 위해서 Normalize()함수를 사용한다.
CCamera 클래스의 변화
이렇게 하면 카메라 컴포넌트 에서는 Transform으로부터 방향 벡터를 얻을 수 있게 된다.
// 헤더파일
#pragma once
#include "CComponent.h"
class CCamera :
public CComponent
{
private:
float m_fAspectRatio;
PROJ_TYPE m_ProjType;
Matrix m_matView;
Matrix m_matProj;
public:
void SetProjType(PROJ_TYPE _Type) { m_ProjType = _Type; }
PROJ_TYPE GetProjType() { return m_ProjType; }
const Matrix& GetViewMat() { return m_matView; }
const Matrix& GetProjMat() { return m_matProj; }
public:
virtual void finaltick() override;
CLONE(CCamera);
public:
CCamera();
~CCamera();
};
// cpp 파일
#include "pch.h"
#include "CCamera.h"
#include "CDevice.h"
#include "CTransform.h"
CCamera::CCamera()
: CComponent(COMPONENT_TYPE::CAMERA)
, m_fAspectRatio(1.f)
, m_ProjType(PROJ_TYPE::ORTHOGRAPHIC)
{
Vec2 vRenderResol = CDevice::GetInst()->GetRenderResolution();
m_fAspectRatio = vRenderResol.x / vRenderResol.y;
}
CCamera::~CCamera()
{
}
void CCamera::finaltick()
{
// ==============
// View 행렬 계산
// ==============
m_matView = XMMatrixIdentity();
// 카메라 좌표를 원점으로 이동
Vec3 vCamPos = Transform()->GetRelativePos();
Matrix matViewTrans = XMMatrixTranslation(-vCamPos.x, -vCamPos.y, -vCamPos.z);
// 카메라가 바라보는 방향을 Z 축과 평행하게 만드는 회전 행렬을 적용
Matrix matViewRot = XMMatrixIdentity();
Vec3 vR = Transform()->GetRelativeDir(DIR_TYPE::RIGHT);
Vec3 vU = Transform()->GetRelativeDir(DIR_TYPE::UP);
Vec3 vF = Transform()->GetRelativeDir(DIR_TYPE::FRONT);
matViewRot._11 = vR.x; matViewRot._12 = vU.x; matViewRot._13 = vF.x;
matViewRot._21 = vR.y; matViewRot._22 = vU.y; matViewRot._23 = vF.y;
matViewRot._31 = vR.z; matViewRot._32 = vU.z; matViewRot._33 = vF.z;
m_matView = matViewTrans * matViewRot;
// =============
// 투영 행렬 계산
// =============
m_matProj = XMMatrixIdentity();
if (PROJ_TYPE::ORTHOGRAPHIC == m_ProjType)
{
// 직교 투영
Vec2 vResolution = CDevice::GetInst()->GetRenderResolution();
m_matProj = XMMatrixOrthographicLH(vResolution.x, vResolution.y, 1.f, 10000.f);
}
else
{
// 원근 투영
m_matProj = XMMatrixPerspectiveFovLH(XM_PI / 2.f, m_fAspectRatio, 1.f, 10000.f);
}
// 행렬 업데이트
g_transform.matView = m_matView;
g_transform.matProj = m_matProj;
}
위와 같은 코드로 회전 행렬을 세팅해주고, 원근 투영, 직교 투영 여부에 따른 분기처리를 해준다.
CCameraMoveScript 클래스의 변화
원근 투영을 적용하고, 이동 방식을 변경하여 코드를 아래와 같이 변경한다.
// define.h 헤더에 코드 추가
enum class PROJ_TYPE
{
ORTHOGRAPHIC,
PERSPECTIVE,
};
// 헤더파일
#pragma once
#include "CScript.h"
class CCameraMoveScript :
public CScript
{
private:
float m_fCamSpeed;
public:
virtual void tick() override;
private:
void Camera2DMove();
void Camera3DMove();
CLONE(CCameraMoveScript);
public:
CCameraMoveScript();
~CCameraMoveScript();
};
// cpp파일
#include "pch.h"
#include "CCameraMoveScript.h"
#include "CTransform.h"
#include "CCamera.h"
CCameraMoveScript::CCameraMoveScript()
: m_fCamSpeed(100.f)
{
}
CCameraMoveScript::~CCameraMoveScript()
{
}
void CCameraMoveScript::tick()
{
if (PROJ_TYPE::ORTHOGRAPHIC == Camera()->GetProjType())
Camera2DMove();
else
Camera3DMove();
}
void CCameraMoveScript::Camera2DMove()
{
// 키 입력에 따른 카메라 이동
Vec3 vPos = Transform()->GetRelativePos();
if (KEY_PRESSED(KEY::W))
{
vPos.y += DT * m_fCamSpeed;
}
if (KEY_PRESSED(KEY::S))
{
vPos.y -= DT * m_fCamSpeed;
}
if (KEY_PRESSED(KEY::A))
{
vPos.x -= DT * m_fCamSpeed;
}
if (KEY_PRESSED(KEY::D))
{
vPos.x += DT * m_fCamSpeed;
}
Transform()->SetRelativePos(vPos);
}
void CCameraMoveScript::Camera3DMove()
{
Vec3 vPos = Transform()->GetRelativePos();
Vec3 vRot = Transform()->GetRelativeRot();
Vec3 vFront = Transform()->GetRelativeDir(DIR_TYPE::FRONT);
Vec3 vUp = Transform()->GetRelativeDir(DIR_TYPE::UP);
Vec3 vRight = Transform()->GetRelativeDir(DIR_TYPE::RIGHT);
float fSpeed = m_fCamSpeed;
if (KEY_PRESSED(KEY::LSHIFT))
fSpeed *= 5.f;
if (KEY_PRESSED(KEY::W))
{
vPos += DT * vFront * fSpeed;
}
if (KEY_PRESSED(KEY::S))
{
vPos -= DT * vFront * fSpeed;
}
if (KEY_PRESSED(KEY::A))
{
vPos -= DT * vRight * fSpeed;
}
if (KEY_PRESSED(KEY::D))
{
vPos += DT * vRight * fSpeed;
}
if (KEY_PRESSED(KEY::RBTN))
{
Vec2 vMouseDir = CKeyMgr::GetInst()->GetMouseDir();
vRot.y += DT * vMouseDir.x * 5.f;
vRot.x -= DT * vMouseDir.y * 5.f;
}
Transform()->SetRelativePos(vPos);
Transform()->SetRelativeRot(vRot);
}