
이번엔 충돌이다.
충돌은 Mesh끼리가 아닌 Collider를 통해서 이루어진다. 만약 Mesh로 하게된다면 연산량이 많아지기에 연산량을 줄이기 위해서 간단한 도형(Box, Sphere, 등)의 Collider를 활용해서 연산한다.
Collider클래스를 생성하기에 앞서 상위클래스로 BaseCollider클래스를 만들고 이 클래스를 BoxCollider와 SphereCollider가 상속을 받는 형식으로 구현하자.
BoxCollider : OBB와 AABB 두가지 방식이 있고, OBB가 더 상위호환이지만, 공부할겸 두개 다 만드는 방식으로 진행ColliderType과 레이케스팅의 충돌인지, 다른 물체와의 충돌인지를 체크하는 기능함수 추가Ray는 SimpleMath::Ray를 사용#pragma once
#include "Component.h"
enum class ColliderType
{
Sphere,
AABB,
OBB,
};
class BaseCollider : public Component
{
public:
BaseCollider(ColliderType colliderType);
virtual ~BaseCollider();
// Ray는 SimpleMath::Ray를 사용 (Types.h에서 using사용)
virtual bool Intersects(Ray& ray, OUT float& distance) = 0;
ColliderType GetColliderType() { return _colliderType; }
private:
ColliderType _colliderType;
};
ComponentType에 collider 추가GameObject에서 GetCollider()함수 생성#include "pch.h"
#include "BaseCollider.h"
BaseCollider::BaseCollider(ColliderType colliderType) : Component(ComponentType::Collider), _colliderType(colliderType)
{ }
BaseCollider::~BaseCollider() { }
// Component.h
enum class ComponentType : uint8
{
Transform,
MeshRenderer,
ModelRenderer,
Camera,
ModelAnimator,
Light,
Collider, // 추가
// ...
Script,
End,
};
// GameObject.h
class GameObject : public enable_shared_from_this<GameObject>
{
public:
shared_ptr<BaseCollider> GetCollider();
}
// GameObject.cpp
shared_ptr<BaseCollider> GameObject::GetCollider()
{
shared_ptr<Component> component = GetFixedComponent(ComponentType::Collider);
return static_pointer_cast<BaseCollider>(component);
}
우선은 조금 더 간단한 SphereCollider 우선 생성하자.
BaseCollider를 상속받는 형식으로 생성BoundingSphere : 원점 위치와 반지름으로 구성되어 있으며, 물체끼리의 충돌 및 레이케스팅 관련 기능이 들어가 있음#pragma once
#include "BaseCollider.h"
class SphereCollider : public BaseCollider
{
public:
SphereCollider();
virtual ~SphereCollider();
virtual void Update() override;
// OUT은 아무런 기능x, 리턴하는 용도로 사용한다는 힌트주는 정도
virtual bool Intersects(Ray& ray, OUT float& distance) override;
BoundingSphere& GetBoundingSphere() { return _boundingSphere; }
void SetRadius(float radius) { _radius = radius; }
private:
float _radius = 1.f;
BoundingSphere _boundingSphere; //충돌 영역 표시
};
#include "pch.h"
#include "SphereCollider.h"
SphereCollider::SphereCollider() : BaseCollider(ColliderType::Sphere)
{
}
SphereCollider::~SphereCollider()
{
}
void SphereCollider::Update()
{
_boundingSphere.Center = GetGameObject()->GetTransform()->GetPosition();
Vec3 scale = GetGameObject()->GetTransform()->GetScale();
_boundingSphere.Radius = _radius * max(max(scale.x, scale.y), scale.z); // x y z중에 가장 큰값으로 설정
}
bool SphereCollider::Intersects(Ray& ray, OUT float& distance)
{
// 피격이 되면 거기를 distance에 리턴해주겠다
return _boundingSphere.Intersects(ray.position, ray.direction, OUT distance);
}
SphereCollider로 생성해보자void CollisionDemo::Init()
{
// 위 코드는 생략
{
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"Sphere");
obj->GetMeshRenderer()->SetMesh(mesh);
obj->GetMeshRenderer()->SetPass(0);
}
// Collider
{
auto collider = make_shared<SphereCollider>();
collider->SetRadius(0.5f);
obj->AddComponent(collider);
}
CUR_SCENE->Add(obj);
}
void CollisionDemo::Update()
{
if (INPUT->GetButtonDown(KEY_TYPE::LBUTTON))
{
int32 mouseX = INPUT->GetMousePos().x;
int32 mouseY = INPUT->GetMousePos().y;
// Picking
}
}
#pragma once
class Scene
{
public:
shared_ptr<class GameObject> Pick(int32 screenX, int32 screenY);
};
shared_ptr<class GameObject> Scene::Pick(int32 screenX, int32 screenY)
{
// 카메라오브젝트->카메라컴포넌트
shared_ptr<Camera> camera = GetCamera()->GetCamera();
float width = GRAPHICS->GetViewport().GetWidth();
float height = GRAPHICS->GetViewport().GetHeight();
// 프로젝션Matrix
Matrix projectionMatrix = camera->GetProjectionMatrix();
// 스크린 -> 뷰포트
float viewX = (2.0f * screenX / width - 1.0f) / projectionMatrix(0, 0);
float viewY = (-2.0f * screenX / height + 1.0f) / projectionMatrix(1, 1);
Matrix viewMatrix = camera->GetViewMatrix();
Matrix viewMatrixInv = viewMatrix.Invert();
const auto& gameObjects = GetObjects();
float minDistance = FLT_MAX;
shared_ptr<GameObject> picked;
for (auto& gameObject : gameObjects)
{
if (gameObject->GetCollider() == nullptr)
continue;
// ViewSpace에서 Ray 정의, ViewSpace에서 0,0,0은 카메라 위치
Vec4 rayOrigin = Vec4(0.f, 0.f, 0.f, 1.f);
Vec4 rayDir = Vec4(viewX, viewY, 1.0f, 0.f);
// WorldSpace에서 Ray 정의
Vec3 worldRayOrigin = XMVector3TransformCoord(rayOrigin, viewMatrixInv);
Vec3 worldRayDir = XMVector3TransformNormal(rayDir, viewMatrixInv);
worldRayDir.Normalize();
Ray ray = Ray(worldRayOrigin, worldRayDir);
float distance = 0.f;
if (gameObject->GetCollider()->Intersects(ray, OUT distance) == false)
continue;
if (distance < minDistance)
{
minDistance = distance;
picked = gameObject;
}
}
return picked;
}
void CollisionDemo::Update()
{
if (INPUT->GetButtonDown(KEY_TYPE::LBUTTON))
{
int32 mouseX = INPUT->GetMousePos().x;
int32 mouseY = INPUT->GetMousePos().y;
Ray ray;
// Picking
auto pickObj = CUR_SCENE->Pick(mouseX, mouseY);
if (pickObj)
{
CUR_SCENE->Remove(pickObj);
}
}
}

