본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 67 - Projection

 

필요한 개념


Projcetion : 3D위치를 2D로 바꾸도록 하기위해



3D -> W,V,P -> Vp(Viewport) -> 2D

 


* Viewport변환
화면의 비율로 정규화된 값을
화면 공간으로 바꿔주면 된다.
원래는 렌더링 파이프라인대로 한다면 정규화된(-1 ~ +1) NDC공간(렌더링파이프라인에서 화면 비율을 만들기 위해 사용되는 좌표)으로 변환해줘야한다.
그러나 우리는 화면공간으로 해줄것이다.(가로 0 ~ 1280, 0 ~ 720)으로

이걸 통해서 3D 위치를 2D 좌표로 변환하여 텍스트도 넣을 수 있다. (본 위치에 텍스트를 넣었음)

 

 

 

 

 

Viewport.h


더보기
#pragma once

class Viewport
{
public:
	Viewport(float width, float height, float x = 0, float y = 0, float minDepth = 0, float maxDepth = 1);
	~Viewport();

	void RSSetViewport();
	void Set(float width, float height, float x = 0, float y = 0, float minDepth = 0, float maxDepth = 1);

	float GetWidth() { return width; }
	float GetHeight() { return height; }

	// 3D위치를 2D로 변환
	// Vector2로 리턴해도 되지만, 2D위치를 3D좌표로 변환한다고 생각하고 Vector3로
	void Project(Vector3* pOut, Vector3& source, Matrix& W, Matrix& V, Matrix& P);

private:
	float x, y;
	float width, height;
	float minDepth, maxDepth;

	D3D11_VIEWPORT viewport;
};

 

 

 

 

 

 

 

Viewport.Cpp


더보기
#include "Framework.h"
#include "Viewport.h"

Viewport::Viewport(float width, float height, float x, float y, float minDepth, float maxDepth)
{
	Set(width, height, x, y, minDepth, maxDepth);
}

Viewport::~Viewport()
{
	
}

void Viewport::RSSetViewport()
{
	D3D::GetDC()->RSSetViewports(1, &viewport);
}

void Viewport::Set(float width, float height, float x, float y, float minDepth, float maxDepth)
{
	viewport.TopLeftX = this->x = x;
	viewport.TopLeftY = this->y = y;
	viewport.Width = this->width = width;
	viewport.Height = this->height = height;
	viewport.MinDepth = this->minDepth = minDepth;
	viewport.MaxDepth = this->maxDepth = maxDepth;

	RSSetViewport();
}

void Viewport::Project(Vector3* pOut, Vector3& source, Matrix& W, Matrix& V, Matrix& P)
{
	// Projection : 3D -> W,V,P -> Vp -> 2D
    
    
	// 위치 - W->V->P == 위치 - W * V * P
	Vector3 position = source;

	Matrix wvp = W * V * P;
	// position 위치를 wvp공간으로 변환해야 하니까
	D3DXVec3TransformCoord(pOut, &position, &wvp);

	// Viewport변환
	//화면의 비율로 정규화된 값을
	//화면 공간으로 바꿔주면 된다.
	// 원래는 렌더링 파이프라인대로 한다면 정규화된(-1 ~ +1) NDC공간(렌더링파이프라인에서 화면 비율을 만들기 위해 사용되는 좌표)으로 변환해줘야한다.
	// 그러나 우리는 화면공간으로 해줄것이다.(가로 0 ~ 1280, 0 ~ 720)으로

	// x : 뷰표트가 가지고 있는 x(시작지점)
	pOut->x = ((pOut->x + 1.0f) * 0.5f) * width + x;
	// 밑에서 부터 위로 올라가는 좌표니까 -붙음
	pOut->y = ((-pOut->y + 1.0f) * 0.5f) * height + y;
	// 프로젝션의 z 비율을 만드는 식(이후 깊이를 다룰때 설명함)
	pOut->z = (pOut->z * (maxDepth - minDepth)) + minDepth;
}

 

 

 

 

