当前位置: 首页 > 工具软件 > blur.js > 使用案例 >

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

百里弘致
2023-12-01

1. demo效果

2. 实现要点

2.1 绘制箭头

首先绘制矩形,然后将两个矩形合并生成一个箭头,然后循环生成五个箭头作为底色,单独绘制一个白色箭头作为当前高亮元素,使白色箭头不断切换到底色中的位置,模拟流动效果

 float band(float t,float start,float end,float blur){
   float step1 = smoothstep(start-blur,start+blur,t);
   float step2 = 1.- smoothstep(end-blur,end+blur,t);
   return step1*step2;
 }

 //绘制矩形
 float rect(vec2 uv,float left,float right,float bottom,float top,float blur){
   float band1 = band(uv.x,left,right,blur);
   float band2 = band(uv.y,bottom,top,blur);  
   return  band1*band2;
 }

 //绘制单个箭头
 vec3 oneArrow(vec2 st,vec3 lineColor,float width,float blur){
   vec3 color = vec3(0.0);
   vec2 uv = st;
   float diff = 0.01;
   st = rotate2d( -PI/4.0) * st;
   st.y += sin(PI/4.0)*width + diff;
   float pct = rect(st,-0.3,0.0,-width,width,blur);
   color += lineColor*pct;

   uv = rotate2d( PI/4.0) * uv;
   uv.y -= sin(PI/4.0)*width + diff;
   pct = rect(uv,-0.3,0.0,-width,width,blur);
   color = max(color,lineColor*pct);
   return color;
 }

 //绘制底色箭头5个
 vec3 drawArrows(vec2 st){
   vec3 color = vec3(0.0);
   vec3 lineColor = vec3(0.17,0.97,1.0);
   st.x += 0.4;
   for(float i = 0.0;i < 5.0;i++){
     st.x -= 0.2;
     color += oneArrow(st,lineColor,0.04,0.004);
   }

   return color;
 }

 //绘制白色流动箭头
 vec3 drawLightArrows(vec2 st,float speed){
   vec3 color = vec3(0.0);
   vec3 lineColor = vec3(1.0);
   st.x += 0.2;

   // 依据时间取模5,5个数据一个循环
   float flag = floor(mod(u_time*speed,5.0));
   //坐标右移[1~5]个单位
   st.x -= flag*0.2;
   color += oneArrow(st,lineColor,0.04,0.009);
   return color;
 }

2.2 绘制不同方向流动箭头

已经制作了向右流动的箭头,使用within函数将屏幕坐标分为四块,每一块重新映射为-1到1,在使用旋转函数分别绘制其他三个方向的流动箭头即可

//绘制不同方向流动箭头:使用type匹配,默认-向右;2.0-向上;3.0-向下;4.0-向左
vec3 flowingArrows(vec2 st,float type,float speed){

  if(type==2.0){
    st = rotate2d( PI/2.0) * st;
  }
  if(type==3.0){
    st = rotate2d( -PI/2.0) * st;
  }
  if(type==4.0){
    st = rotate2d( PI) * st;
  }
  
  vec3 color = vec3(0.0);

  color += drawArrows(st);
  color = max(color,drawLightArrows(st,speed));

  return color;
}

// 将a-b范围的t重新映射到c-d范围
float remap(float a, float b, float c, float d, float t)
{
  return sat(((t-a)/(b-a))*(d-c) + c);  
}

//将leftBottom和rightTop框定的范围重新映射到-1,1
vec2 within(vec2 uv,vec2 leftBottom,vec2 rightTop){
  uv.x = remap(leftBottom.x, rightTop.x, -1.0, 1.0, uv.x);
  uv.y = remap(leftBottom.y, rightTop.y, -1.0, 1.0, uv.y);
  return uv;
}


