2013年8月4日日曜日

path tracing でレンダリング(c++) その2DOF (Depth Of Field)

先日作ったレンダラにレンズを加えてDOF(Depth Of Field)を再現してみた。
センサーとレンズの位置関係を決めるとピクセルと共役な点(集光する点)が求まる。
はじめにその共役点を求めて、絞りの各座標からそこへ向けて光線を飛ばす。




センサーの分割だけでなく絞りも分割するので、ピンホールカメラに対して計算時間がかなり増えてしまう。MacbookAirだとなかなか時間がかかる(
並列化もしてないし。)ファンも結構回る。






上の図は瞳分割4x4でに50sample/direction。下の図は瞳分割なしの50*16 sample/pixel

ところで家のデスクトップはCore i5の2.67GHzだが、こっちのほうが遅い。
理由は多分32bitのGCCを使ったせいだろう。こんなにも速度が違うのか、初めて知った。

/*
pathtrace_basic: basic path tracing from smallpt
pathtrace_dof: introducing a camera model.
this subaperture
*/

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <ctime>
//#define drand48() (double(rand()) / RAND_MAX)

class Vec {
public:
 double x, y, z;

 // constructor
 Vec(double x_ = 0.0, double y_ = 0.0, double z_ = 0.0) {x = x_; y = y_; z = z_;} 


 //operator overloading
 Vec operator+(const Vec &b) { return Vec(x+b.x, y+b.y, z+b.z); }
 Vec operator-(const Vec &b) { return Vec(x-b.x, y-b.y, z-b.z); }
 Vec operator*(double b) { return Vec(b*x, b*y, b*z); }
 Vec operator%(const Vec &b) { return Vec(y*b.z - z*b.y, z*b.x-x*b.z, x*b.y-y*b.x);} // cross product
 Vec mult(const Vec &b) { return Vec(x*b.x, y*b.y, z*b.z); }
 Vec& norm() { return *this = *this * (1/sqrt(x*x + y*y + z*z)); }
 double dot(const Vec &b) { return x*b.x + y*b.y + z*b.z; }
};

class Ray {
public:
 Vec o, d;
 Ray(Vec o_, Vec d_) : o(o_), d(d_) {} // constructor
};

class Plane2D {
public:
 double x, y;
 int xres, yres;
 Plane2D(double x_, double y_, int xres_, int yres_): 
 x(x_), y(y_), xres(xres_), yres(yres_) {}
};

class Sensor : public Plane2D {
public:
 Vec *c; //color array
 Sensor(double x_, double y_, int xres_, int yres_): Plane2D(x_, y_, xres_, yres_) {
  c = new Vec[xres*yres];
 }
 ~Sensor(){
  delete c;
 }
};

class Lens : public Plane2D {
public:
 double f;
 Lens(double f_, double x_, double y_, int xres_, int yres_): Plane2D(x_, y_, xres_, yres_) {
  f = f_;
 } 
};

class Camera {
public:
 Sensor* snsr;
 Lens* lens;
 double a;   //distance from lens to sensor, suppose lens and snsr are parallel.
 double beta;  //magnification
 Vec pos;   //center of the lens
 Vec dir;
 Vec up;
 Vec hrz;

 //constructor
 Camera(Sensor& s, Lens& l, double a_, Vec pos_, Vec dir_, Vec up_) {
  snsr = &s; lens = &l;
  a = a_; pos = pos_; dir = dir_; up = up_;
  hrz = (up%dir).norm();
  beta = l.f / (a-l.f);
 }

 Vec conj(int xi, int yi);
 Vec lens_coord(int ui, int vi);
};

Vec Camera::conj(int xi, int yi) {
 //input coordinate on the sensor
 //returns coordinate of the conjugate position
 Vec d = hrz * (0.5 - (0.5+xi)/snsr->xres) * snsr->x + up * (0.5 - (0.5+yi)/snsr->yres) * (-snsr->y) + dir * a; //from pix to the lens' center
 return pos + d * beta;
}
Vec Camera::lens_coord(int ui, int vi) { 
 return pos + hrz*((0.5+ui)/lens->xres - 0.5)*lens->x + up*((0.5+vi)/lens->yres - 0.5) * (-lens->y);
}



