DirectX

80_Picking

devRiripong 2024. 3. 28.
반응형

나머지 부분들을 넣어보고 물체끼리 충돌했을 때 판정해서 일어나는 것까지 넣어본다.

 

다음 순서 obb, aabb라는 게 생긴다.

 

기본적으로 둘 다 박스 collider다.

 

aabb는 world에서 제공하는 축이랑 박스의 축이 일치할 때다. 연산하기가 쉽다.

축이 틀어져서 비스듬이 있는걸 obb라고 한다.

 

성능을 따질 것인지 미세한 판정을 따질 것인지 정해야 한다.

성능은 포기하더라도 리얼리티를 살리려면 obb를 사용하고,

그게 아니면 aabb를 사용한다.

 

1. BaseCollider를 상속한 AABBBoxCollider클래스와 OBBBoxCollider클래스 만들기

만드는 거에서는 obb를 일반적이라 생각하면 된다.

Engine/04. Component/Collision 필터에 BaseCollider를 상속한 AABBBoxCollider 클래스를 만든다.

BaseCollider를 상속한 OBBBoxCollider도 만든다.

나중에 공부가 끝나면 OBB로 통합을 하면 된다.

 

2. AABBBoxCollider 클래스 채우기

AABBBoxCollider부터 채워준다.

	BoundingBox _boundingBox; 

를 선언해주는데 F12를 눌러 BoundingBox를 살펴보면

// Axis-aligned bounding box라고 축이랑 정렬된 박스라는 뜻이다.

기본 형태가 이렇게 되어 있다는 걸 볼 수 있고,

사용하는 방법 SphereCollider랑 다르지 않다. 온갖 함수들이 종류별로 준비되어 있다.

#pragma once
#include "BaseCollider.h"
class AABBBoxCollider :  public BaseCollider
{
public:
	AABBBoxCollider(); 
	virtual ~AABBBoxCollider(); 

	virtual void Update() override; 
	virtual bool Intersects(Ray& ray, OUT float& distance) override;

	BoundingBox& GetBoundingBox() { return _boundingBox; } 

private: 
	BoundingBox _boundingBox;
};
#include "pch.h"
#include "AABBBoxCollider.h"

AABBBoxCollider::AABBBoxCollider() : BaseCollider(ColliderType::AABB)
{
}

AABBBoxCollider::~AABBBoxCollider()
{
}

void AABBBoxCollider::Update()
{
}

bool AABBBoxCollider::Intersects(Ray& ray, OUT float& distance)
{
	return _boundingBox.Intersects(ray.position, ray.direction, OUT distance); 
}

 

3. OBBBoxCollider클래스 채우기

OBB도 똑같다.

#pragma once
#include "BaseCollider.h"
class OBBBoxCollider :  public BaseCollider
{
public: 
	OBBBoxCollider(); 
	virtual ~OBBBoxCollider(); 

	virtual void Update() override; 
	virtual bool Intersects(Ray& ray, OUT float& distance) override;

	BoundingOrientedBox& GetBoundingBox() { return _boundingBox; }

private: 
	BoundingOrientedBox _boundingBox; 
};
#include "pch.h"
#include "OBBBoxCollider.h"

OBBBoxCollider::OBBBoxCollider() : BaseCollider(ColliderType::OBB)
{
}

OBBBoxCollider::~OBBBoxCollider()
{
}

void OBBBoxCollider::Update()
{
}

bool OBBBoxCollider::Intersects(Ray& ray, OUT float& distance)
{
	return _boundingBox.Intersects(ray.position, ray.direction, OUT distance);
}

Engine을 빌드한다.

 

4. CollisionDemo 클래스에서 collider 컴포넌트를 가진 obj를 2개를 만들어 충돌되게 하기

Raycast가 잘 된다고 하면 물체들끼리 서로 충돌 판정이 되는지 확인하는 식으로 작업을 해 볼 것이다.

 

CollisionDemo 클래스에서 진행을 해본다.

CollisionDemo::Init의 //Mesh 부분의 코드를 복붙 해서 진행한다.

첫 번째 Mesh의 경우는

			auto mesh = RESOURCES->Get<Mesh>(L"Cube");

Sphere를 Cube로 바꾼다.

