지금까진 쉐이더 코드에서 Vertex연산시 별 다른 연산을 추가하지 않았다.
앞으로 추가할 코드는 앞에서 배운 Matrix연산을 추가하는 작업을 위주로 한다.
// 현재의 Vertex 쉐이더
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = float4(input.pos, 1.f);
//output.pos += offset0;
output.pos.x += float_0;
output.pos.y += float_1;
output.pos.z += float_2;
output.color = input.color;
output.uv = input.uv;
return output;
}
수학과 관련된 라이브러리를 받아온다.
DirectXTK12에서 SimpleMath의 h/cpp/inl을 가져온다.
참고로 inl은 inline을 모두 담아둔 파일이다.
// EnginePch.h
// ...
using Vec2 = DirectX::SimpleMath::Vector2;
using Vec3 = DirectX::SimpleMath::Vector3;
using Vec4 = DirectX::SimpleMath::Vector4;
using Matrix = DirectX::SimpleMath::Matrix;
// Vector4, Matrix 내부에 Vector연산, Matrix연산에 관한 부분이 모두 포함이 되어있다
// ...
참고 union을 쓰는 이유?
union
{
struct
{
float _11, _12, _13, _14;
float _21, _22, _23, _24;
float _31, _32, _33, _34;
float _41, _42, _43, _44;
};
float m[4][4];
};
변수는 하나만 생성되고 접근을 두 가지방법으로 가능
_11
로 접근가능 m[0][0]
으로도 접근가능
Transform 클래스
우선 사용할 변수를 먼저 선언하자
class Transform : public Component
{
// ...
private:
// Parent 기준, Wrold기준 아님
Vec3 _localPosition = {};
Vec3 _localRotation = {};
Vec3 _localScale = { 1.f, 1.f, 1.f };
// Parent 기준 Matrix
Matrix _matLocal= {};
// World 기준 Matrix
Matrix _matWorld = {};
weak_ptr<Transform> _parent;
};
여기서 parent는 해당 오브젝트의 상대적 위치를 갖을 오브젝트를 의미한다.
예를 들어 parent가 2만큼 이동하면 자녀 오브젝트도 동일하게 움직이게 됨.
이렇게 동시에 움직이는 오브젝트 하이어라키가 필요하며 모든 게임엔진에서 지원이 된다.
// 위치 정보를 받는 함수 필요
public:
// Parent 기준
const Vec3& GetLocalPosition() { return _localPosition; }
const Vec3& GetLocalRotation() { return _localRotation; }
const Vec3& GetLocalScale() { return _localScale; }
const Matrix& GetLocalToWorldMatrix() { return _matWorld; }
const Vec3& GetWorldPosition() { return _matWorld.Translation(); }
Vec3 GetRight() { return _matWorld.Right(); }
Vec3 GetUp() { return _matWorld.Up(); }
Vec3 GetLook() { return _matWorld.Backward(); }
void SetLocalPosition(const Vec3& position) { _localPosition = position; }
void SetLocalRotation(const Vec3& rotation) { _localRotation = rotation; }
void SetLocalScale(const Vec3& scale) { _localScale = scale; }
public:
void SetParent(shared_ptr<Transform> parent) { _parent = parent; }
weak_ptr<Transform> GetParent() { return _parent; }
void Transform::FinalUpdate()
{
// World Matrix를 만들어주는 과정이다.
// (S) Scale
Matrix matScale = Matrix::CreateScale(_localScale);
// (R) Rotation
Matrix matRotation = Matrix::CreateRotationX(_localRotation.x);
matRotation *= Matrix::CreateRotationY(_localRotation.y);
matRotation *= Matrix::CreateRotationZ(_localRotation.z);
// (T) Translation
Matrix matTranslation = Matrix::CreateTranslation(_localPosition);
// Parent를 기준으로한 Local Matrix임을 잊지 말자
_matLocal = matScale * matRotation * matTranslation;
_matWorld = _matLocal;
shared_ptr<Transform> parent = GetParent().lock();
// Parent가 없다면 그냥 Local이 World Matrix가 된다
if (parent != nullptr)
{
// 부모가 있다면 부모의 World Matrix을 곱해준다.
_matWorld *= parent->GetLocalToWorldMatrix();
}
}
void Transform::PushData()
{
// World -> View -> Projection이 필요한데
// View, Projection은 Camera Component에서 관리한다.
// 따라서 Camera 클래스의 구현이 끝나고 다시 설명한다.
Matrix matWVP = _matWorld * Camera::S_MatView * Camera::S_MatProjection;
CONST_BUFFER(CONSTANT_BUFFER_TYPE::TRANSFORM)->PushData(&matWVP, sizeof(matWVP));
}
참고로 이제 Transform 정보를 GPU레지스터에 넘겨야하는데 따라서 Constant Buffer할당시 할당되는 변수도 달라진다.
// EnginePch.h
struct TransformParams
{
Matrix matWVP;
};
void Engine::Init(const WindowInfo& info)
{
// ...
// b0 Constant buffer에 TransformParams을 할당
CreateConstantBuffer(CBV_REGISTER::b0, sizeof(TransformParams), 256);
CreateConstantBuffer(CBV_REGISTER::b1, sizeof(MaterialParams), 256);
// 쉐이더 역시 TRANSFORM_PARAMS로 받게 한다.
cbuffer TRANSFORM_PARAMS : register(b0)
{
row_major matrix matWVP;
// row_major : DirectX 기준에서는 행을 기준으로 데이터를 읽고 m[0][0] -> m[0][1] -> m[0][2] ...
// 쉐이더는 열을 기준으로 데이터를 읽는데 m[0][0] -> m[1][0] -> m[2][0] ...
// 행을 기준으로 데이터를 읽어달라는 명령
};
// ...
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = mul(float4(input.pos, 1.f), matWVP);
// 좌표에 matMVP를 반영해 달라.
output.color = input.color;
output.uv = input.uv;
return output;
}
// ...
View, Projection을 만들어보자
Camera 클래스
#pragma once
#include "Component.h"
enum class PROJECTION_TYPE
{
PERSPECTIVE, // 원근 투영
ORTHOGRAPHIC, // 직교 투영
};
class Camera : public Component
{
public:
Camera();
virtual ~Camera();
virtual void FinalUpdate() override;
void Render();
private:
PROJECTION_TYPE _type = PROJECTION_TYPE::PERSPECTIVE;
float _near = 1.f;
float _far = 1000.f;
float _fov = XM_PI / 4.f; // 기본 45도(pi/4)
float _scale = 1.f;
Matrix _matView = {};
Matrix _matProjection = {};
public:
// TEMP
static Matrix S_MatView;
static Matrix S_MatProjection;
};
// ...
Matrix Camera::S_MatView;
Matrix Camera::S_MatProjection;
// ...
void Camera::FinalUpdate()
{
// World Matrix를 역행렬취하면 View가 나온다.
_matView = GetTransform()->GetLocalToWorldMatrix().Invert();
float width = static_cast<float>(GEngine->GetWindow().width);
float height = static_cast<float>(GEngine->GetWindow().height);
// Projection 좌표계는 DirectX지원함수로 구한다.
if (_type == PROJECTION_TYPE::PERSPECTIVE)
_matProjection = ::XMMatrixPerspectiveFovLH(_fov, width / height, _near, _far);
else
// 직교투영, 원근법을 적용하지 않음.
_matProjection = ::XMMatrixOrthographicLH(width * _scale, height * _scale, _near, _far);
// 여기는 임시로 사용하는 부분이다.
S_MatView = _matView;
S_MatProjection = _matProjection;
}
void Camera::Render()
{
shared_ptr<Scene> scene = GET_SINGLE(SceneManager)->GetActiveScene();
// TODO : Layer 구분
const vector<shared_ptr<GameObject>>& gameObjects = scene->GetGameObjects();
for (auto& gameObject : gameObjects)
{
if (gameObject->GetMeshRenderer() == nullptr)
continue;
gameObject->GetMeshRenderer()->Render();
}
}
앞에서 나왔지만 메모리 올리는 부분을 다시 보자
void Transform::PushData()
{
// WVP한 최종 결과물을 넘긴다.
Matrix matWVP = _matWorld * Camera::S_MatView * Camera::S_MatProjection;
CONST_BUFFER(CONSTANT_BUFFER_TYPE::TRANSFORM)->PushData(&matWVP, sizeof(matWVP));
}
shared_ptr<Scene> SceneManager::LoadTestScene()
{
// ...
gameObject->AddComponent(make_shared<Transform>());
shared_ptr<Transform> transform = gameObject->GetTransform();
transform->SetLocalPosition(Vec3(0.f, 100.f, 200.f));
transform->SetLocalScale(Vec3(100.f, 100.f, 1.f));
// ...
#pragma region Camera
shared_ptr<GameObject> camera = make_shared<GameObject>();
camera->AddComponent(make_shared<Transform>());
camera->AddComponent(make_shared<Camera>()); // Near=1, Far=1000, FOV=45도
camera->AddComponent(make_shared<TestCameraScript>());
camera->GetTransform()->SetLocalPosition(Vec3(0.f, 100.f, 0.f));
scene->AddGameObject(camera);
#pragma endregion
// ...
카메라를 움직이게 해보자.
#pragma once
#include "MonoBehaviour.h"
class TestCameraScript : public MonoBehaviour
{
public:
TestCameraScript();
virtual ~TestCameraScript();
virtual void LateUpdate() override;
private:
float _speed = 100.f;
};
#include "pch.h"
#include "TestCameraScript.h"
#include "Transform.h"
#include "Camera.h"
#include "GameObject.h"
#include "Input.h"
#include "Timer.h"
TestCameraScript::TestCameraScript()
{
}
TestCameraScript::~TestCameraScript()
{
}
void TestCameraScript::LateUpdate()
{
Vec3 pos = GetTransform()->GetLocalPosition();
if (INPUT->GetButton(KEY_TYPE::W))
pos += GetTransform()->GetLook() * _speed * DELTA_TIME;
if (INPUT->GetButton(KEY_TYPE::S))
pos -= GetTransform()->GetLook() * _speed * DELTA_TIME;
if (INPUT->GetButton(KEY_TYPE::A))
pos -= GetTransform()->GetRight() * _speed * DELTA_TIME;
if (INPUT->GetButton(KEY_TYPE::D))
pos += GetTransform()->GetRight() * _speed * DELTA_TIME;
if (INPUT->GetButton(KEY_TYPE::Q))
{
Vec3 rotation = GetTransform()->GetLocalRotation();
rotation.x += DELTA_TIME * 0.5f;
GetTransform()->SetLocalRotation(rotation);
}
if (INPUT->GetButton(KEY_TYPE::E))
{
Vec3 rotation = GetTransform()->GetLocalRotation();
rotation.x -= DELTA_TIME * 0.5f;
GetTransform()->SetLocalRotation(rotation);
}
GetTransform()->SetLocalPosition(pos);
}