enum Refl_type { DIFF, SPEC, REFR};

class Sphere {
public:
 double rad;
 Vec pos, emi, color;
 Refl_type refl;

 //constructor
 Sphere(double rad_, Vec pos_, Vec emi_, Vec color_, Refl_type refl_):
  rad(rad_), pos(pos_), emi(emi_), color(color_), refl(refl_) {}

 double intersect(const Ray &r) {
  //returns distance, 0 if missed
  Vec op = pos - r.o;
  double t, eps=1e-4;
  double b = op.dot(r.d);
  double det = b*b - op.dot(op) + rad*rad;
  if (det < 0) {
   return 0;
  } else { 
   det = sqrt(det);
  }

  return (t = b-det) > eps ? t : ((t = b + det) > eps ? t : 0);
 }
};

Sphere spheres[] = {//Scene: 
 //     radius, position,               emission,    color,     material 
   Sphere(1e5,    Vec(-1e5-50, 0.0, 0.0), Vec(),     Vec(.75,.25,.25), DIFF),//Left 
  Sphere(1e5,    Vec( 1e5+50, 0.0, 0.0), Vec(),     Vec(.25,.25,.75), DIFF),//Rght 
  Sphere(1e5,    Vec(0.0, 0.0, -1e5-50), Vec(),     Vec(.75,.75,.75), DIFF),//Back 
  //Sphere(1e5,    Vec(0.0, 0.0,  1e5+1000),Vec(),     Vec(),            DIFF),//Frnt 
  Sphere(1e5,    Vec(0.0, -1e5-50, 0.0), Vec(),     Vec(.75,.75,.75), DIFF),//Botm 
  Sphere(1e5,    Vec(0.0,  1e5+50, 0.0), Vec(),     Vec(.75,.75,.0), DIFF),//Top 
  Sphere(16.5,   Vec(-20, -50+16.5, -50), Vec(),     Vec(.99,0.0,.0),  DIFF),//
  Sphere(16.5,   Vec(  0, -50+16.5, 0), Vec(),     Vec(1,1,1)*.999,  REFR),//Glas 
 Sphere(16.5,   Vec( 20, -50+16.5, 50), Vec(),     Vec(.99, .99, .0),  DIFF),//
  Sphere(300,    Vec(0.0, 349., 50.0),   Vec(6,6,6), Vec(),    DIFF) //Lite 
 }; 

inline double clamp(double x) {
 return x < 0 ? 0 : x > 1 ? 1 : x;
}

inline int toInt(double x) {
 return int(pow(clamp(x), 1/2.2)*255+.5);
}

inline bool intersect(const Ray &r, double &t, int &id) {
 //loop over the spheres,
 //and store the closest distance and the id.
 int n = sizeof(spheres) / sizeof(Sphere);
 double d, inf = t = 1e20;

 for (int i = 0; i < n; i++) {
  if ((d=spheres[i].intersect(r))>1e-4 && d < t) {
   t = d;
   id = i;
  }
 }
 return t<inf;
}

