Ray Marching (Sphere Tracing) 沒學 Ray Marching 是看不懂 ShaderToy 的 Code 的

Ray Tracing (光源追蹤)

3D電腦繪圖中,要決定一個 PIXEL 要畫甚麼顏色,我們會模擬一道從假想的攝影機或眼睛射出的光,通過了一個平面的銀幕,最後射向某個物體。

射到物體後,我們會知道光線觸擊了甚麼物體,與那個點是在空間中的哪個位置。

所以一道光就是有一個位置與一個方向組成的。

Ray Tracing 要計算觸及點時,是從光線起始位置,朝著光線方向每次移動一小段距離,然後計算新位置與空間中所有物件有無碰撞。

這是種效率很差的方法。

Ray Marching (光源行進?)

如果我們描述空間中的物件,是用 “某一點 x 觸及到該物件的最小距離 (Distance to the Scene, 以下簡稱 DTS)” 來表示的話,那我們每次從任一個點要移動光線時,就可以不只移動一小段距離,而是先計算出光線出發點與所有物件的 DTS,然後移動其中最小的 DTS。

或是說 DTS 就是保證某個距離內,光線絕對不會碰到任何東西的意思。

因為每次移動光線的距離變長許多,所以在非極端的情況下,效率比 Ray Tracing 好非常多。

舉例:

一個在 XZ 平面上,高度為 0 的平面,空間中任何一點 a 與該平面的距離一定是 a.y。

一個圓球,中心點在 0,0,0,半徑為 R,空間中任何一點 a 與該平面的距離是 length( a ) – R。

所以,空間中任何一點 a,觸及該空間中所有物件的最小距離是:

float DTS( vec3 a ) { return min( a.y, length( a ) – R ); }

所以假設我有一道光,從 pos (10, 10, 10) 出發,方向是 dis( -1, -0.5, -1 ),要計算那個光會集中在哪個點的方法。

for i [0, MAX_STEP]

ds = DTS( pos ) // 離光線出發點最近的物件距離

if ds < 0.00001 then break; // DS 太小表示撞到東西了

pos = ds + pos * dir  // 光線可以安全的射 ds 這麼長而不用擔心碰到東西

if length(pos – (10,10,10)) > 10000.0 then break; // 射了太遠都碰不到東西就不算了

每一個 PIXEL,或是對每一個 shader 的 UV 都可以用這樣算出觸碰的距離,有了距離當然也能知道觸碰的位置 (pos + dir * ds)。這就是 Ray Marching

Normal (法線或法向量)

就一個物體中,任一個點垂直於該平面的向量。

要如何計算 f(x) 中,某個點 x 的斜率?

斜率 = f( x + delta ) – f(x) / ( delta )

用類似的方法就能算法向量了。

有了 normal,計算它與光源方向的角度,角度越大越亮,越小越暗。

Shadow (陰影)

當我們有了一個光線觸及點 a,要如何知道該點有沒有影子呢?

一、計算從光源到該點的 DTS

二、計算從光源道該點的實際距離

三、如果 DTS < 實際距離,表示光源在射向該點時,有撞到東西,所以該點就應該是陰影。