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

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>
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 下面是一个简单的 three.js 烟雾 shader 示例,可以作为参考: ``` const smokeVertexShader = ` varying vec3 vWorldPosition; void main() { vec4 worldPosition = modelMatrix * vec4(position, 1.0); vWorldPosition = worldPosition.xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`; const smokeFragmentShader = ` uniform vec3 color; uniform float opacity; varying vec3 vWorldPosition; void main() { float depth = gl_FragCoord.z / gl_FragCoord.w; float fogFactor = smoothstep(100.0, 300.0, depth); gl_FragColor = vec4(color, opacity) * fogFactor; }`; const smokeMaterial = new THREE.ShaderMaterial({ uniforms: { color: { value: new THREE.Color(0xaaaaaa) }, opacity: { value: 0.5 } }, vertexShader: smokeVertexShader, fragmentShader: smokeFragmentShader, transparent: true }); const smokeGeometry = new THREE.PlaneGeometry(1000, 1000); const smokeMesh = new THREE.Mesh(smokeGeometry, smokeMaterial); smokeMesh.position.set(0, 0, -500); scene.add(smokeMesh); ``` 这个 shader 通过计算深度值来模拟烟雾效果。在顶点着色器中,我们通过将顶点位置乘以模型矩阵得到世界坐标系下的位置,然后将它传递给片元着色器。在片元着色器中,我们计算每个像素的深度值,并根据深度值计算烟雾因子,最后将它乘以颜色和不透明度来得到最终的颜色。 你可以根据需要调整烟雾的颜色、不透明度、大小和位置。 ### 回答2: three.js是一个功能强大的JavaScript库,它提供了一套易于使用的工具和功能,用于在Web上创建交互式的3D图形。其中一个功能是烟雾shader,它允许我们在我们的场景中添加逼真的烟雾效果。 three.js的烟雾shader通过在场景中创建一个气体云层,并向其应用特定的效果来实现。这个效果通常是通过使用Perlin噪声来模拟烟雾的动态外观的。这种噪声会根据时间和空间的变化来生成一个连续的、无缝的云状图案。 通过使用three.jsShaderMaterial和自定义着色器,我们可以将烟雾效果应用于物体或整个场景。使用这个材质,我们可以设置烟雾的颜色、透明度、密度等参数,以实现我们想要的效果。 烟雾shader可以使我们的场景更加生动和逼真。它可以在火焰、蒸汽机、爆炸等各种场景中添加真实的烟雾效果。在游戏开发、虚拟现实和建筑可视化等领域,烟雾shader都可以发挥重要的作用,提高用户体验和视觉效果。 尽管实现烟雾shader可能需要一些编程和图形学的知识,但由于three.js库已经提供了许多封装好的功能和模块,我们可以轻松地集成和使用它们。此外,three.js的官方文档和社区也提供了大量的教程和示例代码,帮助我们快速上手和理解相关概念。 总的来说,three.js的烟雾shader是一个强大的工具,它为我们提供了在Web上创建逼真烟雾效果的能力。它可以为我们的场景增加动态和真实感,提高用户体验和视觉效果。对于对3D图形感兴趣的开发者来说,three.js的烟雾shader是一个不可忽视的资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值