Cube를 기반으로 똑같이 맞춰주면 된다. AABB를 테스트하고 싶다면

그대로 가만히 있는 상태에서 맞춰주고 정상적으로 피격 판정이 되는지 확인해 보면 된다.

 

CollisionDemo.cpp에

#include "AABBBoxCollider.h"
#include "OBBBoxCollider.h"

를 추가하고,

먼저 가만히 있는 상태라 가정하고 만들어 준다고 하면,

		{
			auto collider = make_shared<AABBBoxCollider>();
			collider->GetBoundingBox().Extents = Vec3(0.5f); 
			obj->AddComponent(collider);
		}

이렇게 해준다.

지금은 두 충돌체가 완전히 같은 위치에 포개져 있기 때문에

		obj->GetOrAddTransform()->SetLocalPosition(Vec3(3.f, 0.f, 0.f));

이렇게 두 번째 Mesh인 Sphere의 위치를 바꿔서

왼쪽에는 Cube, 오른쪽에는 Sphere로 일단 내버려두도록 한다.

 

1) AABBBoxCollider 컴포넌트를 사용할 경우 

	// Mesh
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Cube");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}
		{
			auto collider = make_shared<AABBBoxCollider>();
			collider->GetBoundingBox().Extents = Vec3(0.5f);
			obj->AddComponent(collider);
		}

		CUR_SCENE->Add(obj);
	}

	// Mesh
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(3.f, 0.f, 0.f));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}
		{
			auto collider = make_shared<SphereCollider>();
			collider->SetRadius(0.5f);
			obj->AddComponent(collider);
		}

		CUR_SCENE->Add(obj);
	}

 

Cube 메쉬 obj는 AABBBoxCollider를 사용하여 collider 컴포넌트를 만들어 obj에 붙였다.

두 번째 Sphere mesh는 그대로 두었다. 

 

움직이면서 Targeting 하는 걸 만들기 위해서

CollisionDemo.h에

#include "MonoBehaviour.h"
class MoveScript : public MonoBehaviour
{
public:
	virtual void Update() override; 
};

이 코드를 추가한다.

Update를 해서 움직이면서 움직이는 물체를 피격하는 걸 한번 넣어 보려고 한다.

 

CollisionDemo.cpp에 

void MoveScript::Update()
{
	auto pos = GetTransform()->GetPosition(); 
	pos.x -= DT * 1.0f; 
	GetTransform()->SetPosition(pos); 
}

이렇게 움직인다고 정하고,

 

CollisionDemo::Init의 두 번째 Mesh인 Sphere에

		{
			obj->AddComponent(make_shared<MoveScript>());
		}

이렇게 MoveScript 컴포넌트를 추가한다.

 

CollisionDemo::Init에서

// Light 부분을 삭제한다.

 

실행하면

이렇게 Sphere가 왼쪽으로 이동하고 각각을 클릭하면 각각 사라진다.

 

2) OBBBoxCollider 컴포넌트를 사용할 경우 

	// Mesh
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Cube");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}
		//{
		//	auto collider = make_shared<AABBBoxCollider>();
		//	collider->GetBoundingBox().Extents = Vec3(0.5f);
		//	obj->AddComponent(collider);
		//}
		{
		obj->GetOrAddTransform()->SetRotation(Vec3(0, 45, 0));

		auto collider = make_shared<OBBBoxCollider>();
		collider->GetBoundingBox().Extents = Vec3(0.5f);
		collider->GetBoundingBox().Orientation = Quaternion::CreateFromYawPitchRoll(45, 0, 0);
		obj->AddComponent(collider);
		}

		CUR_SCENE->Add(obj);
	}

Cube 메쉬를 가진 첫번째 obj에 

OBBBoxCollider를 사용할 경우에는

Rotation을 세팅 했다면 이것에 따라서 Orientation을 맞춰줘야 한다.

