我报名参加金石计划1期应战——分割10万奖池,这是我的第3篇文章,点击查看活动概况

便是照着葫芦画瓢,以此来娴熟shader绘图的基本模式。 原比如是shader toy上的,传送门,

截图,我不想录制GIF了算了 ,反正不用我的流量,截图作用不太好.

以下是原始作用图

shader 雷达图

拆分图形

通过前面的造型函数,再来仿写这个就不是那么困难了。动手之前,先拆分一下。

用shader作图,就只能是画家算法了,先画最下面的图层。

静态的,有四个同心圆,只不过颜色和半径不同算了,可归为一类。

一个叉叉,其实便是两条线段。

第四层圆弧

第五层圆弧

扫描的部分能够分为一个线段(射线)和一个扇形区域

四个三角形

方针物体及其分散光波

随机漂动的实心圆点

就这么多,咱们一个个来制作。按照掩盖次序,肯定是最终画 扫描作用和热门分散作用。 其他的没有遮挡关系

画圆

我自己也写了个简略的造型函数画圆,可是,在宽度不行的状况下作用不是太好。 所以,又去解析实例代码中的画法。 由于我的坐标和shadertoy的坐标是有些不同的,我是纯uv坐标,所以不能直接用它的,而且,这样也是学习的进程。关于两个smoothstep相减,有不解的当地请参阅造型函数的弥补

#define SMOOTH(r,R) (1.0-smoothstep(R-.001,R+.001, r))
float circle (float r, vec2 st, float w) { 
      // return  step(-w,-abs(length(st)-r) ) ; 画圆仍是得用平滑
      // return smoothstep(w,0.0, abs(length(st) - r)) ;//仍是不行平滑
      return smoothstep(w,0.0, abs(sqrt(dot(st,st) )- r)) ;// 不行
}
//看来用两个stepsmooth 相减的作用要优于直接用肯定值
float circle2(vec2 uv,  float radius, float width)
{
    float r = length(uv );
    return SMOOTH(r-width,radius)-SMOOTH(r+width,radius);
}

我最终发现,这个宽度不行就会出问题是必定的,算了,我也不是什么完美主义者(主要是火力缺乏啊)。 这样咱们就能够完成前四个同心圆。

