프로그래밍 공부
작성일
2023. 10. 12. 16:31
작성자
WDmil
728x90

DirectX의 3D연산에서는 대부분 행렬연산을 사용한다.

 

Local좌표와 World좌표로 객체를 움직이기 위해 행렬연산을 사용하는데, 4X4 행렬을 이용해서 연산하게되면 데이터값의 연산이 빠르게 이루어지고 쉽게 판별할 수 있기 때문이다.

 

어떠한 Vertex의 위치좌표를 원하는대로 변경하는 함수를 만들어보자.

위와같은 방식의 함수들을 제작하여 입력된 객체를 이동시키거나, 조절하거나, 회전시키는것이 목표이다.


#pragma once

class Vector3
{
public:
	Vector3(float x = 0.0f, float y = 0.0f, float z = 0.0f)
		: value(XMVectorSet(x, y, z, 0))
	{}
	Vector3(Float3 value) : value(XMLoadFloat3(&value)) {}
	Vector3(Vector4 value) : value(value) {}

	operator Vector4() { return value; }

	void SetX(const float& x) { value = XMVectorSetX(value, x); }
	void SetY(const float& y) { value = XMVectorSetY(value, y); }
	void SetZ(const float& z) { value = XMVectorSetZ(value, z); }

	float GetX() const { return XMVectorGetX(value); }
	float GetY() const { return XMVectorGetY(value); }
	float GetZ() const { return XMVectorGetZ(value); }

	// 함수를 끌어다가 쓰는것.
	__declspec(property(get = GetX, put = SetX)) float x;
	__declspec(property(get = GetY, put = SetY)) float y;
	__declspec(property(get = GetZ, put = SetZ)) float z;

	void operator += (const Vector3& v) { value += v.value; }
	void operator -= (const Vector3& v) { value -= v.value; }


private:
	Vector4 value;	// 라이브러리의 도움을 받기 위해 기본 자료형을 이렇게 짠다.
};

위와같이 Vertex를 나타내는 Vector3를 생성하여 작업할것이다.

 

원래부터 사용하는 XMVECTOR는 다양한 사용방식을 보장하고, 여러가지 DIrectX의 API에 사용되나 접근이 힘들기 때문에 다양한 접근과 변경을 위해 위와같은 헤더를 재작한다.

기본제공 객체 이지만, 접근과 수정이 힘들고. 함수를 사용해야하는점이 불편하다.

 

Vector3라고 미리 생성한뒤에, 값을 기입하면 해당 기입된 데이터를 SetX, Y, Z를 통해 변경해주는 함수를 자동적으로 동작하게 함수를 미리 구현해놓은 것이다.

 

위와같은 함수를 구현한뒤.

__declspec(property(get = GetX, put = SetX)) float x;
__declspec(property(get = GetY, put = SetY)) float y;
__declspec(property(get = GetZ, put = SetZ)) float z;

로 정의하게 되면.

 

Vector3 def; 를 생성하고

 

def.x = 10;으로 하였을 때.

def.SetX(10)과 같은 효과가 나타날것이고.

def = XMVectorSetX(def, x); 와 같을것이다.

 

나머지 연산자 오버헤드는 일반적인 오버헤드와 같다고 이해하면 된다.


#pragma once

class Transform
{
public:
	Transform();

	void UpdateWorld();

	void Translate(Vector3 direction);
	void Rotate(Vector3 direction);

	Vector3 GetLocalPosition() const { return localPosition; }
	Vector3 GetLocalRotation() const { return localRotation; }
	Vector3 GetLocalScale() const { return localScale; }

	void SetLocalPosition(const Vector3& position) { localPosition = position; }
	void SetLocalRotation(const Vector3& rotation) { localRotation = rotation; }
	void SetLocalScale(const Vector3& scale) { localScale = scale; }

