[MetalKit]Ambient Occlusion in Metal环境光遮蔽

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

MetalKit系统文章目录


今天我们将学习ambient occlusion环境光遮蔽.我们将使用Shadows in Metal part 2的playground代码.首先,让我们添加一个新的对象类型-矩形盒子:

struct Box {
    float3 center;
    float size;
    Box(float3 c, float s) {
        center = c;
        size = s;
    }
};

下一步,让我为新的结构体再添加一个新的距离函数:

float distToBox(Ray r, Box b) {
    float3 d = abs(r.origin - b.center) - float3(b.size);
    return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}

然后,更新我们的场景:

float distToScene(Ray r) {
    Plane p = Plane(0.0);
    float d2p = distToPlane(r, p);
    Sphere s1 = Sphere(float3(0.0, 0.5, 0.0), 8.0);
    Sphere s2 = Sphere(float3(0.0, 0.5, 0.0), 6.0);
    Sphere s3 = Sphere(float3(10., -5., -10.), 15.0);
    Box b = Box(float3(1., 1., -4.), 1.);
    float dtb = distToBox(r, b);
    float d2s1 = distToSphere(r, s1);
    float d2s2 = distToSphere(r, s2);
    float d2s3 = distToSphere(r, s3);
    float dist = differenceOp(d2s1, d2s2);
    dist = differenceOp(dist, d2s3);
    dist = unionOp(dist, dtb);
    dist = unionOp(d2p, dist);
    return dist;
}

我们刚才做的是首先绘制一个半径为8的球体,一个半径为6的球体,并求出它们的差集.因为它们中心相同,所以小的那个看不到,除非我们做个横截面.这就是为什么我们用到了第三个球体,大很多而且中心也不同.我们再取一次差集,就能看到第一个差集的结果.最后,我们添加一个盒子,来让它更好看更多样.如果你现在运行playground你将看到类似的图像:

ao_1.png

下一步,让我们删除lighting()shadow()函数,因为我们不再需要他们了.还有,删除Light结构体和内核中的两个实例.现在让我们创建一个ambient occlusion环境光遮蔽的替代函数:

float ao(float3 pos, float3 n) {
    return n.y * 0.5 + 0.5;
}

我们在灯光中只用到了法线的y分量,就像有一个正上方的灯光一样.在内核中,创建法线之后(在else括号中),调用ao()函数:

float o = ao(ray.origin, n);
col = col * o;

只有一个基本(正上方)灯光时,没有阴影了.如果你现在运行playground你将看到类似的图像:

ao_2.png

是时候来点真正的ambient occlusion环境光遮蔽了. Ambient环境光意味着灯光不是来自一个定义好的光源,而是意味着一般的背景光照. * Occlusion遮蔽意思是多少环境光被阻挡了.我们在曲面上取一个射线碰撞的点,观察它的周围.如果周围有一个物体,那颜色值阻挡场景中的大部分光源,所以这是一个暗区.如果周围没有东西,那就是亮区.对于处于中间状态的情况,我们需要精确计算出多少光被阻塞了.介绍一下cone tracing圆锥追踪*概念.

cone tracing圆锥追踪的想法就是在场景中使用一个圆锥体代替射线.如果圆锥与物体相交,我们不仅仅能得到一个简单的true/false的结果.我们可以得到物体在该点处覆盖了多少圆锥体.但是我们如何追踪一个圆锥呢?我们可以使用许多球体来做一个圆锥.试着想一下许多球体排成一行,一头小一头大.这就是我们目前能近似得到的圆锥体.下面是我们需要步骤:

  • 从曲面上的一个点开始
  • 沿法线方向走出曲面
  • 每次迭代,用距离函数确定球体的多少被场景填充了
  • 每次迭代,离曲面的距离翻倍,同时球体尺寸翻倍

因为我们每步都把球体尺寸翻倍,这就意味着我们只需要几步迭代就可以很快从曲面表面出来.这也给了我们一个很棒的宽的圆锥.下面是完整的ao()函数:

