[RT in One Weekend Series 10] Dielectrics + Snell’s Law (번역)

목차: Series 1: Index and Overview (Link)
이전 글: Series 9: Metal (Link)
다음 글: Series 11: Positionable Camera (Link)

투명한 Surface를 가진 물체 (물, 다이아몬드, 컵 등)을 Dielectric(유전체)이라고 한다. Ray가 투명한 Surface를 가진 물체와 Intersection(부딪히게) 되면 Ray는 Reflection(반사)와 Refraction(굴절)이 발생한다. 우리가 구현하고 있는 Ray Tracer에서는 Ray가 투명한 Surface를 가진 물체를 만나면 Ray를 반사하거나, 굴절시키는 것 중 1개만을 Random으로 적용할 계획이다.

MK: 아마 하나의 Pixel에 여러 개의 Ray를 보내는 Anti-Aliasing 기법으로 인해서 Random 하게 하나만의 연산을 수행해도 투명한 물체를 그릴 수 있는 것 같다.

출처 1에 따르면 Refracted Ray가 구현 후 에러가 발생하였을 때 디버깅하기 가장 어려운 부분이라고 한다. 그래서 출처 1에서는 Ray가 Object에 Hit 하는 경우 모든 Ray를 Refracted 되도록 구현하여서 잘못된 이미지를 먼저 보여준다. 해당 구현 설명이 없어서 따로 코드를 작성하지 않았다. 우리가 구현하고 있는 Ray Tracer에서는 Snell’s Law를 사용해서 Refraction(굴절)을 구현한다.

MK: 출처1에 제공되는 설명을 제대로 이해하지 못하여서 출처 2의 글을 기준으로 Snell’s Law 설명을 작성한다. 그림 1은 Snell’s Law를 설명하기 위한 그림이다. Snell’s Law는 아래 Equation(식)을 만족한다.

  • EQ1 (Snell’s Law): N1 * sin( Θ1) = N2 * sin( Θ2)

그림 1: Snell’s Law (출처 2)

예제를 사용해서 Snell’s Law를 계산하는 방법을 설명할 계획이다. 예를 들어서 Ray가 표현을 때리는 윗부분을 Air라고 가정하고, Ray가 Refraction 되는 부분을 물이라고 가정하자. N1, N2 값을 Refractive Index라고 한다. Air의 Refractive Index 값이 보통 1.0이고, 물(온도 20도 기준)의 경우 Refractive Index 값은 1.33이다. 그리고 Ray가 Air에서 통과해서 물로 향한다고 가정한다. Air에 부딪히는 Ray의 각도 (항상 표면의 Noraml Vector를 기준으로 함)이 41도라고 가정한다. 그럼 위 EQ1에 따라 아래 Equation(식)과 같이 표현 할 수 있다.

  • EQ2: 1.0 * Sin(41) = 1.33 * Sin( Θ2)

위 EQ2에서 Θ값을 계산하면 Θ는 대략 30이 된다. 그럼 그림 1과 같이 표면에서 물의 방향 Normal Vector를 기준으로 30도만큼 방향이 굴절되어서 Ray가 나아가게 된다. 역시나 예제로 설명하는 게 가장 쉬운 것 같다.

Snell’s Law는 한가지 문제점이 있다고 한다. N(Refractive Index)값이 큰 경우 “Total Internal Reflection” 문제가 발생한다. “Total Internal Reflection”의 경우 Refraction 되는 Ray가 부딪힌 표면으로 다시 반사되는 현상이다 (출처 3). 가끔 물을 보면 거울을 보듯이 완벽하게 반사되는 현상을 볼 수 있다. 이러한 부분을 “Total Internal Reflection”이라고 한다. 다시 Ray Tracer를 구현하는 부분으로 돌아와서 Refraction 코드(코드 1-1 참조)를 작성해보자. 아래 코드는 Refraction 코드를 추가한 Vec3 파일이다.

코드 1: Refraction 코드

#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;
}

vec3 reflect(const vec3 &v, const vec3 & n){
    return v - 2 * dot(v, n) * n;
}

//MK: 코드 1-1 - Refraction 연산
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;
}

#endif

이제 Refraction 코드를 사용하기 위해서 Dielectric Material 클래스를 생성한다. 아래 코드2는 Dielectric Object를 그리기 위한 코드이다. 기존의 다른 Object와 같은 파일에 코드들 작성하였다.

코드 2: Dielectric 클래스 코드

#ifndef MKMATERIAL_H
#define MKMATERIAL_H

#include "mkvec3.h"

class material{
    public:
        virtual bool scatter(const ray &rIn, const hitRecord &rec, vec3 &attenuation, ray &scattered) const = 0;
};