	Transform* GetParent() { return parent; }
	void SetParent(Transform* transform) { parent = transform; }
	void SetPivot(Vector3 pivot) { this->pivot = pivot; }

protected:
	Vector3 localPosition;
	Vector3 localRotation;
	Vector3 localScale = { 1, 1, 1 }; // 배율이기 떄문에 0이 되면 안됨.

	Matrix world;

private:
	Transform* parent = nullptr;

	Vector3 pivot;
};

Transform을 제작한다.

Transform은 Matrix값과 Position, Rotation, scale 값을 가지고, 해당 값을 이용하여 Vertex. 정점값을 수정하는 역할을 한다.

 

기본적인 Get과 Set함수를 구현하고, Parent와 Pivot이 존재하는데. 

이는 회전주체 Vertex나. 회전주체 객체를 설정하는데 사용된다.

 

이에대한 설명은 후에 작성한다.

#include "Framework.h"

Transform::Transform()
{
	world = XMMatrixIdentity();
}

void Transform::UpdateWorld()
{
	world = XMMatrixTransformation(pivot, XMQuaternionIdentity(),
		localScale, pivot,
		XMQuaternionRotationRollPitchYawFromVector(localRotation),
		localPosition);

	if (parent)
		world *= parent->world;

	// pivot의 역할
	/*
		pivot만큼 곱해서 이동시키고 마지막에 역행렬로 사용해서 다시 음으로 곱해버리면 피봇 전값이 나온다.
	*/
}

void Transform::Translate(Vector3 direction)
{
	localPosition += direction;
}

void Transform::Rotate(Vector3 direction)
{
	localRotation += direction;
}

 

위에서 설정한대로 데이터를 변경하는데, DirectX에서는 XMMatrixTransformation으로 SRT값을 한번에 받아서 처리해주는 함수가 존재한다.

각각 매개변수를 살펴보자.

 

먼저 pivot값을 받아온다. 여기서 pivot값은 해당 scale이나, roatation의 원점을 이야기한다.

 

원점에서부터 얼마나 커질것인지. 원점에서부터 얼마나 회전할 것인지.

 

원점기준으로 어떻게 회전할것 인지를 설정하는 부분이다.

 

XMQuaternionIdentity()는. 단위행렬을 리턴해주는 함수이다.

 

localScale은. Scale을 담당하는 단위행렬에 XYZ값만 추가한 행렬이디ㅏ.

XMQuaternionRotationRollPitchYawFromVector(localRotation) 은, localRotation에서 기입된 Vector3의 XYZ축의 회전에 대해 각각의 행렬을 곱해주는 함수이다.

 

함수이름이 참 직관적이라고 여겨지는 부분은.

 

Quaternion을 회전한다.

 

z축을따라 회전(roll)

x축을 따라 회전(pitch)

y축을 따라 회전(yaw)

시켜라.

 

Vector에서 값을 받아와서(From)

 

진짜 그냥 독일식 영어마냥 붙여버려서 쓰는게 너무 웃기지 않은가.

 

아무튼. 위와같은 함수를 사용해서 UpdateWorld를 구성하고.

 

밑에 parent를 넣어서. 부모상속관계일 때. 상속된 데이터의 행렬을 현재 world 행렬에 곱해버려라. 라고 작성해놓으면 된다.

 


자 이제 Transform과 Vector3의 생성이 끝났다면,

 

Cube에 Transform을 상속시켜서 작동해보자.

 

#pragma once

class Cube : public Transform
{
public:
	Cube(Float3 size = { 1, 1, 1});
	~Cube();

	void Update();
	void Render();

private:
	VertexShader* vertexShader;
	PixelShader* pixelShader;

	VertexBuffer* vertexBuffer;
	IndexBuffer* indexBuffer;

	MatrixBuffer* worldBuffer;

	vector<VertexColor> vertices;
	vector<UINT> indices;
};

위와같이 Transform을 그냥 상속시켜버리면 된다.

 

그리고 전에 작성했던 Render() 부분에 worldBuffer->set(world); 를 붙이면된다.

