雑記帳
僕用勉強ノート 「レイトレーシング」の巻

Haskell でレイトレーシングのチュートリアルを追いかける その1 - 球面の描画

今までOpenGLを使ったポリゴンベースのコンピュータグラフィックの描画は行ったことはあるものの、よりリアリティの高いといわれるレイトレーシングによる描画というものには挑戦したことがなかった。そんな中、こんなサイトを偶然見つけてしまい、流し読みしてみたところ、キャッチーな出力画像に魅せられてまんまと興味をそそられ、この度そのチュートリアルに則ってレイトレーシングによる画像の生成に挑戦してみることを決意。
まずは球体を描画するところまで。
感想としては、「ベクトル」ってやっぱり便利。
球面の方程式や直線の方程式がこんなところでも役立つとは。
コードの実行結果
リンク先の結果と一緒なのでそちらを是非見てほしい
ソースコード
-- https://raytracing.github.io/books/RayTracingInOneWeekend.html section 5 with Haskell --

import Data.Char
import Data.Functor
import Control.Monad
import Control.Lens
import Linear.Vector
import Linear.Metric
import Linear.V3
import Linear.Quaternion


main = do
  let
    -- Image
    aspect_ratio = 16.0 / 9
    image_width = 256
    image_height = round $ fromInteger image_width / aspect_ratio
    -- Camera
    viewport_height = 2.0
    viewport_width = aspect_ratio * viewport_height
    focal_length = 1.0
    origin = zero
    horizontal = viewport_width *^ unit _x
    vertical = viewport_height *^ unit _y
    lower_left_corner = origin - horizontal ^/2 - vertical ^/2 - focal_length *^ unit _z


  -- Render

  img_data <- return $ "P3\n" ++ show image_width ++ " " ++ show image_height ++ "\n255\n"
  putStr $ img_data


  foldr (>>) (return ()) $ (fmap $ ($) $ \(j, i) ->
    let
      u = fromInteger i / (fromInteger image_width - 1.0)
      v = fromInteger j / (fromInteger image_height - 1.0)
      r = Ray {orig = origin, dir = lower_left_corner + u *^ horizontal + v *^ vertical - origin}
      pixcel_color = ray_color $ r
    in
      write_color $ pixcel_color) $
      (,) <$> [image_height - 1, image_height - 2 .. 0] <*> [0, 1 .. image_width - 1]



---------------------
-- A Ray Data Type --
---------------------

data Ray = Ray {
  orig :: V3 Double,  -- Origin of this ray (As a position in 3D Euclidean space)
  dir :: V3 Double    -- Direction of this ray (As a direction vector in 3D Euclidean space)
} deriving (Show)


at :: Ray -> Double -> V3 Double
at r t = (orig r) + t *^ (dir r)



---------------
-- Utilities --
---------------

write_color :: RealFrac a => V3 a -> IO ()
write_color v =
  let
    f = show.floor.(255.999*)
  in
    do
      tmp <- return $ f(v ^._x) ++ " " ++ f(v ^._y) ++ " " ++ f(v ^._z) ++ "\n"
      putStr $ tmp


ray_color :: Ray -> V3 Double
ray_color r =
  if hit_sphere (V3 0 0 (-1)) 0.5 r then
    V3 1 0 0
  else
    let
      unit_direction = normalize $ (dir r)
      t = 0.5 * (unit_direction ^._y + 1.0)
    in
      lerp t (V3 0.5 0.7 1.0) (V3 1.0 1.0 1.0)


hit_sphere :: V3 Double -> Double -> Ray -> Bool
hit_sphere center radius r =
  let
    oc = orig r - center
    a = quadrance (dir r)
    b = 2 * (oc `dot` dir r)
    c = quadrance oc - radius ^ 2
    discriminant = b ^ 2 - 4*a*c
  in
    (discriminant > 0)