BoundingBox는 결국 회전이 들어가는 거다 보니까 어떤 식으로 건 회전을 표현을 해줄 수 있어야 한다.

 struct BoundingOrientedBox
 {
     static constexpr size_t CORNER_COUNT = 8;

     XMFLOAT3 Center;            // Center of the box.
     XMFLOAT3 Extents;           // Distance from the center to each side.
     XMFLOAT4 Orientation;       // Unit quaternion representing rotation (box -> world).

짐벌락 현상을 막기위해 FLOAT4 짜리 쿼터니언으로 들고 있는 경우도 있다.

여기선 Quaternion으로 Quaternion::CreateFromYawPitchRoll을 이용해 맞춰줬다.

실행하

이렇게 Cube가 회전된 상태로 나온다. 

 

다시 AABBBoxCollider방식으로 되돌린다.

 

5. 부딪혔는지 탐지하고 판정하는 Intersects 함수를 3가지 Collider 클래스에 각각 정의하기

1) BaseCollider에 Intersects를 순수가상함수로 선언하기

이제 물체들끼리 부딪히는거를 탐지하는 게 유용할 수 있다.

그 부분을 추가해 본다.

BaseCollider.h로 가서

	virtual bool Intersects(shared_ptr<BaseCollider>& other) = 0; 

다른 애랑 충돌했을 때를 이런식으로 파줄 예정이다.

 

상대방이 무엇이냐에 따라 그리고 내가 무엇이냐에 따라가지고 요 부분은 완전히 달라져야 한다.

 

2) SphereCollider에서 Intersects 함수 정의하기

SphereCollider부터 돌아가서 파줄 건데

마찬가지로

	virtual bool Intersects(shared_ptr<BaseCollider>& other) override;

요런 걸 추가해 주면 된다.

 

SphereCollider.cpp에

#include "AABBBoxCollider.h"
#include "OBBBoxCollider.h"

를 추가하고

bool SphereCollider::Intersects(shared_ptr<BaseCollider>& other)
{
	ColliderType type = other->GetColliderType(); 

	switch (type)
	{
	case ColliderType::Sphere:
		return _boundingSphere.Intersects(dynamic_pointer_cast<SphereCollider>(other)->GetBoundingSphere()); 
	case ColliderType::AABB:
		return _boundingSphere.Intersects(dynamic_pointer_cast<AABBBoxCollider>(other)->GetBoundingBox());
	case ColliderType::OBB:
		return _boundingSphere.Intersects(dynamic_pointer_cast<OBBBoxCollider>(other)->GetBoundingBox());
	}

	return false; 
}

이런 코드가 3번 들어가야 한다.

얘뿐만 아니라 나머지 애들도 비슷한 코드를 넣어줘야 될 테니까

OBBBoxCollider와 AABBBoxCollider에서도 동일한 느낌의 코드를 넣어주면 된다.

 

3) AABBBoxCollider에서 Intersects 함수 정의하기

AABBBoxCollider.h부터

	virtual bool Intersects(shared_ptr<BaseCollider>& other) override; 

 

AABBBoxCollider.cpp에

#include "SphereCollider.h"
#include "OBBBoxCollider.h"

를 추가하고

bool AABBBoxCollider::Intersects(shared_ptr<BaseCollider>& other)
{
	ColliderType type = other->GetColliderType();

	switch (type)
	{
	case ColliderType::Sphere:
		return _boundingBox.Intersects(dynamic_pointer_cast<SphereCollider>(other)->GetBoundingSphere());
	case ColliderType::AABB:
		return _boundingBox.Intersects(dynamic_pointer_cast<AABBBoxCollider>(other)->GetBoundingBox());
	case ColliderType::OBB:
		return _boundingBox.Intersects(dynamic_pointer_cast<OBBBoxCollider>(other)->GetBoundingBox());
	}

	return false;
}

 

4) OBBBoxCollider에서 Intersects 함수 정의하기

마지막으로

OBBBoxCollider.h에서도 동일한 코드를 넣어주면 된다.

	virtual bool Intersects(shared_ptr<BaseCollider>& other) override; 

OBBBoxCollder.cpp에

#include "SphereCollider.h"
#include "AABBBoxCollider.h"

 