void main(){
            vec4 colorT = texture2D(NAME, vec2(.5));
            float t = u_Time/1000. ;
            float ratio  = u_CanvasSize.x/u_CanvasSize.y;
            vec2 st  =gl_FragCoord.xy / u_CanvasSize ;
            // 仍是先把坐标处理到中心方便点
            st-= vec2(.5)  ;
            st.x*=ratio ;
            vec3 color  =vec3(0.0) ;
            float gap = .03;
            vec3 color1= vec3(1) ;
            vec3 color2= vec3(0.3) ;
            // 漏了一个小圆
            color = mix( color, blue1, circle2( st,0.03, 0.0011)) ;
            color = mix( color, blue1, circle2( st,0.15, 0.001)) ;
            color = mix( color, blue1, circle2( st,0.25, 0.001)) ;
            color += vec3(circle2( st,0.35, 0.002)) ;

shader 雷达图

对称的圆弧

第五个圆弧,不再是完好的圆了。 能够弄一个通用的从起始到结束弧度的圆弧函数,可是这儿我就偷个懒, 咱们要画的是两段对称的圆弧,反过考虑便是, 只要在x轴周围的一个区域不画就能够了。 这个规模便是 [-, ]和[-, +]. 咱们肯定是要靠反三角函数才干得出视点,可是这儿不必要,咱们仅仅比较大小,直接拿正弦值进行比较即可,刚好正弦又是关于 x= /2 对称的,所以咱们只需要判断一边即可。

最终加上动画.

float circle3(vec2 uv,  float radius, float width, float sinval)
{
    float r = length(uv );    
      // sinval = y/r 对边比斜边    
    return  abs(uv.y/r) < sinval ? 0.0:SMOOTH(r-width,radius)-SMOOTH(r+width,radius);
}
....
// 画第五层圆弧 
   color = mix( color, blue3, circle3( st,0.38, 0.001, 0.5 + 0.2*sin(t))) ;

shader 雷达图
后边的第五层也是如法炮制,只不过还要写一个和上面函数反着制作函数,便是之前放弃的弧度区域是现在要制作的区域,最简略的办法是直接1 – 去之前的函数,可是仍是有点不太合适。 所以还得来一个。

// 没看懂他那个逻辑 ,我直接用视点判断吧 用正弦的规模 刚好符合这个轴对称 【-0.1,0.1】的都不画
float circle4(vec2 uv,  float radius, float width, float min,float max)
{
    float r = length(uv );    
      // sinval = y/r 对边比斜边 
      float sinval = uv.y / r ;
    return  sinval > min && sinval < max ? SMOOTH(r-width,radius)-SMOOTH(r+width,radius):0.0;
}
..... 
      // 六层层要分4次画
            color = mix( color, blue1, circle4( st,0.45, 0.003, sin(gap), sin(pi/4. -gap))) ;
            color = mix( color, blue1, circle4( st,0.45, 0.003,  -sin(pi/4.-gap), -sin(gap))) ;
             //  想要视点上的均匀就得用视点。
            color =  mix( color, blue3 -0.1, circle4( st,0.45, 0.003, sin(pi/4. +gap), sin(pi/2. -gap))) ;
            color =  mix( color, blue3 -0.1, circle4( st,0.45, 0.003,  - sin(pi/2. -gap),-sin(pi/4. +gap))) ;

shader 雷达图

圆弧到这儿就画完了。 补上十字,直线应该没啥好说了的吧。

float crossAngle(float x , float y , float radius){
      float r = sqrt(x*x + y*y) ;// 点乘再开平方会比直接length或者distance要好吗
      return r<radius && (abs(x-y)<0.001 || abs(x+y)<0.001 )? 1.0:0.0;
}
....
color = mix(color,  color2,crossAngle(st.x,st.y,0.35)) ;

shader 雷达图

扫描

先来个简略的射线扫描。

shader 雷达图

float line(vec2 st, float k, float b,float w){
      return smoothstep(w,0., abs(st.y -st.x * k -b) );
}
// 改直线为射线 这次的思路是先画一条九点钟方向的射线 然后偏移 这儿的偏移仅仅是旋转
// 先把当前坐标 逆偏移到 三点钟方向
float ray(vec2 st, float angle ,float r, float w){
      st *= rotate(-angle) ; 
      return length(st)<r && st.x>0.? smoothstep(w, 0., abs(st.y )):.0;
}

然后加上扇形,其实原图便是个扇形,和上面画的圆弧相对应,可谓是一个是填充,一个是描边。

制作扇形便是把之前的等于半径改成小于半径即可, 然后这个突变,是视点突变。

加了偏移之后,呈现了问题。我想调试一下,可是shader没法像js那样调试,那就用js调试,哈哈,我真是个机灵鬼。

shader 雷达图

好吧,然后发现bug是由于,我处理视点大于180的状况,这行代码,应该在偏移之前的, 细心点,嗯。最终扫描是要放在最上层的,这儿先做,

float sector2(vec2 st , float r ,float endAng, float offset ){
      float d = length(st) ;
      // 余弦值的值域比较好控制 就用它,仍是直接用反三角函数算了
      float cosVal = st.y/ d ; // 把这个d变成r ,成果便是扇形减去三角形剩下圆弧
      float ang = acos(cosVal) ;
      if(st.x<0.) ang = 2.0 * pi - ang ;
      // 相同 这个偏移要逆着用
      ang-=offset ;
      // 我逻辑是比较视点大小 所以小于零要处理   有while 可是我写了错了
      ang = mod(ang,2.*pi);
      if(ang <0.)ang+=2.*pi;
      // 突变 便是百分比  
      float percent  = ang / endAng ;
      return d < r && ang < endAng ? percent:0. ;
}
color = mix(color, blue3, sector2(st,0.35, pi/2.5, t+pi/2. - pi/2.5 )) ;

shader 雷达图

方针物体及光圈分散动画

说起来,初学前端的时候,就学了一个这样css动画,地图热门符号。 画圆,加上半径周期变改变,这个作用啊,就差不多了。 咱们这个更简略,只要一个圆环的半径改变,内部那个圆点其实没变。

// 由于我默许便是圆点圆心,所以pos便是偏移。
float circleMove(vec2 st, vec2 pos, float radius, float w ){
      // 简略一点,直接逆变换然后调用之前得函数
      st-=pos ;
      return circle(radius, st,w) ;
}
// 圆环加径向突变 加移动
float circleWave(vec2 st ,vec2 pos ,float radius, float width){ 
      // 还需要判断是否超出了扫描规模,超出了不予烘托
      if(length(st) >0.35)return 0.;
      st -=pos ;
      float d  = length(st) ; 
      float percent = (radius - d) /width ;// 反了?的确反了 难怪作用有点奇怪,按我现在的写法,w实际上是双倍的
      // float percent = 1.- (radius - d) /width/2.0 ;//上面得会有负数,所以才觉得怪怪得,扫描是加法,所以作用是那样得 
      // float percent =  1.-( 1.+ (radius - d) /width)/2. ;//先把值域搞到0-1 然后取反
      return (SMOOTH(d-width,radius)-SMOOTH(d+width,radius))*percent;
//  return  circle2(vec2 uv,  float radius, float width)
}

之前的逻辑是有些问题的,宽度实际上是参数的两倍,也因而我核算的百分比是有负值的

shader 雷达图

shader 雷达图

         // 发现方针 
            vec2 pos = vec2(.15*(sin(t/30.)+cos(t/50.)), .18*cos(t/20.+1.)) ;
            // 仍是 要一个小圆环 里面一个圆点 圆点还有用得  可是要能漂移,所以得重写一个
            color = mix(color, red, circleMove(st,pos,.01,0.002)) ;
 color = mix(color, red, circleWave(st,pos,r1, 0.02)) ;

到这儿基本上功德圆满。 再加上随机漂浮的小白点,和上面的逻辑能够说一毛相同了。

float circledot(vec2 st , vec2 pos , float r ){
      st -=pos ;
      float  d= length(st) ;
      return smoothstep(0.0, 0.01, r -d) ; // 要求是 d < r
}
.......
 vec2 pos1 =vec2(.4*sin(-t/10.)+ .1* cos(-t/10.0+1.), .15*cos(-t/4.) + .17*sin(t/50.+1.));
            vec2 pos2 =vec2(.2*sin(t/30.-1.)+ .1*cos(t/20.0)+.1*sin(t/5.) , .16*cos(-t/10.)- .09*cos(-t/40.+1.) - .18*sin(-t/50.));
            color = mix(color, color1, circledot(st,pos1,.01)) ;
            color = mix(color, color1, circledot(st,pos2,.01)) ;

我这儿的扫描仍是用了加法,和原作品比较,这样扇形扫到方针的时候会有些不同,当然了,雷达不是这么扫描的。还有,热门分散不能超过第四层圆弧。

shader 雷达图

最终三角形

三角形的确没啥难画,只不过我之前想着用间隔场怎么画,到现在还没搞明白。仍是用三条直线围成一个吧。

下面的函数,是有些奇妙的,是一条水平或者竖直的线和一组垂直交叉的线限制成的。两条垂直的线其斜率互为倒数取反。 而45角的斜率的肯定相同,所以这儿后边直接便是一个肯定值省事。 实际上便是三条线限制为一个三角。

shader 雷达图

shader 雷达图

// 看上去这个函数很麻烦,看不懂,先直接用 radius应该是三角形的位置 
float triangles(vec2 uv, float radius)
{
    vec2 d = uv;
    //加法代表或,乘法代表且 。
    // 剖析第一个三角  -0.009 < x- radius < 0  ===> x在【r-0.009, r】内,     x - radius 
    return RS(-.009, 0.0, d.x-radius) * (1.0-smoothstep( 0.007+d.x-radius,0.009+d.x-radius, abs(d.y)))
         + RS( 0.0, 0.009, d.x+radius) * (1.0-smoothstep( 0.007-d.x-radius,0.009-d.x-radius, abs(d.y)))
         + RS(-.009, 0.0, d.y-radius) * (1.0-smoothstep( 0.007+d.y-radius,0.009+d.y-radius, abs(d.x)))
         + RS( 0.0, 0.009, d.y+radius) * (1.0-smoothstep( 0.007-d.y-radius,0.009-d.y-radius, abs(d.x)));
}
float triangle(vec2 st, float r ){
      float  x= st.x,y = st.y;// 0.7近似根号2的一半
      return step(-r, y) *step( y - x, -r*0.7 ) *step( y + x, -r*0.7 )
      +      step( y,r) *step( r*0.7,y - x  ) *step( r*0.7, y + x )
      +      step( x,r) *step( y-x, -r*0.7  ) *step( r*0.7, x+y )
      +      step(-r, x) *step( r*.7 ,y - x) *step( y + x, -r*0.7 )
      ;
}
 // 画三角形
            color+=triangles(st,0.45 + 0.05*sin(t))* vec3(0.79, 0.19, 0.19) 

这就完了,完好代码见码上。