//MK: 코드 2-1 - Dielectric 클래스 추가
class dielectric : public material{
    public:
        dielectric(float ri) : refIdx (ri){
        }
        virtual bool scatter(const ray &rIn, const hitRecord &rec, vec3 &attenuation, ray &scattered) const {
            vec3 outwardNormal;
            vec3 reflected = reflect(unitVector(rIn.direction()), rec.normal);
            float niOverNt;
            attenuation = vec3(1.0, 1.0, 1.0);
            vec3 refracted;
            if(dot(rIn.direction(), rec.normal) > 0){
                outwardNormal = -rec.normal;
                niOverNt = refIdx;
            }
            else{
                outwardNormal = rec.normal;
                niOverNt = 1.0 / refIdx;
            }
            if(refract(rIn.direction(), outwardNormal, niOverNt, refracted)){
                scattered = ray(rec.p, refracted);
            }
            else{
                scattered = ray(rec.p, reflected);
                return false;
            }
            return true;
        }
     private:
        float refIdx;
};

class lambertian : public material{
    public:
        lambertian(const vec3 &a): albedo(a) {}
        virtual bool scatter(const ray &rIn, const hitRecord &rec, vec3 &attenuation, ray &scattered) const {
            vec3 target = rec.p + rec.normal + randomInUnitSphere();
            scattered = ray(rec.p, unitVector(target-rec.p));
            attenuation = albedo;
            return true;
        }
    private:
        vec3 albedo;
};

class metal : public material{
    public:
        metal(const vec3 &a, float f): albedo(a){
            if (f < 1.0){
                fuzz = f;
            }
            else{
                fuzz = 1.0;
            }
        }
        virtual bool scatter(const ray &rIn, const hitRecord &rec, vec3 &attenuation, ray &scattered) const{
            vec3 reflected = reflect(unitVector(rIn.direction()), rec.normal);
            scattered = ray(rec.p, unitVector(reflected+fuzz*randomInUnitSphere()));
            attenuation = albedo;
            return (dot(scattered.direction(), rec.normal) > 0);
        }
    private:
        vec3 albedo;
        float fuzz;
};

#endif

주목해야 하는 부분은 Attenuation 값을 모두 1로 설정한다. Glass 등의 Dielectric Surface는 실제로 빛을 흡수하지 않기 때문에 1로 설정한다. 출처 1은 일부로 Attenuation 값에서 Blue를 0으로 설정하여서 에러를 만들었다. 에러가 없을 때와 에러가 있을 2가지 그림을 모두 그려볼 계획이다. 아래 코드3은 Main 함수를 수정하여 하나의 Dielectric Object를 생성하는 코드이다.

코드 3: Dielectric Object 추가

#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 = "Ch9.ppm";
    ofstream writeFile(fileName.data());
    if(writeFile.is_open()){
        writeFile.flush();
        writeFile << "P3\n" << nx << " " << ny << "\n255\n";
        hitable *list[4];
//MK: 코드 3-1 - Dielectric 물체 추가
        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));
        hitable *world = new hitableList(list, 4);
        camera cam;
        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;
}

위 코드를 컴파일하면 그림 2의 결과 이미지를 확인할 수 있다. 그림 2는 앞에서 언급한 에러가 발생하지 않은 경우이다. 반면 그림 3은 Attenuation에서 Blue 값을 0으로 에러를 추가하였을 때 생성되는 결과 이미지이다.

그림 2: Dielectric Material 코드 결과

그림 3: Dielectric Material Class에서 Blue 값을 흡수하는 경우 (attenuation = vec3 (1.0, 1.0, 0.0))

실제 Glass(유리)는 보는 각도에 따라 Reflection이 발생하기도 한다. 만약 윈도우를 가파른 각도로 보게 되면 가끔 거울처럼 보이는 경우가 있다. 이러한 Reflection을 계산하기 위해서 복잡한 계산식을 사용해야 하야한다. 하지만 대부분의 경우 Christophe Schlick이 제안한 Polynomial Approximation 연산을 사용한다. 아래 식은 Christophe Schlick이 제안한 연산식이다.

  • EQ3: R(Θ) = R0 + (1 – R0) * (1 – cos(Θ))5
  • EQ4: R0 = ((N1 – N2)/(N1 + N2))2

위 식에 대한 연산을 추가한 코드는 코드 4(코드 4-1확인)과 같다.

코드 4: Christophe Schlick 코드 추가

#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;
}

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;
}

