1.平面作切片。我们知道平面的SDF函数如下:
但是该方法会行进很多次才能到达表面,所以通过加上planeHeight会减少行进次数。
float sdfPlane(vec3 p, float y) {
return vec3(0., p.y-y, 0.)
}
如果要让平面与当前raymarching步进的位置P产生关联,如让地面倾斜或翻转一定角度,可让P与地面作点积运算,如:
float sdfPlane(vec3 p, float y) {
// 这里需要归一化
return dot(p, normalize(vec3(0., 1.,0.)));
}
此时的结果还是上图中显示的地面。但是如果把 normalize(vec3(0., 1.,0.)
改为 normalize(vec3(0., 1.,1.)
,可以看到平面翻转了。并且平面所形成的表面与法线指向的方向一致。
这有什么用?我们会用翻转的平面作为切面对其它SDF作切割处理。如,我们在RayMarching 不同SDF的混合 差集中介绍的那样,让平面移动到Cube的指定位置,然后做差集处理:
做差集处理后(即
max(-sdfPlane, sdfBox)
)得到:
2.模型双面开启。首先可以思考下应该怎么做能让SDF模型双面开启?回顾SDF模型内部,与当前行进距离之间的结果是 <0.001(假设等于0.001时为模型表面)
,以Box为例,我们可以对Box的SDF结果取绝对值,这样Box内部的计算结果也为正数了,那么之前为 -0.001
的位置此时也变为了 0.001
,这样Box就能开启双面渲染了。还是以Plane与Box交集的方式使得Box模型只显示公共的部分(max(sdfPlane, sdfBox)
):
此时对Box的SDF结果取绝对值(max(sdfPlane, abs(sdfBox))
),会发现我们把Box的盖子掀开了,可以完全看到内部的形态:
3.调整模型外壳厚度。上图我们已经把模型的盖板掀开了,并看到了模型内部成功开启了双面渲染,但是这盒子的外壳太薄了,一装东西必然就碎。我们需要加厚外壳,怎么做?通过(max(sdfPlane, abs(sdfBox)-.05)
。这时候变成了一个质量很好的垃圾桶。可以自己代入一些数值就知道原因了。
4.让平面以正弦波方式行进。为了让平面作为切面的状态有更丰富的形状,我们可以对平面以正弦波的形态行进。通过:
float sdfPlane(vec3 p) {
return dot(p, vec3(0., 1., 0.))-sin(p.x);
}
有些地方会出现摩尔纹,这是因为行进过程步进过度导致:
为了让步进恢复正常,可以让进入模型内部的步进回到外部,这可以通过对步进取绝对值。如:
vec2 raymarch(vec3 ro, vec3 rd) {
float t = 0.;
float y = -1.;
for(float i = 0.; i<STEP; i++) {
vec2 d = dist(ro + rd*t);
// 让进入模型内部的步进回到模型外面
if(abs(d.x) < .001) {
y = d.y;
break;
}
if(t > STEP) {
t = -1.;
break;
}
// 当步进错误进入模型内部时,d.x必然为负,此时会加上负数,直至回到模型外部。
t += d.x;
}
return vec2(t, y);
}
可以看到摩尔纹消失了:
然后我们可以继续增加正弦函数的频率,比如在原有的基础上乘以3:
float sdfPlane(vec3 p, float y) {
return dot(p, normalize(vec3(0., 1.,0.)))-sin(p.x*3.);
}
可以看到输出的图像已不忍直视:
造成这个问题的原因是,步进相比上面还更过度,都没有在模型内部,而是跳过了整个模型,这导致了之前的矫正无法进行:
根据上图我们已经知道了该问题的原因是由于步进过长导致,那么为了减少步进长度可以让步进长度缩短为原来的倍数,如每一步缩短为原来的0.5倍,vec2(sdfPlane(p, 1.)*0.5, 1.)
问题得到有效缓解,但是该方式增加了行进的次数,会有性能损耗:
5.任意模型的正弦波形式。
p.z += sin(p.x*5.+iTime)*0.1;
float testBox = sdBox(p-vec3(0.,2., 0.), vec3(2.,1.,.1));
6.模型的其它形态:
缩放
,我们可以对当前位置点p进行缩放操作,以实现SDF的结果最终是放大还是缩小的状态。当p乘以大于1的整数倍数时,实际上SDF模型最后会被缩小:
float scale = 2.;
boxPos *= scale;
float testBox = sdBox(boxPos-vec3(0.,2., 0.), vec3(1.,1.,1.));
原因是因为,相机所在的位置被拉远了。如:我们的相机的起始点最开始是 vec3 ro = vec3(0.,4.,-2.)
那么乘以2后ro就变为 vec3 ro = vec3(0.,8.,-4.)
。所以就呈现了相反的效果:
拉远后,SDF模型显得更小:
2.上小下大或上大下小的模型缩放
。看下面的模型,y值为-1~1之间:
我们想要让Cube下大上小,并有平滑过渡的感觉,可以这么做:
vec3 boxPos = vec3(p);
float scale = mix(1., 2., smoothstep(-1., 1., boxPos.y));
boxPos.xz *= scale;
vec2 testBox = vec2(sdBox(boxPos, vec3(1.,1.,1.)), 0.);
但是在某个视角下,发现模型会消失:
这是上面说到的模型的SDF过度行进产生的结果。所以我们需要对sdBox的计算结果除以scale。这样即可避免:
vec3 boxPos = vec3(p);
float scale = mix(1., 2., smoothstep(-1., 1., boxPos.y));
boxPos.xz *= scale;
// 除以scale以避免过度行进
vec2 testBox = vec2(sdBox(boxPos, vec3(1.,1.,1.))/scale, 0.);
还可以让其扭曲:
mat2 Rot(float a) {
float s = sin(a);
float c = cos(a);
return mat2(c, -s, s, c);
}
vec3 boxPos = vec3(p);
float scale = mix(1., 2., smoothstep(-1., 1., boxPos.y));
boxPos.xz *= scale;
boxPos.xz *= Rot(boxPos.y);
vec2 testBox = vec2(sdBox(boxPos, vec3(1.,1.,1.))/scale, 0.);
3.物体镜像
。可以通过对于想镜像的轴取绝对值即可完成,比如 p.x = abs(p.x)
:
vec3 boxPos = vec3(p);
boxPos.x = abs(boxPos.x);
boxPos.x -= 2.;
float scale = mix(1., 2., smoothstep(-1., 1., boxPos.y));
boxPos.xz *= scale;
boxPos.xz *= Rot(boxPos.y);
vec2 testBox = vec2(sdBox(boxPos, vec3(1.,1.,1.))/scale, 0.);