├── README.md └── raytracing.playground ├── Contents.swift ├── Sources ├── material.swift ├── objects.swift ├── pixel.swift └── ray.swift ├── contents.xcplayground ├── playground.xcworkspace ├── contents.xcworkspacedata └── xcuserdata │ └── marius.xcuserdatad │ └── UserInterfaceState.xcuserstate └── timeline.xctimeline /README.md: -------------------------------------------------------------------------------- 1 | # Ray tracing in the iPad Playground 2 | 3 | Repository to accompany the following blog posts: 4 | - [Ray tracing in a Swift playground part 6](http://metalkit.org/2016/08/07/ray-tracing-in-a-swift-playground-part-6.html) 5 | -------------------------------------------------------------------------------- /raytracing.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | 2 | let image = imageFromPixels(width: 400, height: 200, ns: 10) 3 | image 4 | -------------------------------------------------------------------------------- /raytracing.playground/Sources/material.swift: -------------------------------------------------------------------------------- 1 | 2 | import simd 3 | 4 | protocol Material { 5 | func scatter(ray_in: Ray, _ rec: Hit_record, _ attenuation: inout float3, _ scattered: inout Ray) -> Bool 6 | } 7 | 8 | struct Lambertian: Material { 9 | var albedo = float3() 10 | 11 | func scatter(ray_in: Ray, _ rec: Hit_record, _ attenuation: inout float3, _ scattered: inout Ray) -> Bool { 12 | let target = rec.p + rec.normal + random_in_unit_sphere() 13 | scattered = Ray(origin: rec.p, direction: target - rec.p) 14 | attenuation = albedo 15 | return true 16 | } 17 | } 18 | 19 | struct Metal: Material { 20 | var albedo = float3() 21 | var fuzz: Float = 0 22 | 23 | func scatter(ray_in: Ray, _ rec: Hit_record, _ attenuation: inout float3, _ scattered: inout Ray) -> Bool { 24 | let reflected = reflect(normalize(ray_in.direction), n: rec.normal) 25 | scattered = Ray(origin: rec.p, direction: reflected + fuzz * random_in_unit_sphere()) 26 | attenuation = albedo 27 | return dot(scattered.direction, rec.normal) > 0 28 | } 29 | } 30 | 31 | struct Dielectric: Material { 32 | var ref_index: Float = 1 33 | 34 | func scatter(ray_in: Ray, _ rec: Hit_record, _ attenuation: inout float3, _ scattered: inout Ray) -> Bool { 35 | var reflect_prob: Float = 1 36 | var cosine: Float = 1 37 | var ni_over_nt: Float = 1 38 | var outward_normal = float3() 39 | let reflected = reflect(ray_in.direction, n: rec.normal) 40 | attenuation = float3(1, 1, 1) 41 | if dot(ray_in.direction, rec.normal) > 0 { 42 | outward_normal = -rec.normal 43 | ni_over_nt = ref_index 44 | cosine = ref_index * dot(ray_in.direction, rec.normal) / length(ray_in.direction) 45 | } else { 46 | outward_normal = rec.normal 47 | ni_over_nt = 1 / ref_index 48 | cosine = -dot(ray_in.direction, rec.normal) / length(ray_in.direction) 49 | } 50 | let refracted = refract(v: ray_in.direction, n: outward_normal, ni_over_nt: ni_over_nt) 51 | if refracted != nil { 52 | reflect_prob = schlick(cosine, ref_index) 53 | } else { 54 | scattered = Ray(origin: rec.p, direction: reflected) 55 | reflect_prob = 1.0 56 | } 57 | if Float(drand48()) < reflect_prob { 58 | scattered = Ray(origin: rec.p, direction: reflected) 59 | } else { 60 | scattered = Ray(origin: rec.p, direction: refracted!) 61 | } 62 | return true 63 | } 64 | } 65 | 66 | func refract(v: float3, n: float3, ni_over_nt: Float) -> float3? { 67 | let uv = normalize(v) 68 | let dt = dot(uv, n) 69 | let discriminant = 1.0 - ni_over_nt * ni_over_nt * (1.0 - dt * dt) 70 | if discriminant > 0 { 71 | return ni_over_nt * (uv - n * dt) - n * sqrt(discriminant) 72 | } 73 | return nil 74 | } 75 | 76 | func schlick(_ cosine: Float, _ index: Float) -> Float { 77 | var r0 = (1 - index) / (1 + index) 78 | r0 = r0 * r0 79 | return r0 + (1 - r0) * powf(1 - cosine, 5) 80 | } 81 | -------------------------------------------------------------------------------- /raytracing.playground/Sources/objects.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import simd 4 | 5 | protocol Hitable { 6 | func hit(r: Ray, _ tmin: Float, _ tmax: Float) -> Hit_record? 7 | } 8 | 9 | struct Hit_record { 10 | var t: Float 11 | var p: float3 12 | var normal: float3 13 | var mat_ptr: Material 14 | } 15 | 16 | struct Hitable_list: Hitable { 17 | var list: [Hitable] 18 | 19 | func hit(r: Ray, _ tmin: Float, _ tmax: Float) -> Hit_record? { 20 | var hit_anything: Hit_record? 21 | for item in list { 22 | if let aHit = item.hit(r: r, tmin, hit_anything?.t ?? tmax) { 23 | hit_anything = aHit 24 | } 25 | } 26 | return hit_anything 27 | } 28 | } 29 | 30 | struct Sphere: Hitable { 31 | var center: float3 32 | var radius: Float 33 | var mat: Material 34 | 35 | init(c: float3, r: Float, m: Material) { 36 | center = c 37 | radius = r 38 | mat = m 39 | } 40 | 41 | func hit(r: Ray, _ tmin: Float, _ tmax: Float) -> Hit_record? { 42 | let oc = r.origin - center 43 | let a = dot(r.direction, r.direction) 44 | let b = dot(oc, r.direction) 45 | let c = dot(oc, oc) - radius * radius 46 | let discriminant = b * b - a * c 47 | if discriminant > 0 { 48 | var t = (-b - sqrt(discriminant) ) / a 49 | if t < tmin { 50 | t = (-b + sqrt(discriminant) ) / a 51 | } 52 | if tmin < t && t < tmax { 53 | let point = r.point_at_parameter(t: t) 54 | let normal = (point - center) / float3(radius) 55 | return Hit_record(t: t, p: point, normal: normal, mat_ptr: mat) 56 | } 57 | } 58 | return nil 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /raytracing.playground/Sources/pixel.swift: -------------------------------------------------------------------------------- 1 | 2 | import CoreImage 3 | import simd 4 | 5 | public struct Pixel { 6 | var r: UInt8 7 | var g: UInt8 8 | var b: UInt8 9 | var a: UInt8 10 | public init(red: UInt8, green: UInt8, blue: UInt8) { 11 | r = red 12 | g = green 13 | b = blue 14 | a = 255 15 | } 16 | } 17 | 18 | func random_scene() -> Hitable_list { 19 | var objects = [Hitable]() 20 | objects.append(Sphere(c: float3(0, -1000, 0), r: 1000, m: Lambertian(albedo: float3(0.5, 0.5, 0.5)))) 21 | for a in -2..<3 { 22 | for b in -2..<3 { 23 | let materialChoice = drand48() 24 | let center = float3(Float(a) + 0.9 * Float(drand48()), 0.2, Float(b) + 0.9 * Float(drand48())) 25 | if length(center - float3(4, 0.2, 0)) > 0.9 { 26 | if materialChoice < 0.8 { // diffuse 27 | let albedo = float3(Float(drand48()) * Float(drand48()), Float(drand48()) * Float(drand48()), Float(drand48()) * Float(drand48())) 28 | objects.append(Sphere(c: center, r: 0.2, m: Lambertian(albedo: albedo))) 29 | } else if materialChoice < 0.95 { // metal 30 | let albedo = float3(0.5 * (1 + Float(drand48())), 0.5 * (1 + Float(drand48())), 0.5 * (1 + Float(drand48()))) 31 | objects.append(Sphere(c: center, r: 0.2, m: Metal(albedo: albedo, fuzz: Float(0.5 * drand48())))) 32 | } else { // glass 33 | objects.append(Sphere(c: center, r: 0.2, m: Dielectric())) 34 | } 35 | } 36 | } 37 | } 38 | objects.append(Sphere(c: float3(0, 0.7, 0), r: 0.7, m: Dielectric())) 39 | objects.append(Sphere(c: float3(-3, 0.7, 0), r: 0.7, m: Lambertian(albedo: float3(0.4, 0.2, 0.1)))) 40 | objects.append(Sphere(c: float3(3, 0.7, 0), r: 0.7, m: Metal(albedo: float3(0.7, 0.6, 0.5), fuzz: 0.0))) 41 | return Hitable_list(list: objects) 42 | } 43 | 44 | public func imageFromPixels(width: Int, height: Int, ns: Int) -> CIImage { 45 | var pixel = Pixel(red: 0, green: 0, blue: 0) 46 | var pixels = [Pixel](repeating: pixel, count: width * height) 47 | let lookFrom = float3(10, 1.5, -5) 48 | let lookAt = float3() 49 | let cam = Camera(lookFrom: lookFrom, lookAt: lookAt, vup: float3(0, -1, 0), vfov: 15, aspect: Float(width) / Float(height)) 50 | let world = random_scene() 51 | DispatchQueue.concurrentPerform(iterations: width) { i in 52 | for j in 0...size)) 71 | let image = CGImage(width: width, height: height, bitsPerComponent: bitsPerComponent, bitsPerPixel: bitsPerPixel, bytesPerRow: width * MemoryLayout.size, space: rgbColorSpace, bitmapInfo: bitmapInfo, provider: providerRef!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) 72 | return CIImage(cgImage: image!) 73 | } 74 | -------------------------------------------------------------------------------- /raytracing.playground/Sources/ray.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import simd 4 | 5 | struct Ray { 6 | var origin: float3 7 | var direction: float3 8 | func point_at_parameter(t: Float) -> float3 { 9 | return origin + t * direction 10 | } 11 | } 12 | 13 | struct Camera { 14 | let lower_left_corner, horizontal, vertical, origin, u, v, w: float3 15 | var lens_radius: Float = 0.1 16 | init(lookFrom: float3, lookAt: float3, vup: float3, vfov: Float, aspect: Float) { 17 | let theta = vfov * Float(M_PI) / 180 18 | let half_height = tan(theta / 2) 19 | let half_width = aspect * half_height 20 | origin = lookFrom 21 | w = normalize(lookFrom - lookAt) 22 | u = normalize(cross(vup, w)) 23 | v = cross(w, u) 24 | lower_left_corner = origin - half_width * u - half_height * v - w 25 | horizontal = 2 * half_width * u 26 | vertical = 2 * half_height * v 27 | } 28 | func get_ray(s: Float, _ t: Float) -> Ray { 29 | return Ray(origin: origin, direction: lower_left_corner + s * horizontal + t * vertical - origin) 30 | } 31 | } 32 | 33 | func random_in_unit_sphere() -> float3 { 34 | var p = float3() 35 | repeat { 36 | p = 2.0 * float3(Float(drand48()), Float(drand48()), Float(drand48())) - float3(1, 1, 1) 37 | } while dot(p, p) >= 1.0 38 | return p 39 | } 40 | 41 | func color(r: Ray, _ world: Hitable, _ depth: Int) -> float3 { 42 | if let hit = world.hit(r: r, 0.001, Float.infinity) { 43 | var scattered = r 44 | var attenuantion = float3() 45 | if depth < 5 && hit.mat_ptr.scatter(ray_in: r, hit, &attenuantion, &scattered) { // depth = 50 46 | return attenuantion * color(r: scattered, world, depth + 1) 47 | } else { 48 | return float3(0, 0, 0) 49 | } 50 | } 51 | let unit_direction = normalize(r.direction) 52 | let t = 0.5 * unit_direction.y + 1 53 | return (1 - t) * float3(1, 1, 1) + t * float3(0.5, 0.7, 1.0) 54 | } 55 | -------------------------------------------------------------------------------- /raytracing.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /raytracing.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /raytracing.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetalKit/raytracing/6801ad6e2872176763f52a14f7cc5ca28e95a3d7/raytracing.playground/playground.xcworkspace/xcuserdata/marius.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /raytracing.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------