Chango的數學Shader世界(十六)RayTrace三維分形(一)—— ue4中最簡單的RayMarch

目的:

最近接觸一些分形Shader。自己實現了一個2D fbm,感覺很好看。然後又看到人家三維的分形,看起來很棒,心裏癢癢。

影視大作和網上一堆令人驚歎的分形藝術,我也不再多說了。

本篇旨在對在ue4中實現分形的第一步,RayTrace和RayMarch加以具體解釋,然後給出一個最簡單的RayMarch材質,便於讀者擴展。

(RayMarch一個球形“雲”)

 

參考:

我之前對一篇ShaderToy的解析

 

觀察:

1.分形是什麼?它有什麼實際意義?

分形指縮放之後還自相似的圖形。

分形的特點,在於以極少的初始條件產生極豐富的信息。大自然也處處是分形。

分形的意義在整個科學裏比較鬆散。分形在上世紀最熱的時候,人們預言稱21世紀是“分形的世紀”。(像不像現在的人工智能?)事實證明人類至今還對其知之甚少,難以研究。現在,涼到了正常水平。

除了帥以外,分形的幾個實際應用:

1.海岸線估計:數據證明海岸線具有自相似性,所以只要給出一個粗略的海岸線輪廓,再進行分形後計算長度,就能估算實際的海岸線長度。
2.圖片超分辨率,壓縮...
3.混沌理論

2.簡述RayTrace,RayTrace實際意義,什麼時候該用哪個?

RayMarch:沿着視線,每隔一小步長收集此點顏色信息,最終累加。不反射視線。
RayTrace:沿着視線,看是否與場景相交,在交點處計算顏色(光照模型,陰影...),並反/折/衍射

因爲RayMarch在交線段上每一個步長都要收集信息,所以肯定處理半透明的,有體積的東西。常見如體積雲,體積陰影...

因爲它計算量大,效果好,只有在特別追求效果或互動的時候,才用它。

RayTrace不用多說,一般的透明半透明場景。現在顯卡也支持硬件光追,十分提升場景的真實感。也許也可以應用更多PBR算法了。

3.爲什麼想要RayTrace分形,卻先RayMarch?

RayMarch:沿着視線收集信息,不反射
RayTrace:沿着視線,碰到立馬求顏色,並反/折/衍射

你看,他們的關鍵都是沿着視線去收集,區別不大。

而且你會發現很多問題是兩者的混合。比如我們之後RayTrace Julia Set的時候,由於不能得到Julia Set分形的解析式,只能通過Distance Estimate 慢慢“RayMarch”直到十分接近分形表面,再進行RayTrace。

所以,拿到距離場再進行RayMarch,到達表面再進行RayTrace,是現代渲染的基本操作。

 

分析:

1.March

現在,我們來RayMarch一箇中心在圓點,半徑爲100的雲球。

簡單起見,我們不作其他判斷,暴力地每一個步長都判斷。

僞代碼

僞代碼
float re=0;
for(int t=0;t<3000;t++)
{
    NowPos = CamPos + viewDir * t * Step;
    if(NowPos在球內)
    {
        //gain也可換成其他值,比如0.0001
        re+=1/3000.0f;
    }
}
return re;

顯而易見,當越接近邊緣,視線穿過的“雲點”越少,就顯得越薄。

最重要的問題來了,上面的僞代碼中,在ue4中,這個viewDir怎麼計算?

2.NDC空間

標準化設備座標空間(Normalized Device Coordinates,NDC)

座標系:

特點:

1.所有需要渲染到相機的圖元都在這裏面(或部分與之相交)
2.x,y,z屬於[-1,1]。(如果有說z屬於[0,1]的,那是老的或錯的)
3.它就是MVP之後的結果。
  M(模型空間到世界空間)
  ->V(世界空間到相機空間)
  ->P(相機到投影空間,xyz已除w)
  ->調整原點位置和座標系大小(投影空間到NDC)

在NDC中,

我們屏幕上的每一個像素點,都是視線射線端點,射線方向朝正Z。視線端點充滿了Z=-1,x,y屬於[-1,1]的正方形。

可惜UE4材質函數沒有直接提供從NDC到其他空間的轉換。我嫌麻煩,那就還是和大多數算法一樣,在相機空間中進行RayMarch,再轉世界系。

3.相機空間

在相機空間中,

我們的相機位置成爲射線端點,射線方向由相機指向nearClipPlane上的每一個像素採樣中心

???這啥玩意兒,這我在其他RayTrace教程裏咋每看見過?ShaderToy裏也沒見過?

沒錯,這個詞是我發明的,但是存在的。我發現大部分光追教程和ShaderToy裏的視線方向,都沒涉及到相機的nearClipPlane和FOV。也就是說,他們大多數都是錯的

以文首參考那篇ShaderToy爲例,如果你大幅調整窗口長寬比,你會發現結果會變畸形,並偏移中心。ShaderToy窗口一般不會想着動,而且很多固定視角或只移動一點點,畸變不明顯,無大礙。

一個典型的錯誤例子

當相機空間=世界空間,射線方向=normalize((2u-1)*ratio,2v-1,1)
ratio=屏幕分辨率x/屏幕分辨率y

我也不知道它咋推來的,可能和NDC搞混了,或者就是推錯了。如果把1換成ratio,那麼就正確了。

正確的推導

當水平FOV=90度:相機空間射線方向=normalize(2u-1,(2v-1)/ratio,1)
ratio=屏幕分辨率x/屏幕分辨率y

它是由最初推導簡化來的:

當水平FOV=90度:相機空間射線方向=normalize((2u-1)*near,(2v-1)/ratio*near,near)

我們必須考慮屏幕上(u,v)點,對應空間裏的屏幕(也就是nearClipPlane)中的哪個點,然後再與相機位置(0,0,0)相減得到視線方向。

當FOV確定爲90度(UE4默認),那麼我們可得maxX=near。

那麼此視線向量就是(dx,dy,near)。

假設

dx=k*(2u-1)

dy=p*(2v-1)

又有當u=1,有dx=(2u-1)*k=maxX

那麼k=maxX=near

所以dx=(2u-1)*near

又當v=1,dy=dx/ratio=near/ratio=(2v-1)*p

那麼p=near/ratio

所以dy=(2v-1)*near/ratio

所以

當水平FOV=90度:相機空間射線方向=normalize((2u-1)*near,(2v-1)/ratio*near,near)

FOV是個好東西,喜歡攝影一定要了解。

不過有一點,ue4中v座標朝下,而相機系中y座標朝上,要反一下,所以最終是

normalize((2u-1),-(2v-1)/ratio,1)

然後將射線方向轉到世界空間,再March,就o了。

 

步驟

1.建立一個後期材質

float2 pos = -1.0 + 2.0 * uv;
float3 rayDir = normalize(float3(pos.x, -pos.y*Size.y/Size.x, 1));
return rayDir;
int i=0;
float3 nowPos;
float re=0;
for(i=0;i<RayDis;i++)
{
  nowPos = camPos+ RayStep*rayDir*i;
  if(length(nowPos)<100.0f){
    re+=GainStep;
  }
}
return re;

 

2.附加到後期體積的Material中。對於實際項目,需要通過Custom處理渲染順序。

顯然,如果我們將March的條件改成fbm或其他比較複雜的函數,能模擬很多不同的形狀的雲和霧。

 

結語

方法的方法還是比方法重要。

下節我們將在此基礎上改進,並簡單RayMarch出一個JuliaSet的形狀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章