[DirectX11] 2D화면 클릭해서 3D물체 선택하기(picking ray)
picking ray
마우스로 화면을 클릭하여 3D 물체를 선택하게 하려면 어떻게 해야 할까?
2D의 스크린 좌표에 그 지점을 투영한 3D 좌표를 구해야 한다.
view space는 3D 공간이지만, 2D 좌표로부터 3D로 변환하기 때문에 하나의 좌표값으로 대응되지 않는다.
따라서 눈, 즉 의 원점에서 클릭한 점을 지나는 방향 벡터로 광선(ray)을 쏜다.
그리고 광선이 여러 물체를 지날 수 있지만, 카메라에 가장 가까운 것을 선택하도록 한다.

screen space에서 클릭한 점을 가지고 view space에서 picking ray를 구현해야 하므로 좌표 변환이 필요하다.
화면을 그리기 위한 좌표 변환에서 반대로 진행하면 된다.
Screen spcae → View space
screen space에서 클릭한 점P를 View space의 좌표값으로 표현하자.
먼저 screen space 상의 점 P를 Normaliszed Device Coordinates(NDC) 로 표현한다.
아래의 viewport 행렬은 NDC에서 screen space로 변환하는 행렬이다.

변수들은 D3D11_VIEWPORT에 포함되어 있다.
게임에서, viewport는 백버퍼이고 depth는 0~1사이이므로, 백버퍼 크기가 h*w일 때 아래와 같이 간단히 쓸 수 있다.


라 하면, 이에 대응하는 screen 좌표는 다음과 같이 구해진다.


NDC좌표를 스크린 좌표값을 가지고 표현하면 아래와 같다.

이제 변환된 NDC 에서의 점을 view space의 점으로 표현한다.
view space에서 투영된 점은 x좌표에 종횡비 r을 나누어서 NCD 로 변환된다.
따라서 NDC의 점을 view space의 점으로 변환하기 위해 x좌표에 r을 곱하면 된다.
즉, 아래는 화면에서 클릭된 점 P의 위치를 view space에서의 좌표값으로 나타낸 것이다.

마지막으로 이 점을 지나는 ray를 구한다.
즉, view space에서, 원점에서 출발하고, screen space로부터 변환된 좌표P를 지나는 ray를 구한다.

위의 그림에서


이고, (α는 view angle)
이므로, 대입하고 정리하면

이때 rtan값과 tan값은 각각 Projection 행렬의 0행0열, 1행,1열 값의 역수이므로, 이를 이용해서 계산할 수 있다.
결과적으로 picking ray를 구하는 코드는 아래와 같다. DirectX::SimpleMath 라이브러리를 이용하여 간단하게 작성하였다.
float viewX = (+2.0f * screenX / width - 1.0f)/ProjectionMatrix(0,0);
float viewY = (-2.0f * screenY / height + 1.0f)/ProjectionMatrix(1,1);
Vector4 rayOrigin = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
Vector4 rayDir = Vector4(viewX, viewY, 1.0f, 1.0f);
Ray ray = Ray(rayOrigin, rayDir);
View space → World/Local space
위에서 구한 picking ray는 view space에서만 사용 가능하다.
world space에서 사용하기 위해서는 뷰 변환 행렬의 역행렬을 곱하면 된다.
마찬가지로 local space에서 사용하기 위해서는 world space의 좌표에 월드 변환 행렬의 역행렬을 곱하면 된다.
Vector4 toLocal = XMMatrixMultiply(invView, invWorld);
Vector4 rayOrigin = XMVector3TransformCoord(rayOrigin, toLocal);
Vector4 rayDir = XMVector3TransformNormal(rayDir, toLoacl);
rayDir = XMVector3Normalize(rayDir);
오브젝트의 로컬 좌표를 월드 좌표로 변환하여 월드 좌표에서 ray의 intersection을 테스트하면,
오브젝트의 수많은 정점을 대상으로 변환해야 하므로 비용이 많이 든다.
ray만 로컬 공간으로 변환하는 것이 효율적이다.
참고자료
Introduction to 3D Game Programming with DirectX11 - Frank D. Luna
Rookiss님 인프런 강의