├── image.jpg ├── .gitignore ├── LICENSE.txt ├── README.md ├── smallpt.cpp └── smallpt.swift /image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackpal/smallpt-swift/HEAD/image.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore ppm files 2 | *.ppm 3 | 4 | # Ignore compiled c++ version 5 | smallpt 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | 3 | Copyright (c) 2006-2008 Kevin Beason (kevin.beason@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Small Path Tracer in Swift 2 | 3 | This is a straightforward translation of http://www.kevinbeason.com/smallpt/ 4 | to Swift. 5 | 6 | # Example Image 7 | 8 | ![An example output image](image.jpg) 9 | 10 | # Setup 11 | 12 | + Install Xcode 6.1 13 | + Install a PPM image viewing program such as 14 | [Toy Viewer](https://itunes.apple.com/us/app/toyviewer/id414298354?mt=12) 15 | 16 | # Build and run 17 | 18 | xcrun swiftc -O -sdk `xcrun --show-sdk-path --sdk macosx` smallpt.swift 19 | ./smallpt 20 | open image.ppm 21 | 22 | You can also run the swift file directly. And in either case you can 23 | supply the number of samples per pixel. (Default = 4 samples per pixel.): 24 | 25 | ./smallpt.swift 512 && open image.ppm 26 | 27 | # Performance 28 | 29 | On a MacBook Pro (Retina, 15-inch, Early 2013): 30 | 31 | Language | time (seconds) 32 | ----------------------|--------------------------------------------------- 33 | C++ single threaded | 5.6 s 34 | Swift single threaded | 13.8 s (of which 5.6 s is rendering, 8 s is file output) 35 | Swift GCD | 9.8 s (of which 1 s is rendering, 8 s is file output) 36 | 37 | ## Discussion 38 | 39 | - Swift file output seems to be very slow compared to C. 40 | - partly because it is unbuffered 41 | - partly because it is converting from Unicode to UTF8 42 | -------------------------------------------------------------------------------- /smallpt.cpp: -------------------------------------------------------------------------------- 1 | #include // smallpt, a Path Tracer by Kevin Beason, 2008 2 | #include // Make : g++ -O3 -fopenmp smallpt.cpp -o smallpt 3 | #include // Remove "-fopenmp" for g++ version < 4.2 4 | struct Vec { // Usage: time ./smallpt 5000 && xv image.ppm 5 | double x, y, z; // position, also color (r,g,b) 6 | Vec(double x_=0, double y_=0, double z_=0){ x=x_; y=y_; z=z_; } 7 | Vec operator+(const Vec &b) const { return Vec(x+b.x,y+b.y,z+b.z); } 8 | Vec operator-(const Vec &b) const { return Vec(x-b.x,y-b.y,z-b.z); } 9 | Vec operator*(double b) const { return Vec(x*b,y*b,z*b); } 10 | Vec mult(const Vec &b) const { return Vec(x*b.x,y*b.y,z*b.z); } 11 | Vec& norm(){ return *this = *this * (1/sqrt(x*x+y*y+z*z)); } 12 | double dot(const Vec &b) const { return x*b.x+y*b.y+z*b.z; } // cross: 13 | Vec operator%(Vec&b){return Vec(y*b.z-z*b.y,z*b.x-x*b.z,x*b.y-y*b.x);} 14 | }; 15 | struct Ray { Vec o, d; Ray(Vec o_, Vec d_) : o(o_), d(d_) {} }; 16 | enum Refl_t { DIFF, SPEC, REFR }; // material types, used in radiance() 17 | struct Sphere { 18 | double rad; // radius 19 | Vec p, e, c; // position, emission, color 20 | Refl_t refl; // reflection type (DIFFuse, SPECular, REFRactive) 21 | Sphere(double rad_, Vec p_, Vec e_, Vec c_, Refl_t refl_): 22 | rad(rad_), p(p_), e(e_), c(c_), refl(refl_) {} 23 | double intersect(const Ray &r) const { // returns distance, 0 if nohit 24 | Vec op = p-r.o; // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0 25 | double t, eps=1e-4, b=op.dot(r.d), det=b*b-op.dot(op)+rad*rad; 26 | if (det<0) return 0; else det=sqrt(det); 27 | return (t=b-det)>eps ? t : ((t=b+det)>eps ? t : 0); 28 | } 29 | }; 30 | Sphere spheres[] = {//Scene: radius, position, emission, color, material 31 | Sphere(1e5, Vec( 1e5+1,40.8,81.6), Vec(),Vec(.75,.25,.25),DIFF),//Left 32 | Sphere(1e5, Vec(-1e5+99,40.8,81.6),Vec(),Vec(.25,.25,.75),DIFF),//Rght 33 | Sphere(1e5, Vec(50,40.8, 1e5), Vec(),Vec(.75,.75,.75),DIFF),//Back 34 | Sphere(1e5, Vec(50,40.8,-1e5+170), Vec(),Vec(), DIFF),//Frnt 35 | Sphere(1e5, Vec(50, 1e5, 81.6), Vec(),Vec(.75,.75,.75),DIFF),//Botm 36 | Sphere(1e5, Vec(50,-1e5+81.6,81.6),Vec(),Vec(.75,.75,.75),DIFF),//Top 37 | Sphere(16.5,Vec(27,16.5,47), Vec(),Vec(1,1,1)*.999, SPEC),//Mirr 38 | Sphere(16.5,Vec(73,16.5,78), Vec(),Vec(1,1,1)*.999, REFR),//Glas 39 | Sphere(600, Vec(50,681.6-.27,81.6),Vec(12,12,12), Vec(), DIFF) //Lite 40 | }; 41 | inline double clamp(double x){ return x<0 ? 0 : x>1 ? 1 : x; } 42 | inline int toInt(double x){ return int(pow(clamp(x),1/2.2)*255+.5); } 43 | inline bool intersect(const Ray &r, double &t, int &id){ 44 | double n=sizeof(spheres)/sizeof(Sphere), d, inf=t=1e20; 45 | for(int i=int(n);i--;) if((d=spheres[i].intersect(r))&&df.y && f.x>f.z ? f.x : f.y>f.z ? f.y : f.z; // max refl 55 | if (++depth>5) if (erand48(Xi).1?Vec(0,1):Vec(1))%w).norm(), v=w%u; 59 | Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm(); 60 | return obj.e + f.mult(radiance(Ray(x,d),depth,Xi)); 61 | } else if (obj.refl == SPEC) // Ideal SPECULAR reflection 62 | return obj.e + f.mult(radiance(Ray(x,r.d-n*2*n.dot(r.d)),depth,Xi)); 63 | Ray reflRay(x, r.d-n*2*n.dot(r.d)); // Ideal dielectric REFRACTION 64 | bool into = n.dot(nl)>0; // Ray from outside going in? 65 | double nc=1, nt=1.5, nnt=into?nc/nt:nt/nc, ddn=r.d.dot(nl), cos2t; 66 | if ((cos2t=1-nnt*nnt*(1-ddn*ddn))<0) // Total internal reflection 67 | return obj.e + f.mult(radiance(reflRay,depth,Xi)); 68 | Vec tdir = (r.d*nnt - n*((into?1:-1)*(ddn*nnt+sqrt(cos2t)))).norm(); 69 | double a=nt-nc, b=nt+nc, R0=a*a/(b*b), c = 1-(into?-ddn:tdir.dot(n)); 70 | double Re=R0+(1-R0)*c*c*c*c*c,Tr=1-Re,P=.25+.5*Re,RP=Re/P,TP=Tr/(1-P); 71 | return obj.e + f.mult(depth>2 ? (erand48(Xi)

