AOBenchをF#で実装した。
http://lucille.atso-net.jp/aobench/ で開催されているAOBenchをF#にて実装しました。
実装としてはC#版からの多くを拝借して移植。
あんまり関数型言語っぽくしてない。画像処理のような大きな配列を書き換える(リストから別のリストを作るのはやりやすいんだけどね)作業は、手続き型的に書いたほうがいいと思う。
ヴァリアントとマッチをうまくつかうと素直な流れで書くことができますね。
速度はCore2Duo T8100 2.1GHzで約9.4秒でした。
以下ソース
#light open System open System.Drawing open System.Drawing.Imaging open System.IO open System.Runtime.InteropServices open System.Diagnostics let IMAGE_WIDTH = 256; let IMAGE_HEIGHT = 256; let NSUBSAMPLES = 2; let NAO_SAMPLES = 8; // # of sample rays for ambient occlusion ///-------------------------------------------------------------------------------------- type Vec(x:double, y:double, z:double) = member v.x = x member v.y = y member v.z = z member v.length = sqrt(x*x+y*y+z*z) let vdot(v0:Vec, v1:Vec) = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z let vadd(v0:Vec, v1:Vec) = Vec(v0.x+v1.x,v0.y+v1.y,v0.z+v1.z) let vsub(v0:Vec, v1:Vec) = Vec(v0.x-v1.x,v0.y-v1.y,v0.z-v1.z) let vcross(v0:Vec, v1:Vec) = Vec(v0.y * v1.z - v0.z * v1.y,v0.z * v1.x - v0.x * v1.z,v0.x * v1.y - v0.y * v1.x) let vnormalize(v:Vec) = let l = 1.0/v.length in Vec(v.x*l,v.y*l,v.z*l) ///-------------------------------------------------------------------------------------- type HitInfo = | Hit of double * Vec * Vec (* distance, position, normal *) | NoHit ///-------------------------------------------------------------------------------------- type Ray(o:Vec,d:Vec) = member r.org = o member r.dir = d member r.pos t = vadd(r.org, Vec(r.dir.x*t,r.dir.y*t,r.dir.z*t)) ///-------------------------------------------------------------------------------------- type IGeometry = abstract intersect: Ray * double -> HitInfo ///-------------------------------------------------------------------------------------- type Sphere(c:Vec,r:double) = member s.c = c member s.r2 = r * r interface IGeometry with member s.intersect(r:Ray, dist:double)= let rs = vsub(r.org,s.c) in let B = vdot(rs,r.dir) in let C = vdot(rs,rs) - s.r2 in let D = B*B-C in if D > 0.0 then let t = -B - sqrt(D) in if (t > 0.0) && (t < dist) then let p = r.pos(t) in Hit(t,p,vsub(p,s.c)) else NoHit else NoHit ///-------------------------------------------------------------------------------------- type Plane(p:Vec,n:Vec) = member s.p = p member s.n = n interface IGeometry with member s.intersect(r:Ray, dist:double)= let d = -vdot(s.p ,n) in let v = vdot(r.dir,n) in if (abs(v) < 1.0e-6) then NoHit else let t = -(vdot(r.org,s.n) + d) / v in if ( (t > 0.) && (t < dist) ) then let p = r.pos(t) in Hit(t,p,n) else NoHit ///-------------------------------------------------------------------------------------- let rnd = System.Random();; let spheres = [Sphere(Vec(-2.0,0.0,-3.5),0.5) ; Sphere(Vec(-0.5, 0.0, -3.0), 0.5) ; Sphere(Vec(1.0, 0.0, -2.2), 0.5)];; let planes = [Plane(Vec(0.0, -0.5, 0.0), Vec(0.0, 1.0, 0.0))];; ///-------------------------------------------------------------------------------------- let intersection_geometry(g:IGeometry, r:Ray, t:double) = g.intersect(r,t); let rec intersection_list(lst:'a list, info:HitInfo, r:Ray, t:double) = match lst with | [] -> info | y::ys -> let h = intersection_geometry(y,r,t) in match h with | Hit(d,p,n) -> intersection_list(ys,h,r,d) | NoHit -> intersection_list(ys,info,r,t) let intersection(r:Ray, t:double) = let h = intersection_list(spheres,NoHit,r,t) in match h with |Hit(d,p,n) -> intersection_list(planes,h,r,d) |NoHit -> intersection_list(planes,h,r,t) ///-------------------------------------------------------------------------------------- let ortho_basis(normal:Vec) = let v = Vec(0., 0., 0.) let orthoBasis = Array.create 3 v orthoBasis.[2] <- normal let tmp = if (normal.x < 0.6) && (normal.x > -0.6) then Vec(1.0,0.0,0.0) elif (normal.y < 0.6) && (normal.y > -0.6) then Vec(0.0,1.0,0.0) elif (normal.z < 0.6) && (normal.z > -0.6) then Vec(0.0,0.0,1.0) else Vec(1.0,0.0,0.0) orthoBasis.[0] <- vnormalize(vcross(tmp, orthoBasis.[2])) orthoBasis.[1] <- vnormalize(vcross(orthoBasis.[2], orthoBasis.[0])) orthoBasis let ambient_occlusion(p:Vec, n:Vec) = let ntheta = NAO_SAMPLES let nphi = NAO_SAMPLES let eps = 0.0001 let rp = Vec(p.x+eps*n.x,p.y+eps*n.y,p.z+eps*n.z) let basis = ortho_basis(n) let mutable count = 0 for j=0 to ntheta-1 do for i=0 to nphi-1 do let theta = Math.Sqrt(rnd.NextDouble()) let phi = 2.0 * Math.PI * rnd.NextDouble() let x = Math.Cos(phi) * theta let y = Math.Sin(phi) * theta let z = Math.Sqrt(1.0 - theta * theta) let dx = x*basis.[0].x + y * basis.[1].x + z * basis.[2].x let dy = x*basis.[0].y + y * basis.[1].y + z * basis.[2].y let dz = x*basis.[0].z + y * basis.[1].z + z * basis.[2].z let rd = Vec(dx,dy,dz) let r = Ray(rp,rd) let h = intersection(r,10000.0) let pc = match h with | Hit(t,p,n) -> 1 | NoHit -> 0 count <- count + pc let occlusionRatio = double(ntheta * nphi - count) / (double)(ntheta * nphi) Vec(occlusionRatio,occlusionRatio,occlusionRatio) let shoot(x:double,y:double) = let t = 10000.0 in let r = Ray(Vec(0.,0.,0.),vnormalize(Vec(x,y,-1.))) in match intersection(r,t) with |Hit(t,p,n) -> ambient_occlusion(p,n) |NoHit -> Vec(0.,0.,0.) ///-------------------------------------------------------------------------------------- let save_image(pngFilePath:string, width:int, height:int, byteImage:byte array) = let bmp = new Bitmap(width, height,PixelFormat.Format24bppRgb) let bd = bmp.LockBits(new Rectangle(new Point(), bmp.Size), ImageLockMode.WriteOnly, bmp.PixelFormat); Marshal.Copy( byteImage, 0, bd.Scan0, bd.Stride * bmp.Height); bmp.UnlockBits(bd); bmp.Save(pngFilePath); ///-------------------------------------------------------------------------------------- let clamp(c:double) = if c < 0.0 then byte(0) elif c > 1.0 then byte(255) else byte(c*255.) let render(byteImage:byte array, width:int, height:int, nsubsamples:int) = let w2 = double(width)*0.5 let h2 = double(height)*0.5 let total = width*height*3 let img = Array.create total 0.0 for y=0 to height-1 do for x=0 to width-1 do for v=0 to nsubsamples-1 do for u=0 to nsubsamples-1 do let px = (double(x) + (double(u) / double(nsubsamples)) - w2)/w2 let py = -(double(y) + (double(v) / double(nsubsamples)) - h2)/h2 let ao = shoot(px,py) let idx = 3*(y*width+x) let r = img.[idx+0] let g = img.[idx+1] let b = img.[idx+2] img.[idx+0] <- r+ao.x img.[idx+1] <- g+ao.y img.[idx+2] <- b+ao.z let total = 1.0/double(nsubsamples*nsubsamples) let idx = 3*(y*width+x) let r = img.[idx+0] let g = img.[idx+1] let b = img.[idx+2] img.[idx+0] <- r*total img.[idx+1] <- g*total img.[idx+2] <- b*total byteImage.[idx+0] <- clamp(img.[idx+0]) byteImage.[idx+1] <- clamp(img.[idx+1]) byteImage.[idx+2] <- clamp(img.[idx+2]) let run() = let w = IMAGE_WIDTH let h = IMAGE_HEIGHT let total = w*h*3 let b0 = byte(0) let img = Array.create total b0 let sw = Stopwatch() sw.Start() render(img, w, h, NSUBSAMPLES) sw.Stop() Console.WriteLine("Elapsed: {0}[sec]", double(sw.ElapsedMilliseconds) / 1000.0) save_image("ao.png",w,h,img) Console.WriteLine("Hit any key....") let c = Console.ReadKey() () ///-------------------------------------------------------------------------------------- run();; ///--------------------------------------------------------------------------------------