纯shader实现雷达扫描效果(three.js实战13)

Posted 点燃火柴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了纯shader实现雷达扫描效果(three.js实战13)相关的知识,希望对你有一定的参考价值。

1. demo效果

 

2. 实现要点

2.1 绘制雷达边框

        这一步其实就是绘制一个圆,输入一个屏幕坐标st,原型坐标还有半径,使用distance求出坐标上任意点到圆心的距离,将这个距离接近圆半径的点绘制

    float drawCircle(vec2 st,vec2 center,float radius) { 
      float pct = distance(st,center);
      float line_width = 0.01;
      return smoothstep(radius-line_width,radius,pct)-smoothstep(radius,radius+line_width,pct);
    }

2.2 绘制雷达扫描起始线

        这一步主要是绘制一个线段,这个线段的起点是圆心,终点是圆上0度角的位置,在绘制点通过旋转矩阵将屏幕坐标st旋转起来,来实现扫描起始线旋转的效果,有一点需要注意,为了实现顺时针旋转,旋转矩阵传入的值需要取负

    //二维旋转矩阵
    mat2 rotate2d(float _angle){
      return mat2(cos(_angle),-sin(_angle),sin(_angle),cos(_angle));
    }

    //绘制指定两点间的直线
    float line_segment_with_two_point (vec2 st,vec2 start,vec2 end,float line_width){
      vec2 line_vector_from_end = normalize(vec2(start.x,start.y) - vec2(end.x,end.y));//结束点指向起始点的向量
      vec2 line_vector_from_start = -line_vector_from_end;//起始点指向结束点的向量
      vec2 st_vector_from_end = st - vec2(end.x,end.y); //结束点指向画布中任意点的向量
      vec2 st_vector_from_start = st - vec2(start.x,start.y);//起始点指向画布中任意点的向量

      float proj1 = dot(line_vector_from_end,st_vector_from_end);
      float proj2 = dot(line_vector_from_start,st_vector_from_start);

      if(proj1>0.0&&proj2>0.0){//通过点乘结果>0判断是否同相,过滤掉线段两头超出部分
        //屏幕上任意点到直线的垂直距离
        float dist = sin(acos(dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end)))*length(st_vector_from_end);
        
        return pow(1.0-smoothstep(0.0,line_width,dist),6.0);
      }
    }

    float flowingLine(vec2 st,float line_width,float radius){
      //u_time前加负号使之变为顺时针旋转
      st = rotate2d( -u_time*1.5) * st;

      float x  = cos(0.0)*radius;
      float y  = sin(0.0)*radius;

      return  line_segment_with_two_point (st,vec2(x,y),vec2(0.0),line_width);
    }

2.3 绘制扫描光和被扫描物体

        这一步稍微麻烦一些,首先绘制扫描光思路与绘制扫描线起始点一样,要创建一个四分之一圆的渐变扇形,绘制这个扇形前也通过旋转矩阵使屏幕坐标旋转起来,以实现扫描光旋转,要注意旋转矩阵的入参要与上一步中的一致,否则旋转速度不一致;

        接下来绘制物体,因为物体不需要跟着扫描光旋转,所以要使用未改变的屏幕坐标,绘制物体后使用mix函数将物体与上一次的结果累加

    //绘制小点模拟被扫描物体
    float smallPoint(vec2 st,vec2 position,vec2 vector,float radius){

      // 从原点指向物体的向量
      vec2 positionVector = position - vec2(0.0);

      //雷达扫描光起始线与指向物体的向量间的夹角
      float angle = acos(dot(positionVector,vector)/(length(positionVector)*length(vector)));

      float dist = distance(st,position);

      return (1.0 -step(radius*sin(angle/2.0-PI/16.0),dist));

    }

    //绘制雷达扫描光及被扫描物体
    float sectorFlowingLight(vec2 oriSt,float radius){
      vec2 st = oriSt;
      //u_time前加负号使之变为顺时针旋转
      st = rotate2d( -u_time*1.5) * st;

      vec2 center = vec2(0.0);

      vec2 vector0 = vec2(cos(0.0),sin(0.0)) - center;
      vec2 vector1 = st -center;
      vec2 vector2 = vec2(cos(PI/2.0),sin(PI/2.0)) - center;

      float dotProd = dot(vector1,vector2);

      //计算0度线与圆心到st上向量的夹角
      float angle = acos(dot(vector0,vector1)/(length(vector0)*length(vector1)));

    
      if(dotProd>0.0&&dotProd<1.0&&angle>0.0&&angle<PI/2.0&&distance(st,vec2(0.0))<0.6){
        //雷达扫描光
        float pct = (1.0-smoothstep(0.0,PI/2.0,angle))*0.7;

        //小圆点1
        float point1 = smallPoint(oriSt,vec2(0.3,0.3),vector1,0.04);
        pct = mix(pct,point1,0.1);  

        //小圆点2
        float point2 = smallPoint(oriSt,vec2(0.1,0.2),vector1,0.06);
        pct = mix(pct,point2,0.3);  

        return  pct; 
      } 
    }

