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

本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.git

MetalKit系統文章目錄github


今天咱們從Peter Shirley’s mini book導入ray tracer射線追蹤器Swiftplayground中.我將不會解釋什麼是Ray Tracing射線追蹤及它是怎麼工做的,我會請你本身先去讀一讀這本書由於它對Kindle訂閱者是免費的.若是你不是訂閱者,只須要像我同樣購買這本書就好了.若是你對這個話題感興趣,那花費$2.99是絕對值得的.swift

咱們要作的第一件事就是建立一個數據結構體來保存像素信息.在playground中,在Sources文件夾下建立一個新文件命名爲pixel.swift.下一步,編寫Pixel結構體.它只是一個簡單結構體,各用一個變量來保存 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
    }
}
複製代碼

下一步,咱們須要建立一個數組來保存屏幕上的像素.爲了計算每一個像素的顏色,咱們只須要給全部像素的Red設置爲0,同時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)
}
複製代碼

最後,咱們須要一個方法來從像素建立出一個可繪製的圖片.Core Image框架提供了CGImageCreate() 方法,它只須要幾個參數就能夠渲染圖片:框架

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建立一個窗口像素的集合,並用這個集合來建立渲染出的圖片:ide

let width = 800
let height = 400
var pixelSet = makePixelSet(width, height)
var image = imageFromPixels(pixelSet)
image
複製代碼

你應該能看到下面的圖面:工具

raytracing1.png

OK,你可能會好奇,可是ray tracing射線追蹤在哪裏呢?咱們稍後再解釋這個.在Sources文件夾下面,讓咱們建立一個便利的類命名爲vec3.swift,在裏面放一些數學工具方法:post

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)
}
複製代碼

下一步,咱們須要建立一個ray射線結構體.它擁有一個origin原點成員,一個direction方向,還有一個方法能夠計算任意給定參數的ray tracing方程式:學習

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

而後咱們須要計算顏色,基於ray射線是否接觸到咱們在屏幕中間建立的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)
}
複製代碼

你注意到咱們用到了另外一個方法,叫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(:),使其在每一個像素被加入集合前,給每一個像素建立一個ray射線並計算它的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的主頁面,看看產生的新圖像:

raytracing2.png

敬請關注本文的第2部分,咱們將會計算燈光和陰影,產生更真實的圖像渲染.

源代碼source code 已發佈在Github上.

下次見!

相關文章
相關標籤/搜索