실습 자료의 내용과 동일하다. 원본을 먼저 찾아보는 것을 추천한다.
광선을 충돌시키고, 충돌한 지점에서 교점을 구하는 데는 성공했지만 지금으로써는 허점이 너무 많다.
테스트를 위해 구체를 하나만 배치해서 렌더링하고 있지만 레이를 쏘았을 때 충돌한 구체가 두 개 이상이라면 어떻게 할 것인가?
인간의 시점에서는 너무나도 당연하게 가장 먼저 마주친 원만 렌더링하면 된다고 생각하지만, 우리의 컴퓨터는 그 당연함이 당연하지 않게 된다. 레이를 쏘았을 때 가장 먼저 마주친 물체에 대한 정보를 저장해서 이후에 충돌하게 되더라도 해당 픽셀에는 가장 먼저 부딪힌 물체를 렌더링하게끔 해야 한다.
t에 관한 매개 방정식으로 광선 벡터를 나타낸 수식이다.
t는 광선 벡터의 크기(길이, 스칼라), O는 원점, D는 방향(단위 벡터)이다.
근의 공식으로 충돌을 처리하고 나온 t가 음수일 경우 카메라 뒤로 뻗어나갔다는 의미이기에 렌더링을 하지 못하게 처리해주어야 한다.
t가 양수이지만 너무 멀리 있을 경우라면 현실에서도 보이지 않는 것처럼 광선이 유효한 최대값을 가지고 있어야 한다.
카메라를 설정했을 때, 카메라에서 쏘는 광선이 구 안쪽면을 보는지, 바깥쪽 면을 보는지 검사해야 한다.
우리가 카메라에서 ray를 쐈는데 이 카메라가 구 안에 있는지, 밖에 있는지 판단하기 위해 벡터의 내적을 이용할 것이다. 단위 백터끼리 단위 벡터끼리 내적한 값이 곧 두 벡터가 이루는 사이각을 나타내게 되기 때문이다.
이때, 카메라에서 쏘는 ray와 구와 ray가 부딪히는 지점에서의 법선벡터 n을 내적한다.
위 그림처럼 내적의 결과가 양수이면 ray가 구 내부에 있다는 것을 알 수 있다.
이처럼 카메라가 구의 내부, 외부에 있냐에 따라 내적의 부호가 다르다는 것을 알 수 있다.
우리는 이것을 통해 카메라가 구 내부에 있을 때에는 구의 안쪽을 향하는 법선 벡터를 찾아야 한다.
즉, 내적 값이 양수가 나왔을 때(카메라가 구 내부에 있을 때), 기존 법선 벡터의 방향을 바꾸어 주면 되는 것이다.
코사인 세타는 세타라는 각을 낀 삼각형의 두 선분(밑변, 빗면)의 비율을 나타낸 것이다.
각도에 따른 코사인의 값은 정해져 있다.
코사인 그래프의 x축은 세타, y축은 코사인 값을 의미한다.
세타가 0도일 때 코사인 값은 1이 된다. 세타가 90도 일때 코사인 값은 0이 된다. 세타가 180도 일때 코사인 값은 -1이 된다. 예각일 경우 코사인 값의 범위는 1 ~ 0, 둔각일 경우 코사인 값의 범위는 0 ~ -1을 의미한다.
이를 토대로 코사인 값을 알면 세타인 각도도 알 수 있게 된다.
아래 사이트에서 실제 삼각함수의 그래프를 조작해볼 수 있다.
https://www.geogebra.org/m/t257mbKd
벡터를 내적하면 위처럼 두 벡터의 크기(길이, 스칼라)에 코사인 세타를 곱하게 된다.
내적은 스칼라, 값이라는 의미다. 무슨 값인가? 수평이 되는 백터에 얼마나 영향을 주었는가를 의미하는 값이다.
그림을 그려보면 훨씬 직관적이게 이해할 수 있다.
도형의 안쪽에서 광선을 쏜 경우, 두 벡터는 같은 방향을 가리키게 된다.
각도는 예각을 이루게 되며, 내적의 결과는 양수가 나오게 된다.
도형의 바깥에서 광선을 쏜 경우, 두 벡터는 다른 방향을 가리키게 된다.
각도는 둔각을 이루게 되며, 내적의 결과는 음수가 나오게 된다.
struct s_hit_record
{
t_point3 p; // 교점(충돌 지점)에 대한 좌표.
t_vec3 normal; // 교점에서 뻗어나온 법선(단위 벡터)
double tmin; // 기본 0, 물체가 뒤에 있을 경우에는 감지하지 않는다.
double tmax; // 광선의 가시거리, 일정 거리를 벗어나면 감지하지 않는다.
double t; // 광선의 원점과 교점 사이의 거리.
t_bool front_face;
};
t_bool hit_sphere(t_sphere *sp, t_ray *ray, t_hit_record *rec)
{
t_vec3 oc; // 0에서부터 벡터로 나타낸 구의 중심.
//a, b, c는 각각 t에 관한 근의 공식 2차 방정식의 계수
double a;
double half_b; // b가 half_b로
double c;
double discriminant; // 판별식
double sqrtd;
double root;
t_vec3 half;
oc = vminus(ray->orig, sp->center); // 0, 0, 0 - 구의 중심점
a = vlength2(ray->dir);
half_b = vdot(oc, ray->dir);
c = vlength2(oc) - sp->radius2;
discriminant = half_b * half_b - a * c;
if (discriminant < 0)
return (FALSE);
sqrtd = sqrt(discriminant); // 판별식에 루트를 씌움.
root = (-half_b - sqrtd) / a; // 근의 공식 해, 작은 근부터 고려.
if (root < rec->tmin || rec->tmax < root)
{
root = (-half_b + sqrtd) / a; // 큰 근 역시 tmin, tmax와의 비교
if (root < rec->tmin || rec->tmax < root) // 큰 근조차 tmin보다 작다면 hit하지 않은 것이므로 FALSE를 반환.
return (FALSE);
}
rec->t = root; // 광선의 원점과 교점까지의 거리
rec->p = ray_at(ray, root); // 교점의 좌표
half = vminus(rec->p, sp->center);
rec->normal = vdivide(half, sp->radius);
set_face_normal(ray, rec); // 카메라가 구의 안쪽에 있을 경우 광선과 법선은 같은 방향을 향하게 된다. 법선과 광선이 반대방향을 향햐도록 확인하는 함수를 추가했다.
return (TRUE);
}
구체와의 충돌을 체크할 때, 만약 판별식을 체크해서 충돌한다고 판단된 경우 해당 광선과 가장 가까운 물체인지를 비교하는데, 그것이 바로 rec->tmax이다. 카메라와 물체와의 거리인 root 값이 rec→tmax보다 크다면 기록된 충돌보다 뒤에 있다는 의미이므로 바로 리턴을 해준다.
rec→t에 원점과 교점까지의 거리를 저장한다. 이 값이 곧 rec->tmax 값으로 사용된다.
rec→p에 실제 충돌한 지점의 좌표를 저장한다.
rec→normal에 해당 교점의 법선 표준화하여 저장한다.
t_bool hit(t_object *world, t_ray *ray, t_hit_record *rec)
{
t_bool hit_anything;
t_hit_record *temp_rec;
temp_rec = rec;
hit_anything = FALSE;
while (world)
{
if (hit_obj(world, ray, temp_rec))
{
hit_anything = TRUE;
temp_rec->tmax = temp_rec->t;
rec = temp_rec;
}
world = world->next;
}
return (hit_anything);
}
구체들을 연결 리스트로 돌아가면서 검사할 때, hit_obj가 참일 경우 해당 광선에서 물체에 충돌함과 동시에 가장 가까운 충돌이라는 의미이므로 tmax 값을 갱신하고 rec에 변경 사항을 갱신한다.
mini_raytracing_in_c/06.hit_record.md at main · GaepoMorningEagles/mini_raytracing_in_c