float ao(float3 pos, float3 n) {
    float eps = 0.01;
    pos += n * eps * 2.0;
    float occlusion = 0.0;
    for (float i=1.0; i<10.0; i++) {
        float d = distToScene(Ray(pos, float3(0)));
        float coneWidth = 2.0 * eps;
        float occlusionAmount = max(coneWidth - d, 0.);
        float occlusionFactor = occlusionAmount / coneWidth;
        occlusionFactor *= 1.0 - (i / 10.0);
        occlusion = max(occlusion, occlusionFactor);
        eps *= 2.0;
        pos += n * eps;
    }
    return max(0.0, 1.0 - occlusion);
}

让我们一行一行看看这些代码.首先,我们定义了eps变量,它包含了圆锥半径和距离曲面的距离.然后,我们移出去一点来避免我们碰撞到我们离开的表面.下一步,我们定义occlusion遮蔽变量,初始化为nil(场景是完全被照亮的).然后,我们进入循环,每次迭代我们拿到场景距离,将半径加倍以便知道圆锥的多少被遮蔽了,确保排队了灯光的负值,拿到遮蔽数量(比率)乘以圆锥宽度,给远处的遮蔽(可以从迭代次数获取远近)设置一个低的影响因子,保存当前最高的遮蔽值,将eps加倍并沿法线移动同样距离.然后返回一个值,它代表有多少光线到达了这个点.

现在让我们创建个camera结构体.它需要一个位置.我们只需储存一个射线来代替摄像机方向.最后rayDivergence给我们一个因子,代表射线扩散了多少.

struct Camera {
    float3 position;
    Ray ray = Ray(float3(0), float3(0));
    float rayDivergence;
    Camera(float3 pos, Ray r, float div) {
        position = pos;
        ray = r;
        rayDivergence = div;
    }
};

下一步,设置摄像机.需要一个摄像机位置,观察目标/朝向,视场和视图坐标:

Camera setupCam(float3 pos, float3 target, float fov, float2 uv, int x) {
    uv *= fov;
    float3 cw = normalize(target - pos );
    float3 cp = float3(0.0, 1.0, 0.0);
    float3 cu = normalize(cross(cw, cp));
    float3 cv = normalize(cross(cu, cw));
    Ray ray = Ray(pos, normalize(uv.x * cu + uv.y * cv + 0.5 * cw));
    Camera cam = Camera(pos, ray, fov / float(x));
    return cam;
}

现在我们只需要初始化摄像机.我们让它环绕场景,朝向中心(0,0,0).添加到内核,放在uv变量创建后:

float3 camPos = float3(sin(time) * 10., 3., cos(time) * 10.);
Camera cam = setupCam(camPos, float3(0), 1.25, uv, width);

然后删除ray变量,用cam.ray替换内核中用到它的地方.如果你现在运行playground你将看到类似的图像:

ao_3.png

要看这份代码的动画效果,我在下面使用一个Shadertoy嵌入式播放器.只要把鼠标悬浮在上面,并单击播放按钮就能看到动画:<译者注:简书不支持嵌入播放器,我用gif代替https://www.shadertoy.com/embed/4ltSWf>

AmbientOcclusion.mov.gif

源代码source code已发布在Github上.
下次见!

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

推荐阅读更多精彩内容

  • 本系列文章是对 http://metalkit.org 上面MetalKit内容的全面翻译和学习. MetalKi...
    苹果API搬运工阅读 319评论 0 1
  • 一、四大光照类型1.环境光(Ambient Light) 一个物体即使没有直接被光源照射,但是只要有光线通过其他物...
    CarlDonitz阅读 1,490评论 0 0
  • File open:打开 save:保存 Global Shift settings:设置最大绝对坐标,最大实体对...
    huihut阅读 9,706评论 0 2
  • 1.介绍 HTTP(HyperText Transfer Protocol)是超文本传输协议。最新版本是HTTP ...
    启才阅读 399评论 0 1
  • 其实,说起家。 每次提起,心里都是很是难过。 其实,我也经常回去看看,只是工作忙了,很多时候,都只能是遥望。 家,...
    陪月亮摘星星阅读 311评论 0 4