//MK: 코드 4-1 - Schlick 함수 추가
float schlick(float cosine, float refIdx){
    float r0 = (1 - refIdx)/(1+refIdx);
    r0 = r0 * r0;
    return r0 + (1 - r0) * pow((1 - cosine), 5);
}

#endif

이제 Dielectric 클래스를 수정해야 한다. 아래 코드 5는 Christophe Schlick 식을 추가한 Dielectric 클래스이다.

코드 5: Christophe Schlick을 추가한 Dielectric 클래스 코드

#ifndef MKMATERIAL_H
#define MKMATERIAL_H

#include "mkvec3.h"

class material{
    public:
        virtual bool scatter(const ray &rIn, const hitRecord &rec, vec3 &attenuation, ray &scattered) const = 0;
};

//MK: 코드 5-1 - Dielectric 코드 수정
class dielectric : public material{
    public:
        dielectric(float ri) : refIdx (ri){
        }
        virtual bool scatter(const ray &rIn, const hitRecord &rec, vec3 &attenuation, ray &scattered) const {
            vec3 outwardNormal;
            vec3 reflected = reflect(unitVector(rIn.direction()), rec.normal);
            float niOverNt;
            attenuation = vec3(1.0, 1.0, 1.0);
            vec3 refracted;
            float reflectProb;
            float cosine;
            if(dot(rIn.direction(), rec.normal) > 0){
                outwardNormal = -rec.normal;
                niOverNt = refIdx;
                cosine = refIdx * dot(rIn.direction(), rec.normal)/rIn.direction().length();
            }
            else{
                outwardNormal = rec.normal;
                niOverNt = 1.0 / refIdx;
                cosine = -dot(rIn.direction(), rec.normal)/rIn.direction().length();
            }
            if(refract(rIn.direction(), outwardNormal, niOverNt, refracted)){
                reflectProb = schlick(cosine, refIdx);
            }
            else{
                reflectProb = 1.0;
            }
            if(drand48() < reflectProb){
                scattered = ray(rec.p, reflected);
            }
            else{
                scattered = ray(rec.p, refracted);
            }
            return true;
        }
     private:
        float refIdx;
};

class lambertian : public material{
    public:
        lambertian(const vec3 &a): albedo(a) {}
        virtual bool scatter(const ray &rIn, const hitRecord &rec, vec3 &attenuation, ray &scattered) const {
            vec3 target = rec.p + rec.normal + randomInUnitSphere();
            scattered = ray(rec.p, unitVector(target-rec.p));
            attenuation = albedo;
            return true;
        }
    private:
        vec3 albedo;
};

class metal : public material{
    public:
        metal(const vec3 &a, float f): albedo(a){
            if (f < 1.0){
                fuzz = f;
            }
            else{
                fuzz = 1.0;
            }
        }
        virtual bool scatter(const ray &rIn, const hitRecord &rec, vec3 &attenuation, ray &scattered) const{
            vec3 reflected = reflect(unitVector(rIn.direction()), rec.normal);
            scattered = ray(rec.p, unitVector(reflected+fuzz*randomInUnitSphere()));
            attenuation = albedo;
            return (dot(scattered.direction(), rec.normal) > 0);
        }
    private:
        vec3 albedo;
        float fuzz;
};

#endif

해당 클래스를 사용해서 약간 트릭을 추가하면 신기한 그림을 그릴 수 있다고 한다. 예를 들어서 Dielectric Sphere을 그릴 때 마이너스(-) 값의 반지름을 사용하면 Geometry는 문제가 없으나 Normal Vector 값이 반대로 생성되어서 물방울과 같이 비어있는 형태의 그림을 그릴 수 있다고 한다. 위 설명과 같은 이미지를 그리기 위해서 Main 함수를 약간 수정하였다 (아래 코드 6 확인).

코드 6: 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 = "Ch9_2.ppm";
    ofstream writeFile(fileName.data());
    if(writeFile.is_open()){
        writeFile.flush();
        writeFile << "P3\n" << nx << " " << ny << "\n255\n";
        hitable *list[5];
//MK: 코드 6-1: 마이너스 값 반지름을 물체를 추가
        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);
        camera cam;
        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;
}

그림 4: 마이너스 반지름 값을 사용한 Sphere 결과 이미지

위 그림 4는 마이너스 반지름 값을 사용해서 그린 이미지 결과이다. 구가 물방울처럼 투명하게 보이는 것을 확인 할 수 있다.

출처

  1. http://www.realtimerendering.com/raytracing/Ray%20Tracing%20in%20a%20Weekend.pdf
  2. https://www.math.ubc.ca/~cass/courses/m309-01a/chu/Fundamentals/snell.htm
  3. https://en.wikipedia.org/wiki/Snell%27s_law

Leave a Comment