[MetalKit]Shadows in Metal part 1陰影1

本系列文章是對(duì) http://metalkit.org 上面MetalKit內(nèi)容的全面翻譯和學(xué)習(xí).

MetalKit系統(tǒng)文章目錄


lighting and shadows光照和陰影Computer Graphics計(jì)算機(jī)圖形學(xué)中一個(gè)相當(dāng)重要的話題.本文是關(guān)于MetalShadow陰影系列文章的第一篇.我們將使用第15部分Using metal part 15中playground的代碼.讓我們建立一個(gè)基礎(chǔ)場(chǎng)景:

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;
}

我們首先創(chuàng)建differenceOp()函數(shù),它返回兩個(gè)有符號(hào)距離間的差異.這為我們?cè)谖矬w表面雕刻出形狀提供了便利.下一步,我們創(chuàng)建distanceToRect()函數(shù),它確定一個(gè)給定的點(diǎn)是在四邊形內(nèi)部或外部.在1st行,我們用給定的中心來偏移當(dāng)前坐標(biāo)系.在2nd行我們得到當(dāng)前點(diǎn)的對(duì)稱坐標(biāo).在3rd行我們得到到兩邊的距離.然后我們創(chuàng)建distanceToScene()函數(shù),它給出了到場(chǎng)景中任意物體的最近距離.注意在MSLfmod()函數(shù)使用的是trunc()而不是floor(),因?yàn)槲覀冞€想要使用負(fù)值,所以我們需要?jiǎng)?chuàng)建一個(gè)自定義的mod運(yùn)算符,所以我們使用了GLSLmod()的定義x - y * floor(x/y).我們需要modulus運(yùn)算來繪制大量小三角形,它們彼此距離0.1且互為鏡像.最后,我們?nèi)@些函數(shù)來生成一個(gè)形狀,它看起來有點(diǎn)像有窗戶的高樓:

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);
}

如果你現(xiàn)在運(yùn)行playground,你會(huì)看到類似的圖像:

shadows_1.png

要產(chǎn)生陰影,我們需要第一-得到光源距離,第二-得到光源方向,第三-朝著該方向前進(jìn)直到我們碰到光源或物體.所以讓我們?cè)?strong>lightPos處創(chuàng)建一個(gè)光源,為了有趣我們將讓它動(dòng)起來.我們使用從主機(jī)(API)代碼傳遞過來的,原來的timeruniform參數(shù).然后,我們得到任意給定點(diǎn)到lightPos的距離,并根據(jù)到光源的距離給像素著色-只要不在物體內(nèi)部.我們想讓離光源近的顏色亮,遠(yuǎn)的顏色暗.我們用max()函數(shù)來避免燈光亮度出現(xiàn)負(fù)值.用下面幾行代碼替換內(nèi)核中的最后一行:

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);

如果你現(xiàn)在運(yùn)行playground,你會(huì)看到類似的圖像:

shadows_2.png

我們已經(jīng)完成了前兩步(燈光位置和方向),所以繼續(xù)處理第三步-真實(shí)的陰影函數(shù):

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.;
} 

讓我們一行一行看看代碼.我們首先得到從點(diǎn)指向燈光的方向.下一步,我們得出到燈光的距離,這樣我們就知道了我們需要沿著燈光射線移動(dòng)多遠(yuǎn).然后,我們用一個(gè)循環(huán)來將射線分成許多小步.如果步數(shù)不夠多,可能會(huì)跳過去我們的物體,這會(huì)導(dǎo)致陰影中出現(xiàn)"破洞".下一步,我們計(jì)算出當(dāng)前沿射線前進(jìn)了多遠(yuǎn),并沿射線前進(jìn)同樣距離來找到空間中的采樣點(diǎn).然后,我們看看我們離平面上的那個(gè)點(diǎn)還有多遠(yuǎn),并測(cè)試我們是否在物體內(nèi)部.如果在,因?yàn)槲覀冊(cè)陉幱爸芯头祷?strong>0,否則射線沒有碰到任何物體就返回1.終于快到了觀看陰影的時(shí)間了!在內(nèi)核中,用下面幾行替換最后一行:

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

我們用0.5來衰減陰影效果,當(dāng)然,也可以設(shè)置為其它值試試效果.如果你現(xiàn)在運(yùn)行playground,你會(huì)看到類似的圖像:

shadows_3.png

現(xiàn)在每循環(huán)只前進(jìn)一像素,性能很不好.我們可以通過加速沿射線方向的前進(jìn)來改善性能.我們并不需要前進(jìn)那么小的步長.我們可以大步前進(jìn)只要不跨越我們的物體就行.我們可以安全地向任何方向步進(jìn)一個(gè)到場(chǎng)景的距離而不是一個(gè)固定步長,這樣我們可以快速路過空白區(qū)域!當(dāng)找到到最近曲面的距離后,我們并不知道曲面的方向,所以實(shí)際上我們有了一個(gè)和場(chǎng)景中最近部分相交的圓的半徑.我們可以追蹤射線,它總是會(huì)遇到圓的邊緣,當(dāng)圓的半徑變成0時(shí)就意味著它是和曲面的相交點(diǎn).對(duì)了,這就是我們上次學(xué)習(xí)的raymarching技術(shù)!只需簡單地用下面幾行替換getShadow()函數(shù)中的內(nèi)容:

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中步長取決于到曲面的距離.在空白區(qū)域,它跳過一大段距離,可以跑得更長.但是,如果平行于物體并離得很近,距離就會(huì)很小,跳過的長度也很小.這就意味著射線跑得很慢.當(dāng)使用固定步長時(shí),它跑不遠(yuǎn).用80或更多步,我就應(yīng)該主可以不產(chǎn)生陰影中的"破洞"了.如果你再運(yùn)行playground,圖像看上去幾乎沒變,但陰影現(xiàn)在更快了.要看這份代碼的動(dòng)畫效果,我在下面使用一個(gè)Shadertoy嵌入式播放器.只要把鼠標(biāo)懸浮在上面,并單擊播放按鈕就能看到動(dòng)畫:<譯者注:簡書不支持嵌入播放器,我用gif代替https://www.shadertoy.com/embed/lt3SzB>

shadow1.mov.gif

這種類型的陰影被稱為hard shadows硬陰影.下次我們將學(xué)習(xí)soft shadows軟陰影,它看起來更真實(shí)更好看.
源代碼source code已發(fā)布在Github上.
下次見!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驳庭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敛劝,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)稼虎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來招刨,“玉大人霎俩,你說我怎么就攤上這事〕量簦” “怎么了打却?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谎倔。 經(jīng)常有香客問我柳击,道長,這世上最難降的妖魔是什么片习? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任捌肴,我火速辦了婚禮蹬叭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘状知。我一直安慰自己秽五,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布试幽。 她就那樣靜靜地躺著筝蚕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铺坞。 梳的紋絲不亂的頭發(fā)上起宽,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音济榨,去河邊找鬼坯沪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛擒滑,可吹牛的內(nèi)容都是我干的腐晾。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼丐一,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼藻糖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起库车,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤巨柒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后柠衍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洋满,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年珍坊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牺勾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阵漏,死狀恐怖驻民,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情履怯,我是刑警寧澤川无,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站虑乖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晾虑。R本人自食惡果不足惜疹味,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一仅叫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧糙捺,春花似錦诫咱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至签钩,卻和暖如春掏呼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铅檩。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工憎夷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昧旨。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓拾给,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兔沃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蒋得,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容