DirectX

85_Raycasting(수학)

devRiripong 2024. 3. 31. 14:53
반응형

 

1. Sphere, Box, Plane에 대한 Raycast 코드 추가하기

레이저 쏴서 레이저 피격을 맞추는 거에 대해 다뤄 본다.

 

Raycast Sphere

Raycast Axis Aligned Bounding Box

Raycast Oriented Bounding Box(생략)

Raycast plane

핵심 4개만 추렸다.

이걸 이용해서 여러 개를 만들어서 최종적으로 거리 계산을 통해서 구하는 식으로 해 볼 것이다.

 

 

	//-------------------
	// Raycast
	//-------------------
	static bool Raycast(const Sphere3D& sphere, const Ray3D& ray, OUT float& distance);
	static bool Raycast(const AABB3D& aabb, const Ray3D& ray, OUT float& distance);
	static bool Raycast(const Plane3D& plane, const Ray3D& ray, OUT float& distance);

 

bool MathUtils::Raycast(const Sphere3D& sphere, const Ray3D& ray, OUT float& distance)
{
	Vec3 e = sphere.position - ray.origin;

	float rSq = sphere.radius * sphere.radius;
	float eSq = e.LengthSquared();

	float a = e.Dot(ray.direction);

	float bSq = eSq - (a * a);
	float f = sqrt(rSq - bSq);

	// No collision has happened
	if (rSq - (eSq - (a * a)) < 0.0f)
		return false;

	// Ray starts inside the sphere
	if (eSq < rSq)
	{
		distance = a + f;
		return true;
	}

	// else Normal intersection
	distance = a - f;
	return true;
}

// Cyrus-Beck clipping
// AABB를 구성하는 6개의 평면에 대해 클리핑 진행
// Point3D point = ray.origin + ray.direction * t;
bool MathUtils::Raycast(const AABB3D& aabb, const Ray3D& ray, OUT float& distance)
{
	Vec3 min = AABB3D::GetMin(aabb);
	Vec3 max = AABB3D::GetMax(aabb);

	// TODO : 0 나누기 방지 위해 +@ 더해줌
	float t1 = (min.x - ray.origin.x) / ray.direction.x;
	float t2 = (max.x - ray.origin.x) / ray.direction.x;

	float t3 = (min.y - ray.origin.y) / ray.direction.y;
	float t4 = (max.y - ray.origin.y) / ray.direction.y;

	float t5 = (min.z - ray.origin.z) / ray.direction.z;
	float t6 = (max.z - ray.origin.z) / ray.direction.z;

	// Largest min value
	float tmin = fmaxf(
		fmaxf(
			fminf(t1, t2),
			fminf(t3, t4)
		),
		fminf(t5, t6)
	);

	// Smallest max value
	float tmax = fminf(
		fminf(
			fmaxf(t1, t2),
			fmaxf(t3, t4)
		),
		fmaxf(t5, t6)
	);

	if (tmax < 0)
		return false;

	if (tmin > tmax)
		return false;

	if (tmin < 0.0f)
	{
		distance = tmax;
		return true;
	}

	distance = tmin;
	return true;
}

bool MathUtils::Raycast(const Plane3D& plane, const Ray3D& ray, OUT float& distance)
{
	float nd = ray.direction.Dot(plane.normal);
	float pn = ray.origin.Dot(plane.normal);

	if (nd >= 0.0f)
		return false;

	float t = (plane.distance - pn) / nd;

	if (t >= 0.0f)
	{
		distance = t;
		return true;
	}

	return false;
}

 

 

2. Triangle에 대한 Raycast 코드 추가하기

마지막으로 Triangle에 대해 알아본다.

 

Point in Triangle

Closest Point

Triangle to Sphere

 

Terrain에서 이동을 할 때 삼각형들이 만들어졌다는 걸 가정하고 클릭하면 이동을 해야 한다.

1차적으로 클릭한 범위가 삼각형 안에 속해 있는지 판별할 때 응용할 수 있다.

어떠한 점이 삼각형 내부에 있는지를 판별할 수 있어야 한다.

전형적인 외적의 예다. 크로스 해준 방향이 같은지 아닌지를 확인해서 만들어 준다.

 

좌표계 중에 특이한 게 Barycentric이란 게 있다.

