[MetalKit]33-Ambient-Occlusion-in-Metal環境光遮蔽

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

MetalKit系統文章目錄git


今天咱們將學習ambient occlusion環境光遮蔽.咱們將使用Shadows in Metal part 2的playground代碼.首先,讓咱們添加一個新的對象類型-矩形盒子:github

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

而後,更新咱們的場景:post

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()函數:ui

float o = ao(ray.origin, n);
col = col * o;
複製代碼

只有一個基本(正上方)燈光時,沒有陰影了.若是你如今運行playground你將看到相似的圖像:spa

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上.

下次見!

相關文章
相關標籤/搜索