ProjectionDemo.h


더보기
#pragma once
#include "Systems/IExecute.h"

class ProjectionDemo : public IExecute
{
public:
	virtual void Initialize() override;
	virtual void Ready() override {};
	virtual void Destroy() override;
	virtual void Update() override;
	virtual void PreRender() override {};
	virtual void Render() override;
	virtual void PostRender() override {};
	virtual void ResizeScreen() override {};

private:
	void Mesh();
	void Airplane();
	void Kachujin();
	void KachujinCollider();

	void Pass(UINT mesh, UINT model, UINT anim);

private:
	Shader* shader;

	CubeSky* sky;

	Material* floor;
	Material* stone;
	Material* brick;
	Material* wall;

	MeshRender* cube;
	MeshRender* cylinder;
	MeshRender* sphere;
	MeshRender* grid;

	ModelRender* airplane = NULL;

	ModelAnimator* kachujin = NULL;

	// Collider의 인스턴싱은 몇개 인지 모른다.(그래서 *하나 더 붙음)
	ColliderObject** colliders;

	// 나중에 Path 관리를 편하게 하기 위해
	vector<MeshRender*> meshes;
	vector<ModelRender*> models;
	vector<ModelAnimator*> animators;
};

 

 

 

 

 

 

 

ProjectionDemo.cpp


더보기
#include "stdafx.h"
#include "ProjectionDemo.h"

void ProjectionDemo::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
	Context::Get()->GetCamera()->Position(1, 36, -85);
	

	shader = new Shader(L"55_Render.fx");

	sky = new CubeSky(L"Environment/GrassCube1024.dds");

	Mesh();
	Airplane();
	Kachujin();
	KachujinCollider();
}

void ProjectionDemo::Destroy()
{
	SafeDelete(shader);
	
	SafeDelete(sky);
	SafeDelete(cube);
	SafeDelete(cylinder);
	SafeDelete(sphere);
	SafeDelete(grid);

	SafeDelete(floor);
	SafeDelete(stone);
	SafeDelete(brick);
	SafeDelete(wall);

	SafeDelete(airplane); 
	SafeDelete(kachujin);
}

void ProjectionDemo::Update()
{
	Vector3 position;
	airplane->GetTransform(0)->Position(&position);

	position.x -= 5;
	position.z = 0.0f;

	// 2D 위치로 변환
	Vector3 position2D;
	// world는 필요가 없다. position을 가져와서 써서
	Matrix W, V, P;
	D3DXMatrixIdentity(&W);
	V = Context::Get()->View();
	P = Context::Get()->Projection();
	// 2D 위치가 나옴
	Context::Get()->GetViewport()->Project(&position2D, position, W, V, P);

	// 2D 위치로 글자 출력
	Gui::Get()->RenderText(position2D.x, position2D.y, 1, 0, 0, "AirPlane");

	sky->Update();

	cube->Update();
	grid->Update();
	cylinder->Update();
	sphere->Update();

	airplane->Update();
	kachujin->Update();

	for (UINT i = 0; i < kachujin->GetTransformCount(); i++)
	{
		// 카추진 위치 얻어옴(업데이트 시킬)
		Matrix attach;
		kachujin->GetAttachTransform(i, &attach);

		position = Vector3(0, 0, 0);
		// position이 없고 world행렬 공간만 사용한다면
		// position에 0, 0, 0 주고 world만 그대로 넣어주면 된다.
		Context::Get()->GetViewport()->Project(&position2D, position, attach, V, P);

		// 글씨가 왼쪽 정렬이어서 왼쪽 위치 부터 나타남
		Gui::Get()->RenderText(position2D.x, position2D.y, 1, 1, 1, "Hand");

		// 얼마만큼 이동할지
		colliders[i]->Collider->GetTransform()->World(attach);
		colliders[i]->Collider->Update();
	}
}