ABC에서 얼마만큼 위치해 있는지를 찾는 방식이 있다.

좌표가 있을 때 0과 1 사이에 있어야 삼각형 안에 있다고 판별하는 게 있다.

직접 만들 때는 공식을 보고 사용하면 된다.

 

//-------------------
// Triangle
//-------------------

static bool PointInTriangle(const Point3D& p, const Triangle3D& t);

static Plane3D FromTriangle(const Triangle3D& t);
static Vec3 Barycentric(const Point3D& p, const Triangle3D& t);
static bool Raycast(const Triangle3D& triangle, const Ray3D& ray, float& distance);
static Vec3 ProjectVecOnVec(Vec3 from, Vec3 to);

 

//-------------------
// Triangle
//-------------------

bool MathUtils::PointInTriangle(const Point3D& p, const Triangle3D& t)
{
	Vec3 a = t.a - p;
	Vec3 b = t.b - p;
	Vec3 c = t.c - p;

	Vec3 normPBC = b.Cross(c); // Normal of PBC (u)
	Vec3 normPCA = c.Cross(a); // Normal of PCA (v)
	Vec3 normPAB = a.Cross(b); // Normal of PAB (w)

	if (normPBC.Dot(normPCA) < 0.0f)
		return false;

	else if (normPBC.Dot(normPAB) < 0.0f)
		return false;

	return true;
}

Vec3 MathUtils::Barycentric(const Point3D& p, const Triangle3D& t)
{
	Vec3 ap = p - t.a;
	Vec3 bp = p - t.b;
	Vec3 cp = p - t.c;

	Vec3 ab = t.b - t.a;
	Vec3 ac = t.c - t.a;
	Vec3 bc = t.c - t.b;
	Vec3 cb = t.b - t.c;
	Vec3 ca = t.a - t.c;

	Vec3 v = ab - ProjectVecOnVec(ab, cb);
	float a = 1.0f - (v.Dot(ap) / v.Dot(ab));

	v = bc - ProjectVecOnVec(bc, ac);
	float b = 1.0f - (v.Dot(bp) / v.Dot(bc));

	v = ca - ProjectVecOnVec(ca, ab);
	float c = 1.0f - (v.Dot(cp) / v.Dot(ca));

	return Vec3(a, b, c);
}

Plane3D MathUtils::FromTriangle(const Triangle3D& t)
{
	Plane3D result;

	result.normal = (t.b - t.a).Cross(t.c - t.a);
	result.normal.Normalize();

	result.distance = result.normal.Dot(t.a);

	return result;
}

bool MathUtils::Raycast(const Triangle3D& triangle, const Ray3D& ray, float& distance)
{
	Plane3D plane = FromTriangle(triangle);

	float t = 0;
	if (Raycast(plane, ray, OUT t) == false)
		return false;

	Point3D result = ray.origin + ray.direction * t;

	Vec3 barycentric = Barycentric(result, triangle);

	if (barycentric.x >= 0.0f && barycentric.x <= 1.0f &&
		barycentric.y >= 0.0f && barycentric.y <= 1.0f &&
		barycentric.z >= 0.0f && barycentric.z <= 1.0f)
	{
		distance = t;
		return true;
	}

	return false;
}

Vec3 MathUtils::ProjectVecOnVec(Vec3 a, Vec3 b)
{
	b.Normalize();

	float dist = a.Dot(b);

	return b * dist;
}

 

이 정도만 알아도 일반적인 충돌과 관련된 3D 함수를 한 바퀴 돌았다고 할 수 있다.

 

abb, obb를 기억해야 한다. 15개 축 테스트해서 하나라도 실패하면 실패로 인지한다.

플레인과 관련된 부분들.

무엇보다 중요한 건 내적의 사용법이다. 내적을 알아야 콘텐츠를 만들 수 있는 상황이 있는데 코딩을 해도 수학을 몰라서 못할 수도 있다. 수학이 어느 날 되는 게 아니다 보니까 같이 신경 쓸 필요 가 있다.

벡터, 삼각함수, 역함수 이런 온갖 기본 수학 시리즈들은 기본적으로 사용이 된다.

기본기를 경시하면 안 된다.

 

다음시간부터 다시 렌더링과 관련된 부분을 한다.

 

 

반응형