以前一直都是在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的代码
(
float3 worldpos,
float3 objpos,
float3 viewdir,
float stepsize,
float steps,
float4 color,
float radius,
float time,
float3 lightdir
)
{
float3 seconcenter = { 0, 0, 0 };
float eoff = 0.01;
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 inposy = { worldpos.x, worldpos.y - eoff, worldpos.z };
float Ydelta = distance(outposy, objpos) - distance(inposy, objpos);
float3 inposz = { worldpos.x, worldpos.y, worldpos.z - eoff };
float Zdelta = distance(outposz, objpos) - distance(inposz, objpos);
color.rgb = color.rgb * LightColor;
return float4(LightColor,1);
//return color;
}
worldpos += viewdir * stepsize;
}
我喜欢在VS里写代码然后再粘贴到CustomNode里
注:直接把上面的代码复制粘贴到代码里会报错,不清楚为什么,如果在vs里重新敲一次却不会。估计网页里有些符号和代码里的不一样,虽然他们看起来一样。
顺便总结一下常用Raymarching模型(这些公式来自国外一个朋友)
Sphere - signed - exact![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzAvNS83LzgvNzEvOWU0YWJkMzJhMzFmYjgxYjMyMTFhZmIyYWQwNjA1MjAuanBl.jpe?w=700&webp=1)
float sdSphere( vec3 p, float s )
{
return length(p)-s;
}
Box - unsigned - exact![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzYvNS8yLzUvOTcvOThkMzkyZjExMDdhMzY0YTM4ZGFmZDY3YjFjMmE4MDYuanBl.jpe?w=700&webp=1)
float udBox( vec3 p, vec3 b )
{
return length(max(abs(p)-b,0.0));
}
Round Box - unsigned - exact![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzcvMy80LzAvNzQvMmE3MDk0NTJkNTU1YTQwODA3MTgxZTZiMzIwMzYzM2MuanBl.jpe?w=700&webp=1)
float udRoundBox( vec3 p, vec3 b, float r )
{
return length(max(abs(p)-b,0.0))-r;
}
Box - signed - exact![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzAvNy81LzIvNTkvNGU0YTcyZDk2ZTIzYzgzOGQxNjU0ZmQ0ZDU0ZWRhNDQuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzgvOS8xLzMvNjMvMDkwNDFiZWVkMWFlYjIxZTg2NmQ5ZGU5ZDEyNTE1MWMuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzkvMC8xLzAvNC82MzYxMThlODlmODkzMTU5MDdjYWMyY2NlMWNkY2YxMS5qcGU%3D.jpe?w=700&webp=1)
float sdCylinder( vec3 p, vec3 c )
{
return length(p.xz-c.xy)-c.z;
}
Cone - signed - exact![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzMvMi8zLzQvODYvMWU0YzRlZTZkOWYwZWU0NDc0YWNlMGVlMDI2MWE0MzMuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzQvNi84LzcvNTMvNTdhYTZiYWVkMmUyNjY2YzI0MGY3ZTRkMzYxZjQ3ZDIuanBl.jpe?w=700&webp=1)
float sdPlane( vec3 p, vec4 n )
{
// n must be normalized
return dot(p,n.xyz) + n.w;
}
Hexagonal Prism - signed - exact![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzEvOC80LzcvNS8zMDA4NzU1YWY1YTNmMWM0OGEzYWFiNDA0YjYwMjE2YS5qcGU%3D.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzMvMi8xLzkvNjkvYmU3NzM5YjFiNzc4YjQ4ZWYyODQ1MTFhZjFmNmJhMGMuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzMvNC80LzcvODUvNWY3MDI3NjhiY2EyODhmZTAzNmZiMmU3MDhmNzRhYjkuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzUvOS83LzYvODQvZTY2OTllOTQ2MzJhODM4OTNmYTAzNDI5MTFmYjEwNWQuanBl.jpe?w=700&webp=1)
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![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzIvOS84LzIvNjMvNjY1NTNlZDhmY2I3ZTEyOWMyYTA1YmFiYzgyMTAzZjguanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzMvMS80LzUvNzQvYTA1MjlmN2EzNjcyOWU4MmUxNzM4OGMxMGIxYTc3MDEuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzMvNC81LzUvNzAvZjExMDVlNDM3Njg1MTUwYjZiYWNmOWU5YmM4MWE0MTguanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzkvOS8yLzQvMzgvMWQ1Mjk2ZWExYTFjZmM3Mzk4ZjI0OWQ0MGFkMzFhYjcuanBl.jpe?w=700&webp=1)
float sdTorus88( vec3 p, vec2 t )
{
vec2 q = vec2(length8(p.xz)-t.x,p.y);
return length8(q)-t.y;
}
距离场运算
Union![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzQvNS81LzIvMzQvODJlZTg3Y2NhZTRkNDMwYmY2OTM5NDk4MTU2ZjExZTMuanBl.jpe?w=700&webp=1)
float opU( float d1, float d2 )
{
return min(d1,d2);
}
Substraction![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzMvMi8xLzcvODEvZGI4MmNhMmNiOTlmZjMyYTYxNjY3ZjYzNDUxYmQ4MjUuanBl.jpe?w=700&webp=1)
float opS( float d1, float d2 )
{
return max(-d1,d2);
}
Intersection![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzEvMi80LzUvNTcvZDhhM2RjNTM4YjZjZmRhN2FjYjQ4MDliNzJkMmI5N2UuanBl.jpe?w=700&webp=1)
float opI( float d1, float d2 )
{
return max(d1,d2);
}
Repetition![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzQvNi85LzIvMjIvNDc3YWEzYTc0ZjBhYzMxYzUzN2ExODNmMWNlMDg4NjUuanBl.jpe?w=700&webp=1)
float opRep( vec3 p, vec3 c )
{
vec3 q = mod(p,c)-0.5*c;
return primitve( q );
}
Rotation/Translation![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzIvMS83LzgvOTYvOGU4ZjUwY2ZiOGUyOWQzY2Q3Y2FiYTczZDdkMmM2M2EuanBl.jpe?w=700&webp=1)
vec3 opTx( vec3 p, mat4 m )
{
vec3 q = invert(m)*p;
return primitive(q);
}
Scale![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzYvNC80LzcvNzQvODVkNzdhZWQ1YWRkMzI5OTdhNzdkYzBiMDc3MzBhNWUuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzIvNS84LzkvNTIvZWNhMWIzZjhmZjNiNWNkZjIyZjAxOTE4YmIzYmQ1ZDUuanBl.jpe?w=700&webp=1)
float opDisplace( vec3 p )
{
float d1 = primitive(p);
float d2 = displacement(p);
return d1+d2;
}
Blend![虚幻4中实现简单的raymarch 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzIvMi83LzMvNjIvNmU1ZTc5NTdkYzM0ZmFkNzM3OWVhMzg3YmI5Y2RjMzIuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzcvNC8yLzUvNTcvZTlkMTlkMGM1MzMwOWUzZTM5ZjI2MzkzM2MxNWE4ZmYuanBl.jpe?w=700&webp=1)
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 虚幻4中实现简单的raymarch](https://image.shishitao.com:8440/aHR0cHM6Ly93d3cuaXRkYWFuLmNvbS9pbWdzLzEvOC85LzgvMjcvMGU0MGFhMGVkZjRkNzE0NzEwZDllMWJkMmQ2YWFmYWMuanBl.jpe?w=700&webp=1)
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);
}