5. Adding a Sphere(구 추가하기)
레이 트레이서에 단일 객체를 추가해봅시다. 광선이 구와 충돌하는지 계산하는 것이 비교적 간단하기 때문에 구를 자주 사용합니다.
5.1 Ray-Sphere Intersection(광선-구 교차)
원점을 중심으로 하는 반지름 r의 구에 대한 방정식은 다음과 같습니다:
주어진 점 가 구의 표면에 있다면 입니다. 점이 구의 내부에 있다면 이고, 외부에 있다면 입니다.
구의 중심을 임의의 점 에 두고 싶다면 방정식이 더 복잡해집니다:
그래픽스에서는 모든 내용을 vec3 클래스로 간단하게 표현하기 위해 공식을 벡터로 표현합니다. 점 에서 중심 까지의 벡터는 입니다.
내적의 정의를 사용하면:
따라서 구의 방정식을 벡터 형태로 다시 쓸 수 있습니다:
이것은 "이 방정식을 만족하는 모든 점 P는 구 위에 있다"는 의미입니다. 우리는 광선 P(t)=Q+td가 구의 어딘가에 닿는지 알고 싶습니다. 구에 닿는다면 P(t)가 구 방정식을 만족하는 t가 존재합니다. 따라서 다음이 참인 t를 찾고 있습니다:
광선-구 교차를 계산할 때 원의 방정식을 벡터 형태로 변환하는 이유를 설명해드리겠습니다.
를 확장된 형태로 대체하면:
왼쪽에 세 개의 벡터가 오른쪽의 세 개의 벡터와 내적됩니다. 전체 내적을 풀면 아홉 개의 항을 얻게 됩니다. 모든 것을 전개할 수도 있지만, 그렇게까지 할 필요는 없습니다. t에 대해 풀고 싶으므로 t의 유무에 따라 항을 분리하겠습니다:
벡터 대수 규칙을 따라 내적을 분배합니다:
반지름의 제곱을 왼쪽으로 이동합니다:
이 방정식이 정확히 무엇인지 즉시 알아보기는 어렵지만, 방정식의 벡터와 r은 모두 상수이고 알려져 있습니다. 또한 모든 벡터는 내적에 의해 스칼라로 줄어듭니다. 유일한 미지수는 이고, 가 있으므로 이것은 이차 방정식입니다. 이차 방정식 은 이차 공식을 사용하여 풀 수 있습니다:
광선-구 교차 방정식에서 t를 풀면 a, b, c에 대한 다음 값을 얻습니다:
이차 공식을 사용하여 t를 풀 수 있습니다. 제곱근 안의 판별식(discriminant)은 양수(두 개의 실수 해), 음수(실수 해 없음), 또는 0(하나의 실수 해)일 수 있습니다. 그래픽스에서 이러한 대수적 결과는 기하학적 의미와 직접 연결됩니다.
*그림 5: 광선-구 교차 결과*
왜 벡터 형태로 변환해야 할까요?
•
3차원 공간에서의 계산 단순화: 원래 구의 방정식 (Cx−x)²+(Cy−y)²+(Cz−z)²=r²는 각 축(x, y, z)을 개별적으로 다뤄야 합니다. 이를 벡터 형태로 바꾸면 한 번의 내적 연산으로 처리할 수 있습니다.
•
코드 구현의 효율성: 이미 Vec3 클래스가 있고 내적(dot product) 함수가 구현되어 있다면, 벡터 형태를 사용하면 코드가 훨씬 간결해집니다. x, y, z를 각각 제곱하고 더하는 대신 dot(C-P, C-P) 한 줄로 표현할 수 있습니다.
•
광선 방정식과의 통합: 광선은 이미 벡터 형태로 정의되어 있습니다 (P(t)=Q+td). 구의 방정식도 벡터 형태로 표현하면 두 방정식을 자연스럽게 결합할 수 있습니다.
•
수학적 조작의 용이성: 벡터 대수 규칙(분배법칙, 교환법칙 등)을 사용하여 방정식을 더 쉽게 전개하고 t에 대해 정리할 수 있습니다.
실제 예시
스칼라 형태로 광선-구 교차를 계산하려면:
이것을 전개하면 9개의 항이 나오고, t²의 계수, t의 계수, 상수항을 각각 찾아야 합니다.
벡터 형태로는:
이렇게 하면 벡터 연산 규칙을 사용해 훨씬 간단하게 이차 방정식의 계수를 도출할 수 있습니다.
결국 벡터 형태는 3차원 공간의 기하학적 문제를 더 직관적이고 효율적으로 다룰 수 있게 해주는 도구입니다.
5.2 Creating Our First Raytraced Image(첫 번째 레이트레이싱 이미지 생성)
이 수학을 가져와서 프로그램에 하드코딩하면, z축에서 −1에 작은 구를 배치한 다음 이와 교차하는 모든 픽셀을 빨간색으로 칠하여 코드를 테스트할 수 있습니다.
bool HitSphere(const Point3& center, double radius, const Ray& r)
{
Vec3 oc = center - r.Origin();
auto a = Dot(r.Direction(), r.Direction());
auto b = -2.0 * Dot(r.Direction(), oc);
auto c = Dot(oc, oc) - radius*radius;
auto discriminant = b*b - 4*a*c;
return (discriminant >= 0);
}
Color RayColor(const Ray& r)
{
if (HitSphere(Point3(0,0,-1), 0.5, r))
return Color(1, 0, 0);
Vec3 unitDirection = UnitVector(r.Direction());
auto a = 0.5*(unitDirection.Y() + 1.0);
return (1.0-a)*Color(1.0, 1.0, 1.0) + a*Color(0.5, 0.7, 1.0);
}
C++
복사
*결과 이미지 3: 간단한 빨간 구*
이제 이것은 셰이딩, 반사 광선, 여러 객체 등 모든 종류의 것이 부족하지만, 시작보다 절반 이상 완료에 더 가깝습니다! 주의해야 할 점은 이차 방정식을 풀고 해가 존재하는지 확인하여 광선이 구와 교차하는지 테스트하고 있지만, 음수 t 값을 가진 해도 잘 작동한다는 것입니다. 구의 중심을 z=+1로 변경하면 이 솔루션이 카메라 앞의 객체와 카메라 뒤의 객체를 구별하지 않기 때문에 정확히 같은 그림을 얻게 됩니다. 이것은 기능이 아닙니다! 다음에 이러한 문제를 수정하겠습니다.




