본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 68 - Unprojection

 

 

필요한 개념


이전 Projection은 3D 공간에서 2D공간으로 가는 거였음 

Unprojection : 2D 공간에서 3D 공간으로 돌아오는

Raycast(Picking) ? 마우스의 위치(2D)를 이용하여 3D공간에서 선을 만들어 교차되는 삼각형을 구하는 방식
(반직선을 쏴서 선택하는 것)

즉 2D좌표를 3D좌표로 변환해줘야 한다.

 


2D공간을 3D공간으로 변환한 다음에 
화면 상에 마우스가 있는 지점에 위치를 구해서 근면(nearPlane)에서 선이 나가고 해당 똑같이 먼면(FarPlane)에 비례가 되서 선이 도착한다.
이 선을 구하려고 하는것
이 선으로 충돌되는 삼각형이 있다면 그 삼각형의 위치(Picking Position)를 구해올 것이다.

근면에서 먼면까지의 방향을 만들고 이어지는 선으로 만들었을때 IntersectTri()에 넣어주면 해당 삼각형의 위치가 나오게 된다.
-> Unprojection

여기서의 삼각형은(모든 Mesh들이나, 지형은 삼각형으로 이루어져 있기때문에 가능)


Unprojection은 Projection의 정확히 반대로 계산
Projection : 3D -> W,V,P -> Vp -> 2D
Unprojection : 2D -> Vp -> P,V,W -> 3D

전체 삼각형을 for문을 돌며 체크해줘야 한다.
왼쪽 하단은 빠른데, 우측 상단은 느려짐
그래서 느려지기 때문에 초대형맵을 쓸때는 이 Picking방식이 아니라 화면공간에서 선택하는
ScreenSpacePicking 기법을 사용하면 된다.(수업에서 다루진 않음)

선의 시작 = 근면 마우스 위치, 선의 종료 = 원면 마우스 위치
선의 방향 : 선의 종료 - 선의 시작(두개를 빼주면 방향을 알아낼 수 있다.)

* 선을 DebugLine으로 그려도 보이지 않는다.(->카메라와 동일한면을 바라보고 있다면 보이지 않는다.)
이유는 정확히 카메라하고 선이 일치해서 보이지 않는다.


실행시켜보면 좌측 하단은 프레임이 2000정도 나오는데 지형 우 상단은 프레임이 100프레임정도 나옴
* 이유는 삼각형의 번호가 0에 가까운 쪽에서 교차되면 속도가 매우 빠르지만 아니라면 점점 느려진다.
교차가 되지 않는다면 전부 다 검사해야 교차 여부를 알 수 있으므로 가장 느리다.(반복문이 0부터 쭉 검사해서 그렇다.)

-> 그래서 지형을 나눈다던가, 화면 공간에서의 picking을 사용한다.

 

 

 

 

Viewport.h 추가된 내용


더보기
class Viewport
{
	...
	// Unprojection은 Projection의 정확히 반대로 계산
	void Unproject(Vector3* pOut, Vector3& source, Matrix& W, Matrix& V, Matrix& P);
};

 

 

 

 

 

 

 

Viewport.cpp 추가된 내용


더보기
void Viewport::Unproject(Vector3* pOut, Vector3& source, Matrix& W, Matrix& V, Matrix& P)
{	
	// Unprojection은 Projection의 정확히 반대로 계산
	// Projection : 3D -> W,V,P -> Vp -> 2D
	// Unprojection : 2D -> Vp -> P,V,W -> 3D

	Vector3 position = source;


	// Viewport 변환
	// 다시 NDC로 와야되기 때문에
	// 다시 x 빼주고 / width 나눠준다.
	// projection때는 반으로(0.5)로 줄였으니까 2.0f(2배) 곱해줌
	// 더해준 1.0f를 다시 뺴줌
	// 정확히 반대대로 해줌
	pOut->x = ((position.x - x) / width) * 2.0f - 1.0f;
	// 반대로 뒤집어야 하니까 * -1.0f
	pOut->y = (((position.y - y) / height) * 2.0f - 1.0f) * -1.0f;
	pOut->z = ((position.z - minDepth) / (maxDepth - minDepth));

	// P,V,W 곱해서 역행렬 취하면 됨
	Matrix wvp = W * V * P;
	D3DXMatrixInverse(&wvp, NULL, &wvp);

	D3DXVec3TransformCoord(pOut, pOut, &wvp);
}

 

Terrain.h 추가된 내용


더보기
class Terrain
{	
	...
	// Raycast된 위치를 구해오는 함수
	// Raycast : 반직선을 쏴서 선택하는 것(또 다른 말로 Picking이라고도함)
	Vector3 GetRaycastPosition();
}

 

 

 

 

 

 

 

Terrain.cpp 추가된 내용


더보기
...

Vector3 Terrain::GetRaycastPosition()
{
	// 어떤 삼각형이 광선(반직선)과 충돌될지를 모름
	// 전체 삼각형을 for문을 돌며 체크해줘야 한다.
	// 왼쪽 하단은 빠른데, 우측 상단은 느려짐
	Matrix V = Context::Get()->View();
	Matrix P = Context::Get()->Projection();
	Viewport* vp = Context::Get()->GetViewport();

	Vector3 mouse = Mouse::Get()->GetPosition();

	// 선의 시작 = 근면 마우스 위치, 선의 종료 = 원면 마우스 위치
	// 선의 방향 : 선의 종료 - 선의 시작(두개를 빼주면 방향을 알아낼 수 있다.)

	// 근면에 대한 위치, 원면에 대한 위치 구함
	Vector3 n, f;
	// NDC로 정규화 되었다고 했을때 가장 가까운 면의 z은 0이고, 가장 먼면의 z은 1이다.
	// NDC로 x,y는 -1 ~ 1까지 지만, z은 0 ~ 1까지 이다.
	// z은 0 ~ 1까지인 이유? 뒤는 그릴 필요 없다.
	// 그래서 근면일때 z은 0이다.(NDC일때)
	mouse.z = 0.0f;
	// 근면에 대한 위치를 구해옴
	// 근면의 2D 위치가 3D 위치로 바뀌어서 near에 리턴된다.
	vp->Unproject(&n, mouse, world, V, P);

	mouse.z = 1.0f;
	// 원면의 위치를 구해옴
	vp->Unproject(&f, mouse, world, V, P);

	Vector3 start = n;
	Vector3 direction = f - n;


	// 어떤 삼각형일지 모든 삼각형을 순회함
	for (UINT z = 0; z < height - 1; z++)
	{
		for (UINT x = 0; x < width - 1; x++)
		{
			UINT index[4];
			index[0] = width * z + x;
			index[1] = width * (z + 1) + x;
			index[2] = width * z + x + 1;
			index[3] = width * (z + 1) + (x + 1);

			// 위치를 구해옴
			Vector3 p[4];
			for (int i = 0; i < 4; i++)
				p[i] = vertices[index[i]].Position;

			float u, v, distance;

			// 체크할 삼각형 3개, 반직선 방향
			// U(ddx), V(ddz)가 리턴됨
			// 왼쪽 삼각형 체크
			if (D3DXIntersectTri(&p[0], &p[1], &p[2], &start, &direction, &u, &v, &distance) == TRUE)
				return p[0] + (p[1] - p[0]) * u + (p[2] - p[0]) * v;

			// 오른쪽 삼각형 체크
			if (D3DXIntersectTri(&p[3], &p[1], &p[2], &start, &direction, &u, &v, &distance) == TRUE)
				return p[3] + (p[1] - p[3]) * u + (p[2] - p[3]) * v;
		}
	}

	// 충돌때 return이 안되었다면 어느 삼각형하고도 충돌된게 아니다.
	// 검사가 안되었을 경우
	return Vector3(-1, FLT_MIN, -1);
}

 

 

 

 

 

 

 

GetRaycastDemo.h


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

class GetRaycastDemo : 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:
	Shader* shader;
	class Terrain* terrain;
};

 

 

 

 

 

 

 

GetRaycastDemo.cpp


더보기
#include "stdafx.h"
#include "GetRaycastDemo.h"
#include "Environment/Terrain.h"

void GetRaycastDemo::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(12, 0, 0);
	Context::Get()->GetCamera()->Position(35, 10, -55);
	((Freedom*)Context::Get()->GetCamera())->Speed(20);

	shader = new Shader(L"19_Terrain.fx");

	terrain = new Terrain(shader, L"Terrain/google1.png");
	terrain->Pass(1);
}

void GetRaycastDemo::Destroy()
{
	SafeDelete(shader);
	SafeDelete(terrain);
}

void GetRaycastDemo::Update()
{
	// 위치만 구해와서 출력
	Vector3 position = terrain->GetRaycastPosition();

	string str = "";
	str += to_string(position.x) + ", ";
	str += to_string(position.y) + ", ";
	str += to_string(position.z) + ", ";

	Gui::Get()->RenderText(Vector2(10, 60), Color(1, 0, 0, 1), str);

	terrain->Update();
}

void GetRaycastDemo::Render()
{
	terrain->Render();	
}

 

 

 

 

 

 

 

 

 

 

결과