[MetalKit]31-Shadows-in-Metal-part-1陰影1

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

MetalKit系統文章目錄git


lighting and shadows光照和陰影Computer Graphics計算機圖形學中一個至關重要的話題.本文是關於MetalShadow陰影系列文章的第一篇.咱們將使用第15部分Using metal part 15中playground的代碼.讓咱們創建一個基礎場景:github

float differenceOp(float d0, float d1) {
    return max(d0, -d1);
}

float distanceToRect( float2 point, float2 center, float2 size ) {
    point -= center;
    point = abs(point);
    point -= size / 2.;
    return max(point.x, point.y);
}

float distanceToScene( float2 point ) {
    float d2r1 = distanceToRect( point, float2(0.), float2(0.45, 0.85) );
    float2 mod = point - 0.1 * floor(point / 0.1);
    float d2r2 = distanceToRect( mod, float2( 0.05 ), float2(0.02, 0.04) );
    float diff = differenceOp(d2r1, d2r2);
    return diff;
}
複製代碼

咱們首先建立differenceOp() 函數,它返回兩個有符號距離間的差別.這爲咱們在物體表面雕刻出形狀提供了便利.下一步,咱們建立distanceToRect() 函數,它肯定一個給定的點是在四邊形內部或外部.在1st行,咱們用給定的中心來偏移當前座標系.在2nd行咱們獲得當前點的對稱座標.在3rd行咱們獲得到兩邊的距離.而後咱們建立distanceToScene() 函數,它給出了到場景中任意物體的最近距離.注意在MSLfmod()函數使用的是trunc()而不是floor(),由於咱們還想要使用負值,因此咱們須要建立一個自定義的mod運算符,因此咱們使用了GLSLmod()的定義x - y * floor(x/y).咱們須要modulus運算來繪製大量小三角形,它們彼此距離0.1且互爲鏡像.最後,咱們全這些函數來生成一個形狀,它看起來有點像有窗戶的高樓:安全

kernel void compute(texture2d<float, access::write> output [[texture(0)]],
                    constant float &timer [[buffer(0)]],
                    uint2 gid [[thread_position_in_grid]])
{
    int width = output.get_width();
    int height = output.get_height();
    float2 uv = float2(gid) / float2(width, height);
    uv = uv * 2.0 - 1.0;
    float d2scene = distanceToScene(uv);
    bool i = d2scene < 0.0;
    float4 color = i ? float4( .1, .5, .5, 1. ) : float4( .7, .8, .8, 1. );
    output.write(color, gid);
}
複製代碼

若是你如今運行playground,你會看到相似的圖像:函數

shadows_1.png

要產生陰影,咱們須要第一-獲得光源距離,第二-獲得光源方向,第三-朝着該方向前進直到咱們碰到光源或物體.因此讓咱們在lightPos處建立一個光源,爲了有趣咱們將讓它動起來.咱們使用從主機(API)代碼傳遞過來的,原來的timeruniform參數.而後,咱們獲得任意給定點到lightPos的距離,並根據到光源的距離給像素着色-只要不在物體內部.咱們想讓離光源近的顏色亮,遠的顏色暗.咱們用max()函數來避免燈光亮度出現負值.用下面幾行代碼替換內核中的最後一行:post

float2 lightPos = float2(1.3 * sin(timer), 1.3 * cos(timer));
float dist2light = length(lightPos - uv);
color *= max(0.0, 2. - dist2light );
output.write(color, gid);
複製代碼

若是你如今運行playground,你會看到相似的圖像:性能

shadows_2.png

咱們已經完成了前兩步(燈光位置和方向),因此繼續處理第三步-真實的陰影函數:學習

float getShadow(float2 point, float2 lightPos) {
    float2 lightDir = lightPos - point;
    float dist2light = length(lightDir);
    for (float i=0.; i < 300.; i++) {
        float distAlongRay = dist2light * (i / 300.);
        float2 currentPoint = point + lightDir * distAlongRay;
        float d2scene = distanceToScene(currentPoint);
        if (d2scene <= 0.) { return 0.; }
    }
    return 1.;
} 
複製代碼

讓咱們一行一行看看代碼.咱們首先獲得從點指向燈光的方向.下一步,咱們得出到燈光的距離,這樣咱們就知道了咱們須要沿着燈光射線移動多遠.而後,咱們用一個循環來將射線分紅許多小步.若是步數不夠多,可能會跳過去咱們的物體,這會致使陰影中出現"破洞".下一步,咱們計算出當前沿射線前進了多遠,並沿射線前進一樣距離來找到空間中的採樣點.而後,咱們看看咱們離平面上的那個點還有多遠,並測試咱們是否在物體內部.若是在,由於咱們在陰影中就返回0,不然射線沒有碰到任何物體就返回1.終於快到了觀看陰影的時間了!在內核中,用下面幾行替換最後一行:測試

float shadow = getShadow(uv, lightPos);
shadow = shadow * 0.5 + 0.5;
color *= shadow;
output.write(color, gid);
複製代碼

咱們用0.5來衰減陰影效果,固然,也能夠設置爲其它值試試效果.若是你如今運行playground,你會看到相似的圖像:動畫

shadows_3.png

如今每循環只前進一像素,性能很很差.咱們能夠經過加速沿射線方向的前進來改善性能.咱們並不須要前進那麼小的步長.咱們能夠大步前進只要不跨越咱們的物體就行.咱們能夠安全地向任何方向步進一個到場景的距離而不是一個固定步長,這樣咱們能夠快速路過空白區域!當找到到最近曲面的距離後,咱們並不知道曲面的方向,因此實際上咱們有了一個和場景中最近部分相交的圓的半徑.咱們能夠追蹤射線,它老是會遇到圓的邊緣,當圓的半徑變成0時就意味着它是和曲面的相交點.對了,這就是咱們上次學習的raymarching技術!只需簡單地用下面幾行替換**getShadow()**函數中的內容:

float2 lightDir = normalize(lightPos - point);
float dist2light = length(lightDir);
float distAlongRay = 0.0;
for (float i=0.0; i < 80.; i++) {
    float2 currentPoint = point + lightDir * distAlongRay;
    float d2scene = distanceToScene(currentPoint);
    if (d2scene <= 0.001) { return 0.0; }
    distAlongRay += d2scene;
    if (distAlongRay > dist2light) { break; }
}
return 1.;
複製代碼

raymarching中步長取決於到曲面的距離.在空白區域,它跳過一大段距離,能夠跑得更長.可是,若是平行於物體並離得很近,距離就會很小,跳過的長度也很小.這就意味着射線跑得很慢.當使用固定步長時,它跑不遠.用80或更多步,我就應該主能夠不產生陰影中的"破洞"了.若是你再運行playground,圖像看上去幾乎沒變,但陰影如今更快了.要看這份代碼的動畫效果,我在下面使用一個Shadertoy嵌入式播放器.只要把鼠標懸浮在上面,並單擊播放按鈕就能看到動畫:<譯者注:不支持嵌入播放器,我用gif代替https://www.shadertoy.com/embed/lt3SzB>

shadow1.mov.gif

這種類型的陰影被稱爲hard shadows硬陰影.下次咱們將學習soft shadows軟陰影,它看起來更真實更好看. 源代碼source code已發佈在Github上.

下次見!

相關文章
相關標籤/搜索