AABB, OBB
AABB
- "축 정렬 경계 상자"로, 물체의 3D 공간에서 가장 작은 직육면체 형태의 경계 상자를 의미
- 각 축에 대해 평행하며, 축에 정렬된 가장 작은 직육면체로 물체를 둘러싸는 방식
- 물체의 위치나 회전과는 관계없이 항상 x, y, z축에 맞춰져 있다.
- 특징
1. 회전이 없다
- 간단한 계산 : 계산이 간단하고 빠르다. 물체의 최솟값, 최댓값만을 구하면 되기 때문
- 충돌 검사 : AABB끼리의 충돌검사 나 물체의 포함 여부를 쉽게 확인할 수 있다.
- 사용 예시 : 충돌 감지 시스템에서 빠르게 충돌을 체크하거나, 간단한 물체가 회전하지 않는 경우
OBB
- "회전된 경계 상자", 물체를 둘러싼 직육면체 상자가 회전할 수 있도록 허용한 것
- 물체의 회전에 따라 상자가 회전하는 형태, 이로 인해 물체의 모양에 맞게 더 정확하게 경계를 정의할 수 있다.
- 특징
1. 회전 가능
- 정확성 : 물체와 더 밀접한 형태를 가지기 때문에 더 정밀한 충돌 검사나 포함 여부 확인이 가능하다.
- 복잡한 계산 : AABB보다 더 복잡하고 연산이 많이 필요하다. 물체의 회전 행렬를 고려해야 하므로 그만큼 성능에 부담을 줄 수 있다.
- 사용 예시 : 복잡한 3D물체나, 물체가 회전하는 경우에 더 정밀한 충돌 감지와 경계 계산이 필요할 때 사용
#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);
}
#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& GetBoundingOrientedBox() { 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);
}
void CollisionDemo::Init()
{
//RESOURCES->Init();
_shader = make_shared<Shader>(L"23.RenderDemo.fx");
// Camera
{
auto camera = make_shared<GameObject>();
camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
camera->AddComponent(make_shared<Camera>());
camera->AddComponent(make_shared<CameraScript>());
CUR_SCENE->Add(camera);
}
// Light
{
auto light = make_shared<GameObject>();
light->AddComponent(make_shared<Light>());
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.4f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(0.1f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f);
light->GetLight()->SetLightDesc(lightDesc);
CUR_SCENE->Add(light);
}
// Mesh
// Material
{
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(_shader);
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
material->SetDiffuseMap(texture);
MaterialDesc& desc = material->GetMaterialDesc();
desc.ambient = Vec4(1.f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
RESOURCES->Add(L"Veigar", material);
}
//for (int32 i = 0; i < 10; i++)
{
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"Sphere");
// obj->GetMeshRenderer()->SetMesh(mesh);
// obj->GetMeshRenderer()->SetPass(0);
//}
{
auto mesh = RESOURCES->Get<Mesh>(L"Cube");
obj->GetMeshRenderer()->SetMesh(mesh);
obj->GetMeshRenderer()->SetPass(0);
}
// Collider
//{
// auto collider = make_shared<SphereCollider>();
// collider->SetRadius(0.5f);
// obj->AddComponent(collider);
//}
{
auto collider = make_shared<AABBBoxCollider>();
collider->GetBoundingBox().Extents = Vec3(0.5f);
obj->AddComponent(collider);
}
//{
// obj->GetOrAddTransform()->SetLocalRotation(Vec3(0, 45, 0));
//
// auto collider = make_shared<OBBBoxCollider>();
// collider->GetBoundingOrientedBox().Extents = Vec3(0.5f);
// collider->GetBoundingOrientedBox().Orientation = Quaternion::CreateFromYawPitchRoll(45, 0, 0);
// obj->AddComponent(collider);
//}
CUR_SCENE->Add(obj);
}
{
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);
}
{
obj->AddComponent(make_shared<MoveScript>());
}
CUR_SCENE->Add(obj);
}
}

