[程式] 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