프로그래밍 공부
작성일
2023. 10. 27. 21:45
작성자
WDmil
728x90

Direct3D상에서의 ObjectColiision을 구현해보자.

 

충돌처리는 A객체와 B객채가 존재할 때, A객체와 B객체의 vertices가 겹치는 경우가 생길 경우, 충돌했다고 판정한다.

 

각 vertice를 검사할 수 없음으로. 최외각선 또는 지름을 기준으로 측정하게 된다.

 

우선 3차원상의 충돌처리를 구현 하기전, 2차원상의 충돌처리를 확인하고 3차원을 살펴보자.


원의 충돌처리

원은 충돌처리 상 가장 연산이 조금 들어가는 방법이다.

 

빨간색 이 A원, 초록색이 B원 이라고 할때,

 

A->B의 스칼라값

>

A반지름 + B반지름

 

위 항목이 true면 충돌하지 않음, false이면 충돌한다 라고 이해하면 된다.

 

같을경우 충돌로 처리하든 아니든 은 취향이다.

 

2차원상에서의 충돌처리가 이렇다면, 3차원상에서는 어떨까?

구의 충돌처리

놀랍게도 2차원상의 원 충돌처리와 3차원상의 구 충돌처리는 같은 방식으로 돌아간다.

 

3차원상의 vector 두개의 A->B상 스칼라값을 구한뒤에

 

각 구체의 반지름을 더하고 크기를 비교하면 된다.


원통의 충돌처리

 

원통은 원, 구체 다음으로 쉬운 충돌처리 방식을 가지는데, 밑 이미지를 살펴보자.

평면상 원통은 위와같은 형태의 충돌처리 방식을 가진다.

선분 상에 각 vertice 마다 원이 한개씩 존재한다고 가정하고,

 

A선분과 B선분간의 최단거리를 구한다음, 최단거리에 해당하는 vectice를 기준으로 원을 정의한다음,

 

그 구체를 기준으로 원의 충돌처리를 수행하면 된다.


여기서 3D캡슐의 경우, 원을 구체로 바꾸면 끝난다.


위에서 보았다 싶이 가장 효율적인 충돌처리와 비효율적인 충돌처리가 나뉘는데,

 

다음과 같은 순서대로 충돌의 연산처리 효율성이 달라진다.

 

  1. 구체
  2. 원통
  3. 사각형
  4. 삼각형

순으로 충돌처리가 복잡하다.


마우스를 기준으로한 반직선의 충돌처리

 

RTS게임에 대해 들어보았을것이다.

 

대한민국을 풍미했던 게임중 하나인 스타크래프트(2D게임임으로 충돌처리상 맞지않는 예시일 수 있음)

 

그 후속작인 스타크래프트2 등

 

RTS게임들을 보면 마우스로 어떠한 Object를 클릭하였을 때, 그 Object가 클릭되고 색이 바뀌는, 또는 UI가 선택되는 것을 확인할 수 있다.

 

과연 이런 연산은 어떻게 이루어질까?

 

한번 확인해보자.


위와같은 Cam이 존재하고, Obejct를 바라보고 있을 때, 마우스는 화면상의 2D평면을 지정한다.

대략 위와같이 나타날것이다.

 

과연 이 마우스의 포인터를 어떻게 해야 3D좌표상에 투영할 수 있을까?


마우스의 반직선을 3D좌표상에 투영하기

대략 위와같이 직선을 그어야 한다.

마우스에서 진행된 선분이 Object와 만났을때 충돌이 된다면 색을 바꿔주면 된다.

 


ScreenPointToRay

카메라에서 부터 나타나는 마우스포인터 직선을 이야기한다.

 

우선 스크린 사이즈를 정의해주어야 하는데, 그 이유는, 마우스 Position은 현재 Position위치값이 화면 전체 Position에서 나타나는데,

 

뷰포트는 좌상단이 -1x-1y 우하단이 1x1y로 나타나기 때문이다.

 

그 후에 카메라의 projection행렬을 가져온다.

원근 투영 행렬에서 _11 과 _22 행렬값을 가져오는데.

 

이 값은 각각 화면의 가로세로비율과 카메라의 near와 far평면 사이 거리에 따라 x와 y축 좌표를 조정하는데 사용되는 행렬이다. 11 22 33 값이 scaling Matrix임으로, 비율위치에 따라 vertice의 스케일값을 다시 World에 돌려준다고 생각하면 된다.

 

