虚幻4中实现简单的raymarch

时间:2022-04-06 04:04:29

    以前一直都是在Directx或者UnityShaderLab里做raymarch,最近在研究虚幻4的Shader所以在虚幻4里简单实现了一下这个。

虚幻4中实现简单的raymarch

我的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里重新敲一次却不会。估计网页里有些符号和代码里的不一样,虽然他们看起来一样。

虚幻4中实现简单的raymarch

然后最近写了一个超级CustomNode实现RayMarching效果如下:
虚幻4中实现简单的raymarch
传送门: 超级CustomNode

顺便总结一下常用Raymarching模型(这些公式来自国外一个朋友)

Sphere - signed - exact虚幻4中实现简单的raymarch

float sdSphere( vec3 p, float s )
{
  return length(p)-s;
}

Box - unsigned - exact虚幻4中实现简单的raymarch

float udBox( vec3 p, vec3 b )
{
  return length(max(abs(p)-b,0.0));
}

Round Box - unsigned - exact虚幻4中实现简单的raymarch

float udRoundBox( vec3 p, vec3 b, float r )
{
  return length(max(abs(p)-b,0.0))-r;
}


Box - signed - exact虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

float sdTorus( vec3 p, vec2 t )
{
  vec2 q = vec2(length(p.xz)-t.x,p.y);
  return length(q)-t.y;
}


Cylinder - signed - exact虚幻4中实现简单的raymarch

float sdCylinder( vec3 p, vec3 c )
{
  return length(p.xz-c.xy)-c.z;
}


Cone - signed - exact虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

float sdPlane( vec3 p, vec4 n )
{
  // n must be normalized
  return dot(p,n.xyz) + n.w;
}


Hexagonal Prism - signed - exact虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

float sdTorus82( vec3 p, vec2 t )
{
  vec2 q = vec2(length2(p.xz)-t.x,p.y);
  return length8(q)-t.y;
}


Torus88 - signed虚幻4中实现简单的raymarch

float sdTorus88( vec3 p, vec2 t )
{
  vec2 q = vec2(length8(p.xz)-t.x,p.y);
  return length8(q)-t.y;
}


距离场运算

Union虚幻4中实现简单的raymarch

float opU( float d1, float d2 )
{
    return min(d1,d2);
}

Substraction虚幻4中实现简单的raymarch

float opS( float d1, float d2 )
{
    return max(-d1,d2);
}

Intersection虚幻4中实现简单的raymarch

float opI( float d1, float d2 )
{
    return max(d1,d2);
}

变换操作

Repetition虚幻4中实现简单的raymarch

float opRep( vec3 p, vec3 c )
{
    vec3 q = mod(p,c)-0.5*c;
    return primitve( q );
}

Rotation/Translation虚幻4中实现简单的raymarch

vec3 opTx( vec3 p, mat4 m )
{
    vec3 q = invert(m)*p;
    return primitive(q);
}


Scale虚幻4中实现简单的raymarch

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 the smooth minimum article in this same site.

Displacement虚幻4中实现简单的raymarch

float opDisplace( vec3 p )
{
    float d1 = primitive(p);
    float d2 = displacement(p);
    return d1+d2;
}

Blend虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

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虚幻4中实现简单的raymarch

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