1. 程式人生 > >虛幻4中實現簡單的raymarch

虛幻4中實現簡單的raymarch

    以前一直都是在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 - bound

float 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);
}