3. demo代码

<body>
  <div id="container"></div>
  <script type="text/javascript" src="../three/build/three.js"></script>

  <script>
    var container;
    var camera, scene, planeMesh, renderer;
    var uniforms = {
      u_resolution: {
        type: "v2",
        value: new THREE.Vector2()
      },
      radius: {
        type: "f",
        value: 0.5
      },
      u_time: {
        type: "f",
        value: 0.5
      }
    };
    var vertexShader = `
    attribute vec3 position;
    void main() {
      gl_Position = vec4( position, 1.0 );
    }
    `
    var fragmentShader1 = `
    #define PI 3.1415926535897932384626433832795
    #ifdef GL_ES
    precision mediump float;
    #endif
    uniform vec2 u_resolution;
    uniform float u_time;

    float drawCircle(vec2 st,vec2 center,float radius) { 
      float pct = distance(st,center);
      float line_width = 0.01;
      return smoothstep(radius-line_width,radius,pct)-smoothstep(radius,radius+line_width,pct);
    }


    float line_segment_with_two_point (vec2 st,vec2 start,vec2 end,float line_width){
      vec2 line_vector_from_end = normalize(vec2(start.x,start.y) - vec2(end.x,end.y));//结束点指向起始点的向量
      vec2 line_vector_from_start = -line_vector_from_end;//起始点指向结束点的向量
      vec2 st_vector_from_end = st - vec2(end.x,end.y); //结束点指向画布中任意点的向量
      vec2 st_vector_from_start = st - vec2(start.x,start.y);//起始点指向画布中任意点的向量

      float proj1 = dot(line_vector_from_end,st_vector_from_end);
      float proj2 = dot(line_vector_from_start,st_vector_from_start);

      if(proj1>0.0&&proj2>0.0){//通过点乘结果>0判断是否同相,过滤掉线段两头超出部分
        //屏幕上任意点到直线的垂直距离
        float dist = sin(acos(dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end)))*length(st_vector_from_end);

        //一个向量在单为向量上的投影长度=夹角余弦*向量长度
        float st_proj_line_width = (dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end))*length(st_vector_from_end);
        return pow(1.0-smoothstep(0.0,line_width,dist),6.0);
      } else {
        return 0.0;
      }
    }

    float flowingLine(vec2 st,float line_width,float radius){
      //u_time前加负号使之变为顺时针旋转
      float angle = -u_time;
      float x  = cos(angle)*radius;
      float y  = sin(angle)*radius;

      return  line_segment_with_two_point (st,vec2(x,y),vec2(0.0),line_width);
    }

    float sectorFlowingLight(vec2 st,float radius){

      //u_time前加负号使之变为顺时针旋转
      float angle = -u_time;

      float x  = cos(angle)*radius;
      float y  = sin(angle)*radius;

      float flowingAngle = angle-PI/2.0;
      float x2  = cos(flowingAngle)*radius;
      float y2  = sin(flowingAngle)*radius;

      vec2 center = vec2(0.0);

      vec2 vector0 = vec2(cos(0.0),sin(0.0)) - center;
      vec2 vector1 = vec2(x,y) - center;
      vec2 vector2 = st -center;

      vec3 crossRes = cross(vec3(vector1,0.0),vec3(vector2,0.0));

      float angle1 = atan(y/x);
      float angle2 = atan(y2/x2);
      float stAngle = atan(st.y/st.x);


      if(dot(vec3(0,0,1), crossRes) < 0.0) {
        angle = PI/2.0;
        flowingAngle = angle-PI/2.0;
        x2  = cos(flowingAngle)*radius;
        y2  = sin(flowingAngle)*radius;
        angle2 = atan(y2/x2);
      }
        
      vec2 end = vec2(0.0);
      vec2 start = vec2(x,y);

      vec2 line_vector_from_end = normalize(vec2(start.x,start.y) - vec2(end.x,end.y));//结束点指向起始点的向量
      vec2 line_vector_from_start = -line_vector_from_end;//起始点指向结束点的向量
      vec2 st_vector_from_end = st - vec2(end.x,end.y); //结束点指向画布中任意点的向量
      vec2 st_vector_from_start = st - vec2(start.x,start.y);//起始点指向画布中任意点的向量

      float proj2 = dot(line_vector_from_start,st_vector_from_start);

      //angle = acos(dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end));
      //float stAngle = atan(y/x);

      if(dot(vector1,vector2)>0.0&&stAngle-angle1>0.0&&distance(st,vec2(0.0))<0.6){//通过点乘结果>0判断是否同相,过滤掉线段两头超出部分
 
       // return smoothstep(0.0,2.0,angle2-stAngle)-smoothstep(0.0,2.0,stAngle-angle1);
       
        if(angle1>=0.0&&angle1<PI){
          
          //return smoothstep(-PI,PI,stAngle-angle2);
          //return 1.0-smoothstep(.0,PI,stAngle-angle2);
          return smoothstep(-PI,0.0,angle2-stAngle); 
        }else{
          return smoothstep(0.0,PI,angle2-stAngle); 
        }
           
      }

    }
    float line_segment_smooth(vec2 st,float k,float t,float line_width) {
      float x  = cos(u_time);
      float y  = sin(u_time);
      float fun = st.y-y*st.x/x;
      float min = 0.2;
      float max = 0.6;
        return smoothstep(k*st.x-line_width,k*st.x,fun);

    }
    
    void main( void ) {
      //窗口坐标调整为[-1,1],坐标原点在屏幕中心
      vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;

      //窗口坐标调整为[0,1],坐标原点在屏幕左下角
      //vec2 st = gl_FragCoord.xy/u_resolution;

      vec3 line_color = vec3(0.0,1.0,0.0);

  
      vec3 color = vec3(0.0,0.0,0.0);

      float pct = 0.0;

      pct = drawCircle(st,vec2(0.0,0.0),0.6);
      color +=pct*line_color;

      pct = flowingLine(st,0.02,0.6);
      color += pct*line_color;

      pct = sectorFlowingLight(st,0.6);
      color += pct*line_color;

      gl_FragColor = vec4(color, 1);
    }
    `
    var fragmentShader = `
    #define PI 3.1415926535897932384626433832795
    #ifdef GL_ES
    precision mediump float;
    #endif
    uniform vec2 u_resolution;
    uniform float u_time;

    //二维旋转矩阵
    mat2 rotate2d(float _angle){
      return mat2(cos(_angle),-sin(_angle),sin(_angle),cos(_angle));
    }
    
    float drawCircle(vec2 st,vec2 center,float radius) { 
      float pct = distance(st,center);
      float line_width = 0.01;
      //return smoothstep(pct-0.02,pct,st.y)-smoothstep(pct,pct+0.02,st.y);
      return smoothstep(radius-line_width,radius,pct)-smoothstep(radius,radius+line_width,pct);
    }

    //绘制指定两点间的直线
    float line_segment_with_two_point (vec2 st,vec2 start,vec2 end,float line_width){
      vec2 line_vector_from_end = normalize(vec2(start.x,start.y) - vec2(end.x,end.y));//结束点指向起始点的向量
      vec2 line_vector_from_start = -line_vector_from_end;//起始点指向结束点的向量
      vec2 st_vector_from_end = st - vec2(end.x,end.y); //结束点指向画布中任意点的向量
      vec2 st_vector_from_start = st - vec2(start.x,start.y);//起始点指向画布中任意点的向量

      float proj1 = dot(line_vector_from_end,st_vector_from_end);
      float proj2 = dot(line_vector_from_start,st_vector_from_start);

      if(proj1>0.0&&proj2>0.0){//通过点乘结果>0判断是否同相,过滤掉线段两头超出部分
        //屏幕上任意点到直线的垂直距离
        float dist = sin(acos(dot(line_vector_from_end,st_vector_from_end)/length(st_vector_from_end)))*length(st_vector_from_end);
        
        return pow(1.0-smoothstep(0.0,line_width,dist),6.0);
      }
    }

    float flowingLine(vec2 st,float line_width,float radius){
      //u_time前加负号使之变为顺时针旋转
      st = rotate2d( -u_time*1.5) * st;

      float x  = cos(0.0)*radius;
      float y  = sin(0.0)*radius;

      return  line_segment_with_two_point (st,vec2(x,y),vec2(0.0),line_width);
    }

    //绘制小点模拟被扫描物体
    float smallPoint(vec2 st,vec2 position,vec2 vector,float radius){

      // 从原点指向物体的向量
      vec2 positionVector = position - vec2(0.0);

      //雷达扫描光起始线与指向物体的向量间的夹角
      float angle = acos(dot(positionVector,vector)/(length(positionVector)*length(vector)));

      float dist = distance(st,position);

      return (1.0 -step(radius*sin(angle/2.0-PI/16.0),dist));

    }

    //绘制雷达扫描光及被扫描物体
    float sectorFlowingLight(vec2 oriSt,float radius){
      vec2 st = oriSt;
      //u_time前加负号使之变为顺时针旋转
      st = rotate2d( -u_time*1.5) * st;

      vec2 center = vec2(0.0);

      vec2 vector0 = vec2(cos(0.0),sin(0.0)) - center;
      vec2 vector1 = st -center;
      vec2 vector2 = vec2(cos(PI/2.0),sin(PI/2.0)) - center;

      float dotProd = dot(vector1,vector2);

      //计算0度线与圆心到st上向量的夹角
      float angle = acos(dot(vector0,vector1)/(length(vector0)*length(vector1)));

    
      if(dotProd>0.0&&dotProd<1.0&&angle>0.0&&angle<PI/2.0&&distance(st,vec2(0.0))<0.6){
        //雷达扫描光
        float pct = (1.0-smoothstep(0.0,PI/2.0,angle))*0.7;

        //小圆点1
        float point1 = smallPoint(oriSt,vec2(0.3,0.3),vector1,0.04);
        pct = mix(pct,point1,0.1);  

        //小圆点2
        float point2 = smallPoint(oriSt,vec2(0.1,0.2),vector1,0.06);
        pct = mix(pct,point2,0.3);  

        return  pct; 
      } 
    }

    
    void main( void ) {
      //窗口坐标调整为[-1,1],坐标原点在屏幕中心
      vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;

      //窗口坐标调整为[0,1],坐标原点在屏幕左下角
      //vec2 st = gl_FragCoord.xy/u_resolution;

      vec3 line_color = vec3(0.0,1.0,0.0);

  
      vec3 color = vec3(0.0,0.0,0.0);

      float pct = 0.0;
      
      //绘制圆边界
      pct = drawCircle(st,vec2(0.0,0.0),0.6);
      color +=pct*line_color;

      //绘制雷达扫描光起始线
      pct = flowingLine(st,0.02,0.6);
      color += pct*line_color;

      //绘制雷达扫描光与被扫描物体
      pct = sectorFlowingLight(st,0.6);
      color += pct*line_color;

      gl_FragColor = vec4(color, 1);
    }
    `
    init();
    animate();

    function init() {
      container = document.getElementById('container');

      camera = new THREE.Camera();

      scene = new THREE.Scene();


      var geometry = new THREE.PlaneBufferGeometry(2, 2);

      var material = new THREE.RawShaderMaterial({
        uniforms: uniforms,
        vertexShader: vertexShader,
        fragmentShader: fragmentShader
      });

      planeMesh = new THREE.Mesh(geometry, material);


      scene.add(planeMesh);


      renderer = new THREE.WebGLRenderer();
      renderer.setSize(800, 800); //设置窗口大小800px*800px

      container.appendChild(renderer.domElement);
      uniforms.u_resolution.value.x = renderer.domElement.width;
      uniforms.u_resolution.value.y = renderer.domElement.height;

    }


    function animate() {
      requestAnimationFrame(animate);

      planeMesh.material.uniforms.u_time.value += 0.01;

      renderer.render(scene, camera);
    }
  </script>
</body>

以上是关于纯shader实现雷达扫描效果(three.js实战13)的主要内容,如果未能解决你的问题,请参考以下文章

纯shader实现移动的箭头(three.js实战15)

纯shader实现动态警告可视化组件(three.js实战16)

关于Three.js实现智慧城市我实现的一些特效

关于Three.js实现智慧城市我实现的一些特效

关于Three.js实现智慧城市我实现的一些特效

关于Three.js实现智慧城市我实现的一些特效