bool OBBBoxCollider::Intersects(shared_ptr<BaseCollider>& other)
{
	ColliderType type = other->GetColliderType();

	switch (type)
	{
	case ColliderType::Sphere:
		return _boundingBox.Intersects(dynamic_pointer_cast<SphereCollider>(other)->GetBoundingSphere());
	case ColliderType::AABB:
		return _boundingBox.Intersects(dynamic_pointer_cast<AABBBoxCollider>(other)->GetBoundingBox());
	case ColliderType::OBB:
		return _boundingBox.Intersects(dynamic_pointer_cast<OBBBoxCollider>(other)->GetBoundingBox());
	}

	return false;
}

 

라이브러리를 안 쓰고 다 만들면 오류가 났을 때 수학식에서 잘 못 된 건지 연동할 때 옵션이 잘못된 건지 찾기가 어려울 수 있다. 지금은 라이브러리를 쓰기 때문에 간단하게 만들 수 있는 것이다. 

 

Engine을 빌드한다.

 

6. Scene에 있는 모든 Collider컴포넌트를 가진 obj를 순회하며 서로 충돌하는 Collider가 있는지 체크하는 CheckCollision 함수 정의하기

이제 이걸 어디에 넣어줘야 할까

최적화를 안 하고 넣는다면

매 프레임마다 콜리전 테스트를 한다고 가정을 하고,

 

Scene.h에

	void CheckCollision();

를 선언하고,

LastUpdate에서 CheckCollision을 한다고 가정을 하고, 거기서 물체를 둘둘 찾아서 피격 판정을 해주거나 넣어주면 된다.

가장 단순한 방법 중 하나는

void Scene::CheckCollision()
{
	vector<shared_ptr<BaseCollider>> colliders; 

	for (shared_ptr<GameObject> object : _objects)
	{
		if (object->GetCollider() == nullptr)
			continue; 

		colliders.push_back(object->GetCollider()); 
	}

	// BruteForce
	for (int32 i = 0; i < colliders.size(); i++)
	{
		for (int32 j = i + 1; j < colliders.size(); j++)
		{
			shared_ptr<BaseCollider>& other = colliders[j]; 
			if (colliders[i]->Intersects(other))
			{
				int a = 3; 
			}
		}
	}
}

int a = 3; 여기에 여러 OnCollisionEnter 같은 시리즈들을 끼워 넣으면 된다.

Engine을 빌드하고

int a = 3; 에 중단점을 찍고 실행을 하면 두 개의 물체가 부딪히는 순간 중단점이 잡히게 된다.

 

 

여러 가지 물체들끼리 서로 충돌과 관련된 부분이 적용이 된다라고 볼 수 있고, 이걸 이용해서 간단한 충돌 처리 같은 거, 게임마다 다르지만, 싱글 액션 게임 같은 경우는 뼈 쪽에다 소켓을 붙여서 추적하게 만들어서 칼 하나를 붙여서 칼을 휘두르면 피격 판정을 해서 몬스터를 죽인다거나 이렇게 만들 수 있을 것이고, 모든 관련된 부분들이 여기서 유래되는 거라고 볼 수 있다.

 

논타게팅 MMORPG의 경우 충돌을 클라에서 판단했을까 서버에서 했을까

화살이 어딘가를 때리면 대미지를 줘야 하는데 어디서 했을까?

클라에서 한 거를 서버에서는 아주 러프하게만 체크하는 방식이 많았다.

클라에서 투사체 같은 거 날려서 클라에서 충돌 체크를 하면 서버에서는 검증만 살짝 하는 것이다.

거리가 말이 되는지 등 체크해서 인정하면 이펙트가 터지면서 데미지를 입힌다거나 그렇게 만들어 주는 것이다.

 

MMO에서 무협지처럼 날아다니고 스킬 쓰는 게 힘들고, 해킹도 꼼꼼하게 막아야 한다.

클라에서 충돌 판정과 서버에서 인증이 다를 수 있기 때문에 이런 건 잘 고민할 필요가 있다.

 

만약 Frustum이 필요하면 라이브러리에 추가하면 된다.

라이브러리를 활용하면 필요한 기능을 쉽게 만들 수 있다.

 

 

반응형

'DirectX' 카테고리의 다른 글

82_기본 도형 (수학)  (0) 2024.03.30
81_Terrain Picking  (0) 2024.03.29
79_Sphere Collider  (0) 2024.03.28
78_Viewport  (0) 2024.03.27
77_RenderManager 구조정리  (0) 2024.03.24

댓글