[MetalKit]Ray tracing in a Swift playground4射线追踪4

本系列文章是对 http://metalkit.org 上面MetalKit内容的全面翻译和学习.

MetalKit系统文章目录


让我们继续上周的工作完成ray tracer射线追踪器.首先,还是像往常一样做一些代码清理.我在之前把所有类用结构体替换了,并且本次使用了合适的命名规则(如首字母大写).你可以在本周的代码仓库里看到修改后的代码.简短起见,本次不再详细介绍清理过程,但是你会发现改变其实是很小的.

上次我们重点关注了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不吸收入射射线:

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主页面中,看看新生成的图像:

raytracing8.png

玻璃表面的反射率随角度变化而不同.当你垂直观察它时,反射率几乎没有.随着观察点角度变小,反射率升高,并且外界其它物体在玻璃表面的镜面反射越来越清楚.这个效应可以用Schlick 多项式近似来计算:

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球体:

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上.
下次见!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,945评论 4 60
  • 转载自:https://github.com/Tim9Liu9/TimLiu-iOS 目录 UI下拉刷新模糊效果A...
    袁俊亮技术博客阅读 11,898评论 9 105
  • 你坐在电脑前 观察别人的生活,窥视自己 巨大的空洞填不满诉说的欲望 缄默是一张弓 紧锁的眉头 微窒的气息 没有香烟...
    真真姑娘erer阅读 215评论 0 1
  • 九月刚刚进单位实习,遇见这样一个同事,大三岁的小姐姐,一头乌黑的长发,森女系的服装,不管怎么看都是一个文艺清新的姑...
    玖溪成霖阅读 653评论 0 0
  • 让我再为你 点亮一盏灯光 好让你看见他美丽的天堂 让我再为你 念诵一段经藏 那是你苦痛最好的药汤 哪里有光 哪里就...
    穆诺阅读 189评论 0 0