출처:
Ray Tracing in One Weekend - Shading with Surface Normals
Github - GaepoMorningEagles/mini_raytracing_in_c
이전에 작성한 코드에서 광선과 구의 교점을 구하는 방정식의 b
가 인수로 2를 가지고 있으므로 짝수 근의 공식을 사용할 수 있다. 연산을 줄이기 위해 짝수 근의 공식을 사용하여 b
를 half_b
로 바꿔서 계산했다.
우리는 여러 객체를 나타내는 것이 목표이다. 화면에 객체가 여러 개 있다면 광선하고 만날 수도 있고 만나지 않을 수도 있으며, 광선과 객체의 교점을 구하는 방정식에서는 만나지만 그 앞을 다른 객체가 가로막는다면 실제로는 만나지 않을 수도 있다. 이렇게 다양한 경우를 고려해야 하므로 hit 여부에 대한 정보를 저장하는 hit_record
구조체를 만들었다.
이 구조체에는 광선이 객체와 만나는 점 p
, 그 점에서의 법선벡터 normal
, 그 점에서의 근 t
, 현재 광선에서의 t
값으로 가능한 범위 tmin
과 tmax
, 그리고 광선이 객체의 겉면과 만났는지 여부를 저장하는 front_face
변수가 선언되어 있다. 이 구조체를 이용해서 광선이 구와 어디서 만나는지를 확인한다.
이전에는 판별식이 0보다 크면 그때의 근을 반환했는데, 이제는 hit 여부만 판단하므로 TRUE
혹은 FALSE
만 반환한다. 판별식이 0보다 작으면 FALSE
를 반환하고, 판별식이 0보다 크거나 같으면 root
에 작은 근을 넣어준다. 그리고 그 root
가 rec->tmin
과 rec->tmax
사이에 있는지를 확인한다.(rec
는 hit_record
구조체 변수) 아직은 하나의 객체(구)만 존재하기 때문에 rec->tmin
은 0
, rec->tmax
는 INFINITY
로 맨 처음에 초기화할 때 넣어준 값 그대로이다.
하지만 공간에 여러 개의 객체가 있고, 하나의 광선이 여러 객체를 지날 때 만약 앞에 있는 객체의 hit 여부를 먼저 검사하고 그 다음으로 뒤에 있는 객체를 검사한다면, 뒤의 객체를 검사할 때는 rec->tmax
의 값이 앞에서 만난 객체에서의 hit point, rec->t
로 바뀌어 있을 것이다. 따라서 그때의 root
값이 rec->tmax
보다 크기 때문에 if
문 안으로 들어가서 root
가 큰 근으로 바뀌고 다시 검사를 한다.
만약 카메라가 객체의 내부에 존재한다면, 광선이 지나는 두 점(근) 중에서 작은 근일 때의 점은 카메라 뒤쪽에 위치할 것이다. 그 점을 검사할 때는 그 때의 root
값이 rec->tmin
보다 작기 때문에 if
문 안으로 들어가서 root
가 큰 근으로 바뀌고 다시 검사를 한다.
FALSE
가 리턴되지 않고 rec->tmin
과 rec->tmax
사이에 root
가 존재하게 되면, 이제 hit_record
구조체의 각 변수에 해당하는 값을 넣어준다. rec->t
에는 root
를, rec->p
에는 ray_at()
을 이용하여 광선의 시작점에서 hit 한 그 점을 향하는 단위벡터에 root
를 곱해서 더해준 값을 저장하여 hit point 의 좌표를 저장한다. 그리고 rec->normal
에는 정규화된 법선벡터를 넣어준다.
광선이 객체의 외부에서 구와 교차하는 경우, 법선은 광선을 향한다.(광선의 방향과 법선의 방향이 반대임) 광선이 객체의 내부에서 교차하면 법선은 광선의 방향을 따른다.(광선의 방향과 법선의 방향이 같음) 우리는 법선이 항상 광선을 향하도록, 법선과 광선이 서로 마주보는 방향으로 만들어야 한다. 광선이 객체의 외부에 있으면 법선이 바깥쪽을 가리키고, 객체의 내부에 있으면 법선이 안쪽을 가리키게 하는 것이다.
이를 확인하려면 광선과 법선의 벡터를 내적해서 그 값이 양수인지 음수인지를 확인하면 된다. 값은 0˚~90˚에서는 양수이고 90˚~180˚에서는 음수이므로 두 벡터의 내적값이 양수이면 광선이 객체의 내부에서 교차한 경우이고, 음수이면 외부에서 교차한 경우이다.
우리 코드에서는 rec->front_face
에 광선과 법선벡터를 내적한 값이 0보다 작은지를 담아주었다. 0보다 작으면 참이고 0보다 크거나 같으면 거짓이다. 그리고 rec->normal
에 만약 rec->front_face
가 참이면 그대로 냅두고, 거짓이면 -1을 곱해서 법선벡터의 방향을 반대로 바꿔준다.