Terrain Picking

Jaemyeong Lee·2025년 4월 2일

게임 서버1

목록 보기
214/220

🌍 1. Terrain은 어떻게 만들어야 할까?

🎯 목표

  • 마우스로 Terrain을 클릭했을 때 정확히 클릭한 위치를 알아내기
  • 향후 해당 위치로 캐릭터 이동, 지형 편집 등을 구현하기 위한 기반 만들기

❓ 일반 Mesh처럼 하면 안 되나?

그냥 하나의 커다란 Collider를 Terrain에 붙이면 안 될까?

불가능합니다.
왜냐하면 Terrain은 단순 평면이 아니라 높낮이(산, 언덕 등)를 표현할 수 있어야 하기 때문에 단일 Collider로는 세밀한 충돌 처리가 어렵습니다.

그래서 우리는 Terrain을 삼각형 단위로 나눠서 충돌을 체크해야 해요!


🏗️ 2. Terrain 클래스 만들기

🔨 Terrain 클래스란?

  • Component를 상속받는 Terrain 전용 컴포넌트
  • Grid 형태의 Mesh를 생성하고, Material을 적용
  • 클릭 좌표로부터 피킹 연산 수행 가능

📦 기본 구조

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

private:
	shared_ptr<Mesh> _mesh;
	int32 _sizeX = 0;
	int32 _sizeZ = 0;
};
  • Create(): Grid Mesh 생성
  • Pick(): 마우스 좌표 → Ray → 삼각형 교차 검사

📌 GameObject 연동

GameObject.h에 다음을 추가해야 합니다:

shared_ptr<Terrain> GetTerrain();

그리고 구현도:

shared_ptr<Terrain> GameObject::GetTerrain() {
	auto component = GetFixedComponent(ComponentType::Terrain);
	return static_pointer_cast<Terrain>(component);
}

🧱 3. 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);
  • 10x10 크기의 Grid 생성
  • Material은 "Veigar" 텍스처 적용

이걸 실행하면 큼직한 땅이 깔리게 됩니다.


👆 4. Terrain 클릭 기능 구현하기 (Ray Picking)

이제 마우스로 Terrain을 클릭해서, 그 위치의 실제 좌표를 얻는 기능을 만들어보자!

🎯 핵심 로직: Terrain::Pick()

  1. 화면 좌표(screenX, screenY)를 3D 공간상의 Ray로 변환
  2. Terrain의 각 사각형을 구성하는 2개의 삼각형에 대해 교차 검사
  3. 교차 지점의 좌표를 pickPos에 저장

📌 코드 요약

bool Terrain::Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance) {
	// 1. Unproject → Ray 생성
	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); // near
	Vec3 f = vp.Unproject(Vec3(screenX, screenY, 1), W, V, P); // far

	Vec3 direction = f - n; direction.Normalize();
	Ray ray(n, direction);

	// 2. 삼각형마다 교차 검사
	auto& vertices = _mesh->GetGeometry()->GetVertices();

	for (int z = 0; z < _sizeZ; z++) {
		for (int x = 0; x < _sizeX; x++) {
			// 정점 인덱스
			uint32 i0 = (_sizeX + 1) * z + x;
			uint32 i1 = i0 + 1;
			uint32 i2 = i0 + (_sizeX + 1);
			uint32 i3 = i2 + 1;

			Vec3 p0 = vertices[i0].position;
			Vec3 p1 = vertices[i1].position;
			Vec3 p2 = vertices[i2].position;
			Vec3 p3 = vertices[i3].position;

			// 삼각형 1
			if (ray.Intersects(p0, p1, p2, OUT distance)) {
				pickPos = ray.position + ray.direction * distance;
				return true;
			}
			// 삼각형 2
			if (ray.Intersects(p3, p1, p2, OUT distance)) {
				pickPos = ray.position + ray.direction * distance;
				return true;
			}
		}
	}

	return false;
}

🔗 5. Scene의 Pick 함수에 Terrain Pick 통합

for (auto& gameObject : gameObjects) {
	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;
	}
}

기존 오브젝트 Pick 로직 뒤에 추가해주면 됩니다.


🔬 테스트 & 결과

  • 실행 후 Terrain 클릭하면 선택됨
  • pickPos 위치를 찍으면 정확하게 클릭 지점을 알 수 있음
  • 이를 기반으로 유닛 이동, 지형 편집 등도 가능해짐

⚠️ 부하와 최적화

❗ 삼각형 단위 연산의 문제점

  • 삼각형 수가 많아지면 → 피킹 비용도 급격히 증가
  • Mesh 기반 Ray-Picking은 연산량이 많음

💡 해결 방안

  • 전용 충돌용 Mesh를 별도로 구성 (WOW 게임 리소스처럼)
  • QuadTree, Octree 기반의 공간 분할 구조 적용
  • 레이어 시스템으로 피킹 대상 필터링

profile
李家네_공부방

0개의 댓글