강의

https://inf.run/LBTDz

 

학습 페이지

 

www.inflearn.com

 

오늘은 충돌에 대해 알아보자. 보통 충돌은 Mesh가 아니라 Collider를 통해 이루어진다. 만약 Mesh로 충돌 연산을 하게 된다면 수많은 삼각형에 대한 연산을 해야하기 때문에 연산량을 줄이기 위하여 간략한 기본 도형으로 이루어진 Collider를 통해 연산을 한다.

 

Collider 클래스는 Base Collider라는 클래스를 만들어서 상속받게하자. 지금은 Box, Sphere형태의 Collider만 만들어주도록 하자. Box형 Collider에는 OBB랑 AABB 두가지 방식이 있는데 두가지 다 만들어보자.

 

우선 BaseCollider에서 모든 Collider가 가지고 있어야하는 기능을 넣어주자. 이 기능에는 Get함수와 기본적인 Collider 종류, 레이케스팅을 통해 Collider를  관통했는지 와 다른 물체들 끼리 충돌이 일어났는 지 체크하는 기능이 필요하다.

레이케스팅은 Math라이브러리 이용하여 구현해주면 된다. 앞으로 갈 수 있다거나 땅에 붙어있는 상태를 관찰할 때는 충돌체를 사용하는 것도 방법이지만 레이케스팅을 사용해주는 게 더 좋을 수도 있다. 

BaseCollider.h

#pragma once
#include "Component.h"

enum class ColliderType
{
	Sphere,
	AABB,
	OBB,
};

class BaseCollider : public Component
{
public:
	BaseCollider(ColliderType colliderType);
	virtual ~BaseCollider();

	//레이케스팅 OUT은 명시용 기능 x 
	virtual bool Intersects(Ray& ray, OUT float& distance) = 0;
	ColliderType GetColliderType() { return _colliderType; }

protected:
	ColliderType _colliderType;
};

BaseCollider.cpp

#include "pch.h"
#include "BaseCollider.h"

BaseCollider::BaseCollider(ColliderType colliderType)
	: Component(ComponentType::Collider), _colliderType(colliderType)
{

}

BaseCollider::~BaseCollider()
{

 

기능을 완성한 다음에 ComponentType과 GameObject에서 가져오는 부분을 추가해주자.

GameObject.cpp

shared_ptr<BaseCollider> GameObject::GetCollider()
{
	shared_ptr<Component> component = GetFixedComponent(ComponentType::Collider);
	return static_pointer_cast<BaseCollider>(component);
}

이를 바탕으로Sphere Collider를 만들어주자. Sphere 모양은 BoudingSphere 기능을 사용해주자.이 BoudingSphere는

중심점 위치와 반지름으로 구성되어있고 충돌 및 레이케스팅 관련 기능들을 지원해준다. 이 BoudingSpehre의  세부옵션을 설정할 수 있게 Get함수를 만들어주자.

SphereCollider.h

#pragma once
#include "BaseCollider.h"

class SphereCollider : public BaseCollider
{
public:
	SphereCollider();
	virtual ~SphereCollider();

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

	void SetRadius(float radius) { _radius = radius; }
	BoundingSphere& GetBoundingSphere() { return _boundingSphere; }

private:
	float _radius = 1.f;
	//충돌용 구
	BoundingSphere _boundingSphere;
};

SphereCollider.cpp

#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);
}

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

 

이제 메인함수에서 Sphere Collider를 만들어서 Component로 넣어주자. 

#include "pch.h"
#include "CollisionDemo.h"
#include "RawBuffer.h"
#include "TextureBuffer.h"
#include "Material.h"
#include "SceneDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Mesh.h"
#include "Transform.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
#include "Light.h"
#include "Graphics.h"
#include "SphereCollider.h"

void CollisionDemo::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);
	}

	// 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);
	}

	// 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"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
	}
}

void CollisionDemo::Render()
{

}

그리고 테스트하기 위해 피킹 코드를 만들어주자. 피킹함수는 Scene 클래스에서 만들어주자. 

피킹함수에서는 스크린 좌표에서 클릭한 부분을 3D좌표로 바꿔주어야한다.

Scene.cpp

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 projectionMatrix = camera->GetProjectionMatrix();

	//스크린 ->뷰포트공식
	float viewX = (+2.0f * screenX / width - 1.0f) / projectionMatrix(0, 0);
	float viewY = (-2.0f * screenY / 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.0f, 0.0f, 0.0f, 1.0f);
		Vec4 rayDir = Vec4(viewX, viewY, 1.0f, 0.0f);

		// WorldSpace에서의 Ray 정의
		Vec3 worldRayOrigin = XMVector3TransformCoord(rayOrigin, viewMatrixInv);		//위치까지
		Vec3 worldRayDir = XMVector3TransformNormal(rayDir, viewMatrixInv);				//위치는 그대로 방향만
		worldRayDir.Normalize();

		// WorldSpace에서 연산
		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;

		//Picking
		auto pickObj = CUR_SCENE->Pick(mouseX, mouseY);
		if (pickObj)
		{
			CUR_SCENE->Remove(pickObj);
		}
	}
}

 

이렇게하면 피킹된 오브젝트가 없어진다.

+ Recent posts