Vec radiance(const Ray &r_, int depth_) {
 double t; //distance to intersection
 int id = 0;
 Ray r = r_;
 int depth = depth_;

 Vec color(0,0,0); //accumulated color
 Vec reflect(1,1,1); //accumulated reflectance

 while(1) {
  if (!intersect(r, t, id)) //if missed
   return color;

  const Sphere &obj = spheres[id];  // hit object
  Vec x = r.o + r.d * t;     // intersection point
  Vec n = (x-obj.pos).norm();   //surface normal
  Vec nl = n.dot(r.d) < 0 ? n : n*(-1); //dot becomes negative if ray is goin into the sphere.
  Vec oc = obj.color;     //object color (reflectance).

  color = color + reflect.mult(obj.emi);  //accumulate color if any emission
  //printf("%f, %f, %f", color.x, color.y, color.z);

  double p = oc.x > oc.y && oc.x > oc.z ? oc.x : oc.y > oc.z ? oc.y : oc.z; // max object reflectance
  
  //russian roulet
  if (drand48() < p) {
   oc = oc * (1/p); //normalize by maximum reflectnce
  } else {
   return color;
  }

  reflect = reflect.mult(oc); //accumulate reflectance

  if (obj.refl == DIFF) { //lambert diffusion
   double r1 = 2 * M_PI * drand48();
   double r2 = drand48(), r2s = sqrt(r2);

   // new axis @ a intersection point
   Vec w = nl;
   Vec u = ((fabs(w.x) > 0.1 ? Vec(0,1,0): Vec(1,0,0))%w).norm();
   Vec v = w % u;

   //random sample in a semisphere
   Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm();

   r = Ray(x, d);
   continue;


  } else if (obj.refl == SPEC) { //specular reflection (like mirror)
   r = Ray(x, r.d - n*2*n.dot(r.d));
   continue;
  }

  //dielectric Refraction
  Ray reflRay(x, r.d - n*2*n.dot(r.d)); //reflection ray
  bool into = n.dot(nl) > 0;     // true if ray is goin into the object
  double ni = 1.0, nr = 1.5;
  double nir = into ? ni/nr : nr/ni;
  double ddn = r.d.dot(nl);
  double cos2t = 1 - nir*nir*(1-ddn*ddn);

  if (cos2t < 0) { //total reflection
   r = reflRay;
   continue;
  }
  Vec refr_dir = (r.d * nir - n*((into?1:-1) * (ddn*nir + sqrt(cos2t)))).norm(); //refracted ray
  double R0 = (nr-ni)*(nr-ni)/((nr+ni)*(nr+ni));  //normal reflectance
  double co = 1 - (into ? -ddn : refr_dir.dot(n)); 

  double Re = R0 + (1 - R0) * co * co * co * co * co; //sheered reflectance
  double Tr = 1 - Re;

  if (drand48() < Re) { 
   r = reflRay;  //reflected
  } else {
   r = Ray(x, refr_dir); //refracted
  }
  continue;
 }
}

int main(int argc, char *argv[]){
 srand(time(NULL));
 clock_t sclock, eclock;

 int samps = argc==2 ? atoi(argv[1]): 1; // # samples
   int xres = 256, yres = xres;
   int ures = 1, vres = ures;
   Sensor snsr(100., 100., xres, yres);
   double fl = 100;
   Lens lens(fl, fl/1.4, fl/1.4, ures, vres);
   Camera cam(snsr, lens, 2*fl, Vec(0.0, -0.1, 50+fl*2), Vec(0., 0., -1.), Vec(0.,1.,0.));
   int uv = cam.lens->xres * cam.lens->yres;
   Vec r;

//#pragma omp parallel for schedule(dynamic, 1) private(r)       // OpenMP
   sclock = clock(); 
   for (int y=0; y < cam.snsr->yres; y++){                       // Loop over image rows
      fprintf(stderr,"\rRendering (%d spp) %5.2f%%",samps,100.*y/(cam.snsr->yres-1));
     for (int x=0; x < cam.snsr->xres; x++) {   // Loop over img cols
      Vec pconj = cam.conj(x,y);
      r = Vec(); //radiance

      for (int v=0; v<cam.lens->yres; v++) {
       for (int u=0; u<cam.lens->xres; u++) {
        Vec p = cam.lens_coord(u,v);
        Vec d = (pconj - p).norm();//lens to the conj point
        
        for(int s=0; s<samps; s++) {
         r = r + radiance(Ray(p,d), 0) * (1./(samps*uv));
        }
       }
      }
      int i = (cam.snsr->xres-1-y)*cam.snsr->xres + x;
           cam.snsr->c[i] = Vec(clamp(r.x),clamp(r.y),clamp(r.z));
        }
   }
   eclock = clock();
   printf("time: %.2f1[s]\n",(double)(eclock-sclock)/CLOCKS_PER_SEC);

   FILE *f = fopen("image.ppm", "w");         // Write image to PPM file.
   fprintf(f, "P3\n%d %d\n%d\n", cam.snsr->xres, cam.snsr->yres, 255);
   for (int i=0; i< cam.snsr->xres * cam.snsr->yres; i++) {
     fprintf(f,"%d %d %d ", toInt(cam.snsr->c[i].x), toInt(cam.snsr->c[i].y), toInt(cam.snsr->c[i].z));
    }
}




0 件のコメント:

コメントを投稿