[程式] Ray Marching

作者: YoshiCasa (Yoshi Casa)   2022-03-03 11:15:43
Shader Toy 上,
大部分都是用 Ray Marching 的演算法寫的。
https://www.shadertoy.com/
不懂 Ray Marching 的話,
看那些 code 和看天書差不多。
[Ray Tracing]
3D電腦繪圖中,要決定一個 PIXEL 要畫甚麼顏色,
我們會模擬一道從假想的攝影機或眼睛射出的光,
通過了一個平面的銀幕,最後射中了某個物體。
射到物體後,
我們會知道光線觸擊了甚麼物體,
與那個點是在空間中的哪個位置。
所以一道光就是有一個位置與一個方向組成的。
Ray Tracing 要計算光線觸及點時,
是從光線起始位置,
朝著光線方向每次移動一小段距離,
然後計算新位置與空間中所有物件有無碰撞。
因為每次光線只移動一小段就要重新計算碰撞,
所以這是種效率很差的方法,
因此有了 Ray Marching。
[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: 離光線出發點最近的物件距離
ds = DTS( pos )
// DS 太小表示撞到東西了
if ds < 0.00001 then break;
// 光線可以安全的射 ds 這麼長而不用擔心碰到東西
pos = pos + ds * dir
// 射了太遠都碰不到東西,就別在前進了
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 )
用類似的方法就能算法向量了,
真正的算法在 shader toy 看一下就有了,
就五六行。
有了 normal,計算它與光源方向的角度,
角度越大越亮,越小越暗。
[Shadow (陰影)]
當我們有了一個光線觸及點 a,要如何知道該點有沒有影子呢?
一、計算從光源到該點的 DTS (應該說是光線碰撞距離啦)
二、計算從光源道該點的實際距離
三、如果 DTS < 實際距離,
表示光源在射向該點時,有撞到東西,
所以該點就應該是陰影。
有點無聊的東西,
但如果想抄 shader toy 到 game engine 用,
卻不懂這個的話,連想抄作業都抄不起來的。
作者: nicetw20xx (哇愛台灣)   2022-03-03 12:36:00
推,感謝解說
作者: coolrobin (泳圈)   2022-03-03 22:04:00
未看先推,感謝分享
作者: sargent (Conditional)   2022-03-04 13:12:00
推分享,但我不懂,為什麼要分那麼多次呢?真複雜
作者: grezod (grezod)   2022-03-06 09:52:00

Links booklink

Contact Us: admin [ a t ] ucptt.com