[RT in One Weekend Series 5] Adding a Sphere (번역)

목차: Series 1: Index and Overview (Link)
이전 글: Series 4: Rays, a Simple Camara, and Background (Link)
다음 글: Series 6: Surface Normals and Multiple Objects (Link)

Ray Tracer 예제에서 구(sphere)를 사용하는 경우가 많다고 한다. 가장 큰 이유는 Ray가 Travel을 할 때 구에 Hit을 했는지 여부를 판단하는 데 있어서 계산식이 아주 직관적이기 때문이다. 예를 들어서 특정 포지션 (cx, cy, cz)에 구가 존재하고 구는 지름 R이라고 가정하자. 만약 임의 포지션 (x, y, z)가 구의 표면인지 여부를 판단하기 위해서는 아래의 식을 만족하는지 확인하면 된다.

  • Equation 1: (x – cx) * (x – cx) + (y – cy) * (y – cy) + (z – cz) * (z – cz) = R * R

특정 포지션 (cx, cy, cz)를 C로 가정하면 위 식을 아래와 같이 dot product를 사용한 식으로 변경할 수 있다. Graphic 연산에서는 가능하면 Vector 연산을 사용하도록 변경하는 것이 좋다고 한다.

  • Equation 2: (x – cx) * (x – cx) + (y – cy) * (y – cy) + (z – cz) * (z – cz) = dot((p – C), (p – C))
  • Equation 3: dot((p – C), (p – C)) = R * R

해당 글에서 Ray가 이동하는 경로를 p(t) = A + t*B 함수로 정의하였다. p(t)는 특정 t가 주어졌을 때 Ray가 위치하는 3D 포지션(Position)을 의미한다. 그럼 우리는 p를 p(t)로 변경할 수 있다.

  • Equation 4: dot((p – C), (p – C)) = dot((p(t) – C), (p(t) – C))
  • Equation 5: dot((p(t) – C), (p(t) – C)) = R * R

위 식에서 p(t)를 A + t * B로 변경하면 아래와 같이 식을 변경할 수 있다.

  • Equation 6: dot(A + t * B – C), (A + t * B – C)) = R * R

위 식을 다시 한번 풀어서 작성하면 아래와 같이 표현 할 수 있다.

  • Equation 7: t*t*dot(B, B) + 2*t*dot(B, A-C) + dot(A-C, A-C) = R * R
  • Equation 8: t*t*dot(B, B) + 2*t*dot(B, A-C) + dot(A-C, A-C) – R * R = 0

중학교 수학 시간에 많이 풀었던 이차방정식(Quadratic Equation)이 완성된다. 우리는 A, B, C, R 값을 알고 있다. 그럼 t 값을 찾아야 한다. t 값을 찾기 위해서 아래 근의 공식을 사용하면 된다.

  • 이차방정식: a * x * x + b * x + c = 0
  • 근의 공식: (-b +/- sqar(b*b – 4 * a * c))/2a

그림 1: Ray 이동경로에 따른 Hit Point 개수 그림 (출처 1)

그림 1과 같이 실수(Real Number)의 t 값이 존재하면 해당 Ray는 구가 위치하는 곳을 지난다는 의미이다. 만약 실수  t가 존재하지 않으면 (음수(Negative) 값이 존재하면) 해당 Ray는 구가 위치하는 곳을 지나지 않는다는 의미이다. 위 설명을 코드로 작성하면 아래와 같다.

#include <iostream>
#include <fstream>
#include "mkray.h"

using namespace std;

//MK: 실수 t가 존재하면 True, 존재하지 않으면 False를 Return함
bool hitSphere(const vec3 &center, float radius, const ray &r){
    vec3 oc = r.origin() - center;
    float a = dot(r.direction(), r.direction());
    float b = 2.0 * dot(oc, r.direction());
    float c = dot(oc, oc) - radius * radius;
    float discriminant = b*b - 4*a*c;
    return (discriminant >= 0);
}

vec3 color(const ray &r){
    vec3 ret = vec3(1, 0, 0);
    //MK: 실수가 존재하는 경우 구 색상을 표시함
    if(hitSphere(vec3(0, 0, -1), 0.5, r)){
        return ret;
    }
    vec3 unitDirection = unit_vector(r.direction());
    float t = 0.5 * (unitDirection.y() + 1.0);
    ret = (1.0 - t) * vec3(1.0, 1.0, 1.0) + t * vec3(0.5, 0.7, 1.0);
    return ret;
}

int main(){
    int nx = 400;
    int ny = 200;
    string fileName = "Ch4.ppm";
    ofstream writeFile(fileName.data());
    if(writeFile.is_open()){
        writeFile.flush();
        writeFile << "P3\n" << nx << " " << ny << "\n255\n";
        vec3 lowerLeftCorner(-2.0, -1.0, -1.0);
        vec3 horizontal(4.0, 0.0, 0.0);
        vec3 vertical(0.0, 2.0, 0.0);
        vec3 origin(0.0, 0.0, 0.0);
        for(int j = ny - 1; j >= 0; j--){
            for(int i = 0; i < nx; i++){
                float u = float(i) / float(nx);
                float v = float(j) / float(ny);
                ray r(origin, (lowerLeftCorner + u * horizontal + v * vertical));
                vec3 col = color(r);
                int ir = int(255.99 * col[0]);
                int ig = int(255.99 * col[1]);
                int ib = int(255.99 * col[2]);
                writeFile << ir << " " << ig << " " << ib << "\n";
            }
        }
        writeFile.close();
    }
    return 0;
}

hitSphere() 함수에서 실수가 존재하는지 여부를 판단한다. 만약 실수가 존재하면 해당 Ray를 구와 Hit을 한다는 의미이다. 이 경우 Ray의 최종색상을 빨간색으로 표시하도록 하였다. 위 코드를 실행하면 그림 2와 같은 결과 이미지를 확인할 수 있다.

그림 2: 결과 이미지

출처

  1. http://www.realtimerendering.com/raytracing/Ray%20Tracing%20in%20a%20Weekend.pdf

Leave a Comment