광선 P를 함수로 나타내면 P(t)=A+tb라고 할 수 있다.
여기서 A는 광원(ray origin: 광선의 시작 지점이다.), b는 광선의 진행방향이다. 여기서 A, b는 삼차원 좌표계 위의 벡터이고 t는 광선의 크기와 방향을 결정한다.
#ifndef RAY_H
#define RAY_H
#include "vec3.h"
class ray {
public:
//ray class의 생성자
ray() {}
ray(const point3& origin, const vec3& direction)
: orig(origin), dir(direction)
{}
// 광선의 광원(위에서의 A)과 방향(b)를 반환한다.
point3 origin() const { return orig; }
vec3 direction() const { return dir; }
// 광선의 끝 지점을 반환한다. 혹은 광선의 벡터
point3 at(double t) const {
return orig + t*dir;
}
public:
// 광원의 좌표(A)
point3 orig;
// 광선의 방향(b) 벡터
vec3 dir;
};
#endif
지금까지 삼차원 좌표계를 표현하기 위해 벡터 클래스를 만들었다. 벡터로 표현되는 ray class를 작성했으니 광선을 쏴 보자. 광선이 나아가서 물체에 부딪치고 반사되는 등의 과정에 따라 해당지점의 픽셀의 색상이 결정된다. 결정된 픽셀들이 모여 이미지를 구성한다.
광선을 쏴서 결정되는 픽셀의 색상을 구하는 일련의 과정을 해주는게 ray tracer다.
(1) 광원에서 픽셀까지의 광선을 구한다.
(2) 구한 광선이 물체에 충돌하는 지 판단한다.
(3) 광선과 물체가 만나는 교차점의 색상을 구한다.
픽셀을 찍어 이미지를 만들 도화지라고 상상하자. 뷰 포트는 생성될 이미지에 그려질 풍경을 결정한다. 가상의 공간에서 뷰 포트의 범위의 장면이 이미지로 생성된다. 그러므로 생성될 이미지와 뷰 포트의 종횡비는 같아야한다.
원문에서 사용한 종횡비는 16:9(일반적으로 쓰이는 종횡비라고한다)로 위의 이미지에 나오는 너비 4, 높이 2인 직사각형은 엄밀히 말하면 뷰 포트를 포함하는 가상의 평면이다. 정확한 좌표는 소수점이 길어 편의상 위와 같이 표현한 듯하다.
원점을 광원으로하는 광선들을 차례로 뷰포트를 향해 쏴 해당 지점의 픽셀 값을 결정한다. 도화지에 점을 하나씩 찍어 그림을 그리는 것과 같다.
#include "color.h"
#include "ray.h"
#include "vec3.h"
#include <iostream>
color ray_color(const ray& r) {
// 광선이 닿는 위치에 따라 색상을 결정해 반환하는 함수
vec3 unit_direction = unit_vector(r.direction());
// 반환되는 색상이 y의 좌표에 영향 받도록 t를 설계했다.
auto 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);
}
int main() {
// Image
const auto aspect_ratio = 16.0 / 9.0;
const int image_width = 400;
const int image_height = static_cast<int>(image_width / aspect_ratio);
// Camera
auto viewport_height = 2.0;
auto viewport_width = aspect_ratio * viewport_height;
auto focal_length = 1.0;
auto origin = point3(0, 0, 0);
auto horizontal = vec3(viewport_width, 0, 0);
auto vertical = vec3(0, viewport_height, 0);
auto lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0, 0, focal_length);
// Render
std::cout << "P3\n" << image_width << " " << image_height << "\n255\n";
for (int j = image_height-1; j >= 0; --j) {
std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush;
for (int i = 0; i < image_width; ++i) {
// 차례로 픽셀의 위치를 지정할 offset u,v
auto u = double(i) / (image_width-1);
auto v = double(j) / (image_height-1);
// u,v에 따른 픽셀 위치에 닿는 광선을 생성한다.
ray r(origin, lower_left_corner + u*horizontal + v*vertical - origin);
// 광선에 따라 결정된 색상을 반환 받는다.
color pixel_color = ray_color(r);
write_color(std::cout, pixel_color);
}
}
std::cerr << "\nDone.\n";
}
ray_color
가 어렵다면 그냥 y 좌표에 따라 다른 색상이 반환된다는 것만 챙기자.
아래와 같이 이미지가 출력 된다.