Double { return sqrt(x*x+y*y+z*z) } 43 | func norm() -> Vec { return self * (1/self.length()) } 44 | func dot(b : Vec) -> Double { return x*b.x+y*b.y+z*b.z } 45 | }; 46 | 47 | func +(a :Vec, b:Vec) -> Vec { return Vec(x:a.x+b.x, y:a.y+b.y, z:a.z+b.z) } 48 | func -(a :Vec, b:Vec) -> Vec { return Vec(x:a.x-b.x, y:a.y-b.y, z:a.z-b.z) } 49 | func *(a :Vec, b : Double) -> Vec { return Vec(x:a.x*b, y:a.y*b, z:a.z*b) } 50 | func *(a :Vec, b : Vec) -> Vec { return Vec(x:a.x*b.x, y:a.y*b.y, z:a.z*b.z) } 51 | // cross product: 52 | func %(a :Vec, b : Vec) -> Vec{ 53 | return Vec(x:a.y*b.z-a.z*b.y, y:a.z*b.x-a.x*b.z, z:a.x*b.y-a.y*b.x) 54 | } 55 | 56 | struct Ray { let o, d : Vec; init(o : Vec, d : Vec) {self.o = o; self.d = d}} 57 | 58 | enum Refl_t { case DIFF; case SPEC; case REFR } // material types, used in radiance() 59 | 60 | struct Sphere { 61 | let rad : Double // radius 62 | let p, e, c : Vec // position, emission, color 63 | let refl : Refl_t // reflection type (DIFFuse, SPECular, REFRactive) 64 | init(rad :Double, p: Vec, e: Vec, c: Vec, refl: Refl_t) { 65 | self.rad = rad; self.p = p; self.e = e; self.c = c; self.refl = refl; } 66 | func intersect(r: Ray) -> Double { // returns distance, 0 if nohit 67 | let op = p-r.o // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0 68 | let eps = 1e-4 69 | let b = op.dot(r.d) 70 | let det = b*b-op.dot(op)+rad*rad 71 | if (det<0) { 72 | return 0 73 | } 74 | let det2=sqrt(det) 75 | let t=b-det2 76 | if t > eps { 77 | return t 78 | } 79 | let t2 = b+det2 80 | if t2 > eps {return t2} 81 | return 0 82 | } 83 | } 84 | 85 | let spheres :[Sphere] = [//Scene: radius, position, emission, color, material 86 | Sphere(rad:1e5, p:Vec(x: 1e5+1,y:40.8,z:81.6), e:Vec(), c:Vec(x:0.75,y:0.25,z:0.25), refl:Refl_t.DIFF),//Left 87 | Sphere(rad:1e5, p:Vec(x:-1e5+99,y:40.8,z:81.6),e:Vec(), c:Vec(x:0.25,y:0.25,z:0.75), refl:Refl_t.DIFF),//Rght 88 | Sphere(rad:1e5, p:Vec(x:50,y:40.8,z: 1e5), e:Vec(), c:Vec(x:0.75,y:0.75,z:0.75), refl:Refl_t.DIFF),//Back 89 | Sphere(rad:1e5, p:Vec(x:50,y:40.8,z:-1e5+170), e:Vec(), c:Vec(), refl:Refl_t.DIFF),//Frnt 90 | Sphere(rad:1e5, p:Vec(x:50,y: 1e5,z: 81.6), e:Vec(), c:Vec(x:0.75,y:0.75,z:0.75), refl:Refl_t.DIFF),//Botm 91 | Sphere(rad:1e5, p:Vec(x:50,y:-1e5+81.6,z:81.6),e:Vec(), c:Vec(x:0.75,y:0.75,z:0.75), refl:Refl_t.DIFF),//Top 92 | Sphere(rad:16.5,p:Vec(x:27,y:16.5,z:47), e:Vec(), c:Vec(x:1,y:1,z:1)*0.999, refl:Refl_t.SPEC),//Mirr 93 | Sphere(rad:16.5,p:Vec(x:73,y:16.5,z:78), e:Vec(), c:Vec(x:1,y:1,z:1)*0.999, refl:Refl_t.REFR),//Glas 94 | Sphere(rad:600, p:Vec(x:50,y:681.6-0.27,z:81.6),e:Vec(x:12,y:12,z:12), c:Vec(), refl:Refl_t.DIFF) //Lite 95 | ] 96 | func clamp(x : Double) -> Double { return x < 0 ? 0 : x > 1 ? 1 : x; } 97 | 98 | func toInt(x : Double) -> Int { return Int(pow(clamp(x),1/2.2)*255+0.5); } 99 | 100 | func intersect(r : Ray, inout t: Double, inout id: Int) -> Bool { 101 | let n = spheres.count 102 | let inf = 1e20 103 | t = inf 104 | for (var i = n-1; i >= 0; i--) { 105 | let d = spheres[i].intersect(r) 106 | if (d != 0.0 && d.alloc(3) 116 | init(a : UInt16) { 117 | pbuffer[2] = a 118 | } 119 | func next() -> Double { return erand48(pbuffer) } 120 | } 121 | 122 | func radiance(r: Ray, depthIn: Int, Xi : drand) -> Vec { 123 | var t : Double = 0 // distance to intersection 124 | var id : Int = 0 // id of intersected object 125 | if (!intersect(r, &t, &id)) {return Vec() } // if miss, return black 126 | let obj = spheres[id] // the hit object 127 | let x=r.o+r.d*t 128 | let n=(x-obj.p).norm() 129 | let nl = (n.dot(r.d) < 0) ? n : n * -1 130 | var f=obj.c 131 | let p = f.x > f.y && f.x > f.z ? f.x : f.y > f.z ? f.y : f.z; // max refl 132 | let depth = depthIn+1 133 | // Russian Roulette: 134 | if (depth>5) { 135 | // Limit depth to 150 to avoid stack overflow. 136 | if (depth < 150 && Xi.next()0.1 ? Vec(x:0, y:1, z:0) : Vec(x:1, y:0, z:0)) % w).norm() 147 | let v = w % u 148 | let d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm() 149 | return obj.e + f * radiance(Ray(o: x, d:d), depth, Xi) 150 | case Refl_t.SPEC: // Ideal SPECULAR reflection 151 | return obj.e + f * (radiance(Ray(o:x, d:r.d-n*2*n.dot(r.d)), depth, Xi)) 152 | case Refl_t.REFR: 153 | let reflRay = Ray(o:x, d:r.d-n*2*n.dot(r.d)) // Ideal dielectric REFRACTION 154 | let into = n.dot(nl)>0 // Ray from outside going in? 155 | let nc = 1, nt=1.5 156 | let nnt = into ? Double(nc) / nt : nt / Double(nc) 157 | let ddn = r.d.dot(nl) 158 | let cos2t=1-nnt*nnt*(1-ddn*ddn) 159 | if (cos2t<0) { // Total internal reflection 160 | return obj.e + f * radiance(reflRay, depth, Xi) 161 | } 162 | let tdir = (r.d * nnt - n * ((into ? 1 : -1)*(ddn*nnt+sqrt(cos2t)))).norm() 163 | let a = nt-Double(nc), b = nt+Double(nc), R0 = a*a/(b*b), c = 1-(into ? -ddn : tdir.dot(n)) 164 | let Re=R0+(1-R0)*c*c*c*c*c,Tr=1-Re,P=0.25+0.5*Re,RP=Re/P,TP=Tr/(1-P) 165 | return obj.e + f * (depth>2 ? (Xi.next()

(count:w*h, repeatedValue:Vec()) 182 | let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 183 | dispatch_apply(UInt(h), globalQueue) { 184 | let y = Int($0) 185 | let Xi = drand(a:UInt16(0xffff & (y * y * y))) 186 | let spp = samps*4 187 | let percent = 100.0 * Double(y)/Double(h-1) 188 | stderr.write(String(format:"\rRendering (%d spp) %.2f%%", spp, percent)) 189 | for (var x = 0; x < w; x++) { // Loop cols 190 | let i=(h-y-1)*w+x 191 | var r = Vec() 192 | for (var sy=0; sy<2; sy++) { // 2x2 subpixel rows 193 | for (var sx=0; sx<2; sx++) { // 2x2 subpixel cols 194 | for (var s=0; s < samps; s++) { 195 | let r1=2*Xi.next(), dx=r1<1 ? sqrt(r1)-1: 1-sqrt(2-r1) 196 | let r2=2*Xi.next(), dy=r2<1 ? sqrt(r2)-1: 1-sqrt(2-r2) 197 | let part1 = ( ( (Double(sx)+0.5 + Double(dx))/2 + Double(x))/Double(w) - 0.5) 198 | let part2 = ( ( (Double(sy)+0.5 + Double(dy))/2 + Double(y))/Double(h) - 0.5) 199 | let d = cx * part1 200 | + cy * part2 201 | + cam.d 202 | let rr = radiance(Ray(o:cam.o+d*140, d:d.norm()), 0, Xi) 203 | r = r + rr * (1.0 / Double(samps)) 204 | } // Camera rays are pushed ^^^^^ forward to start in interior 205 | } 206 | } 207 | let result = r*0.25 208 | c[i] = result 209 | } 210 | } 211 | let f = FileStream(path: "image.ppm") // Write image to PPM file. 212 | f.write("P3\n\(w) \(h)\n\(255)\n") 213 | for (var i=0; i