렌더링 과정을 개선하였으니 이제 드디어 Picking을 구현해보고자 한다.
음, 피킹에 대한 구현을 하려면 이 기능이 어떤 클래스에게 들어가 있어야 할까? 일단 원리를 알아보자.
마우스 입력으로 2D 좌표를 받은 후, 이를 3d vector로 변환해야 한다. 일단 마우스 좌표는 이미 기능이 있으니, 어떻게 해야 마우스 좌표를 3D로 바꾸느냐가 핵심이겠군.
먼저 마우스 좌표를 그냥 화면 해상도에 따른 좌표공간에 두지 않고, -1~ +1로 일종의 정규화(?)된 공간의 좌표로 변환을 해주어야 한다.
즉 화면 좌표계는 다음과 같은데,
(0,0)
. . . . . .. . . . . . . . . . .
.
.
.
.
. . . . . .. . . . . . . . . . .(1280, 720) -> 대충 보자;
이걸 중심이 0,0이고 위는 +1, 아래는 -1이 되는 좌표로 변환과정을 거쳐주어야 한다. 왜냐하면 Projection을 수행하기 위해 원래 소실점에 해당하는 원점을 중심으로 모든 점들의 좌표를 (-1, -1, 0) ~ (1, 1, 1) 사이로 맞춰준 후 Projection을 수행하여 2차원 공간의 점으로 바꿔주기 때문이다. 그러니까 현재 내가 찍은 마우스의 x,y 좌표가 projection행렬 연산까지 수행 완료된 2d공간 상의 어느 한 점이라고 가정하기 위한 작업이다.
+1
-1 + +1
-1
후, 텍스트로 그리기 힘들다. 대충 이렇게 바꿔줘야 한다. 바꿔주려면
mouse X는 0~ 1280까지 값을 가질 수 있는데, 1280/2 부분이 0이 되어야 한다.
x= 0일 때 -1이 되고, 1280일때 +1 이 되려면 어떤 식으로 계산을 해야할까?
일단 x /1280을 하면 0~ 1 사이 값이 된다. 이걸 -1~ 1 사이 값으로 바꿔주려면
그냥 0.5를 빼주었을 경우 -0.5 ~ +0.5 가 되는데, 여기서 다시 x2를 해주면 -1 ~ +1 이 된다.
이를 식으로 표현한다면
2 * ((x/1280) - 0.5) = 2*x /1280 -1 이다.
y축은 거꾸로 위로 올라갈 수록 양수로 증가해야하므로 이 값에 -1을 곱해서 반대로 바꿔주면 된다. 이 계산을 해줌으로써 마우스 좌표를 -1~ +1로 변환할 수 있게 되었다.
float pointX = (2.0f * screenX / renderer.gfx.width - 1.0f);
float pointY = (-2.0f * screenY / renderer.gfx.height + 1.0f);
투영행렬에서 (1행 1열)은 x좌표 변환을 담당하고 2행 2열은 y좌표 변환을 담당한다. 현재 정점이 행렬 계산시 이 값들을 곱해서 나온 결과물이므로 거꾸로 이 값들을 나눠주면 프로젝션이 진행되기 전의 x,y 좌표를 얻을 수 있다. 이 과정을 UnProjection이라고 하자.
dx::XMFLOAT3X3 projection33;
dx::XMStoreFloat3x3(&projection33, renderer.mainCamera->projectionMatrix);
direction = {
(2.0f * screenX / renderer.gfx.width - 1.0f) / projection33._11,
(-2.0f * screenY / renderer.gfx.height + 1.0f) / projection33._22,
1.0f
};
여기서 XMFLOAT3X3에 행렬 값을 가져온 이유는 4번째 행, 열은 연산시 필요가 없기 때문에 필요한 부분만 떼어온 것이다. 그리고 z축을 따로 계산하지 않는 이유는 해당 점이 view 공간에서 무조건 z축이 0이 되기 때문이다.
이렇게 view Space에서의 벡터를 얻어낼 수 있으며, Origin이 CameraPosition이 되며 direction 벡터로 이루어진 Ray를 정의할 수 있게 되었다.
class Ray : public MonoBehavior
{
public:
dx::XMFLOAT3 origin;
dx::XMFLOAT3 direction;
//@desc 마우스 좌표로부터 Ray를 생성.(카메라에서 마우스 좌표를 향하는 Ray)
Ray(int screenX, int screenY);
Ray();
static Ray RayObjectSpace(Ray& ray, class Transform& transform);
};
하지만 cameraPosition은 World 좌표계지만 direction은 여전히 view 좌표계에 있으므로 direction에 카메라 view행렬의 역행렬을 곱해주어 view에서 world로 좌표를 이동시켜 준다. 이 과정은 UnView...가 되려나? ㅋㅋㅋ
//Transforming View Space to World Space
auto inverseViewMat = dx::XMMatrixInverse(nullptr, renderer.mainCamera->viewMatrix);
dx::XMStoreFloat3(
&direction,
dx::XMVector3Normalize(
dx::XMVector3TransformNormal(dx::XMLoadFloat3(&direction), inverseViewMat)
)
);
자, 그러면 이제 World 상에서 어떤 방향을 향하는 벡터를 마우스 위치를 토대로 알아냈다. 이제 이 Ray와 가상의 Sphere간의 Intersect 테스트를 해보고자 한다.
교차 판정을 따로 글을 작성해서 아주 자세하게 써놨다. 이걸 보고 참고하면 되겠다.
'진행과정 기록 > GameEngine' 카테고리의 다른 글
20200210 Custom Vector 클래스와 Multiple Materials (0) | 2020.02.10 |
---|---|
규칙 (0) | 2020.02.09 |
20200206 어떻게 다른 타입의 클래스포인터를 한곳에 저장하고, 또 꺼낼 수 있을까? (0) | 2020.02.06 |
20200205 File Manager (0) | 2020.02.05 |
20200204 Model 임포트(Re) 하... (0) | 2020.02.04 |