class BaseCollider : public Component
{
public:
ColliderType GetColliderType() { return _colliderType; }
}
// SphereCollider.h
class SphereCollider : public BaseCollider
{
public:
virtual bool Intersects(shared_ptr<BaseCollider>& other) override;
}
// SphereCollider.cpp
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)->GetBoundingOrientedBox());
}
return false;
}
// AABBBoxCollider.h
class AABBBoxCollider : public BaseCollider
{
public:
virtual bool Intersects(shared_ptr<BaseCollider>& other) override;
}
// AABBBoxCollider.cpp
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)->GetBoundingOrientedBox());
}
return false;
}
// OBBBoxCollider.h
class OBBBoxCollider : public BaseCollider
{
public:
virtual bool Intersects(shared_ptr<BaseCollider>& other) override;
}
// OBBBoxCollider.cpp
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)->GetBoundingOrientedBox());
}
return false;
}
CheckCollision 추가Update()가 호출된 이후에 충돌체크하는게 좋기에 LateUpdate()에서 호출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))
{
// BreakPoint 걸기위한 임시
int a = 3;
}
}
}
}

Init()에서 만들어줬다면, 매번 만들어주기에는 번거롭고, 다른 물체와는 다르게 작동되기 때문에 따로 Terrain이라는 클래스를 생성하자#pragma once
#include "Component.h"
class Terrain : public Component
{
public:
Terrain();
~Terrain();
void Create(int32 sizeX, int32 sizeZ, shared_ptr<Material> material);
bool Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance);
int32 GetSizeX() { return _sizeX; }
int32 GetSizeZ() { return _sizeZ; }
private:
shared_ptr<Mesh> _mesh;
int32 _sizeX = 0;
int32 _sizeZ = 0;
};
#include "pch.h"
#include "Terrain.h"
#include "MeshRenderer.h"
Terrain::Terrain() : Component(ComponentType::Terrain){}
Terrain::~Terrain(){}
void Terrain::Create(int32 sizeX, int32 sizeZ, shared_ptr<Material> material)
{
_sizeX = sizeX;
_sizeZ = sizeZ;
// weakPointer는 변환을 해서 사용해줘야 함
auto gameObject = _gameObject.lock();
gameObject->GetOrAddTransform();
if (gameObject->GetMeshRenderer() == nullptr)
{
gameObject->AddComponent(make_shared<MeshRenderer>());
}
_mesh = make_shared<Mesh>();
_mesh->CreateGrid(sizeX, sizeZ);
gameObject->GetMeshRenderer()->SetMesh(_mesh);
gameObject->GetMeshRenderer()->SetPass(0);
gameObject->GetMeshRenderer()->SetMaterial(material);
}
bool Terrain::Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance)
{
return false;
}
Terrain클래스를 만들어줌으로써 메인에서 Terrain생성 코드가 많이 단축된다.//기본 코드
void CollisionDemo::Init()
{
// Terrain
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
obj->AddComponent(make_shared<MeshRenderer>());
{
auto mesh = make_shared<Mesh>();
mesh->CreateGrid(10, 10);
obj->GetMeshRenderer()->SetMeah(mesh);
obj->GetMeshRenderer()->SetPass(0);
}
{
obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar");
}
}
}
// Terrain클래스 생성 이후
void CollisionDemo::Init()
{
// Terrain
{
auto obj = make_shared<GameObject>();
obj->AddComponent(make_shared<Terrain>());
obj->GetTerrain()->Create(10, 10, RESOURCES->Get<Material>(L"Veigar"));
CUR_SCENE->Add(obj);
}
}
Terrain을 생성할때 코드를 보면 하나의 사각형을 두개의 삼각형으로 만들어서 크기에 맞게 생성해주는 방식이었다.bool Terrain::Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance)
{
Matrix W = GetTransform()->GetWorldMatrix();
Matrix V = Camera::S_MatView;
Matrix P = Camera::S_MatProjection;
Viewport& vp = GRAPHICS->GetViewport();
Vec3 n = vp.Unproject(Vec3(screenX, screenY, 0), W, V, P);
Vec3 f = vp.Unproject(Vec3(screenX, screenY, 1), W, V, P);
Vec3 start = n;
Vec3 direction = f - n;
direction.Normalize();
Ray ray = Ray(start, direction);
const auto& vertices = _mesh->GetGeometry()->GetVertices();
for (int32 z = 0; z < _sizeZ; z++)
{
for (int32 x = 0; x < _sizeX; x++)
{
uint32 index[4];
index[0] = (_sizeX + 1) * z + x;
index[1] = (_sizeX + 1) * z + (x + 1);
index[2] = (_sizeX + 1) * (z + 1) + x;
index[3] = (_sizeX + 1) * (z + 1) + (x + 1);
Vec3 p[4];
for (int32 i = 0; i < 4; i++)
{
p[i] = vertices[index[i]].position;
}
// [2]
// | \
// [0] - [1]
if (ray.Intersects(p[0], p[1], p[2], OUT distance))
{
pickPos = ray.position + ray.direction * distance;
return true;
}
// [2] - [3]
// \ |
// [1]
if (ray.Intersects(p[3], p[1], p[2], OUT distance))
{
pickPos = ray.position + ray.direction * distance;
return true;
}
}
}
return false;
}
Scene의 Pick()에서 Terrain을 가져와서 Pick()함수를 호출해주면 된다.shared_ptr<class GameObject> Scene::Pick(int32 screenX, int32 screenY)
{
// 위 코드는 동일해서 추가x
// Terrain
for (auto& gameObject :_objects)
{
if (gameObject->GetTerrain() == nullptr)
continue;
Vec3 pickPos;
float distance = 0.f;
if (gameObject->GetTerrain()->Pick(screenX, screenY, OUT pickPos, OUT distance) == false)
continue;
if (distance < minDistance)
{
minDistance = distance;
picked = gameObject;
}
}
return picked;
}
