[MetalKit]Raymarching in Metal射線行進(jìn)

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

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


Raymarching射線步進(jìn) 是一種用在實(shí)時(shí)圖形的快速渲染方法.幾何體通常不是傳遞到渲染器的,而是在著色器中用Signed Distance Fields (SDF)函數(shù)來創(chuàng)建的,這個(gè)函數(shù)用來描述場(chǎng)景中一個(gè)點(diǎn)到物體的一個(gè)面之間的最短距離.當(dāng)點(diǎn)在物體內(nèi)部時(shí)SDF函數(shù)返回一個(gè)負(fù)數(shù).SDFs非常有用,因?yàn)樗屛覀儨p少了Ray Tracing射線追蹤的采樣數(shù).類似于Ray Tracing射線追蹤,在Raymarching中我們也有從觀察平面的每個(gè)像素發(fā)出的射線,每條射線被用來確定是否和某個(gè)物體相交.

這兩種技術(shù)的不同在于,在射線追蹤中是用嚴(yán)格的方程組來確定相交的,而在Raymarching中相交是估算的.用SDFs我們可以沿著射線步進(jìn)直到我們離某個(gè)物體過近.這種方法相比準(zhǔn)確確定相交來說花費(fèi)的計(jì)算不算多,當(dāng)場(chǎng)景有很多物體并且光照很復(fù)雜時(shí),準(zhǔn)確確定相交代價(jià)很大.Raymarching另一大應(yīng)用場(chǎng)景是體積渲染(霧,水,云),這些用Ray Tracing射線追蹤不好做因?yàn)榇_定和這些的相交非常困難.

我們可以用 Using MetalKit part 10中的playground來繼續(xù)下去,下面會(huì)解釋這些明顯的改動(dòng).讓我們從兩個(gè)基本構(gòu)建塊開始,這是我們?cè)趦?nèi)核用到的最小單元:一個(gè)射線和一個(gè)物體(球體).

struct Ray {
    float3 origin;
    float3 direction;
    Ray(float3 o, float3 d) {
        origin = o;
        direction = d;
    }
};

struct Sphere {
    float3 center;
    float radius;
    Sphere(float3 c, float r) {
        center = c;
        radius = r;
    }
};

因?yàn)槲覀兪菑?code>第10部分開始寫的,那我們還要寫一個(gè)SDF來計(jì)算從一個(gè)給定的點(diǎn)到球體的距離.與原有函數(shù)不同之處在于,我們現(xiàn)在的點(diǎn)是沿著射線marching步進(jìn)的,所以我們用射線位置來代替:

float distToSphere(Ray ray, Sphere s) {
    return length(ray.origin - s.center) - s.radius;
}

我們需要做的是計(jì)算從一個(gè)給定點(diǎn)到一個(gè)圓(不是球體因?yàn)槲覀冞€沒有3D化)的距離,像這樣:

float dist(float2 point, float2 center, float radius) {
    return length(point - center) - radius;
}

...
float distToCircle = dist(uv, float2(0.), 0.5);
bool inside = distToCircle < 0.;
output.write(inside ? float4(1.) : float4(0.), gid);
...

我們現(xiàn)在需要有一個(gè)射線,并沿著它步進(jìn)穿過場(chǎng)景,所以用下面幾行替換內(nèi)核中的最后三行:

Sphere s = Sphere(float3(0.), 1.);
Ray ray = Ray(float3(0., 0., -3.), normalize(float3(uv, 1.0)));
float3 col = float3(0.);
for (int i=0.; i<100.; i++) {
    float dist = distToSphere(ray, s);
    if (dist < 0.001) {
        col = float3(1.);
        break;
    }
    ray.origin += ray.direction * dist;
}
output.write(float4(col, 1.), gid);

讓我們一行一行來看這些代碼.我們首先創(chuàng)建了一個(gè)球體和一個(gè)射線.注意射線的z值接近于0時(shí),球體看起來更大因?yàn)樯渚€離場(chǎng)景更近,相反,當(dāng)它遠(yuǎn)離0,球體看上去更小了,原因很明顯-我們用射線作為了隱性攝像機(jī).下面我們定義顏色來初始化一個(gè)純黑色.現(xiàn)在raymarching最精華的地方來了!我們循環(huán)一定次數(shù)(步數(shù))來確保我們行進(jìn)足夠細(xì)膩.我們?cè)谶@里用100,但你可以嘗試一個(gè)更大數(shù)值的步數(shù),來觀察渲染圖像的質(zhì)量的改善,當(dāng)然也會(huì)消耗更多的計(jì)算資源.在循環(huán)里,我們計(jì)算當(dāng)前位置沿射線到場(chǎng)景的距離,同時(shí)也檢查我們是否接觸到了場(chǎng)景中的物體,如果接觸到了就將其著色為白色并跳出循環(huán),否則就更新射線位置向場(chǎng)景前進(jìn)一些.

注意我們規(guī)范化了射線方向來覆蓋邊緣情況,例如向量(1,1,1)(屏幕邊角)的長(zhǎng)度會(huì)是sqrt(1 * 1 + 1 * 1 + 1 * 1)即大約1.732.這意味著我們需要向前移動(dòng)射線位置大約1.73*dist,也就是大約我們需要前進(jìn)距離的兩倍,這可能會(huì)讓我們因?yàn)槌^射線交點(diǎn)而錯(cuò)過/穿過物體.為此,我們規(guī)范化了方向,來確保它的長(zhǎng)度始終是1.最后,我們將顏色寫入到輸出紋理中.如果你現(xiàn)在運(yùn)行playground,你應(yīng)該會(huì)看到類似的圖像:

