그래픽스 기술

[Ray Tracing] 광선과 삼각형의 충돌을 통해 삼각형 그리기

doyyy_0 2024. 10. 16. 10:51

 

  • 광선(ray)은 시작점 O와 방향 벡터 D를 가진 선으로 정의됩니다. 이는 수식으로 다음과 같이 표현됩니다:P(t)=O+tDP(t) = O + tD여기서 t는 파라미터로, 광선의 시작점에서부터 어떤 위치까지를 결정하는 변수입니다. P(t)는 광선의 특정 지점입니다.
  • 삼각형(triangle)은 세 개의 점으로 정의됩니다. 예를 들어, 삼각형의 세 꼭짓점을 V0, V1, V2라고 할 때, 이 삼각형은 이 세 점을 이용해 형성된 면입니다.

다음 그림과 코드를 보며 이해해봅시다.

orig는 시점, dir은 ray의 방향, v0,v1,v2는 삼각형의 세 꼭짓점, faceNormal은 삼각형 평면에서의 수직벡터입니다.

 

bool IntersectRayTriangle(const vec3 &orig, const vec3 &dir,
						  const vec3 &v0, const vec3 &v1,
						  const vec3 &v2, vec3 &point, vec3 &faceNormal,
						  float &t, float &u, float &v)
{
	/*
	 * 기본 전략
	 * - 삼각형이 놓여있는 평면과 광선의 교점을 찾고,
	 * - 그 교점이 삼각형 안에 있는지 밖에 있는지를 판단한다.
	 */

	/* 1. 삼각형이 놓여 있는 평면의 수직 벡터 계산 */
	faceNormal = normalize(glm::cross(v1 - v0, v2 - v0));
	//주의: 삼각형의 넓이가 0일 경우에는 계산할 수 없음

	// 삼각형 뒷면을 그리고 싶지 않은 경우 (Backface culling)
	if (dot(-dir,faceNormal)< 0.0f) return false;

	// 평면과 광선이 수평에 매우 가깝다면 충돌하지 못하는 것으로 판단
	if (std::abs(dot(dir,faceNormal))< 1e-2f) return false; // t 계산시 0으로 나누기 방지

	/* 2. 광선과 평면의 충돌 위치 계산 */
	t = (dot(v0, faceNormal) - dot(orig, faceNormal)) / dot(dir, faceNormal);

	// 광선의 시작점 이전에 충돌한다면 렌더링할 필요 없음
	if (t<0.0f) return false;

	point = orig + t * dir; // 충돌점

	/* 3. 그 충돌 위치가 삼각형 안에 들어 있는 지 확인 */

	// 작은 삼각형들 3개의 normal 계산
	const vec3 normal0 = normalize(cross(point - v1, v0 - v1));
	const vec3 normal1 = normalize(cross(point - v0, v2 - v0));
    const vec3 normal2 = normalize(cross(point - v2, v1 - v2));
    
	// 방향만 확인하면 되기 때문에 normalize() 생략 가능
	// 아래에서 cross product의 절대값으로 작은 삼각형들의 넓이 계산

	if (dot(normal0, faceNormal) < 0.0f) return false;
	if (dot(normal1, faceNormal) < 0.0f) return false;
	if (dot(normal2, faceNormal) < 0.0f) return false;

	

	return true;
}

 

 

그림과 코드를 같이 보며 이해해봅시다. 여기서 헷갈릴 부분은 p가 삼각형 안에 있는지 확인할 때 인데, 왼손 법칙을 잘 이용하고, faceNormal이 정해지면 v0,v1,v2의 평면과 방향이 확실히 정해졌다는 뜻을 유의하고 생각해 봅시다.