| 1 | |
|---|
| 2 | module Main () where |
|---|
| 3 | |
|---|
| 4 | import System (getArgs) |
|---|
| 5 | import System.IO (withBinaryFile, IOMode(..), hPutStrLn) |
|---|
| 6 | import System.Random.Mersenne |
|---|
| 7 | import Data.Maybe (catMaybes, listToMaybe) |
|---|
| 8 | import Data.List (sort) |
|---|
| 9 | import qualified Data.ByteString.Char8 as B |
|---|
| 10 | |
|---|
| 11 | data Vec = Vec { |
|---|
| 12 | x :: Double, |
|---|
| 13 | y :: Double, |
|---|
| 14 | z :: Double |
|---|
| 15 | } deriving (Eq) |
|---|
| 16 | |
|---|
| 17 | data Isect = Isect { |
|---|
| 18 | isect_t :: Double, |
|---|
| 19 | isect_p :: Vec, |
|---|
| 20 | isect_n :: Vec |
|---|
| 21 | } deriving (Eq) |
|---|
| 22 | |
|---|
| 23 | instance 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 | |
|---|
| 29 | data Sphere = Sphere { |
|---|
| 30 | sphere_center :: Vec, |
|---|
| 31 | sphere_radius :: Double |
|---|
| 32 | } |
|---|
| 33 | |
|---|
| 34 | data Plane = Plane { |
|---|
| 35 | plane_p :: Vec, |
|---|
| 36 | plane_n :: Vec |
|---|
| 37 | } |
|---|
| 38 | |
|---|
| 39 | data Ray = Ray { |
|---|
| 40 | ray_org :: Vec, |
|---|
| 41 | ray_dir :: Vec |
|---|
| 42 | } |
|---|
| 43 | |
|---|
| 44 | sq x = x * x |
|---|
| 45 | |
|---|
| 46 | clamp lower upper = min upper . max lower |
|---|
| 47 | |
|---|
| 48 | drand48 = getStdGen >>= random |
|---|
| 49 | |
|---|
| 50 | justIf cond val |
|---|
| 51 | | cond = Just val |
|---|
| 52 | | otherwise = Nothing |
|---|
| 53 | |
|---|
| 54 | zerovec = Vec { x = 0, y = 0, z = 0 } |
|---|
| 55 | |
|---|
| 56 | v0 `vadd` v1 = Vec (x v0 + x v1) (y v0 + y v1) (z v0 + z v1) |
|---|
| 57 | v0 `vsub` v1 = Vec (x v0 - x v1) (y v0 - y v1) (z v0 - z v1) |
|---|
| 58 | v0 `vscale` s = Vec (x v0 * s) (y v0 * s) (z v0 * s) |
|---|
| 59 | |
|---|
| 60 | v0 `vdot` v1 = (x v0 * x v1) + (y v0 * y v1) + (z v0 * z v1) |
|---|
| 61 | |
|---|
| 62 | v0 `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 | |
|---|
| 68 | vlength c = sqrt $ c `vdot` c |
|---|
| 69 | |
|---|
| 70 | vnormalize c |
|---|
| 71 | | l > 1.0e-17 = c `vscale` (1.0 / l) |
|---|
| 72 | | otherwise = c |
|---|
| 73 | where |
|---|
| 74 | l = vlength c |
|---|
| 75 | |
|---|
| 76 | ray_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 | |
|---|
| 91 | ray_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 | |
|---|
| 103 | orthoBasis 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 | |
|---|
| 114 | hemisphereDirectionRay 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 | |
|---|
| 131 | data Object = ObjSphere Sphere | ObjPlane Plane |
|---|
| 132 | |
|---|
| 133 | findRayObjIntersect ray (ObjSphere sphere) = ray_sphere_intersect ray sphere |
|---|
| 134 | findRayObjIntersect ray (ObjPlane plane) = ray_plane_intersect ray plane |
|---|
| 135 | |
|---|
| 136 | ambient_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 | |
|---|
| 157 | screenRay 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 | |
|---|
| 163 | findNearestIsect objs ray = |
|---|
| 164 | listToMaybe $ sort $ catMaybes $ map (findRayObjIntersect ray) objs |
|---|
| 165 | |
|---|
| 166 | data Scene = Scene { |
|---|
| 167 | scene_objs :: [Object] |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | raytrace shading objs ray = do |
|---|
| 171 | shading $ findNearestIsect objs ray |
|---|
| 172 | |
|---|
| 173 | oversampling 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 | |
|---|
| 180 | calcPixelColor trace w h nsubsamples x y = do |
|---|
| 181 | oversampling trace makeRay nsubsamples |
|---|
| 182 | where |
|---|
| 183 | makeRay = screenRay nsubsamples w h x y |
|---|
| 184 | |
|---|
| 185 | render 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 | |
|---|
| 194 | rendering 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 | |
|---|
| 201 | saveppm 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 | |
|---|
| 208 | init_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 | |
|---|
| 218 | getParam idx defaultVal args |
|---|
| 219 | | length args > idx = args !! idx |
|---|
| 220 | | otherwise = defaultVal |
|---|
| 221 | |
|---|
| 222 | main = 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 |
|---|