void ProjectionDemo::Render()
{
	// 메인 뷰포트를 만들때 화면 넓이를 사용
	static float width = D3D::Width();
	static float height = D3D::Height();
	static float x = 0.0f;
	static float y = 0.0f;

	ImGui::InputFloat("Width", &width, 1.0f);
	ImGui::InputFloat("Height", &height, 1.0f);
	ImGui::InputFloat("X", &x, 1.0f);
	ImGui::InputFloat("Y", &y, 1.0f);

	Context::Get()->GetViewport()->Set(width, height, x, y);

	// Perspective의 FOV는 시야각을 결정한다.(기본은 PI / 4, 45도를 사용)
	// FOV(field of view) -> 시야각을 의미(더 자세한건 나중에 그림자할때 설명)
	static float fov = 0.25f;
	ImGui::InputFloat("Fov", &fov, 0.001f);

	// 0이 되면 안되는 이유가 0이면 내 눈안에 들어가 있는 것이다.
	// 피부정도를 약간 늘려준다 생각
	// 실제 내부적으로는 나누기 연산을 해서 0을 주면 안된다.
	// maxZ은 기본값 1000으로 되어있다.
	// WP가 완료되고 Viewport로 넘어갈때
	// minZ 이 0이 되고 maxZ이 1로 정규화됨
	static float minZ = 0.1, maxZ = 1000.0f;
	ImGui::InputFloat("minZ", &minZ, 1.0f);
	ImGui::InputFloat("MaxZ", &maxZ, 1.0f);

	Perspective* perspective = Context::Get()->GetPerspective();
	perspective->Set(D3D::Width(), D3D::Height(), minZ, maxZ, Math::PI * fov);

	sky->Render();
	
	Pass(0, 1, 2);

	wall->Render();
	sphere->Render();

	brick->Render();
	cylinder->Render();

	stone->Render();
	cube->Render();

	floor->Render();
	grid->Render();

	airplane->Render();
	kachujin->Render();

	for (UINT i = 0; i < kachujin->GetTransformCount(); i++)
		colliders[i]->Collider->Render();
}

void ProjectionDemo::Mesh()
{
	//Create Material
	{
		floor = new Material(shader);
		floor->DiffuseMap("Floor.png");
		//floor->SpecularMap("Floor_Specular.png");
		//floor->NormalMap("Floor_Normal.png");
		//floor->Specular(1, 1, 1, 20);

		stone = new Material(shader);
		stone->DiffuseMap("Stones.png");
		//stone->SpecularMap("Stones_Specular.png");
		//stone->NormalMap("Stones_Normal.png");
		//stone->Specular(1, 1, 1, 20);

		brick = new Material(shader);
		brick->DiffuseMap("Bricks.png");
		//brick->SpecularMap("Bricks_Specular.png");
		//brick->NormalMap("Bricks_Normal.png");
		//brick->Specular(1, 0.3f, 0.3f, 20);

		wall = new Material(shader);
		wall->DiffuseMap("Wall.png");
		//wall->SpecularMap("Wall_Specular.png");
		//wall->NormalMap("Wall_Normal.png");
		//wall->Specular(1, 1, 1, 20);
	}

	//Create Mesh
	{
		Transform* transform = NULL;

		cube = new MeshRender(shader, new MeshCube());
		transform = cube->AddTransform();
		transform->Position(0, 5, 0);
		transform->Scale(20, 10, 20);

		grid = new MeshRender(shader, new MeshGrid(5, 5));
		transform = grid->AddTransform();
		transform->Position(0, 0, 0);
		transform->Scale(12, 1, 12);

		cylinder = new MeshRender(shader, new MeshCylinder(0.5f, 3.0f, 20, 20));
		sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20));
		for (UINT i = 0; i < 5; i++)
		{
			transform = cylinder->AddTransform();
			transform->Position(-30, 6, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);

			transform = cylinder->AddTransform();
			transform->Position(30, 6, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);


			transform = sphere->AddTransform();
			transform->Position(-30, 15.5f, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);

			transform = sphere->AddTransform();
			transform->Position(30, 15.5f, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);
		}
	}

	sphere->UpdateTransforms();
	cylinder->UpdateTransforms();
	cube->UpdateTransforms();
	grid->UpdateTransforms();

	// 렌더링 할 것들을 추가
	meshes.push_back(sphere);
	meshes.push_back(cylinder);
	meshes.push_back(cube);
	meshes.push_back(grid);
}

