[42cursurs]Ray Tracing in One Weekend 5-Surface Normals and Multiple Objects(1)

이상헌·2020년 10월 9일
0

이전 포스트에서 풍경 안에 구를 그렸지만 입체감이 없어 그저 붉은 원처럼 보인다. 이번엔 shading으로 표면의 입체감을 살려보자.

Shading with Surface Normals

원문의 normal은 구의 중심에서 구의 표면의 임의의 점 P방향의 방향벡터를 뜻한다. normal이니 방향벡터니 설명하면 길어지니 우리가 챙길 것은 구의 표면에 따라 다른 색상을 입혀서 구의 입체감을 표현하기 위해 normal 이라는 방법을 썼다는 것만 챙기자.

main.cc

double hit_sphere(const point3& center, double radius, const ray& r) {
    vec3 oc = r.origin() - center;
    auto a = dot(r.direction(), r.direction());
    auto b = 2.0 * dot(oc, r.direction());
    auto c = dot(oc, oc) - radius*radius;
    auto discriminant = b*b - 4*a*c;
    if (discriminant < 0) {
        return -1.0;
    } else {
    // 근의 공식으로 교차점의 t값을 구한다.
        return (-b - sqrt(discriminant) ) / (2.0*a);
    }
}

color ray_color(const ray& r) {
    auto t = hit_sphere(point3(0,0,-1), 0.5, r);
    if (t > 0.0) { // 구와 교차할 경우
    	// 교차점 P의 위치벡터에서 구의 중심 C의 위치벡터를 빼면 벡터 PC다.
        // 벡터 PC의 단위벡터인 normal N을 구했다.
        vec3 N = unit_vector(r.at(t) - vec3(0,0,-1));
        // normal의 값에 따라 반환하는 색상이 달라진다. 
        return 0.5*color(N.x()+1, N.y()+1, N.z()+1);
    }
    vec3 unit_direction = unit_vector(r.direction());
    t = 0.5*(unit_direction.y() + 1.0);
    return (1.0-t)*color(1.0, 1.0, 1.0) + t*color(0.5, 0.7, 1.0);
}

Simplifying the Ray-Sphere Intersection Code

새로 작성한 코드의 hit_sphere의 연산을 줄이는 작업이다. 크게 다른 점은 없다.

main.cc- before

double hit_sphere(const point3& center, double radius, const ray& r) {
    vec3 oc = r.origin() - center;
    auto a = dot(r.direction(), r.direction());
    auto b = 2.0 * dot(oc, r.direction());
    auto c = dot(oc, oc) - radius*radius;
    auto discriminant = b*b - 4*a*c;

    if (discriminant < 0) {
        return -1.0;
    } else {
        return (-b - sqrt(discriminant) ) / (2.0*a);
    }
}

main.cc- after

double hit_sphere(const point3& center, double radius, const ray& r) {
    vec3 oc = r.origin() - center;
    auto a = r.direction().length_squared();
    auto half_b = dot(oc, r.direction());
    auto c = oc.length_squared() - radius*radius;
    auto discriminant = half_b*half_b - a*c;

    if (discriminant < 0) {
        return -1.0;
    } else {
    // b 대신 half_b, 2a 를 a로 약분해줬다.
        return (-half_b - sqrt(discriminant) ) / a;
    }
}

근의 공식의 분자 분모를 각각 2로 나눠서 연산을 줄였다.

An Abstraction for Hittable Objects

물체 여러개를 생성하기 위해 객체를 추상화한다. 이렇게 나눠줘야 이후에 코딩할 때도 편하고 가독성도 좋아진다.

hittable.h

#ifndef HITTABLE_H
#define HITTABLE_H

#include "ray.h"

struct hit_record {
// 교차점의 좌표와 normal, 교차점에 닿은 광선의 t값을 기록할 구조체
    point3 p;
    vec3 normal;
    double t;
};

class hittable {
    public:
    // 광선의 교차여부를 판단하는 함수
        virtual bool hit(const ray& r, double t_min, double t_max, hit_record& rec) const = 0;
};

#endif

hittable은 광선과 충돌 가능한 '특성'이라고 이해하자. 구는 충돌 가능한 특성을 지니고 있다. 앞으로 다양한 물체를 생성할 때 편리 하려고 저런 짓을 한다고 생각하고 넘어가자. hit함수의 t_min, t_max는 광원에 가장 가까운 물체를 선정하기 위해 주는 t의 범위다. 불투명한 물체A 뒤에 있는 물체B에 직선으로 뻗은 광선이 닿을 수 있다해도, 그 앞에 먼저 광선에 부딪힌 물체A가 있을 경우 광선이 물체B에 닿지않았다고 판단하기 위함이다.

sphere.h

hittable한 구를 클래스로 구현했다.

#ifndef SPHERE_H
#define SPHERE_H

#include "hittable.h"
#include "vec3.h"

class sphere : public hittable {
    public:
    // sphere class의 생성자
        sphere() {}
        sphere(point3 cen, double r) : center(cen), radius(r) {};
	// hittable하므로 교차여부를 판별하는 기능의 함수를 가진다.
        virtual bool hit(
            const ray& r, double tmin, double tmax, hit_record& rec) const override;

    public:
    // sphere의 중심 좌표와 반지름 길이를 가진다.
        point3 center;
        double radius;
};

// hittable class의 hit함수 구현
bool sphere::hit(const ray& r, double t_min, double t_max, hit_record& rec) const {
    vec3 oc = r.origin() - center;
    auto a = r.direction().length_squared();
    auto half_b = dot(oc, r.direction());
    auto c = oc.length_squared() - radius*radius;
    auto discriminant = half_b*half_b - a*c;

    if (discriminant > 0) {
        auto root = sqrt(discriminant);

        auto temp = (-half_b - root) / a;
        if (temp < t_max && temp > t_min) {
            rec.t = temp;
            rec.p = r.at(rec.t);
            rec.normal = (rec.p - center) / radius;
            return true;
        }

        temp = (-half_b + root) / a;
        if (temp < t_max && temp > t_min) {
            rec.t = temp;
            rec.p = r.at(rec.t);
            rec.normal = (rec.p - center) / radius;
            return true;
        }
    }

    return false;
}


#endif

구현된 hit함수를 보면 hit_sphere와 달라진 점이 있다. 원래 근의 공식의 값이 최대 두 개이므로 저렇게 짰다. 그럼 hit_sphere에서는 어째서 한가지만 반환하는가 의문이 든다. 우리가 sphere를 class로 구현한 경우 광원과 구의 중심에 따라 물체와 교차하는 광선의 t값이 어떻게 될지 모르기 때문이다. 복잡하다면 hit의 경우 일반화한거고 hit_sphere의 경우 특정 구를 정해놓고 쓰므로 t에 대해서도 알고 있기 때문이라고만 알아두자.

profile
배고픈 개발자 sayi입니다!

0개의 댓글