[MetalKit]13-Ray-tracing-in-a-Swift-playground4射線追蹤4

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

MetalKit系統文章目錄github


讓咱們繼續上週的工做完成ray tracer射線追蹤器.首先,仍是像往常同樣作一些代碼清理.我在以前把全部類用結構體替換了,而且本次使用了合適的命名規則(如首字母大寫).你能夠在本週的代碼倉庫裏看到修改後的代碼.簡短起見,本次再也不詳細介紹清理過程,可是你會發現改變實際上是很小的.swift

上次咱們重點關注了lambertianmetal材料是如何被渲染的.最後一種咱們須要關注的材料類型叫dielectric介電質,就是當你看水或者某個玻璃物體時看到的那樣.當dielectric被射線撞擊時,射線會分紅兩部分:一個反射(反彈)射線和一個折射(新增)射線.折射用斯涅爾定律 Snell’s law來描述.讓咱們來看看這個定律怎麼翻譯成代碼.在material.swift建立一個refract() 函數:函數

func refract(v: float3, n: float3, ni_over_nt: Float) -> float3? {
    let uv = normalize(v)
    let dt = dot(uv, n)
    let discriminant = 1.0 - ni_over_nt * ni_over_nt * (1.0 - dt * dt)
    if discriminant > 0 {
        return ni_over_nt * (uv - n * dt) - n * sqrt(discriminant)
    }
    return nil
}
複製代碼

下一步,讓咱們建立一個Dielectric結構體.注意attenuation衰減老是1由於dielectrics不吸取入射射線:post

struct Dielectric: Material {
    var ref_index: Float = 1
    
    func scatter(ray_in: Ray, _ rec: Hit_record, inout _ attenuation: float3, inout _ scattered: Ray) -> Bool {
        var ni_over_nt: Float = 1
        var outward_normal = float3()
        let reflected = reflect(ray_in.direction, n: rec.normal)
        attenuation = float3(1, 1, 1)
        if dot(ray_in.direction, rec.normal) > 0 {
            outward_normal = -rec.normal
            ni_over_nt = ref_index
        } else {
            outward_normal = rec.normal
            ni_over_nt = 1 / ref_index
        }
        let refracted = refract(ray_in.direction, n: outward_normal, ni_over_nt: ni_over_nt)
        if refracted != nil {
            scattered = Ray(origin: rec.p, direction: refracted!)
        } else {
            scattered = Ray(origin: rec.p, direction: reflected)
            return false
        }
        return true
    }
}
複製代碼

咱們先計算向外的法線-能夠用射線和碰撞點的點積正負來判斷-而後用它來計算折射射線.當它是nil時,咱們反射射線,不然咱們折射射線.在pixel.swift中替換第二個metal球體,換成dielectric的:學習

object = sphere(c: float3(x: -1, y: 0, z: -1), r: 0.5, m: Dielectric())
複製代碼

在playground主頁面中,看看新生成的圖像:ui

raytracing8.png

玻璃表面的反射率隨角度變化而不一樣.當你垂直觀察它時,反射率幾乎沒有.隨着觀察點角度變小,反射率升高,而且外界其它物體在玻璃表面的鏡面反射愈來愈清楚.這個效應能夠用Schlick 多項式近似來計算:spa

func schlick(cosine: Float, _ index: Float) -> Float {
    var r0 = (1 - index) / (1 + index)
    r0 = r0 * r0
    return r0 + (1 - r0) * powf(1 - cosine, 5)
}
複製代碼

scatter() 函數須要用這個近似值來修正:翻譯

func scatter(ray_in: Ray, _ rec: Hit_record, inout _ attenuation: float3, inout _ scattered: Ray) -> Bool {
    var reflect_prob: Float = 1
    var cosine: Float = 1
    var ni_over_nt: Float = 1
    var outward_normal = float3()
    let reflected = reflect(ray_in.direction, n: rec.normal)
    attenuation = float3(1, 1, 1)
    if dot(ray_in.direction, rec.normal) > 0 {
        outward_normal = -rec.normal
        ni_over_nt = ref_index
        cosine = ref_index * dot(ray_in.direction, rec.normal) / length(ray_in.direction)
    } else {
        outward_normal = rec.normal
        ni_over_nt = 1 / ref_index
        cosine = -dot(ray_in.direction, rec.normal) / length(ray_in.direction)
    }
    let refracted = refract(ray_in.direction, n: outward_normal, ni_over_nt: ni_over_nt)
    if refracted != nil {
        reflect_prob = schlick(cosine, ref_index)
    } else {
        scattered = Ray(origin: rec.p, direction: reflected)
        reflect_prob = 1.0
    }
    if Float(drand48()) < reflect_prob {
        scattered = Ray(origin: rec.p, direction: reflected)
    } else {
        scattered = Ray(origin: rec.p, direction: refracted!)
    }
    return true
}
複製代碼

注意咱們如今折射是基於反射閾值設置爲1.有一個獲得凹玻璃球的簡單方式(小把戲).若是半徑是負的,儘管幾何體不受影響,但法線會指向內部,就獲得一個漂亮的凹玻璃球.讓咱們再加一個半徑爲負值的dielectric球體:code

object = sphere(c: float3(x: -1, y: 0, z: -1), r: -0.49, m: Dielectric())
複製代碼

如今你應該能看到凹玻璃球了.在總結以前,咱們還須要去作一件事-修復攝像機,這樣咱們從不一樣角度和距離來觀察物體了.首先,咱們須要給攝像機一個field of view視場.而後,咱們須要一個lookFrom點和一個lookAt點來設置攝像機的觀察方向.最後,咱們須要一個up上方向向量,這樣就能夠沿着這個方向旋轉攝像機同時老是保持up向量方向不變.在ray.swift中讓咱們用下面的代碼替換舊攝像機:

struct Camera {
    let lower_left_corner, horizontal, vertical, origin, u, v, w: float3
    var lens_radius: Float = 0.0
    init(lookFrom: float3, lookAt: float3, vup: float3, vfov: Float, aspect: Float) {
        let theta = vfov * Float(M_PI) / 180
        let half_height = tan(theta / 2)
        let half_width = aspect * half_height
        origin = lookFrom
        w = normalize(lookFrom - lookAt)
        u = normalize(cross(vup, w))
        v = cross(w, u)
        lower_left_corner = origin - half_width * u - half_height * v - w
        horizontal = 2 * half_width * u
        vertical = 2 * half_height * v
    }
    func get_ray(s: Float, _ t: Float) -> Ray {
        return Ray(origin: origin, direction: lower_left_corner + s * horizontal + t * vertical - origin)
    }
}
複製代碼

pixel.swift中,用下面代碼替換調用攝像機的代碼:

let lookFrom = float3(0, 1, -4)
let lookAt = float3()
let vup = float3(0, -1, 0)
let cam = Camera(lookFrom: lookFrom, lookAt: lookAt, vup: vup, vfov: 50, aspect: Float(width) / Float(height))
複製代碼

在playground主頁面中,看看新生成的圖像:

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

下次見!

相關文章
相關標籤/搜索