目的:
最近接觸一些分形Shader。自己實現了一個2D fbm,感覺很好看。然後又看到人家三維的分形,看起來很棒,心裏癢癢。
影視大作和網上一堆令人驚歎的分形藝術,我也不再多說了。
本篇旨在對在ue4中實現分形的第一步,RayTrace和RayMarch加以具體解釋,然後給出一個最簡單的RayMarch材質,便於讀者擴展。
(RayMarch一個球形“雲”)
參考:
觀察:
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)。
假設
又有當u=1,有
那麼。
所以
又當v=1,
那麼
所以
所以
當水平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的形狀。