카메라에서 바라보는 Ray를 뽑으려면 카메라 행렬의 역행렬을 곱해야함으로 보정해서 사용한다.

 

즉, Ray값은 Camera의 역행렬값에서 ViewPort Matrix값을 곱해서 Normalize해준 것이라고 볼 수 있다.

Ray Camera::ScreenPointToRay(Vector3 screenPoint)
{
	Vector3 screenSize(WIN_WIDTH, WIN_HEIGHT, 1.0f);

	Float2 point;
	point.x = (screenPoint.x / screenSize.x) * 2.0f - 1.0f;
	point.y = (screenPoint.y / screenSize.y) * 2.0f - 1.0f;

	Float4x4 temp;
	XMStoreFloat4x4(&temp, projection);

	screenPoint.x = point.x / temp._11;
	screenPoint.y = point.y / temp._22;
	screenPoint.z = 1.0f;

	screenPoint = XMVector3TransformNormal(screenPoint, world);
	// 카메라 행렬을 다시 곱해줘서 카메라 기준 반직선 구함.


	return Ray(localPosition, screenPoint.GetNormalized());

대강 Ray를 찍어주는 코드


 

대략 위와같이 직선을 그어야 한다.

자 우리는 이제 카메라의 뷰포트에서 마우스의 위치기준 으로 쏘아낸 반직선을 만들어내었다.

 

이제 이 반직선이 구체와 접촉하였는지 접촉하지 않았는지를 판별해야한다.

 

보고있는 카메라 기준으로 반직선을 날린다음 그 반직선과 충돌처리를 한다. 생각해보자.

 

우리가 중학교 때 배웠던 이차방정식을 생각해보면 된다.

지겹게 보았던 이차방정식이다.

이걸 이용해서 쉽게 풀어낼 수 있는데,

대강 위와같은 이차방정식이 존재한다고 해보자.

 

여기서 구체랑 뷰포트를 추가하게되면 이렇게 보이게 될것이다.

그렇다! 우리는 2차방정식의 수식에 반직선이 접촉했는지 아닌지를 검사하면 된다.

 

$x = \frac{-b\pm \sqrt{b^{2}-4ac}}{2a}$

 

위 수식이 이차방정식의 수식인데

 

우리는 위 수식에 값을 대입해서 결과값만 뽑아먹으면 된다.

 

우리는 x값이 도출되는지 아닌지를 검사하면 되는데,

 

충돌하는지만 검사하려면, b^2이 c보다 큰지 작은지만 검사하면된다.

bool SphereCollider::IsRayCollision(IN const Ray& ray, OUT Contact* contact)
{
	Vector3 P = ray.pos;
	Vector3 D = ray.dir;

	Vector3 C = GetGlobalPosition();
	Vector3 A = P - C;

	float b = Vector3::Dot(A, D);
	float c = Vector3::Dot(A, A) - pow(Radius(), 2);
	
	if (b * b  > c)
	{
		if (contact != nullptr)
		{
			float t = -b - sqrt(pow(b, 2) - c);

			contact->distance = t;
			contact->hitPoint = P + D * t;
		}
		return true;
	}

	return false;
}

a는 이미 normalize된 마우스의 방향벡터를 가리킴으로  a를 기입하지 않아도 된다.

b는 레이의 시작점에서 구체의 중심까지의 벡터와 Ray의 방향의 내적이다.

c는 벡터 A의 제곱에서 구체의 반지름의 제곱을 뺀값이다.

A의 제곱은 레이의 시작점에서 구체의 중심까지의 벡터를 제곱한것임으로 Dot을 한 값은 레이 시작점에서 구체 중심점까지의 거리가 나온다.

 

구체의 반지름의 제곱은 구체의 크기이다.

 

즉, c는 레이의 사적점에서 구체 중심까지의 거리와 구체의 크기 사이의 차이를 계산한다.

 

이 값이 0보다 작으면 구체를 지나치지 않고 0보다 크면 레이와 구체가 충돌하였다 라고 판단한다.

 

이때 ,t에 대입되는 값은. x임으로 x는, 레이와 구체가 닿는 지점까지의 거리를 나타낸다.

 

이차방정식에서 - 와 + 는 앞부분 뒷부분을 가리킴으로 항상 닿는 부분은 가장 처음 닿는 부분이 카메라기준 가장 가까운 곳 임으로, -로 계산한다.

 

hitPoint는, 레이의 시작점P 에서 레이의 방향으로 t만큼 이동하여 충돌지점까지 이동한 위치. 충돌지점을 계산한다.

 

 

 

 

728x90