서비스
교육 철학
⚫

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)에 두고 싶다면 방정식이 더 복잡해집니다:
(Cx−x)2+(Cy−y)2+(Cz−z)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)까지의 벡터는 (C−P)(C−P)입니다.
내적의 정의를 사용하면:
(C−P)⋅(C−P)=(Cx−x)2+(Cy−y)2+(Cz−z)2(C−P)⋅(C−P)=(Cx−x)²+(Cy−y)²+(Cz−z)²
따라서 구의 방정식을 벡터 형태로 다시 쓸 수 있습니다:
(C−P)⋅(C−P)=r2(C−P)⋅(C−P)=r²
이것은 "이 방정식을 만족하는 모든 점 P는 구 위에 있다"는 의미입니다. 우리는 광선 P(t)=Q+td가 구의 어딘가에 닿는지 알고 싶습니다. 구에 닿는다면 P(t)가 구 방정식을 만족하는 t가 존재합니다. 따라서 다음이 참인 t를 찾고 있습니다:
광선-구 교차를 계산할 때 원의 방정식을 벡터 형태로 변환하는 이유를 설명해드리겠습니다.
(C−P(t))⋅(C−P(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+(C−Q))⋅(−td+(C−Q))=r2(−td+(C−Q))⋅(−td+(C−Q))=r²
벡터 대수 규칙을 따라 내적을 분배합니다:
t2d⋅d−2td⋅(C−Q)+(C−Q)⋅(C−Q)=r2t²d⋅d−2td⋅(C−Q)+(C−Q)⋅(C−Q)=r²
반지름의 제곱을 왼쪽으로 이동합니다:
t2d⋅d−2td⋅(C−Q)+(C−Q)⋅(C−Q)−r2=0t²d⋅d−2td⋅(C−Q)+(C−Q)⋅(C−Q)−r²=0
이 방정식이 정확히 무엇인지 즉시 알아보기는 어렵지만, 방정식의 벡터와 r은 모두 상수이고 알려져 있습니다. 또한 모든 벡터는 내적에 의해 스칼라로 줄어듭니다. 유일한 미지수는 tt이고, t2t²가 있으므로 이것은 이차 방정식입니다. 이차 방정식 ax2+bx+c=0ax²+bx+c=0은 이차 공식을 사용하여 풀 수 있습니다:
(−b±√(b2−4ac))/2a(−b±√(b²−4ac))/2a
광선-구 교차 방정식에서 t를 풀면 a, b, c에 대한 다음 값을 얻습니다:
a=d⋅da=d⋅d
b=−2d⋅(C−Q)b=−2d⋅(C−Q)
c=(C−Q)⋅(C−Q)−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+t∗dx))2+(Cy−(Qy+t∗dy))2+(Cz−(Qz+t∗dz))2=r2(Cx - (Qx + t*dx))² + (Cy - (Qy + t*dy))² + (Cz - (Qz + t*dz))² = r²
이것을 전개하면 9개의 항이 나오고, t²의 계수, t의 계수, 상수항을 각각 찾아야 합니다.
벡터 형태로는:
(C−(Q+t∗d))⋅(C−(Q+t∗d))=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로 변경하면 이 솔루션이 카메라 앞의 객체와 카메라 뒤의 객체를 구별하지 않기 때문에 정확히 같은 그림을 얻게 됩니다. 이것은 기능이 아닙니다! 다음에 이러한 문제를 수정하겠습니다.