Created
December 16, 2025 03:45
-
-
Save tyfkda/b14d1e00dd794738b1dde77125dedad2 to your computer and use it in GitHub Desktop.
シンプルなライトトレーサー(smallptベース、ピンホールカメラ、NEE)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <math.h> // smallpt, a Path Tracer by Kevin Beason, 2008 | |
| #include <stdlib.h> // Make : g++ -O3 -fopenmp smallpt.cpp -o smallpt | |
| #include <stdio.h> // Modified by tyfkda, changed to light tracer. | |
| #define USE_OPENMP | |
| #ifdef USE_OPENMP | |
| #include <omp.h> | |
| #endif | |
| struct Vec { // Usage: time ./smallpt 5000 && xv image.ppm | |
| double x, y, z; // position, also color (r,g,b) | |
| explicit Vec(double xyz_=0){ x=y=z=xyz_; } | |
| Vec(double x_, double y_, double z_){ x=x_; y=y_; z=z_; } | |
| Vec operator+(const Vec &b) const { return Vec(x+b.x,y+b.y,z+b.z); } | |
| Vec operator-(const Vec &b) const { return Vec(x-b.x,y-b.y,z-b.z); } | |
| Vec operator*(double b) const { return Vec(x*b,y*b,z*b); } | |
| Vec mult(const Vec &b) const { return Vec(x*b.x,y*b.y,z*b.z); } | |
| double squareLength() const { return x*x+y*y+z*z; } | |
| double length() const { return sqrt(squareLength()); } | |
| Vec norm() const { return *this * (1/length()); } | |
| double dot(const Vec &b) const { return x*b.x+y*b.y+z*b.z; } // cross: | |
| Vec operator%(const Vec&b) const {return Vec(y*b.z-z*b.y,z*b.x-x*b.z,x*b.y-y*b.x);} | |
| }; | |
| struct Ray { Vec o, d; Ray(Vec o_, Vec d_) : o(o_), d(d_) {} }; | |
| enum Refl_t { DIFF, SPEC, REFR }; // material types, used in radiance() | |
| struct Sphere { | |
| double rad; // radius | |
| Vec p, e, c; // position, emission, color | |
| Refl_t refl; // reflection type (DIFFuse, SPECular, REFRactive) | |
| Sphere(double rad_, Vec p_, Vec e_, Vec c_, Refl_t refl_): | |
| rad(rad_), p(p_), e(e_), c(c_), refl(refl_) {} | |
| double intersect(const Ray &r) const { // returns distance, inf if nohit | |
| Vec op = p-r.o; // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0 | |
| double t, eps=1e-4, b=op.dot(r.d), det=b*b-op.dot(op)+rad*rad; | |
| if (det<0) return INFINITY; else det=sqrt(det); | |
| return (t=b-det)>eps ? t : ((t=b+det)>eps ? t : INFINITY); | |
| } | |
| }; | |
| Sphere spheres[] = {//Scene: radius, position, emission, color, material | |
| Sphere(1e5, Vec(-1e5+1,40.8,81.6), Vec(),Vec(.75,.25,.25),DIFF),//Left | |
| Sphere(1e5, Vec( 1e5+99,40.8,81.6),Vec(),Vec(.25,.25,.75),DIFF),//Rght | |
| Sphere(1e5, Vec(50,40.8,-1e5), Vec(),Vec(.75,.75,.75),DIFF),//Back | |
| Sphere(1e5, Vec(50,40.8, 1e5+296), Vec(),Vec(), DIFF),//Frnt | |
| Sphere(1e5, Vec(50,-1e5, 81.6), Vec(),Vec(.75,.75,.75),DIFF),//Botm | |
| Sphere(1e5, Vec(50, 1e5+81.6,81.6),Vec(),Vec(.75,.75,.75),DIFF),//Top | |
| Sphere(16.5,Vec(27,16.5,47), Vec(),Vec(1,1,1)*.999, SPEC),//Mirr | |
| Sphere(16.5,Vec(73,16.5,78), Vec(),Vec(1,1,1)*.999, REFR),//Glas | |
| // Sphere(600, Vec(50,681.6-.27,81.6),Vec(12,12,12), Vec(), DIFF),//Lite | |
| Sphere(1.5, Vec(50,81.6-16.5,81.6),Vec(4,4,4)*100, 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, double max_dist=INFINITY){ | |
| size_t n=sizeof(spheres)/sizeof(Sphere); double d; t=max_dist; | |
| for(size_t i=n;i>0;) if(d=spheres[--i].intersect(r), d<t){t=d;id=i;} | |
| return t<max_dist; | |
| } | |
| struct Random { | |
| unsigned short Xi[3]; | |
| Random(unsigned long seed) { Xi[0] = seed>>32; Xi[1] = seed>>16; Xi[2] = seed; } | |
| inline double uniform01() { return erand48(Xi); } | |
| Vec uniform_unit_direction() { | |
| double r1=2*M_PI*uniform01(), r2=1-2*uniform01(); | |
| return Vec(cos(r1)*sqrt(1-r2*r2), sin(r1)*sqrt(1-r2*r2), r2); | |
| } | |
| Vec cos_weighted_hemisphere_direction(const Vec& normal) { | |
| double r1=2*M_PI*uniform01(), r2=uniform01(), r2s=sqrt(r2); | |
| Vec w=normal, u=((fabs(w.x)>.1?Vec(0,1,0):Vec(1,0,0))%w).norm(), v=w%u; | |
| return u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2); | |
| } | |
| }; | |
| inline double Gschlick(const Vec& v, const Vec& n, double k) { | |
| double dot = n.dot(v); | |
| return dot / (dot * (1 - k) + k); | |
| }; | |
| inline double G_GGX(const Vec& v, const Vec& l, const Vec& n, double k) { | |
| return Gschlick(v, n, k) * Gschlick(l, n, k); | |
| }; | |
| struct CameraScreenParam { | |
| const Ray* cam; | |
| const Vec icx; | |
| const Vec icy; | |
| int w, h; | |
| CameraScreenParam(const Ray* cam_, const Vec& cx, const Vec& cy, int w, int h) | |
| : cam(cam_), icx(cx*(1/cx.dot(cx))), icy(cy*(1/cy.dot(cy))), w(w), h(h) {} | |
| inline int pinhole_sensor_index(const Vec& x, const Vec& nl) const { | |
| Vec V = cam->o-x; double dist = V.length(); V = V * (1.0 / dist); | |
| if (nl.dot(V) <= 0) return -1; | |
| double VdotZ = -V.dot(cam->d); if (VdotZ <= 0) return -1; | |
| Vec sensor_pos = V * (1.0 / VdotZ); | |
| int ix = floor((0.5 - sensor_pos.dot(icx)) * w); | |
| int iy = floor((0.5 - sensor_pos.dot(icy)) * h); | |
| if (ix < 0 || ix >= w || iy < 0 || iy >= h) return -1; | |
| double t; int id; if (intersect(Ray(x, V), t, id, dist)) return -1; | |
| return (h-iy-1)*w+ix; | |
| } | |
| }; | |
| inline Vec connect_to_camera( | |
| const Vec& x, const Vec& nl, const Vec& n, enum Refl_t refl, const Vec& L, const Vec& fl, | |
| CameraScreenParam* camera_screen, Vec* c, Random* rng | |
| ) { | |
| const Ray& cam = *camera_screen->cam; | |
| Vec V = cam.o-x; double dist = V.length(); V = V * (1.0 / dist); // カメラ方向ベクトルと距離 | |
| Vec nextdir; double brdf = 0; bool refracted = false; | |
| switch (refl) { | |
| case DIFF: | |
| brdf = M_1_PI; // Diffuse BRDF | |
| nextdir = rng->cos_weighted_hemisphere_direction(nl); | |
| break; | |
| case REFR: | |
| { | |
| bool into = n.dot(nl)>0; // Ray from outside going in? | |
| double nc=1, nt=1.5, nnt=into?nc/nt:nt/nc, ddn=-L.dot(nl), cos2t; | |
| if ((cos2t=1-nnt*nnt*(1-ddn*ddn))>=0){ | |
| Vec tdir = (L*(-nnt) - n*((into?1:-1)*(ddn*nnt+sqrt(cos2t)))).norm(); | |
| double a=nt-nc, b=nt+nc, R0=a*a/(b*b), c = 1-(into?-ddn:tdir.dot(n)); | |
| double Re=R0+(1-R0)*c*c*c*c*c; | |
| if (rng->uniform01()>=Re) { | |
| refracted = true; | |
| nextdir = tdir; | |
| // TODO: 透過材質のBSDF | |
| // 無理やりBlinn-Phongを使う | |
| Vec L = nl * (-2 * nl.dot(nextdir)) - nextdir; // 入射方向:屈折ベクトルの法線方向以外を反転したベクトル | |
| Vec H = (V + L).norm(); // ハーフベクトル | |
| const double shininess = 100; | |
| double coeff = (shininess + 1) / (4 * M_PI); // 修正Phongの正規化係数:分母が2じゃなく4なのは不明 | |
| double HdotN = fmax(0.0, H.dot(nl)); | |
| brdf = coeff * pow(HdotN, shininess); | |
| break; | |
| } | |
| // フレネル反射:下の反射に合流 | |
| } // else 全反射:下の反射に合流 | |
| } | |
| // Fallthrough | |
| case SPEC: | |
| { | |
| nextdir = nl*2*nl.dot(L)-L; // 反射ベクトル | |
| const double roughness = 0.3, alpha = roughness * roughness, alpha2 = alpha * alpha; | |
| Vec H = (V + L).norm(); // ハーフベクトル | |
| double HdotN = fmax(0.0, H.dot(nl)); | |
| double LdotN = fmax(0.0, L.dot(nl)); | |
| double VdotN = fmax(0.0, V.dot(nl)); | |
| double D = (alpha * alpha) / (M_PI * pow((HdotN * HdotN) * (alpha2 - 1) + 1, 2)); | |
| double k = alpha / 2; | |
| double G_1 = G_GGX(V, L, nl, k); // こっちだとラフネスが低い場合に暗くなりすぎる? | |
| double G_2 = Gschlick(V, nl, k); // こっちだとラフネスが高い場合に光源からの輪郭部がおかしい | |
| double G = (G_1 - G_2) * roughness + G_2; // ブレンドさせてみる | |
| double F = 1; | |
| brdf = (F * D * G) / (4 * LdotN * VdotN); // GGX | |
| } | |
| break; | |
| } | |
| if (int idx = camera_screen->pinhole_sensor_index(x, nl * (refracted?-1.0:1.0)); idx >= 0) { | |
| double ZdotV = -cam.d.dot(V); | |
| double VdotN = fabs(V.dot(nl)); // 透過の場合も考慮して絶対値が必要 | |
| double G = VdotN / (dist * dist); | |
| double Gsensor = pow(1.0 / ZdotV, 3); | |
| Vec cc = fl * (brdf * G * Gsensor); | |
| c[idx] = c[idx] + cc; | |
| } | |
| return nextdir; | |
| } | |
| void shot_light(const Sphere& light, CameraScreenParam* camera_screen, Vec* c, Random* rng) { | |
| Vec n = rng->uniform_unit_direction(), nl = n; | |
| Vec x = n * light.rad + light.p; | |
| double light_area = 4 * M_PI * light.rad * light.rad; | |
| Vec fl = light.e * (light_area * M_PI); | |
| enum Refl_t refl = DIFF; | |
| Ray r(x, n * -1); | |
| for (int depth=0; ; ++depth){ | |
| Vec L = r.d * -1; // 入射方向ベクトル | |
| Vec nextdir = connect_to_camera(x, nl, n, refl, L, fl, camera_screen, c, rng); | |
| r = Ray(x, nextdir); | |
| double t; // distance to intersection | |
| int id=0; // id of intersected object | |
| if (!intersect(r, t, id)) break; // if miss, return black | |
| const Sphere &obj = spheres[id]; // the hit object | |
| x=r.o+r.d*t; n=(x-obj.p).norm(); nl=n.dot(r.d)<0?n:n*-1; Vec f=obj.c; | |
| double p = f.x>f.y && f.x>f.z ? f.x : f.y>f.z ? f.y : f.z; // max refl | |
| if (depth>2) { if (rng->uniform01()<p) f=f*(1/p); else break; } //R.R. | |
| fl = fl.mult(f); | |
| refl = obj.refl; | |
| } | |
| } | |
| Sphere& find_light() { | |
| for (auto& sphere : spheres) | |
| if (sphere.e.x + sphere.e.y + sphere.e.z > 0) | |
| return sphere; | |
| throw "No light in the scene"; | |
| } | |
| int main(int argc, char *argv[]){ | |
| int w=640, h=480; long samps = argc==2 ? atol(argv[1]) : 1; | |
| double fov = atan(0.5135 / 2) * 2; // deg2rad(28.8) | |
| Ray cam(Vec(50,52,295.6), Vec(0,-0.042612,-1).norm()); | |
| Vec cx=Vec(w*tan(fov/2)*2/h,0,0), cy=(cx%cam.d).norm()*(tan(fov/2)*2); | |
| CameraScreenParam camera_screen(&cam, cx, cy, w, h); | |
| const Sphere& light = find_light(); | |
| #if !defined(USE_OPENMP) | |
| const int num_threads = 1; | |
| Vec *c=new Vec[w*h]; | |
| #else | |
| const int num_threads = 16; | |
| omp_set_num_threads(num_threads); | |
| Vec **cc=new Vec*[num_threads]; | |
| for (int i=0; i<num_threads; ++i) | |
| cc[i] = new Vec[w*h]; | |
| # pragma omp parallel for schedule(dynamic, 1) | |
| #endif | |
| for (long s=0; s<samps; s++){ | |
| #ifdef USE_OPENMP | |
| Vec *c=cc[omp_get_thread_num()]; | |
| if (omp_get_thread_num() == 0) | |
| #endif | |
| { | |
| long ss = (s + 1) * num_threads; | |
| fprintf(stderr, "\rRendering (%ld/%ld sample) %5.2f%%", ss, samps, 100.*ss/samps); | |
| } | |
| Random rng(s); | |
| shot_light(light, &camera_screen, c, &rng); | |
| } | |
| fprintf(stderr, "\nDone\n"); | |
| FILE *f = fopen("image.ppm", "w"); | |
| fprintf(f, "P3\n%d %d\n%d\n", w, h, 255); | |
| const double sensitivity = pow(h / (2 * tan(fov / 2)), 2); | |
| const double scale = sensitivity / samps; | |
| for (int i=0; i<w*h; i++) { | |
| #ifdef USE_OPENMP | |
| Vec pixel = cc[0][i]; // スレッド別に計算した結果を合算 | |
| for (int j=1; j < num_threads; ++j) | |
| pixel = pixel + cc[j][i]; | |
| #else | |
| const Vec& pixel = c[i]; | |
| #endif | |
| fprintf(f,"%d %d %d ", toInt(pixel.x*scale), toInt(pixel.y*scale), toInt(pixel.z*scale)); | |
| } | |
| fclose(f); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment