[MetalKit]Ray tracing in a Swift playground射線追蹤

本系列文章是對(duì) http://metalkit.org 上面MetalKit內(nèi)容的全面翻譯和學(xué)習(xí).

MetalKit系統(tǒng)文章目錄


今天我們從Peter Shirley’s mini book導(dǎo)入ray tracer射線追蹤器Swiftplayground中.我將不會(huì)解釋什么是Ray Tracing射線追蹤及它是怎么工作的,我會(huì)請(qǐng)你自己先去讀一讀這本書因?yàn)樗鼘?duì)Kindle訂閱者是免費(fèi)的.如果你不是訂閱者,只需要像我一樣購買這本書就行了.如果你對(duì)這個(gè)話題感興趣,那花費(fèi)$2.99是絕對(duì)值得的.

我們要做的第一件事就是創(chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu)體來保存像素信息.在playground中,在Sources文件夾下創(chuàng)建一個(gè)新文件命名為pixel.swift.下一步,編寫Pixel結(jié)構(gòu)體.它只是一個(gè)簡單結(jié)構(gòu)體,各用一個(gè)變量來保存 RGBA 通道.我們初始化alpha通道為255,這意味著在[0~255]范圍的完全不透明:

public struct Pixel {
    var r: UInt8
    var g: UInt8
    var b: UInt8
    var a: UInt8
    init(red: UInt8, green: UInt8, blue: UInt8) {
        r = red
        g = green
        b = blue
        a = 255
    }
}

下一步,我們需要?jiǎng)?chuàng)建一個(gè)數(shù)組來保存屏幕上的像素.為了計(jì)算每個(gè)像素的顏色,我們只需要給所有像素的Red設(shè)置為0,同時(shí)Green則從屏幕左下角的0(沒有任何綠色)漸變到屏幕右上角的255(純綠色).同樣,Blue顏色從屏幕頂部的0漸變到屏幕底部的255.

public func makePixelSet(width: Int, _ height: Int) -> ([Pixel], Int, Int) {
    var pixel = Pixel(red: 0, green: 0, blue: 0)
    var pixels = [Pixel](count: width * height, repeatedValue: pixel)
    for i in 0..<width {
        for j in 0..<height {
            pixel = Pixel(red: 0, green: UInt8(Double(i * 255 / width)), blue: UInt8(Double(j * 255 / height)))
            pixels[i + j * width] = pixel
        }
    }
    return (pixels, width, height)
}

最后,我們需要一個(gè)方法來從像素創(chuàng)建出一個(gè)可繪制的圖片.Core Image框架提供了CGImageCreate()方法,它只需要幾個(gè)參數(shù)就可以渲染圖片:

public func imageFromPixels(pixels: ([Pixel], width: Int, height: Int)) -> CIImage {
    let bitsPerComponent = 8
    let bitsPerPixel = 32
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue) // alpha is last

    let providerRef = CGDataProviderCreateWithCFData(NSData(bytes: pixels.0, length: pixels.0.count * sizeof(Pixel)))
    let image = CGImageCreate(pixels.1, pixels.2, bitsPerComponent, bitsPerPixel, pixels.1 * sizeof(Pixel), rgbColorSpace, bitmapInfo, providerRef, nil, true, CGColorRenderingIntent.RenderingIntentDefault)
    return CIImage(CGImage: image!)
}

下一步,在playground主頁中用給定的widthheight創(chuàng)建一個(gè)窗口像素的集合,并用這個(gè)集合來創(chuàng)建渲染出的圖片:

let width = 800
let height = 400
var pixelSet = makePixelSet(width, height)
var image = imageFromPixels(pixelSet)
image

你應(yīng)該能看到下面的圖面:

raytracing1.png

OK,你可能會(huì)好奇,但是ray tracing射線追蹤在哪里呢?我們稍后再解釋這個(gè).在Sources文件夾下面,讓我們創(chuàng)建一個(gè)便利的類命名為vec3.swift,在里面放一些數(shù)學(xué)工具方法:

struct vec3 {
    var x = 0.0
    var y = 0.0
    var z = 0.0
}

func * (left: Double, right: vec3) -> vec3 {
    return vec3(x: left * right.x, y: left * right.y, z: left * right.z)
}

func + (left: vec3, right: vec3) -> vec3 {
    return vec3(x: left.x + right.x, y: left.y + right.y, z: left.z + right.z)
}

func - (left: vec3, right: vec3) -> vec3 {
    return vec3(x: left.x - right.x, y: left.y - right.y, z: left.z - right.z)
}

func dot (left: vec3, _ right: vec3) -> Double {
    return left.x * right.x + left.y * right.y + left.z * right.z
}

func unit_vector(v: vec3) -> vec3 {
    let length : Double = sqrt(dot(v, v))
    return vec3(x: v.x/length, y: v.y/length, z: v.z/length)
}

下一步,我們需要?jiǎng)?chuàng)建一個(gè)ray射線結(jié)構(gòu)體.它擁有一個(gè)origin原點(diǎn)成員,一個(gè)direction方向,還有一個(gè)方法可以計(jì)算任意給定參數(shù)的ray tracing方程式:

struct ray {
    var origin: vec3
    var direction: vec3
    func point_at_parameter(t: Double) -> vec3 {
        return origin + t * direction
    }
}

然后我們需要計(jì)算顏色,基于ray射線是否接觸到我們在屏幕中間創(chuàng)建的sphere球體:

func color(r: ray) -> vec3 {
    let minusZ = vec3(x: 0, y: 0, z: -1.0)
    var t = hit_sphere(minusZ, 0.5, r)
    if t > 0.0 {
        let norm = unit_vector(r.point_at_parameter(t) - minusZ)
        return 0.5 * vec3(x: norm.x + 1.0, y: norm.y + 1.0, z: norm.z + 1.0)
    }
    let unit_direction = unit_vector(r.direction)
    t = 0.5 * (unit_direction.y + 1.0)
    return (1.0 - t) * vec3(x: 1.0, y: 1.0, z: 1.0) + t * vec3(x: 0.5, y: 0.7, z: 1.0)
}

你注意到我們用到了另一個(gè)方法,叫hit_sphere(),來確定我們的射線是撞到了球體,或者沒有接觸球體:

func hit_sphere(center: vec3, _ radius: Double, _ r: ray) -> Double {
    let oc = r.origin - center
    let a = dot(r.direction, r.direction)
    let b = 2.0 * dot(oc, r.direction)
    let c = dot(oc, oc) - radius * radius
    let discriminant = b * b - 4 * a * c
    if discriminant < 0 {
        return -1.0
    } else {
        return (-b - sqrt(discriminant)) / (2.0 * a)
    }
}

回到pixel.swift文件,更改makePixelSet(:),使其在每個(gè)像素被加入集合前,給每個(gè)像素創(chuàng)建一個(gè)ray射線并計(jì)算它的color顏色:

public func makePixelSet(width: Int, _ height: Int) -> ([Pixel], Int, Int) {
    var pixel = Pixel(red: 0, green: 0, blue: 0)
    var pixels = [Pixel](count: width * height, repeatedValue: pixel)
    let lower_left_corner = vec3(x: -2.0, y: 1.0, z: -1.0)
    let horizontal = vec3(x: 4.0, y: 0, z: 0)
    let vertical = vec3(x: 0, y: -2.0, z: 0)
    let origin = vec3()
    for i in 0..<width {
        for j in 0..<height {
            let u = Double(i) / Double(width)
            let v = Double(j) / Double(height)
            let r = ray(origin: origin, direction: lower_left_corner + u * horizontal + v * vertical)
            let col = color(r)
            pixel = Pixel(red: UInt8(col.x * 255), green: UInt8(col.y * 255), blue: UInt8(col.z * 255))
            pixels[i + j * width] = pixel
        }
    }
    return (pixels, width, height)
}

在playground的主頁面,看看產(chǎn)生的新圖像:

raytracing2.png

敬請(qǐng)關(guān)注本文的第2部分,我們將會(huì)計(jì)算燈光和陰影,產(chǎn)生更真實(shí)的圖像渲染.

源代碼source code 已發(fā)布在Github上.
下次見!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赫编,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子竿痰,更是在濱河造成了極大的恐慌聂沙,老刑警劉巖嫂沉,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件失都,死亡現(xiàn)場離奇詭異,居然都是意外死亡狂窑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門桑腮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泉哈,“玉大人,你說我怎么就攤上這事破讨〈曰蓿” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵提陶,是天一觀的道長烫沙。 經(jīng)常有香客問我,道長隙笆,這世上最難降的妖魔是什么锌蓄? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮撑柔,結(jié)果婚禮上瘸爽,老公的妹妹穿的比我還像新娘。我一直安慰自己铅忿,他們只是感情好蝶糯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辆沦,像睡著了一般昼捍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肢扯,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天妒茬,我揣著相機(jī)與錄音,去河邊找鬼蔚晨。 笑死乍钻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铭腕。 我是一名探鬼主播银择,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼累舷!你這毒婦竟也來了浩考?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤被盈,失蹤者是張志新(化名)和其女友劉穎析孽,沒想到半個(gè)月后搭伤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袜瞬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年怜俐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邓尤。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拍鲤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汞扎,到底是詐尸還是另有隱情殿漠,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布佩捞,位于F島的核電站绞幌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏一忱。R本人自食惡果不足惜莲蜘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帘营。 院中可真熱鬧票渠,春花似錦、人聲如沸芬迄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禀梳。三九已至杜窄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間算途,已是汗流浹背塞耕。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘴瓤,地道東北人扫外。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像廓脆,于是被迫代替她去往敵國和親筛谚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容