void Cube::Render()
{
    worldBuffer->Set(world);
    worldBuffer->SetVS(0);

    vertexBuffer->Set();
    indexBuffer->Set();

    vertexShader->Set();
    pixelShader->Set();

    DC->DrawIndexed(indices.size(), 0, 0);
}

 

그리고 TutorialScene을 만든부분에 UpdateWorld와

키 입력을 받아서 움직이는 함수를 붙여놓으면 다음과 같이 작성할 수 있다.

 

#include "Framework.h"
#include "TutorialScene.h"

TutorialScene::TutorialScene()
{
    Cube* defc = new Cube();
	cubes.emplace_back(defc);
    cubes.push_back(new Cube());

    //cubes.back()->SetPivot(Vector3(0.5f, 0.5f, 0.5f));

    cubes.back()->SetLocalPosition({ 2, 0, 0 });
    cubes[1]->SetParent(cubes[0]);

    XMVECTOR eye = XMVectorSet(3, 3, -3, 0);//Cam Pos
    XMVECTOR focus = XMVectorSet(0, 0, 0, 0);//Cam Look at Pos
    XMVECTOR up = XMVectorSet(0, 1, 0, 0);//Cam Up Vector

    Matrix view = XMMatrixLookAtLH(eye, focus, up);

    Matrix projection = XMMatrixPerspectiveFovLH(XM_PIDIV4,
        (float)WIN_WIDTH / WIN_HEIGHT, 0.1f, 1000.0f);

    viewBuffer = new MatrixBuffer();
    projectionBuffer = new MatrixBuffer();

    viewBuffer->Set(view);
    viewBuffer->SetVS(1);

    projectionBuffer->Set(projection);
    projectionBuffer->SetVS(2);
}

TutorialScene::~TutorialScene()
{
    delete viewBuffer;
    delete projectionBuffer;
}

void TutorialScene::Update()
{
    if (KEY->Press('W'))
        cubes[0]->Translate(Vector3(0, 0, 1) * DELTA);
    if (KEY->Press('S'))
        cubes[0]->Translate(Vector3(0, 0, -1) * DELTA);
    if (KEY->Press('A'))
        cubes[0]->Translate(Vector3(-1, 0, 0) * DELTA);
    if (KEY->Press('D'))
        cubes[0]->Translate(Vector3(1, 0, 0) * DELTA);
    if (KEY->Press('Q'))
        cubes[0]->Translate(Vector3(0, 1, 0) * DELTA);
    if (KEY->Press('E'))
        cubes[0]->Translate(Vector3(0, -1, 0) * DELTA);

    if (KEY->Press('T'))
        cubes[0]->Rotate(Vector3(0, 0, 1) * DELTA);
    if (KEY->Press('G'))
        cubes[0]->Rotate(Vector3(0, 0, -1) * DELTA);
    if (KEY->Press('F'))
        cubes[0]->Rotate(Vector3(-1, 0, 0) * DELTA);
    if (KEY->Press('H'))
        cubes[0]->Rotate(Vector3(1, 0, 0) * DELTA);
    if (KEY->Press('R'))
        cubes[0]->Rotate(Vector3(0, 1, 0) * DELTA);
    if (KEY->Press('Y'))
        cubes[0]->Rotate(Vector3(0, -1, 0) * DELTA);

    for (Cube* cube : cubes)
        cube->UpdateWorld();
}

void TutorialScene::PreRneder()
{
}

void TutorialScene::Render()
{
    for (Cube* cube : cubes)
        cube->Render();
}

void TutorialScene::PostRender()
{
}

void TutorialScene::GUIRender()
{
}

참고로 프로그래밍할때는 나눗셈을 쓰기보다는 곱셈을 하고 쉬프트 연사자로 옆으로 당겨버리는게 더 빠르다.

나누는 비트연산자에서 오버헤드가 발생해서 일정값이 버려지기 때문이다.


동작사항

 

 

728x90