虚幻4中实现简单的raymarch

来源:互联网 发布:nginx 跳转到指定ip 编辑:程序博客网 时间:2024/06/06 04:54

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


顺便总结一下常用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);}

原创粉丝点击