虛幻4中實現簡單的raymarch
阿新 • • 發佈:2019-01-29
以前一直都是在Directx或者UnityShaderLab裡做raymarch,最近在研究虛幻4的Shader所以在虛幻4裡簡單實現了一下這個。
我的step數量調得很低。
剛開始其實不好下手,虛幻的渲染架構過於複雜,高度封裝,我們無法直接像unityshaderlab一樣對shader進行直接編寫,如果想直接自己上傳shader而繞過虛幻的材質系統,那麼成本將是高昂的。首先先簡單捋一下虛幻的shader生成過程。第一步我們在材質系統裡完成各種節點的編寫,這些節點是c++寫的,可以在原始碼的MaterialExpression裡找到全部實現。這些節點帶有一個Fstring的字串,裡面儲存的是HLSL程式碼。在材質編譯的時候,HLSLTranslator會把這些字串插入到usf提供的shader模板中,最後生成我們需要的shader。生成後的shader可以在材質編輯器的HLSLCode裡看到。如果我們想在CustomNode裡呼叫自己的函式,可以在CommonUSF檔案中新增。但是可能會導致引擎崩潰,直到4.17版本,虛幻將usf檔案暴露出來,在4.17版本我們就能給自己的專案新增usf從而CustomNode就能呼叫函數了。除此之外,虛幻的材質如何模擬多pass行為也是一個難題,目前4.17的方式是使用多個Render Target來模擬,但是效率低下。
下面是我ray mach的程式碼
float4 CustomNodeWithTime(
float3 worldpos,
float3 objpos,
float3 viewdir,
float stepsize,
float steps,
float4 color,
float radius,
float time,
float3 lightdir
)
{
float3 seconcenter = { 0, 0, 0 }; float offset = 300 * sin(time);
float eoff = 0.01; seconcenter.x = objpos.x + offset;
seconcenter.z = objpos.z + offset;
seconcenter.y = objpos.y;
for (int i = 0; i <= steps; i++)
{
if ((distance(worldpos, objpos) <= radius) || (distance(worldpos, seconcenter) <= radius - 100))
{
float3 outposx = { worldpos.x + eoff, worldpos.y, worldpos.z };
float3 inposx = { worldpos.x - eoff, worldpos.y, worldpos.z };
float Xdelta = distance(outposx, objpos) - distance(inposx, objpos); float3 outposy = { worldpos.x, worldpos.y + eoff, worldpos.z };
float3 inposy = { worldpos.x, worldpos.y - eoff, worldpos.z };
float Ydelta = distance(outposy, objpos) - distance(inposy, objpos); float3 outposz = { worldpos.x, worldpos.y, worldpos.z + eoff };
float3 inposz = { worldpos.x, worldpos.y, worldpos.z - eoff };
float Zdelta = distance(outposz, objpos) - distance(inposz, objpos); float3 normal = { Xdelta, Ydelta, Zdelta }; float3 LightColor = saturate(dot(normal, lightdir));
color.rgb = color.rgb * LightColor;
return float4(LightColor,1);
//return color;
}
worldpos += viewdir * stepsize; } return float4(0, 0, 0, 0);
}
我喜歡在VS裡寫程式碼然後再貼上到CustomNode裡
注:直接把上面的程式碼複製貼上到程式碼裡會報錯,不清楚為什麼,如果在vs裡重新敲一次卻不會。估計網頁裡有些符號和程式碼裡的不一樣,雖然他們看起來一樣。
然後最近寫了一個超級CustomNode實現RayMarching效果如下:順便總結一下常用Raymarching模型(這些公式來自國外一個朋友)
Sphere - signed - exact
float sdSphere( vec3 p, float s )
{
return length(p)-s;
}
Box - unsigned - exact
float udBox( vec3 p, vec3 b ) { return length(max(abs(p)-b,0.0)); }
Round Box - unsigned - exact
float udRoundBox( vec3 p, vec3 b, float r )
{
return length(max(abs(p)-b,0.0))-r;
}
Box - signed - exact
float sdBox( vec3 p, vec3 b )
{
vec3 d = abs(p) - b;
return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
}
Torus - signed - exact
float sdTorus( vec3 p, vec2 t )
{
vec2 q = vec2(length(p.xz)-t.x,p.y);
return length(q)-t.y;
}
Cylinder - signed - exact
float sdCylinder( vec3 p, vec3 c )
{
return length(p.xz-c.xy)-c.z;
}
Cone - signed - exact
float sdCone( vec3 p, vec2 c )
{
// c must be normalized
float q = length(p.xy);
return dot(c,vec2(q,p.z));
}
Plane - signed - exact
float sdPlane( vec3 p, vec4 n )
{
// n must be normalized
return dot(p,n.xyz) + n.w;
}
Hexagonal Prism - signed - exact
float sdHexPrism( vec3 p, vec2 h )
{
vec3 q = abs(p);
return max(q.z-h.y,max((q.x*0.866025+q.y*0.5),q.y)-h.x);
}
Triangular Prism - signed - exact
float sdTriPrism( vec3 p, vec2 h )
{
vec3 q = abs(p);
return max(q.z-h.y,max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5);
}
Capsule / Line - signed - exact
float sdCapsule( vec3 p, vec3 a, vec3 b, float r )
{
vec3 pa = p - a, ba = b - a;
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
return length( pa - ba*h ) - r;
}
Capped cylinder - signed - exact
float sdCappedCylinder( vec3 p, vec2 h )
{
vec2 d = abs(vec2(length(p.xz),p.y)) - h;
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
Capped Cone - signed - boundfloat sdCappedCone( in vec3 p, in vec3 c )
{
vec2 q = vec2( length(p.xz), p.y );
vec2 v = vec2( c.z*c.y/c.x, -c.z );
vec2 w = v - q;
vec2 vv = vec2( dot(v,v), v.x*v.x );
vec2 qv = vec2( dot(v,w), v.x*w.x );
vec2 d = max(qv,0.0)*qv/vv;
return sqrt( dot(w,w) - max(d.x,d.y) ) * sign(max(q.y*v.x-q.x*v.y,w.y));
}
|
Ellipsoid - signed - bound
float sdEllipsoid( in vec3 p, in vec3 r )
{
return (length( p/r ) - 1.0) * min(min(r.x,r.y),r.z);
}
Triangle - unsigned - exact
float dot2( in vec3 v ) { return dot(v,v); }
float udTriangle( vec3 p, vec3 a, vec3 b, vec3 c )
{
vec3 ba = b - a; vec3 pa = p - a;
vec3 cb = c - b; vec3 pb = p - b;
vec3 ac = a - c; vec3 pc = p - c;
vec3 nor = cross( ba, ac );
return sqrt(
(sign(dot(cross(ba,nor),pa)) +
sign(dot(cross(cb,nor),pb)) +
sign(dot(cross(ac,nor),pc))<2.0)
?
min( min(
dot2(ba*clamp(dot(ba,pa)/dot2(ba),0.0,1.0)-pa),
dot2(cb*clamp(dot(cb,pb)/dot2(cb),0.0,1.0)-pb) ),
dot2(ac*clamp(dot(ac,pc)/dot2(ac),0.0,1.0)-pc) )
:
dot(nor,pa)*dot(nor,pa)/dot2(nor) );
}
Quad - unsigned - exact
float dot2( in vec3 v ) { return dot(v,v); }
float udQuad( vec3 p, vec3 a, vec3 b, vec3 c, vec3 d )
{
vec3 ba = b - a; vec3 pa = p - a;
vec3 cb = c - b; vec3 pb = p - b;
vec3 dc = d - c; vec3 pc = p - c;
vec3 ad = a - d; vec3 pd = p - d;
vec3 nor = cross( ba, ad );
return sqrt(
(sign(dot(cross(ba,nor),pa)) +
sign(dot(cross(cb,nor),pb)) +
sign(dot(cross(dc,nor),pc)) +
sign(dot(cross(ad,nor),pd))<3.0)
?
min( min( min(
dot2(ba*clamp(dot(ba,pa)/dot2(ba),0.0,1.0)-pa),
dot2(cb*clamp(dot(cb,pb)/dot2(cb),0.0,1.0)-pb) ),
dot2(dc*clamp(dot(dc,pc)/dot2(dc),0.0,1.0)-pc) ),
dot2(ad*clamp(dot(ad,pd)/dot2(ad),0.0,1.0)-pd) )
:
dot(nor,pa)*dot(nor,pa)/dot2(nor) );
}
Torus82 - signed
float sdTorus82( vec3 p, vec2 t )
{
vec2 q = vec2(length2(p.xz)-t.x,p.y);
return length8(q)-t.y;
}
Torus88 - signed
float sdTorus88( vec3 p, vec2 t )
{
vec2 q = vec2(length8(p.xz)-t.x,p.y);
return length8(q)-t.y;
}
距離場運算
Union
float opU( float d1, float d2 )
{
return min(d1,d2);
}
Substraction
float opS( float d1, float d2 )
{
return max(-d1,d2);
}
Intersection
float opI( float d1, float d2 )
{
return max(d1,d2);
}
變換操作Repetition
float opRep( vec3 p, vec3 c )
{
vec3 q = mod(p,c)-0.5*c;
return primitve( q );
}
Rotation/Translation
vec3 opTx( vec3 p, mat4 m )
{
vec3 q = invert(m)*p;
return primitive(q);
}
Scale
float opScale( vec3 p, float s )
{
return primitive(p/s)*s;
}
distance deformations
You must be carefull when using distance transformation functions, as the field created might not be a real distance function anymore. You will probably need to decrease your step size, if you are using a raymarcher to sample this. The displacement example below is using sin(20*p.x)*sin(20*p.y)*sin(20*p.z) as displacement pattern, but you can of course use anything you might imagine. As for smin() function in opBlend(), please read thesmooth minimum article in this same site.Displacement
float opDisplace( vec3 p )
{
float d1 = primitive(p);
float d2 = displacement(p);
return d1+d2;
}
Blend
float opBlend( vec3 p )
{
float d1 = primitiveA(p);
float d2 = primitiveB(p);
return smin( d1, d2 );
}
domain deformations
Domain deformation functions do not preserve distances neither. You must decrease your marching step to properly sample these functions (proportionally to the maximun derivative of the domain distortion function). Of course, any distortion function can be used, from twists, bends, to random noise driven deformations.Twist
float opTwist( vec3 p )
{
float c = cos(20.0*p.y);
float s = sin(20.0*p.y);
mat2 m = mat2(c,-s,s,c);
vec3 q = vec3(m*p.xz,p.y);
return primitive(q);
}
Cheap Bend
float opCheapBend( vec3 p )
{
float c = cos(20.0*p.y);
float s = sin(20.0*p.y);
mat2 m = mat2(c,-s,s,c);
vec3 q = vec3(m*p.xy,p.z);
return primitive(q);
}