raymarching1.png

現(xiàn)在我們創(chuàng)建一個(gè)函數(shù)命名為distToScene,它接收一個(gè)射線作為參數(shù),因?yàn)槲覀儸F(xiàn)在卷尺的是找到包含多個(gè)物體的復(fù)雜場(chǎng)景中的最短距離.下一步,我們移動(dòng)球體相關(guān)的代碼到新函數(shù)內(nèi),只返回到球體的距離(暫時(shí)).然后,我們改變球體位置到(1,1,1),半徑0.5,這意味著球體現(xiàn)在在0.5 ... 1.5范圍內(nèi).這里有個(gè)巧妙的花招來做例子:如果我們?cè)?code>0.0 ... 2.0內(nèi)重復(fù)空間,則球體總是處于內(nèi)部.下一步,我們做個(gè)射線的本地副本,并對(duì)原始值取模.然后我們用重復(fù)的射線代入distToSphere()函數(shù).

float distToScene(Ray r) {
    Sphere s = Sphere(float3(1.), 0.5);
    Ray repeatRay = r;
    repeatRay.origin = fmod(r.origin, 2.0);
    return distToSphere(repeatRay, s);
}

通過使用fmod函數(shù),我們重復(fù)空間填滿整個(gè)屏幕,實(shí)際上創(chuàng)建了一個(gè)無限數(shù)量的球體,每一個(gè)都帶著自己的(重復(fù)的)射線.當(dāng)然,我們將只看被屏幕的xy坐標(biāo)之內(nèi)的那些,然而,z坐標(biāo)將讓我們看到球體是如何進(jìn)到無限深度的.在內(nèi)核中,移除球體代碼,將射線移到很遠(yuǎn)的位置,修改dist來給我們留出到場(chǎng)景的距離,最后修改最后一行來顯示更好看的顏色:

Ray ray = Ray(float3(1000.), normalize(float3(uv, 1.0)));
...
float dist = distToScene(ray);
...
output.write(float4(col * abs((ray.origin - 1000.) / 10.0), 1.), gid);

我們將顏色與射線位置相乘.除以10.0因?yàn)閳?chǎng)景相當(dāng)大,射線位置在大部分地方會(huì)大于1.0,這會(huì)讓我們看到純白色.我們用abs()因?yàn)槠聊蛔筮叺?code>x小于0,它會(huì)讓我們看到純黑色,所以我們只需鏡像上/下和左/右的顏色.最后,我們偏移射線位置100,以匹配射線起點(diǎn)(攝像機(jī)).如果你現(xiàn)在運(yùn)行playground,你應(yīng)該會(huì)看到類似的圖像:

raymarching2.png

下一步,我們讓場(chǎng)景動(dòng)起來!我們?cè)?a target="_blank" rel="nofollow">part 12中已經(jīng)看到如何發(fā)送uniforms變量比如timeGPU,所以我們就不再重復(fù)了.

float3 camPos = float3(1000. + sin(time) + 1., 1000. + cos(time) + 1., time);
Ray ray = Ray(camPos, normalize(float3(uv, 1.)));
...
float3 posRelativeToCamera = ray.origin - camPos;
output.write(float4(col * abs((posRelativeToCamera) / 10.0), 1.), gid);

我們添加time到所有三個(gè)坐標(biāo),但我們只讓xy起伏變化而z保持直線.1.部分只是為了阻止攝像機(jī)撞到最近的球體上.要看這份代碼的動(dòng)畫效果,我在下面使用一個(gè)Shadertoy嵌入式播放器.只要把鼠標(biāo)懸浮在上面,并單擊播放按鈕就能看到動(dòng)畫:<譯者注:簡(jiǎn)書不支持嵌入播放器,我用gif代替https://www.shadertoy.com/embed/XtcSDf>

raymarching.mov.gif

感謝 Chris的協(xié)助.
源代碼source code已發(fā)布在Github上.
下次見!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末硬萍,一起剝皮案震驚了整個(gè)濱河市朴乖,隨后出現(xiàn)的幾起案子助赞,更是在濱河造成了極大的恐慌雹食,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钝荡,死亡現(xiàn)場(chǎng)離奇詭異舶衬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)植阴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門圾浅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掠手,“玉大人,你說我怎么就攤上這事狸捕∨绺耄” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵灸拍,是天一觀的道長(zhǎng)做祝。 經(jīng)常有香客問我,道長(zhǎng)鸡岗,這世上最難降的妖魔是什么混槐? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮声登,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揣苏。我一直安慰自己悯嗓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布卸察。 她就那樣靜靜地躺著脯厨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坑质。 梳的紋絲不亂的頭發(fā)上合武,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音涡扼,去河邊找鬼稼跳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛壳澳,可吹牛的內(nèi)容都是我干的岂贩。 我是一名探鬼主播茫经,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼巷波,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼萎津!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抹镊,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤锉屈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后垮耳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颈渊,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年终佛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俊嗽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铃彰,死狀恐怖绍豁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牙捉,我是刑警寧澤竹揍,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站邪铲,受9級(jí)特大地震影響芬位,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜带到,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一昧碉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧揽惹,春花似錦晌纫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至慕嚷,卻和暖如春哥牍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喝检。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工嗅辣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挠说。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓澡谭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親损俭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛙奖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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