Company
교육 철학

5. Adding a Sphere (구 추가하기)

5. Adding a Sphere(구 추가하기)

레이 트레이서에 단일 객체를 추가해봅시다. 광선이 구와 충돌하는지 계산하는 것이 비교적 간단하기 때문에 구를 자주 사용합니다.

5.1 Ray-Sphere Intersection(광선-구 교차)

원점을 중심으로 하는 반지름 r의 구에 대한 방정식은 다음과 같습니다:
x2+y2+z2=r2x²+y²+z²=r²
주어진 점 (x,y,z)(x,y,z)가 구의 표면에 있다면 x2+y2+z2=r2x²+y²+z²=r²입니다. 점이 구의 내부에 있다면 x2+y2+z2<r2x²+y²+z²<r²이고, 외부에 있다면 x2+y2+z2>r2x²+y²+z²>r²입니다.
구의 중심을 임의의 점 (Cx,Cy,Cz)(Cx,Cy,Cz)에 두고 싶다면 방정식이 더 복잡해집니다:
(Cxx)2+(Cyy)2+(Czz)2=r2(Cx−x)²+(Cy−y)²+(Cz−z)²=r²
그래픽스에서는 모든 x/y/zx/y/z 내용을 vec3 클래스로 간단하게 표현하기 위해 공식을 벡터로 표현합니다. P=(x,y,z)P=(x,y,z)에서 중심 C=(Cx,Cy,Cz)C=(Cx,Cy,Cz)까지의 벡터는 (CP)(C−P)입니다.
내적의 정의를 사용하면:
(CP)(CP)=(Cxx)2+(Cyy)2+(Czz)2(C−P)⋅(C−P)=(Cx−x)²+(Cy−y)²+(Cz−z)²
따라서 구의 방정식을 벡터 형태로 다시 쓸 수 있습니다:
(CP)(CP)=r2(C−P)⋅(C−P)=r²
이것은 "이 방정식을 만족하는 모든 점 P는 구 위에 있다"는 의미입니다. 우리는 광선 P(t)=Q+td가 구의 어딘가에 닿는지 알고 싶습니다. 구에 닿는다면 P(t)가 구 방정식을 만족하는 t가 존재합니다. 따라서 다음이 참인 t를 찾고 있습니다:
광선-구 교차를 계산할 때 원의 방정식을 벡터 형태로 변환하는 이유를 설명해드리겠습니다.
(CP(t))(CP(t))=r2(C−P(t))⋅(C−P(t))=r²
P(t)P(t)를 확장된 형태로 대체하면:
(C(Q+td))(C(Q+td))=r2(C−(Q+td))⋅(C−(Q+td))=r²
왼쪽에 세 개의 벡터가 오른쪽의 세 개의 벡터와 내적됩니다. 전체 내적을 풀면 아홉 개의 항을 얻게 됩니다. 모든 것을 전개할 수도 있지만, 그렇게까지 할 필요는 없습니다. t에 대해 풀고 싶으므로 t의 유무에 따라 항을 분리하겠습니다:
(td+(CQ))(td+(CQ))=r2(−td+(C−Q))⋅(−td+(C−Q))=r²
벡터 대수 규칙을 따라 내적을 분배합니다:
t2dd2td(CQ)+(CQ)(CQ)=r2t²d⋅d−2td⋅(C−Q)+(C−Q)⋅(C−Q)=r²
반지름의 제곱을 왼쪽으로 이동합니다:
t2dd2td(CQ)+(CQ)(CQ)r2=0t²d⋅d−2td⋅(C−Q)+(C−Q)⋅(C−Q)−r²=0
이 방정식이 정확히 무엇인지 즉시 알아보기는 어렵지만, 방정식의 벡터와 r은 모두 상수이고 알려져 있습니다. 또한 모든 벡터는 내적에 의해 스칼라로 줄어듭니다. 유일한 미지수는 tt이고, t2가 있으므로 이것은 이차 방정식입니다. 이차 방정식 ax2+bx+c=0ax²+bx+c=0은 이차 공식을 사용하여 풀 수 있습니다:
(b±(b24ac))/2a(−b±√(b²−4ac))/2a
광선-구 교차 방정식에서 t를 풀면 a, b, c에 대한 다음 값을 얻습니다:
a=dda=d⋅d
b=2d(CQ)b=−2d⋅(C−Q)
c=(CQ)(CQ)r2c=(C−Q)⋅(C−Q)−r²
이차 공식을 사용하여 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에 대해 정리할 수 있습니다.

실제 예시

스칼라 형태로 광선-구 교차를 계산하려면:
(Cx(Qx+tdx))2+(Cy(Qy+tdy))2+(Cz(Qz+tdz))2=r2(Cx - (Qx + t*dx))² + (Cy - (Qy + t*dy))² + (Cz - (Qz + t*dz))² = r²
이것을 전개하면 9개의 항이 나오고, t²의 계수, t의 계수, 상수항을 각각 찾아야 합니다.
벡터 형태로는:
(C(Q+td))(C(Q+td))=r2(C - (Q + t*d)) · (C - (Q + t*d)) = r²
이렇게 하면 벡터 연산 규칙을 사용해 훨씬 간단하게 이차 방정식의 계수를 도출할 수 있습니다.
결국 벡터 형태는 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++
복사
*리스트 11: [main.cc] 빨간 구 렌더링*
*결과 이미지 3: 간단한 빨간 구*
이제 이것은 셰이딩, 반사 광선, 여러 객체 등 모든 종류의 것이 부족하지만, 시작보다 절반 이상 완료에 더 가깝습니다! 주의해야 할 점은 이차 방정식을 풀고 해가 존재하는지 확인하여 광선이 구와 교차하는지 테스트하고 있지만, 음수 t 값을 가진 해도 잘 작동한다는 것입니다. 구의 중심을 z=+1로 변경하면 이 솔루션이 카메라 앞의 객체와 카메라 뒤의 객체를 구별하지 않기 때문에 정확히 같은 그림을 얻게 됩니다. 이것은 기능이 아닙니다! 다음에 이러한 문제를 수정하겠습니다.