(DirectX : Basic) 19. Camera

Posted by : at

Category : DirectX



지금까진 쉐이더 코드에서 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);
}

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links