오늘은 Picking에 대한 내용을 복습하고 예제코드를 분석해보자.

우선 예제코드를 실행해보면 

자동차 오브젝트가 있다. 이 자동차 오브젝트를 우클릭해보면 클릭하는 면에 클릭이 적용되고 있는 모습을 볼 수 있다.

 

피킹은 마우스 커서로 우리가 찍은 좌표를 통해 물체를 파악하는 것이다. 이것이 중요한 이유는 3D 게임에서 클릭을 한다고 했을 때 2D화면 좌표계에서 선택한 좌표를 3D로 변환줘야한다. 이를 통해 어떤 것을 클릭해주는 지 알 수 있기 때문이다.

 

피킹 연산은 우리가 물체를 그려주기 위해 했던 로컬,월드,뷰,프로젝션,NDC,Screen까지의 연산의 반대로 해주면 된다.

이때 매쉬랑 충돌 판정을 한다는 것은 매쉬의 모든 삼각형을 순회하면서 레이케스팅을 해주는 것이다.

만약 예제코드와 같이 자동차안에서도 삼각형으로 분할되어 있는 세밀한 부분에 티킹 연산을 해준다고 하면 기존 오브젝트가 있는 로컬좌표로 클릭한 스크린 좌표를 변환해서 연산해주는 것이  더 좋을 것이다.

피킹연산은 CPU에서 이루어 진다. 월드 변환은 GPU에서 이루어 지고 이에 관한 정보도 GPU에서 가지고 있다. 그렇기 때문에 CPU에서는 피킹 연산에서  각 월드 좌표를 가지고 있지 않고 연산량이 적은 좌표계로 변환해서 연산해주면 된다. 

즉 레이케스트의 광선을 오브젝트의 로컬로 변환해서 연산해주면 되는 것이다.

 

그리고 연산에서 먼저 세밀한 부분에 피킹 연산을 해주기 보다는 충돌 영역을 지정해두고 여기에 충돌하면 2단계로 세밀한 부분에 피킹 연산을 해주는 것으로 최적화 해줄 수 있다.

 

코드를 보면 광선을 로컬좌표로 변환하고 있고 이를 통해 레이케스팅 연산을 해주고 있다. 이때 위에서 이야기한대로 먼저 대략적인 충돌영역으로 충돌처리를 해주고 만약 충돌했다면 2단계로 모든 삼각형에 순회를 해주며 충돌 연산을 해주고 있다. 그리고 피킹을 했을 때 가장 가까이 있는 오브젝트에 피킹을 해주어야하기 때문에 이러한 값을 추가해서 검사해주는 것을 볼 수 있다.

void PickingDemo::Pick(int32 sx, int32 sy)
{
	XMMATRIX P = _camera.Proj();

	Matrix m = P;
	// Compute picking ray in view space.
	float vx = (+2.0f * sx / _clientWidth - 1.0f) / m(0, 0); // P(0, 0);
	float vy = (-2.0f * sy / _clientHeight + 1.0f) / m(1, 1); // P(1, 1);

	// 뷰좌표계에서 광선정의
	XMVECTOR rayOrigin = ::XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
	XMVECTOR rayDir = ::XMVectorSet(vx, vy, 1.0f, 0.0f);

	// 광선을 로컬좌표로 변환
	XMMATRIX V = _camera.View();
	XMVECTOR D1 = ::XMMatrixDeterminant(V);
	XMMATRIX invView = ::XMMatrixInverse(&D1, V);

	XMMATRIX W = ::XMLoadFloat4x4(&_meshWorld);
	XMVECTOR D2 = ::XMMatrixDeterminant(W);
	XMMATRIX invWorld = ::XMMatrixInverse(&D2, W);

	XMMATRIX toLocal = ::XMMatrixMultiply(invView, invWorld);

	rayOrigin = ::XMVector3TransformCoord(rayOrigin, toLocal);
	rayDir = ::XMVector3TransformNormal(rayDir, toLocal);

	// Make the ray direction unit length for the intersection tests.
	rayDir = ::XMVector3Normalize(rayDir);

	// If we hit the bounding box of the Mesh, then we might have picked a Mesh triangle,
	// so do the ray/triangle tests.
	//
	// If we did not hit the bounding box, then it is impossible that we hit 
	// the Mesh, so do not waste effort doing ray/triangle tests.

	// Assume we have not picked anything yet, so init to -1.
	_pickedTriangle = -1;
	float tmin = 0.0f;

	if (_meshBox.Intersects(rayOrigin, rayDir, tmin))
	{
		// Find the nearest ray/triangle intersection.
		tmin = MathHelper::Infinity;
		for (UINT i = 0; i < _meshIndices.size() / 3; ++i)
		{
			// Indices for this triangle.
			UINT i0 = _meshIndices[i * 3 + 0];
			UINT i1 = _meshIndices[i * 3 + 1];
			UINT i2 = _meshIndices[i * 3 + 2];

			// Vertices for this triangle.
			XMVECTOR v0 = ::XMLoadFloat3(&_meshVertices[i0].pos);
			XMVECTOR v1 = ::XMLoadFloat3(&_meshVertices[i1].pos);
			XMVECTOR v2 = ::XMLoadFloat3(&_meshVertices[i2].pos);

			// We have to iterate over all the triangles in order to find the nearest intersection.
			float t = 0.0f;

			if (TriangleTests::Intersects(rayOrigin, rayDir, v0, v1, v2, t))
			{
				if (t < tmin)
				{
					// 가장 가까운 삼각형체크
					tmin = t;
					_pickedTriangle = i;
				}
			}
		}
	}
}

+ Recent posts