void ProjectionDemo::Airplane()
{
	airplane = new ModelRender(shader);
	airplane->ReadMesh(L"B787/Airplane");
	airplane->ReadMaterial(L"B787/Airplane");

	Transform* transform = airplane->AddTransform();
	transform->Position(2.0f, 9.91f, 2.0f);
	transform->Scale(0.004f, 0.004f, 0.004f);
	airplane->UpdateTransforms();

	models.push_back(airplane);
}

void ProjectionDemo::Kachujin()
{
	kachujin = new ModelAnimator(shader);
	kachujin->ReadMesh(L"Kachujin/Mesh");
	kachujin->ReadMaterial(L"Kachujin/Mesh");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Idle");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Walk");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Run");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Slash");
	kachujin->ReadClip(L"Kachujin/Salsa Dancing");


	Transform* transform = NULL;

	transform = kachujin->AddTransform();
	transform->Position(0, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(0, 0, 1.0f);

	transform = kachujin->AddTransform();
	transform->Position(-15, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(1, 1, 1.0f);

	transform = kachujin->AddTransform();
	transform->Position(-30, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(2, 2, 0.75f);

	transform = kachujin->AddTransform();
	transform->Position(15, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayBlendMode(3, 0, 1, 2);
	kachujin->SetBlendAlpha(3, 1.5f);

	transform = kachujin->AddTransform();
	transform->Position(30, 0, -32.5f);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(4, 4, 0.75f);

	kachujin->UpdateTransforms();
	// 오른손 쪽(40번 본)
	kachujin->SetAttachTransform(40);

	animators.push_back(kachujin);
}

void ProjectionDemo::KachujinCollider()
{
	// 인스턴싱 개수
	UINT count = kachujin->GetTransformCount();

	colliders = new ColliderObject*[count];

	for (UINT i = 0; i < count; i++)
	{
		colliders[i] = new ColliderObject();

		// 기준 위치
		colliders[i]->Init = new Transform();
		colliders[i]->Init->Position(0, 0, 0);
		colliders[i]->Init->Scale(10, 30, 10);

		colliders[i]->Transform = new Transform();
		colliders[i]->Collider = new Collider(colliders[i]->Transform, colliders[i]->Init);
	}

}

void ProjectionDemo::Pass(UINT mesh, UINT model, UINT anim)
{
	for (MeshRender* temp : meshes)
		temp->Pass(mesh);

	for (ModelRender* temp : models)
		temp->Pass(model);

	for (ModelAnimator* temp : animators)
		temp->Pass(anim);
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

결과


 



모델 자체가 틀어져 있어서 중심점 자체가 살작 틀어져 있다.

2D 위치는 깊이가 없으므로 다른 물체에 가려지지 않는다.
라그나로크와 같은 2.5D 게임은 Z값을 별도로 계산해서 할당한다.

카메라를 모델에 가까이 갈수록(거리에 따라) Z 비율에 따라 크기가 조정이 된다.(거리에 따라 텍스트가 자꾸 이동함)
이게 싫으면 Z을 0으로 주면 된다.(아까보다는 덜 어색함(크기에 변화 폭이 크지 않음))

ImGui에 대한 글씨 기능으로 써서(깊이가 들어감, 크기가 조정됨)
실제 게임에서는 이미지로 글씨를 씀(비트맵 폰트) -> 위치를 사용하면 문제가 되지 않음