open System open System.Windows.Media.Media3D open System.Drawing open System.Windows.Forms type Color(r: float, g: float, b:float) = member this.r = r member this.g = g member this.b = b static member ( * ) (c1:Color, c2:Color) = Color (c1.r*c2.r, c1.g*c2.g, c1.b*c2.b) static member ( * ) (c:Color, s:float) = Color (c.r*s, c.g*s, c.b*s) static member ( + ) (c1:Color, c2:Color) = // there must be a less ugly way to do this let r = Math.Min (c1.r+c2.r, 1.0) let g = Math.Min (c1.g+c2.g, 1.0) let b = Math.Min (c1.b+c2.b, 1.0) Color (r,g,b) static member Zero = Color(0.0,0.0,0.0) let norm (v:Vector3D) = let abs = sqrt (v.X * v.X + v.Y * v.Y + v.Z * v.Z) v / abs type Material = { diffuseColor: Color; specularColor: Color; shininess:float } type Sphere = { center:Point3D; radius:float; material:Material } type Camera = { position:Point3D; lookAt:Point3D; lookUp:Vector3D } type Light = { position:Point3D; color:Color } type Scene = { camera:Camera; spheres:Sphere list; ambientLight:Color; lights:Light list } type Ray = { origin:Point3D; direction:Vector3D } /// Get the position of a ray at a given time let pointAtTime ray time = ray.origin + time * ray.direction type Intersection = { normal:Vector3D; point:Point3D; ray:Ray; material:Material; t:float } let intersect ray sphere = let s = ray.origin - sphere.center let rayDir = norm ray.direction let sv = Vector3D.DotProduct(s,rayDir) let ss = Vector3D.DotProduct(s,s) let discr = sv*sv - ss + sphere.radius*sphere.radius if discr < 0.0 then [] else let normalAtTime t = norm (pointAtTime ray t - sphere.center) let (t1,t2) = (-sv + sqrt(discr), -sv - sqrt(discr)) [ { normal = normalAtTime t1; point = pointAtTime ray t1; ray = ray; material=sphere.material; t=t1 }; { normal = normalAtTime t2; point = pointAtTime ray t2; ray = ray; material=sphere.material; t=t2 } ] let castRay ray (scene:Scene) = scene.spheres |> List.collect (fun x -> intersect ray x) |> List.filter (fun x -> x.t > 0.01) let colorAt intersection scene = // check if we're in shadow let inShadow point light = let ray = { origin = intersection.point; direction = light.position - intersection.point} let intersections = castRay ray scene if intersections.Length = 0 then false else true // nested function for ambient color let ambientColorAt intersection scene = scene.ambientLight * intersection.material.diffuseColor // nested function for specular color let specularColorAt intersection scene = let ks = intersection.material.specularColor let V = scene.camera.position - intersection.point let specularAtLight light = let L = norm (light.position - intersection.point) let H = norm (L + V) let Is = light.color * Math.Pow(Math.Max(0.0, Vector3D.DotProduct(H,intersection.normal)), intersection.material.shininess) //if inShadow intersection.point light then Color(0.0,0.0,0.0) //else ks * Is ks*Is List.sumBy(fun x -> specularAtLight x) scene.lights //nested function for diffuse color let diffuseColorAt intersection scene = let kd = intersection.material.diffuseColor let diffuseAtLight light = let L = norm (light.position - intersection.point) let Id = light.color * Math.Max(0.0, Vector3D.DotProduct(L, intersection.normal)) if inShadow intersection.point light then Color(0.0,0.0,0.0) else kd * Id List.sumBy(fun x -> diffuseAtLight x) scene.lights let ambient = ambientColorAt intersection scene let specular = specularColorAt intersection scene let diffuse = diffuseColorAt intersection scene ambient + diffuse + specular // main function do let width = 480 let height = 320 // Vertical and horizontal field of view let hfov = System.Math.PI/3.5 let vfov = hfov * float(height)/float(width) // Pixel width and height let pw = 2.0 * System.Math.Tan(float(hfov/2.0))/float(width) let ph = 2.0 * System.Math.Tan(float(vfov/2.0))/float(height) let box = new PictureBox(BackColor = Color.White, Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.CenterImage) let bmp = new Bitmap(width,height) // sphere let material1 = { diffuseColor=Color(0.8, 0.1, 0.1); specularColor=Color(0.7,0.7,0.7); shininess=40.0 } let sphere1 = { center=Point3D(-0.1, -0.1, 1.8); radius=0.39; material=material1 } let material2 = { diffuseColor=Color(0.1, 0.1, 0.8); specularColor=Color(0.7,0.7,0.7); shininess=20.0 } let sphere2 = { center=Point3D(0.4, 0.3, 2.0); radius=0.3; material=material2 } // camera let camera = { position=Point3D(0.0,0.0,0.0); lookAt=Point3D(0.0, 0.0, 1.0); lookUp=Vector3D(0.0,1.0,0.0) } // scene let light = { position=Point3D(1.0,4.0,-3.0); color=Color(0.4,0.4,0.4) } let light2 = { position=Point3D(-3.0,-2.0,-1.0); color=Color(0.7,0.7,0.7) } let light3 = { position=Point3D(-1.0,-3.0,-2.0); color=Color(0.2,0.2,0.2) } let scene = { camera=camera; spheres=[sphere1;sphere2]; ambientLight=Color(0.2,0.2,0.2); lights=[light;light2;light3] } // set up the coordinate system let n = norm (camera.position - camera.lookAt) let u = norm (Vector3D.CrossProduct(n, camera.lookUp)) let v = norm (Vector3D.CrossProduct(n, u)) let vpc = camera.position - n for x in 0..(width-1) do for y in 0..(height-1) do let rayPoint = vpc + float(x-width/2)*pw*u + float(y-height/2)*ph*v let rayDir = norm (rayPoint - scene.camera.position) let ray = { origin = scene.camera.position; direction = rayDir } let intersects = castRay ray scene match intersects with | [] -> bmp.SetPixel(x,y,Color.Gray) | _ -> let color = colorAt (List.minBy(fun i -> i.t) intersects) scene bmp.SetPixel(x,y, Color.FromArgb(255, (int)(color.r*255.0), (int)(color.g*255.0), (int)(color.b*255.0))) bmp.Save(@"c:\Users\Martin\Desktop\output.jpg")