void main( void ) {

  //转换为窗口坐标[0,1],坐标原点在屏幕左下角
  //vec2 st = gl_FragCoord.xy/u_resolution.y;
  //窗口坐标调整为[-1,1],坐标原点在屏幕中心
  vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;
  vec2 uv = st;
  vec3 color = vec3(1.0);


 uv = within(st,vec2(-1.0,-1.0),vec2(0.0,0.0));
 color *=flowingArrows(uv,4.0,3.0);

 uv = within(st,vec2(-1.0,-0.0),vec2(0.0,1.0));
 color = max(color,flowingArrows(uv,2.0,1.0));

 uv = within(st,vec2(-0.0,-0.0),vec2(1.0,1.0));
 color = max(color,flowingArrows(uv,3.0,5.0));


 uv = within(st,vec2(0.0,-1.0),vec2(1.0,0.0));
 color = max(color,flowingArrows(uv,1.0,8.0));

  gl_FragColor = vec4(color,1.0);
}

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 clock = new THREE.Clock(); // 创建THREE.Clock对象
    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 fragmentShader = `
    #define PI 3.1415926535897932384626433832795
    #define sat(x) clamp(x, -1., 1.)
    #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 band(float t,float start,float end,float blur){
      float step1 = smoothstep(start-blur,start+blur,t);
      float step2 = 1.- smoothstep(end-blur,end+blur,t);
      return step1*step2;
    }

    //绘制矩形
    float rect(vec2 uv,float left,float right,float bottom,float top,float blur){
      float band1 = band(uv.x,left,right,blur);
      float band2 = band(uv.y,bottom,top,blur);  
      return  band1*band2;
    }

    //绘制单个箭头
    vec3 oneArrow(vec2 st,vec3 lineColor,float width,float blur){
      vec3 color = vec3(0.0);
      vec2 uv = st;
      float diff = 0.01;
      st = rotate2d( -PI/4.0) * st;
      st.y += sin(PI/4.0)*width + diff;
      float pct = rect(st,-0.3,0.0,-width,width,blur);
      color += lineColor*pct;

      uv = rotate2d( PI/4.0) * uv;
      uv.y -= sin(PI/4.0)*width + diff;
      pct = rect(uv,-0.3,0.0,-width,width,blur);
      color = max(color,lineColor*pct);
      return color;
    }

    //绘制底色箭头5个
    vec3 drawArrows(vec2 st){
      vec3 color = vec3(0.0);
      vec3 lineColor = vec3(0.17,0.97,1.0);
      st.x += 0.4;
      for(float i = 0.0;i < 5.0;i++){
        st.x -= 0.2;
        color += oneArrow(st,lineColor,0.04,0.004);
      }

      return color;
    }

    //绘制白色流动箭头
    vec3 drawLightArrows(vec2 st,float speed){
      vec3 color = vec3(0.0);
      vec3 lineColor = vec3(1.0);
      st.x += 0.2;

      // 依据时间取模5,5个数据一个循环
      float flag = floor(mod(u_time*speed,5.0));
      //坐标右移[1~5]个单位
      st.x -= flag*0.2;
      color += oneArrow(st,lineColor,0.04,0.009);
      return color;
    }

    //绘制不同方向流动箭头:使用type匹配,默认-向右;2.0-向上;3.0-向下;4.0-向左
    vec3 flowingArrows(vec2 st,float type,float speed){
 
      if(type==2.0){
        st = rotate2d( PI/2.0) * st;
      }
      if(type==3.0){
        st = rotate2d( -PI/2.0) * st;
      }
      if(type==4.0){
        st = rotate2d( PI) * st;
      }
      
      vec3 color = vec3(0.0);

      color += drawArrows(st);
      color = max(color,drawLightArrows(st,speed));

      return color;
    }

    // 将a-b范围的t重新映射到c-d范围
    float remap(float a, float b, float c, float d, float t)
    {
      return sat(((t-a)/(b-a))*(d-c) + c);  
    }

    //将leftBottom和rightTop框定的范围重新映射到-1,1
    vec2 within(vec2 uv,vec2 leftBottom,vec2 rightTop){
      uv.x = remap(leftBottom.x, rightTop.x, -1.0, 1.0, uv.x);
      uv.y = remap(leftBottom.y, rightTop.y, -1.0, 1.0, uv.y);
      return uv;
    }


    void main( void ) {

      //转换为窗口坐标[0,1],坐标原点在屏幕左下角
      //vec2 st = gl_FragCoord.xy/u_resolution.y;
      //窗口坐标调整为[-1,1],坐标原点在屏幕中心
      vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;
      vec2 uv = st;
      vec3 color = vec3(1.0);
 

     uv = within(st,vec2(-1.0,-1.0),vec2(0.0,0.0));
     color *=flowingArrows(uv,4.0,3.0);

     uv = within(st,vec2(-1.0,-0.0),vec2(0.0,1.0));
     color = max(color,flowingArrows(uv,2.0,1.0));

     uv = within(st,vec2(-0.0,-0.0),vec2(1.0,1.0));
     color = max(color,flowingArrows(uv,3.0,5.0));


     uv = within(st,vec2(0.0,-1.0),vec2(1.0,0.0));
     color = max(color,flowingArrows(uv,1.0,8.0));

      gl_FragColor = vec4(color,1.0);
    }
    `
    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(1000, 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);

      const elapsed = clock.getElapsedTime();

      planeMesh.material.uniforms.u_time.value = clock.getElapsedTime();

      renderer.render(scene, camera);
    }
  </script>
</body>
 类似资料: