root/lang/haskell/aobench/ao.hs @ 30638

Revision 30638, 6.4 kB (checked in by mokehehe, 4 years ago)

高階関数を使ってもっとわかりやすく

Line 
1
2module Main () where
3
4import System (getArgs)
5import System.IO (withBinaryFile, IOMode(..), hPutStrLn)
6import System.Random.Mersenne
7import Data.Maybe (catMaybes, listToMaybe)
8import Data.List (sort)
9import qualified Data.ByteString.Char8 as B
10
11data Vec = Vec {
12        x :: Double,
13        y :: Double,
14        z :: Double
15        } deriving (Eq)
16
17data Isect = Isect {
18        isect_t :: Double,
19        isect_p :: Vec,
20        isect_n :: Vec
21        } deriving (Eq)
22
23instance Ord Isect where
24        i1 < i2         = isect_t i1 < isect_t i2
25        i1 > i2         = isect_t i1 > isect_t i2
26        i1 <= i2        = isect_t i1 <= isect_t i2
27        i1 >= i2        = isect_t i1 >= isect_t i2
28
29data Sphere = Sphere {
30        sphere_center :: Vec,
31        sphere_radius :: Double
32        }
33
34data Plane = Plane {
35        plane_p :: Vec,
36        plane_n :: Vec
37        }
38
39data Ray = Ray {
40        ray_org :: Vec,
41        ray_dir :: Vec
42        }
43
44sq x = x * x
45
46clamp lower upper = min upper . max lower
47
48drand48 = getStdGen >>= random
49
50justIf cond val
51        | cond          = Just val
52        | otherwise     = Nothing
53
54zerovec = Vec { x = 0, y = 0, z = 0 }
55
56v0 `vadd` v1 = Vec (x v0 + x v1) (y v0 + y v1) (z v0 + z v1)
57v0 `vsub` v1 = Vec (x v0 - x v1) (y v0 - y v1) (z v0 - z v1)
58v0 `vscale` s = Vec (x v0 * s) (y v0 * s) (z v0 * s)
59
60v0 `vdot` v1 = (x v0 * x v1) + (y v0 * y v1) + (z v0 * z v1)
61
62v0 `vcross` v1 = Vec cx cy cz
63        where
64                cx = y v0 * z v1 - z v0 * y v1
65                cy = z v0 * x v1 - x v0 * z v1
66                cz = x v0 * y v1 - y v0 * x v1
67
68vlength c = sqrt $ c `vdot` c
69
70vnormalize c
71        | l > 1.0e-17   = c `vscale` (1.0 / l)
72        | otherwise             = c
73        where
74                l = vlength c
75
76ray_sphere_intersect ray sphere =
77        justIf (d > 0.0 && t > 0.0) isect
78        where
79                rs = ray_org ray `vsub` sphere_center sphere
80                b = rs `vdot` ray_dir ray
81                c = rs `vdot` rs - sq (sphere_radius sphere)
82                d = b * b - c
83                t = -b - sqrt d
84                hitpos = ray_org ray `vadd` (ray_dir ray `vscale` t)
85                isect = Isect {
86                        isect_t = t,
87                        isect_p = hitpos,
88                        isect_n = vnormalize $ hitpos `vsub` sphere_center sphere
89                        }
90
91ray_plane_intersect ray plane =
92        justIf (abs v >= 1.0e-17 && t > 0) isect
93        where
94                d = -(plane_p plane `vdot` plane_n plane)
95                v = ray_dir ray `vdot` plane_n plane
96                t = -(ray_org ray `vdot` plane_n plane + d) / v
97                isect = Isect {
98                        isect_t = t,
99                        isect_p = ray_org ray `vadd` (ray_dir ray `vscale` t),
100                        isect_n = plane_n plane
101                        }
102
103orthoBasis n = (basis0, basis1, basis2)
104        where
105                basis2 = n
106                basis1'
107                        | x n < 0.6 && x n > -0.6       = Vec { x = 1, y = 0, z = 0 }
108                        | y n < 0.6 && y n > -0.6       = Vec { x = 0, y = 1, z = 0 }
109                        | z n < 0.6 && z n > -0.6       = Vec { x = 0, y = 0, z = 1 }
110                        | otherwise                                     = Vec { x = 1, y = 0, z = 0 }
111                basis0 = vnormalize $ basis1' `vcross` basis2
112                basis1 = vnormalize $ basis2 `vcross` basis0
113
114hemisphereDirectionRay p (basis0, basis1, basis2) r1 r2 =
115        Ray { ray_org = p, ray_dir = r }
116        where
117                theta = sqrt r1
118                phi = 2.0 * pi * r2
119                v = Vec {
120                        x = cos phi * theta,
121                        y = sin phi * theta,
122                        z = sqrt $ 1.0 - theta * theta
123                        }
124                -- local -> global
125                r = Vec {
126                        x = x v * x basis0 + y v * x basis1 + z v * x basis2,
127                        y = x v * y basis0 + y v * y basis1 + z v * y basis2,
128                        z = x v * z basis0 + y v * z basis1 + z v * z basis2
129                        }
130
131data Object = ObjSphere Sphere | ObjPlane Plane
132
133findRayObjIntersect ray (ObjSphere sphere) = ray_sphere_intersect ray sphere
134findRayObjIntersect ray (ObjPlane  plane)  = ray_plane_intersect  ray plane
135
136ambient_occlusion objs nao_samples isect = do
137        randomRays <- mapM chooseRandomRay samples
138        let occludedNum = length $ filter id $ map isHitOccluder randomRays
139        return $ vscale white $ calcOccludedRatio occludedNum
140        where
141                ntheta = nao_samples
142                nphi = nao_samples
143                eps = 0.0001
144                p = isect_p isect `vadd` (isect_n isect `vscale` eps)
145                basis = orthoBasis $ isect_n isect
146                samples = [(j, i) | j <- [0 .. ntheta-1], i <- [0 .. nphi-1]]
147                chooseRandomRay _ = do
148                        r1 <- drand48
149                        r2 <- drand48
150                        return $ hemisphereDirectionRay p basis r1 r2
151                isHitOccluder ray =
152                        not $ null $ catMaybes $ map (findRayObjIntersect ray) objs
153                calcOccludedRatio occludedSampleNum =
154                        (ntheta * nphi - toEnum occludedSampleNum) / (ntheta * nphi)
155                white = Vec { x = 1, y = 1, z = 1 }
156
157screenRay nsubsamples w h x y u v =
158        Ray { ray_org = zerovec, ray_dir = vnormalize (Vec { x = px, y = py, z = -1 }) }
159        where
160                px = (x + (u / nsubsamples) - (w / 2.0)) / (w / 2.0)
161                py = -(y + (v / nsubsamples) - (h / 2.0)) / (h / 2.0)
162
163findNearestIsect objs ray =
164        listToMaybe $ sort $ catMaybes $ map (findRayObjIntersect ray) objs
165
166data Scene = Scene {
167        scene_objs :: [Object]
168        }
169
170raytrace shading objs ray = do
171        shading $ findNearestIsect objs ray
172
173oversampling trace makeRay nsubsamples = do
174        mapM trace subrays >>= return . averageColor
175        where
176                subpixels = [(u, v) | v <- [0 .. nsubsamples-1], u <- [0 .. nsubsamples-1]]
177                subrays = map (uncurry makeRay) subpixels
178                averageColor = flip vscale (1.0 / sq nsubsamples) . foldl vadd zerovec
179
180calcPixelColor trace w h nsubsamples x y = do
181        oversampling trace makeRay nsubsamples
182        where
183                makeRay = screenRay nsubsamples w h x y
184
185render w h renderPixel = do
186        fimg <- mapM (uncurry renderPixel) screenPixels
187        let img = map fpcol2icol fimg
188        return img
189        where
190                screenPixels = [(x, y) | y <- [0 .. h-1], x <- [0 .. w-1]]
191                fpcol2icol vec = (f2i (x vec), f2i (y vec), f2i (z vec))
192                f2i = clamp 0 255 . floor . (* 255.99)
193
194rendering scene w h nsubsamples nao_samples = do
195        render w h $ calcPixelColor (raytrace shading objs) w h nsubsamples
196        where
197                shading Nothing = return zerovec
198                shading (Just isect) = ambient_occlusion objs nao_samples isect
199                objs = scene_objs scene
200
201saveppm fname w h img =
202        withBinaryFile fname WriteMode $ \fp -> do
203                hPutStrLn fp "P6"
204                hPutStrLn fp $ show (floor w) ++ " " ++ show (floor h)
205                hPutStrLn fp "255"
206                B.hPut fp $ B.pack $ map toEnum $ concatMap (\(r,g,b) -> [r,g,b]) img
207
208init_scene =
209        Scene {
210                scene_objs = [
211                        ObjSphere (Sphere { sphere_center = Vec { x = -2.0, y = 0.0, z = -3.5 }, sphere_radius = 0.5 }),
212                        ObjSphere (Sphere { sphere_center = Vec { x = -0.5, y = 0.0, z = -3.0 }, sphere_radius = 0.5 }),
213                        ObjSphere (Sphere { sphere_center = Vec { x =  1.0, y = 0.0, z = -2.2 }, sphere_radius = 0.5 }),
214                        ObjPlane  (Plane  { plane_p = Vec { x = 0.0, y = -0.5, z = 0.0 }, plane_n = Vec { x = 0.0, y = 1.0, z = 0.0 } })
215                        ]
216                }
217
218getParam idx defaultVal args
219        | length args > idx     = args !! idx
220        | otherwise                     = defaultVal
221
222main = do
223        args <- getArgs
224        let width  = read $ getParam 0 "256" args
225        let height = read $ getParam 1 "256" args
226        let nsubsamples = read $ getParam 2 "2" args
227        let nao_samples = read $ getParam 3 "8" args
228
229        img <- rendering init_scene width height nsubsamples nao_samples
230        saveppm "ao.ppm" width height img
Note: See TracBrowser for help on using the browser.