距离场进门

163 阅读4分钟

距离场 distacnce filed 就是用一个描述模型表面到模型中心距离的函数,来绘制图形的方式。

当然,今天只是进门,我们抛开z分量不谈,只看二维的。

比如最简单的圆形, 就是所有满足 d = r 的点的集合。一般来说,我们会使用极坐标来写距离场函数。

直线

圆形就不用说了吧。 我最近才悟了那个三角形的画法,其实就是直线,然后加上周期偏移。

以下是源代码和效果。

image.png

我要画的直线是x = b, b>0。 因为这个最好画了。 对于这条直线,直线上任意一点(x,y)或者说(d,a) d是距离 a是角度,它们的关系是什么呢?

我之前没有想过用极坐标和直角坐标的换算直接做,但是现在可以试一下,这个是比较通用的一种方式。 y = d * sin(a) ,x = d * cos(a) 。 所以我们这个直线的极坐标方程就是 d * cos(a) = b ;

我之前是看代码的实现,以及几何画图领悟到的。我们现在来看图。

极轴就等于是x 轴, 角度a是点和极点的连线与极轴的夹角, 这个夹角是极轴为起始边, 连线为终边。

所以,我们可以看到所以在直线的点都满足 d * cos(a) = b 这个条件, 而d * cos(a) < b的点都在左边,cos值在[pi/2, 3pi/3]之间是小于零的,所以这里能画出一条直线,可以想象一下,当a 趋于 90度的时候,这个点的y值是不是就是无穷大,的确是一条直线,而不是线段。

image.png

这样就可以写出如下代码,用delta 作角度偏移。 当然,最好就是用直角坐标进行变换,然后再求极坐标。极坐标的平移变换十分不便。

float line (float a , float d , float f, float deltaA){
      
      d *=cos(a+deltaA) ;
      return smoothstep(-.01 ,.001 ,d - f ) - smoothstep(-.001 ,.01 ,d - f ) ;
}
。。。。。。
    color = mix(color, color1, line(a,d0,.5,0.));
    color = mix(color, color3, circle(d0, .5));

下图绿色的线就是结果,那个圆是用来参照的。 image.png

如果按照直角坐标的画法,肯定就是三条线取一个交集,得到一个填充的三角形,用两个填充的三角形相减就得到描边的三角形。

但是这里,我们可以用另一种方式,那就是周期。极坐标很容易就能对角度进行周期处理。

我们尝试一下,修改上面的line 函数,输入一个重复系数N d *=cos(a *N) ; ,暂且取N = 3 。

结果是这样的。 好像不符合预期啊, 实际上就是如此,直接放大角度等于是压缩了绘图区域,所以才会出现,一条线被压缩成一条折线的效果,中间还自带平滑过渡。

image.png

周期变小导致压缩

这个压缩绘图区,就像是一把圆扇,本来是完全打开的360度,现在合起来只剩下 120度。

image.png

为此,我写了个动画来说明问题,逐渐增加放大系数k的值。

float k = (sin(t)*3.) + 4.; // +4 保证系数大于等于1
     float aN = a   * k;
    if(  abs(aN) < PI / 2.){  // 这个判断是为了只显示一个周期的图形

            color = mix(color, color1, line(aN ,d0,.2,0.));
      }

极坐标周期,周期压缩动画.gif

改成填充

周期压缩填充.gif

内部颜色改成角度渐变。

if( abs(aN) < PI / 2.){ color = mix(color, hsbToRgb(vec3(aN/PI ,d0, 1.)), line2(aN ,d0,.2,0.)); }

chrome-capture-2022-8-15.gif

多边形

现在切回正题, 既然不能压缩绘图区域,我们就不压缩。 实际上,我们要的是周期性偏移

比如说现在就是放大三倍,那么在第一个周期内正常绘制,到了第二个周期,我还想画直线,实际上,不就是把第一个周期的直线做一个旋转,旋转量为一个周期的弧度, 第三个周期同样如此,旋转量为两个周期。

看代码。 通过取整函数来确定是第几个周期,从0开始。第N个周期的偏移量是 N-1个周期。

// r是内切圆的半径
float polygon (float a , float d ,float N, float r){ 
      float c = floor(a/2./PI  * N) ;
      d*=cos( a- c * PI *2./N); // 待修复
  return smoothstep(-.01 ,.001 ,d - r ) - smoothstep(-.001 ,.01 ,d - r ) ;
}

补充说明,为啥不能用取模直接实现 。实际上取模也可以实现,只不过这个被除数不是整数,所以需要一点处理。

    float b = a/PI/2.  ;// 01之间  就是大家喜欢说的归一化 单位化
    b *=N;
    b= fract(b)/N; //  之前缩放了的再给他缩放回去
   

来看效果。 还差了一点东西。 因为上面的取整逻辑是从 0 开始的,也就是第一个周期是 [0,2* PI/5] , 这样就只绘制了极轴上方的部分,实际上,我们要的周期应该是 [- PI/5, PI/5]

color = mix(color, color2, polygon(a,d0,5., .2));

image.png

修复一波。

 d*=cos( a- (c + .5) * PI *2./N); 

image.png

到这里这个正多边形就绘制出来了,需要填充的就改成填充就好了,代码的数学运算,简化以后就是文章开头的截图。

到这里,差不多就进门了。

chrome-capture-2022-8-15 (1).gif