목차: Series 1: Index and Overview (Link)
이전 글: Series 11: Poisionable Camera (Link)
다음 글: Series 13: Where Next? (Link)
이번 RT 시리즈의 마지막 Feature는 Defocus Blur이다. Photographers(사진사?)는 Defocus Blur Feature를 보통 “Depth of Field”라고 한다. 실제 카메라를 사용해서 사진을 찍으면 Defocus Blur 현상이 나타나는 이유는 카메라 렌즈가 구멍(Big Hole)으로 만들어져있기 때문이다 (현재 우리가 구현한 코드는 아주 작은 Pinhole 형태를 가정한 것이다). 그래서 특정 거리에 있는 물체는 Focus가 되어서 잘 보이고 그렇지 않은 물체의 경우 흐릿하게 보이는 현상이 있다 (사진을 찍으면 중간에 위치한 캐릭터가 잘나오고 나머지는 약간 흐리게 나오는 현상을 말하는 것 같다).
이렇게 Focus를 조절하기 위해서 보통 렌즈(Lens)의 크기를 조절한다. 핸드폰의 경우 보통 Sensor(Film을 의미하는 것 같음)가 움직여서 Focus 조절 한다고 한다. 실제 카메라에서 더 많은 빛을 받기 원하면 “Aperture (Lens에 있는 Hole을 의미함)”을 크게 만든다. 이 Aperture로 인해서 Defocus Blur가 발생하게 된다. 우리가 만드는 Ray Tracing과 같은 Virtual Camera를 사용하는 경우 더 많은 빛이 필요하지 않기 때문에 Defocus Blur 현상을 만들고 싶은 경우에만 Aperture 관련 부분을 구현한다.
실제 카메라는 Compound Lens(복합 렌즈)를 사용하여 복잡한 구조를 가진다. 아래 그림 1은 실제 카메라가 구성된 형태를 보여준다. 우리는 복잡한 렌즈를 계산하기 위해서 Sensor, Aperture, Lens, Figure 등을 모두 구현하여서 완벽한 결과 이미지를 생성할 수 있다. 이렇게 생성된 이미지를 다시 뒤집어서 실제 카메라를 시뮬레이션 할 수 있다 (보통 카메라는 사진 촬영을 하면 이미지를 뒤집어서 저장한다).
그림 1: 카메라 구성 (출처 1)
하지만 이러한 절차는 복잡한 관계로 카메라 내부를 구현하지 않고 카메라 외부만을 구현하여서 Defocus Blur 현상을 재연할 수 있다. 이전에 구현한 RT에서는 모든 Ray가 항상 동일한 위치에서 출발한다. 하지만 이번 장에서는 Lens의 Surface에서 Ray를 생성하여서 Virtual Film Plane(가상 필름 영역)으로 Ray를 보낸다.
MK: 설명으로는 약간 이해가 어려운 부분이 있는 것 같다. 코드를 보면 작은 원의 무작위 위치에서 Ray를 생성한다. 그리고 해당 Ray를 Virtual Film Plane으로 보낸다. Ray가 발사되는 Virtual Flim Plane의 최종 위치 역시 작은 원의 무작위 위치 값을 사용하여 최종 위치 값을 결정하게 된다 (솔직히 이 부분이 정확히 왜 필요한지 잘 모르겠다).
아래 코드 1은 Defocus Blur 효과를 구현하기 위한 Camera 코드이다. 코드 2는 Lens 효과를 구현하기 위해서 randomInUnitDisk() 함수를 추가한 Vec 코드이다. 마지막으로 코드 3은 새로 작성한 Camera 코드를 사용하기 위한 Main 함수 코드이다.
코드 1: Defocus Blur Camera 코드
#ifndef MKCAMERA_H #define MKCAMERA_H #include "mkray.h" //MK: 코드 1-1 - Defocus Blur 효과 적용 class camera{ public: camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, float aperture, float focusDist){ lensRadius = aperture / 2; float theta = vfov * M_PI/180; float halfHeight = tan(theta/2); float halfWidth = aspect * halfHeight; origin = lookfrom; w = unitVector(lookfrom - lookat); u = unitVector(cross(vup, w)); v = cross(w, u); //MK: Virtual Film Plane을 결정하는 단계로 파악됨 lowerLeftCorner = origin - halfWidth * focusDist * u - halfHeight * focusDist * v - focusDist * w ; horizontal = 2 * halfWidth * focusDist* u; vertical = 2 * halfHeight * focusDist * v; } ray getRay(float s, float t){ vec3 rd = lensRadius * randomInUnitDisk(); //MK: 조금한 원을 사용하여 Random 값을 생성함 vec3 offset = u * rd.x() + v * rd.y(); //MK: 위에서 생성된 Random값을 사용해서 Lens 효과를 구현함 //MK: Ray가 도달하는 위치에 Offset을 빼는데 이 부분의 정확한 의도를 잘 모르겠음 return ray(origin + offset, lowerLeftCorner + s * horizontal + t * vertical - origin - offset); } private: vec3 origin; vec3 lowerLeftCorner; vec3 horizontal; vec3 vertical; vec3 u, v, w; float lensRadius; }; #endif
코드 2: randomInUnitDisk 함수를 추가한 Vec 코드
#ifndef MKVEC3_H #define MKVEC3_H #include <math.h> #include <stdlib.h> #include <iostream> class vec3{ public: vec3(){} vec3(float e0, float e1, float e2){ element[0] = e0; element[1] = e1; element[2] = e2; } inline float x() const{ return element[0];} inline float y() const{ return element[1];} inline float z() const{ return element[2];} inline float r() const{ return element[0];} inline float g() const{ return element[1];} inline float b() const{ return element[2];} inline const vec3& operator+() const{ return *this;} inline vec3 operator-() const {return vec3(-element[0], -element[1], -element[2]);} inline float operator[] (int i) const {return element[i];} inline float &operator[] (int i) {return element[i];} inline vec3& operator+=(const vec3 &v){ element[0] += v.element[0]; element[1] += v.element[1]; element[2] += v.element[2]; return *this; } inline vec3& operator-=(const vec3 &v){ element[0] -= v.element[0]; element[1] -= v.element[1]; element[2] -= v.element[2]; return *this; } inline vec3& operator*=(const vec3 &v){ element[0] *= v.element[0]; element[1] *= v.element[1]; element[2] *= v.element[2]; return *this; } inline vec3& operator/=(const vec3 &v){ element[0] /= v.element[0]; element[1] /= v.element[1]; element[2] /= v.element[2]; return *this; } inline vec3& operator*=(const float t){ element[0] *= t; element[1] *= t; element[2] *= t; return *this; } inline vec3& operator/=(const float t){ float k = 1.0/t; element[0] *= k; element[1] *= k; element[2] *= k; return *this; } inline float length() const{ return sqrt(element[0] * element[0] + element[1] * element[1] + element[2] * element[2]); } inline float squared_length() const{ return (element[0] * element[0] + element[1] * element[1] + element[2] * element[2]); } inline void make_unit_vector(){ float k = 1.0 / (sqrt(element[0] * element[0] + element[1] * element[1] + element[2] * element[2])); element[0] *= k; element[1] *= k; element[2] *= k; }; float element[3]; }; inline std::istream& operator>>(std::istream &is, vec3 &t){ is >> t.element[0] >> t.element[1] >> t.element[2]; return is; } inline std::ostream& operator<<(std::ostream &os, const vec3 &t){ os << t.element[0] << t.element[1] << t.element[2]; return os; } inline vec3 operator+(const vec3 &v1, const vec3 &v2){ return vec3(v1.element[0] + v2.element[0], v1.element[1] + v2.element[1], v1.element[2] + v2.element[2]); } inline vec3 operator-(const vec3 &v1, const vec3 &v2){ return vec3(v1.element[0] - v2.element[0], v1.element[1] - v2.element[1], v1.element[2] - v2.element[2]); } inline vec3 operator*(const vec3 &v1, const vec3 &v2){ return vec3(v1.element[0] * v2.element[0], v1.element[1] * v2.element[1], v1.element[2] * v2.element[2]); } inline vec3 operator/(const vec3 &v1, const vec3 &v2){ return vec3(v1.element[0] / v2.element[0], v1.element[1] / v2.element[1], v1.element[2] / v2.element[2]); } inline vec3 operator*(const float t, const vec3 &v){ return vec3(t * v.element[0], t * v.element[1], t * v.element[2]); } inline vec3 operator/(const vec3 &v, const float t){ return vec3(v.element[0]/t, v.element[1]/t, v.element[2]/t); } inline vec3 operator*(const vec3 &v, const float t){ return vec3(v.element[0] * t, v.element[1] * t, v.element[2] * t); } inline float dot(const vec3 &v1, const vec3 &v2){ return (v1.element[0] * v2.element[0] + v1.element[1] * v2.element[1] + v1.element[2] * v2.element[2]); } inline vec3 cross(const vec3 &v1, const vec3 &v2){ return vec3( (v1.element[1] * v2.element[2] - v1.element[2] * v2.element[1]), -(v1.element[0] * v2.element[2] - v1.element[2] * v2.element[0]), (v1.element[0] * v2.element[1] - v1.element[1] * v2.element[0]) ); } inline vec3 unitVector(vec3 v){ return (v/v.length()); } vec3 randomInUnitSphere(){ vec3 ret; do{ ret = 2.0 * vec3(drand48(), drand48(), drand48()) - vec3(1, 1, 1); }while(ret.squared_length() >= 1.0); return ret; } //MK: 코드 2-1 Defocus Blur 효과를 위해 추가한 코드 vec3 randomInUnitDisk(){ vec3 ret; do{ ret = 2.0 * vec3(drand48(), drand48(), 0) - vec3(1, 1, 0); }while(dot(ret, ret) >= 1.0); return ret; } vec3 reflect(const vec3 &v, const vec3 & n){ return v - 2 * dot(v, n) * n; } bool refract(const vec3 &v, const vec3 &n, float niOverNt, vec3 &refracted){ vec3 uv = unitVector(v); float dt = dot(uv, n); float discriminant = 1.0 - niOverNt*(1-dt*dt); if(discriminant > 0){ refracted = niOverNt * (uv - n * dt) - n * sqrt(discriminant); return true; } return false; } float schlick(float cosine, float refIdx){ float r0 = (1 - refIdx)/(1+refIdx); r0 = r0 * r0; return r0 + (1 - r0) * pow((1 - cosine), 5); } #endif
코드 3: Main 함수 코드
#include <iostream> #include <fstream> #include "mkray.h" #include "mkhitablelist.h" #include "float.h" #include "mksphere.h" #include "mkcamera.h" #include "mkmaterial.h" using namespace std; vec3 color(const ray &r, hitable *world, int depth){ hitRecord rec; if(world->hit(r, 0.001, MAXFLOAT, rec)){ ray scattered; vec3 attenuation; if(depth < 50 && rec.matPtr->scatter(r, rec, attenuation, scattered)){ return attenuation * color(scattered, world, depth + 1); } else{ return vec3(0, 0, 0); } } else{ vec3 unitDirection = unitVector(r.direction()); float t = 0.5 * (unitDirection.y() + 1.0); return (1.0 - t)*vec3(1.0, 1.0, 1.0) + t * vec3(0.5, 0.7, 1.0); } } int main(){ int nx = 400; int ny = 200; int ns = 100; string fileName = "Ch11.ppm"; ofstream writeFile(fileName.data()); if(writeFile.is_open()){ writeFile.flush(); writeFile << "P3\n" << nx << " " << ny << "\n255\n"; hitable *list[5]; list[0] = new sphere(vec3(0, 0, -1), 0.5, new lambertian(vec3(0.1, 0.2, 0.5))); list[1] = new sphere(vec3(0, -100.5, -1), 100, new lambertian(vec3(0.8, 0.8, 0.0))); list[2] = new sphere(vec3(1, 0, -1), 0.5, new metal(vec3(0.8, 0.6, 0.2), 0.3)); list[3] = new sphere(vec3(-1, 0, -1), 0.5, new dielectric(1.5)); list[4] = new sphere(vec3(-1, 0, -1), -0.45, new dielectric(1.5)); hitable *world = new hitableList(list, 5); //MK: 코드 3-1 Defocus Blur을 사용하기 위한 코드 수정 vec3 lookfrom(3, 3, 2); vec3 lookat(0, 0, -1); float diskToFocus = (lookfrom - lookat).length(); float aperture = 3.0; camera cam(lookfrom, lookat, vec3(0, 1, 0), 20, float(nx)/float(ny), aperture, diskToFocus); for(int j = ny - 1; j >= 0; j--){ for(int i = 0; i < nx; i++){ vec3 col(0.0, 0.0, 0.0); for(int s = 0; s < ns; s++){ float u = float(i + drand48()) / float(nx); float v = float(j + drand48()) / float(ny); ray r = cam.getRay(u, v); col += color(r, world, 0); } col /= float(ns); col = vec3( sqrt(col[0]), sqrt(col[1]), sqrt(col[2]) ); 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; }
위 코드를 컴파일해서 실행하면 아래 그림3의 결과를 확인 할 수 있다. 중간에 위치한 파란색 구는 선명하지만, 옆에 있는 다른 2개의 구는 선명도가 떨어진 것을 확인 할 수 있다.
그림 3: 최종 결과 이미지
출처
- http://www.realtimerendering.com/raytracing/Ray%20Tracing%20in%20a%20Weekend.pdf