本人初步認識raymarch是在UE4。25出體積雲的時候,那時候就覺得這個技術特別厲害。但是由於一直沒有時間近期才開始慢慢的學習起來。我也在知乎上翻閱了不少關於raymarch的文章,大多數都是直接搬運的,基本都只講了原理,貼的程式碼在ue中根本實現不出來,有也只是一個球(頭疼)。還有的則是比較複雜的,由於我也是剛學,所以我這邊所有的內容只在材質編輯器中用custom節點實現,不需要進行額外的操作。
由於raymarch原理的東西有很多大佬都講的很清楚了,所以本文只涉及如何實現(如何搬運現有的演算法程式碼),不會涉及過多的原理。
下面就開始介紹,首先搭建一下程式碼的整體框架,包含功能函式的結構體,raymarch主體及各種變數定義。
所有功能函式的結構體定義
raymarch主體
瞭解到以上內容後,我們先不在結構體裡面定義這麼多功能函式,我們先可以製作出一個屋頂。在結構體裡定義一個功能函式sdTriPrism,p為物體表面位置,poffset是物體的偏移,h是三稜柱的高和寬,裡面的是三稜柱的的距離場公式,文章末尾我會貼上鍊接(這裡原理不做介紹)。然後我們需要定義一個場景內距離場的集合,我們在其中呼叫上面定義的三稜柱的函式並將這些引數輸入,rp後面會提到。
struct裡三稜柱功能函式及場景距離場合集
然後我們有了這些可以進行raymarch取樣了,dis則是光線距離物體表面的長度,a用來記錄光線是否擊中物體距離場表面,step是光線步進次數,stepsize是光線步進的距離(太大會擊中不了物體表面,太小如果步進次數不夠也會擊中不到表面,所以我們將其設為引數在例項材質中調整它),position是worldpositon減去objectposition(將其轉換為本地座標,將worldposition進行transform到local也是一樣的效果),ViewDirection則為cameraVector的反向值。最後我們return a,將其輸出到basecolor 和opacitymask
raymarch
材質編輯器中的設定
然後我們用現有的sphere模型拖到場景中並設定縮放值為11,將step設定為100,stepsize設定為10,並將例項材質給予這個sphere,然後可以得到一盒稜柱的3d形狀,由於並沒有法線,所以我們得對其進行法線的計算。
初始稜柱的形狀
法線
我們同樣將演算法寫成功能函式放入struct中,法線的演算法我參考的這個影片,在28分鐘左右的時間
https://www。
youtube。com/watch?
v=grmZ0I5-CgA
演算法是在擊中物體距離場表面的位置進行三個方向的偏移(有些類似ddx,ddy),我這邊偏移的值是0。001。
normal計算
然後有了功能函式後我們直接在最後加上程式碼呼叫normal,p為最終擊中的物體表面距離場位置,輸出一個float4的值,rgb為normal,a為mask。
呼叫normal輸出
輸出normal和a
然後再場景中我們得到了一個帶法線的三稜柱(鋸齒是因為太大了取樣精度不夠,提高step值可以解決)
帶法線的三稜柱
我們屋頂的形狀有了,然後我們發現它需要旋轉,我們轉模型是沒有用的,需要在三稜柱的position上加一個旋轉矩陣。這時候就需要再次在struct中定義一個功能函式rotateX(原理是矩陣變換,不做介紹)矩陣的程式碼我是直接搬運的下方連結的程式碼。
旋轉矩陣
https://www。
shadertoy。com/view/4tcG
Dr
矩陣變換功能函式
然後我們將sceneSDF中的稜柱的position替換成和變換矩陣相乘後的結果,rotateX中的theta值為弧度,就是說如要要轉90度那麼需要3。14*0。5=1。57的theta值。
矩陣變換後的稜柱
矩陣變換後的屋頂
有了屋頂後我們需要柱子,這時候就需要在struct中再定義一個柱子的距離場函式,p和poffset和上面一樣,h為高,r為半徑。
圓柱sdf演算法
然後繼續在sceneSDF裡面新增圓柱,由於我們需要的四個圓柱形狀是一樣的,所以只需要poffset設定不同的值就可以了。這時候我們得到的圓柱同樣也需要旋轉90度才能獲取想要的形狀,剩下的如下設定,最後用min就可以和在一起了。但是由於物體比較大,我這邊設定了step值為500,stepsize為5。
場景距離場集合加入四根圓柱
加入圓柱後的房子
最後就是底層了,底層我用兩個不同大小的roundbox來製作,所以仍然需要一個roundbox的距離場。依舊p和poffset如上,size是長寬高,r為圓角大小。
定義roundbox功能函式
在場景距離場集合中加入兩個roundbox
帶法線的raymarch房子
有了模型以後,那麼就要上貼圖了。我們這邊所用的是一種叫trim projection的技術
附上該技術的原影片連結:
貼圖
https://www。
youtube。com/watch?
v=VaYyPTw0V84
原理大概是對我們計算出的法線進行三個方向的區分,用pow使對比度儘量拉開以後將顏色分別乘以normal的三個軸向後加起來。
這時候我們需要先採樣貼圖,ue4中取樣貼圖的方式如下,由於additionaloutput只能輸出一維的float(是不是我不會用的關係),所以我們這邊需要輸出三個軸向的color,然後append起來,所以下面程式碼中定義了三個不同軸向的color取樣同一張貼圖然後分別和n的三個軸相乘後相加即可得到相應的結果(如果只定義一個軸或者說只定義同一個貼圖然後進行如下操作的話會出現拉伸)
color的計算
n。x
n。z
n。y
上面的是n經過pow之後的三軸結果(無光照模式),不加pow對比度拉不開顏色加一起會不對。這樣整個房子加上貼圖基本就完成了,還可以加normal,但是trim projection的斜面效果並不好,這邊就不演示了。
帶貼圖效果
最終節點圖
上raymarch材質
最後附上所用到的SDF的連結
https://
iquilezles。org/www/arti
cles/distfunctions/distfunctions。htm
參考過的文章:
https://
sainarayan。me/2017/12/1
6/raymarching-using-signed-distance-fields-in-ue4/
白狸奴:RayMarch in UE4[第一章-概念與基礎場景構建](傻瓜向,看不懂自殺) @白狸奴
文中有不足和錯誤之處希望大佬指正,最後附上custom節點程式碼(帶normal,和color一樣輸出就行了)。
struct raymarch
{
float sdRoundBox(float3 p, float3 poffset, float3 size, float r)//圓角boxSDF
{
float3 q = abs(p - poffset) - size;
return length(max(q, 0。0)) + min(max(q。x, max(q。y, q。z)), 0。0) - r;
}
float sdTriPrism(float3 p, float3 poffset, float2 h) //圓角三稜柱SDF
{
p = p + poffset;
const float k = sqrt(3。0);
h。x *= 1 * k;
p。xy /= h。x;
p。x = abs(p。x) - 1。0;
p。y = p。y + 1。0 / k;
if (p。x + k * p。y > 0。0)
p。xy = float2(p。x - k * p。y, -k * p。x - p。y) / 2。0;
p。x -= clamp(p。x, -2。0, 0。0);
float d1 = length(p。xy) * sign(-p。y) * h。x;
float d2 = abs(p。z) - h。y;
return length(max(float2(d1, d2), 0。0)) + min(max(d1, d2), 0。) - 10;
}
float sdCappedCylinder(float3 p, float3 poffset, float h, float r) //圓柱sdf
{
p = p - poffset;
float2 d = abs(float2(length(p。xz), p。y)) - float2(h, r);
return min(max(d。x, d。y), 0。0) + length(max(d, 0。0));
}
float3x3 rotateX(float theta)
{
float c = cos(theta);
float s = sin(theta);
return float3x3(
float3(1, 0, 0),
float3(0, c, -s),
float3(0, s, c)
);
}
float sceneSDF(float3 rp)//場景內sdf合集
{
float sdf = 0;
float TriPrism = sdTriPrism(mul(rotateX(-1。57), (rp + float3(0, 0, -200))), float3(0, 0, 0), float2(113。680374, 295。184296));
float RoundBox = sdRoundBox(rp, float3(0, 0, -185), float3(200, 300, 10), 10);
float RoundBox2 = sdRoundBox(rp, float3(0, 0, -220), float3(220, 320, 10), 12);
float Cylinder = sdCappedCylinder(mul(rp, rotateX(1。57)), float3(150, -50, 250), 30, 150);
float Cylinder2 = sdCappedCylinder(mul(rp, rotateX(1。57)), float3(150, -50, -250), 30, 150);
float Cylinder3 = sdCappedCylinder(mul(rp, rotateX(1。57)), float3(-150, -50, -250), 30, 150);
float Cylinder4 = sdCappedCylinder(mul(rp, rotateX(1。57)), float3(-150, -50, 250), 30, 150);
sdf = min(min(RoundBox, TriPrism), RoundBox2);
sdf = min(min(min(min(sdf, Cylinder), Cylinder3), Cylinder4), Cylinder2);
return sdf;
}
float3 rmnormal(float3 position)//法線計算
{
float2 off = float2(0。001, 0);
return normalize(float3(
sceneSDF(position + off。xyy) - sceneSDF(position - off。xyy),
sceneSDF(position + off。yxy) - sceneSDF(position - off。yxy),
sceneSDF(position + off。yyx) - sceneSDF(position - off。yyx)
));
}
};
raymarch rm;
float dis = 0;
float d0 = 0;
float a = 0;
float3 normal = 0;
float uvscale = 1;
float2 uv = 0;
float4 tex = Texture2DSample(Tex, TexSampler, uv);
float3 position = WorldPosition - object;
for(int i = 0;i { position+=ViewDirection*StepSize; dis = rm。sceneSDF(position); if(dis<0。1) { a=1; break; } else if(dis>1000) { a=0; break; } } float3 color = 0; float3 p = position; normal=rm。rmnormal(p); float3 n = abs(normal); n=pow(n,5); float4 colorxz = Texture2DSample(Tex, TexSampler, position。xz * 0。005); float4 colorxy = Texture2DSample(Tex, TexSampler, position。xy * 0。005); float4 coloryz = Texture2DSample(Tex, TexSampler, position。yz * 0。005); color=colorxy。xyz*n。z+colorxz。xyz*n。y+coloryz。xyz*n。x; float4 normalxz = Texture2DSample(Tex2, Tex2Sampler, position。xz * 0。005); float4 normalxy = Texture2DSample(Tex2, Tex2Sampler, position。xy * 0。005); float4 normalyz = Texture2DSample(Tex2, Tex2Sampler, position。yz * 0。005); float3 stonenormal = normalxy。xyz * n。z + normalxz。xyz * n。y + normalyz。xyz * n。x; stonenormal1=stonenormal。x; stonenormal2 = stonenormal。y; stonenormal3=stonenormal。z; color1 = color。x; color2=color。y; color3 = color。z; return float4(normal,a); return color1; return color2; return color3; return stonenormal1; return stonenormal2; return stonenormal3;