shader 造型函数

576 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

今天就开始讲述造型函数。 如果 第五章 你已经看过,没有大的疑问,就不需要看下面的内容了。 之前说了,shader里面就只有坐标,你想画什么都得靠数学。我们直接开始,先画一条线。

image.png

画直线

一般来说不管画什么线,都是描点连线,直线只需要两个点就确定。 但是,在这里,你只有当前的坐标点, 我们不使用额外的顶点数据。 且看代码和效果。

#ifdef GL_ES
precision mediump float;
#endif

#define  pi 3.14159265359 

uniform float u_Time ;
uniform vec2 u_CanvasSize ;
varying vec3 v_Color ;
 void main(){
            vec2 st  =gl_FragCoord.xy / u_CanvasSize ;// 获取百分比坐标


            vec3 color  =vec3(1,0,1) ; // 画线的颜色
            if(st.x > -.1 && st.x <  .1){ 

                  gl_FragColor = vec4(color,1) ;
       
            }else { 
                  discard;
            }
}

可以看到,效果已经出来了,因为我在webgl里刷了黑色的底色,所以是黑色底。 左边的紫色的‘线’已经画出来了。 虽然有点粗,我来逐一解释一下下列代码。

获取百分比坐标, fragCoord 表示当前点位的物理坐标,xy分量范围分别就是[0,w] [0,h] ,w h是画布尺寸,用当前坐标除以总长,自然就得到百分比坐标。 注意frgaCoord是四维向量,所以这里不能直接除。

至于为什么不直接用物理坐标,而要用百分比。 这是为了便于计算结果在[0,1] 之间

记住,一切操作都是 [0,1]之间!!!

然后声明了一个画线的颜色, 没有alpha通道。

重点来了, 我这个代码的逻辑十分简单,就是判断x 是否在 [-0.1,0.1] 之间, 从几何意义上讲就是当前这个点是否在直线 x =0.1 和 x = -0.1 之间。 只要在这个区域内,我们就给它上色,不在就不管。

我们的大部分绘制的核心逻辑就是判断这个点是否在我们要画的图形内,抽象的线也许没有宽度, 但是实际上的线肯定有,实际上任何图形都有面积,无非大小而已。

image.png

shader 风格

刚才的编程风格不是shader的风格,下面用shader的风格改造一下,shader风格里很少有用判断大于小于的,因为据说用内置函数会比直接条件语句要高效。

注意,函数声明不能写在main函数里, 写了会报错。

float line ( vec2 st){
                  return/ step( abs(st.x) , .1 );  // step(a,b) 就是 a>b? 1: 0 ;
            }

uniform float u_Time ;
uniform vec2 u_CanvasSize ;
varying vec3 v_Color ;
 void main(){

            vec2 st  =gl_FragCoord.xy / u_CanvasSize ;// 获取百分比坐标

            vec3 color  =vec3(0.9, 0.91, 0.06) ; // 底色
            vec3 color1  =vec3(1.,0.,1.) ; // 画线的颜色
           
          
             
             color =mix(color,  color1 , line(st)) ; //  mix(a,b, k) = a
             gl_FragColor = vec4(color,1) ;
       
      
}

该有的注释都有了,我要再提一点,就是这个mix的操作, 前面用了step ,结果就只有两个0,1 0 就是无,1 就是有,表示在图形内。所以这个mix 就变成了 ,在图形内的时候使用紫色,不在的时候使用黄色。

如果你运行代码,可以看到出了除了换了个黄色背景,没变。

改造直线函数

下面来改造一下这个line函数,之前确实太宽了,很难想象这是一条线,当然实际就是一个矩形,一个很长,很窄的矩形的可不就是线吗。 重要的是视觉上,而不是怎么实现的。

除了宽度之外,还有位置,因为坐标原点在左下角,所以都是正数,这个线就在左边了,就像是个边框,我们可以加一个偏移量offset。 那么实际上最后就是位于 x = offset 和 x = -offset之间的区域。

float line ( vec2 st , float w, float offset){
                return step( abs(st.x - offset) , w );  // step(a,b) 就是 a>b? 1: 0 ;
            }
            
       color =mix(color,  color1 , line(st, .01,.5)) ; //  mix(a,b, k) = a         

image.png

如果想画其他方向的线也是这个逻辑, 比如说, 我想画 y= kx + b 这条线,已经包含大部分情况了。只需要把点限制在 y= kx + b + w 和 y= kx + b - w 之间就行了。

用代码就是

float line2 ( vec2 st , float w, float k, float b){
                return step( abs(st.y - st.x*k - b ) , w );  // step(a,b) 就是 a>b? 1: 0 ;
      }       

为什么要用smoothstep

但是如果画斜线的话,你就会发现这个线的锯齿比较严重,线越细越明显,而smoothstep可以较好的解决这个问题。 smoothstep的详解,下篇再说。

float line3 ( vec2 st , float w, float k, float b){
            //     return smoothstep(w,0.0,  abs(st.y - st.x*k - b )  );  // step(a,b) 就是 a>b? 1: 0 ;
                return smoothstep(.0,w,  st.y - st.x*k - b   ) - smoothstep(w, 2. * w ,st.y - st.x*k - b );  // step(a,b) 就是 a>b? 1: 0 ;
}   

image.png