web端的shader Threejs飞线

目标

之前想用粒子来实现一下飞线的效果,看到很多大佬的代码发现使用粒子会是一个不错的选择,因为粒子的渲染比较省性能,之前看到有人使用圆形的粒子 后来发现其实普通的正方形的粒子就行,因为在线的粗度比较小的情况下,是看不太出来是圆形还是方形,下面为最终效果。
three.js动态飞线
当然你可以在codepen上面查看在线演示以及代码。

思路

总的来说,粒子是一个个的小小的点,而点是线的基本构成单位,只要在一个路径上生成比较多的点,然后将他们从开始的地方比较大到最后很小依次排列,就变成了前端大末端细的线条,同时动态更改材质输入的时间,让其在顶点着色器中体现出这个点的大小,就能看到他在动的效果了。而其实他的几何体并没有动,改变的只是不同时间下的不同位置的点的大小而已。

过程

生成路径

最开始的一步, 需要生成路径,在这个例子中路径是在不同的状态下旋转的椭圆路径

    function initCircleCurveGroup(number){
        let curves = [];

        for (let i = 0; i < number; i++){
            let curve = new THREE.EllipseCurve(
                0,  0,           
                Math.random()*20+5, Math.random()*20+5,
                0, 2 * Math.PI,  
                false,           
                0                 
            );
            curves.push(curve);
        }
        return curves;
    }

上面的方法输入一个数字,这个数字为需要生成的路径数量, 所有的椭圆路径短半径和长半径都在5~25之间,所以中间有一小块会被空出来。

材质

材质部分是比较重要的一点,其中顶点着色器是比较关键的部分

  • 生成材质
function initLineMaterial(setting){
      let number = setting ? (Number(setting.number) || 1.0) : 1.0; // 在一个路径中同时存在的个数
      let speed = setting ? (Number(setting.speed) || 1.0) : 1.0;// 速度约大越快
      let length = setting ? (Number(setting.length) || 0.5) : 0.5;// 单根线的长度0-1之间1代表全满
      let size = setting ?(Number(setting.size) || 3.0) : 3.0;// 在最大的地方的大小 默认为3像素
      let color = setting ? setting.color || new THREE.Vector3(0,1,1) : new THREE.Vector3(0,1,1);// 颜色此处以Vector3的方式传入分别为RBG值 都是0-1的范围
      let singleUniforms = {
          u_time: commonUniforms.u_time,
          number: {type: 'f', value:number},
          speed: {type:'f',value:speed},
          length: {type: 'f', value: length},
          size: {type: 'f', value: size},
          color: {type: 'v3', value: color}
      };
      let lineMaterial = new THREE.ShaderMaterial({
          uniforms:singleUniforms,
          vertexShader:document.getElementById('vertexShader').textContent,//顶点着色器部分
          fragmentShader:document.getElementById('fragmentShader').textContent,// 片元着色器部分
          transparent:true,
          //blending:THREE.AdditiveBlending,
      });
      return lineMaterial;
  }

以上的方法会根据配置生成一个自定义的shader材质。
commonUniforms.u_time 是我在全局中同一的一个时间变量 当然这个时间变量也可以是不同材质拥有自己的时间。

  • 顶点着色器
varying vec2 vUv;
    attribute float percent;
    varying float opacity;
    uniform float u_time;
    uniform float number;
    uniform float speed;
    uniform float length;
    uniform float size;

    void main()
    {
        vUv = uv;
        vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
        float l = clamp(1.0-length,0.0,1.0);//空白部分长度占比

        gl_PointSize = clamp(fract(percent*number + l - u_time*number*speed)-l ,0.0,1.) * size * (1./length);

        opacity = gl_PointSize/size;
        gl_Position = projectionMatrix * mvPosition;
    }

虽然顶点着色器的代码部分比较少,但是却是最为重要的部分,在此处我们专注于计算单个的点的大小。
首先percent代表的是该顶点在整个路径中的位置, 数值在0-1之间,0代表起点的位置 1代表终点的位置。
fract函数将整个的内容夹在0-1之间,相当于是取小数的部分。
l为空白部分的长度占比,在fract函数内部+l为的是让整个函数向前偏移空白的位置,这样线在最开始时是在起始位置,而在外面-l是因为让整个函数向下平移l个单位,这样在整个函数<l的地方为空白即0。
最后在外面需要乘以(1.length)是让点的大小在0-1之间而不是0-length之间,最后*size 让点在0 - size之间变化。
-片元着色器

#ifdef GL_ES
    #ifdef GL_ES
    precision mediump float;
    #endif

    varying float opacity;
    uniform vec3 color;

    void main(){
        if(opacity <=0.2){
            discard;
        }
        gl_FragColor = vec4(color,1.0);
    }

这块比较简单,鉴于点的显示机制,即便点的大小为0 ,点仍旧会被渲染,所以我们将实际像素大小0.2一下的内容统统不渲染。

生成多个自己的路径并且进行动画

到目前为止已经有生成一个椭圆路径的函数 以及生成线的材质的函数。下面的内容比较开放 可以选择自己喜欢的方式

// 根据curve和颜色 生成线条
    /**
     * @param curve {THREE.Curve} 路径,
     * @param matSetting {Object} 材质配置项
     * @param pointsNumber {Number} 点的个数 越多越细致
     * */
    function initFlyLine(curve,matSetting, pointsNumber){

        var points = curve.getPoints( pointsNumber );
        var geometry = new THREE.BufferGeometry().setFromPoints( points );

        let length = points.length;
        var percents = new Float32Array(length);
        for (let i = 0; i < points.length; i+=1){
            percents[i] = (i/length);
        }

        geometry.addAttribute('percent', new THREE.BufferAttribute(percents,1));

        let lineMaterial = initLineMaterial(matSetting);

        var flyLine = new THREE.Points( geometry, lineMaterial );
        let euler = new THREE.Euler(Math.random()*Math.PI, Math.random()*Math.PI,0);
        flyLine.setRotationFromEuler(euler);
        scene.add(flyLine);
    }

这个函数会生成一条线并且会旋转随机的角度。

  • 生成随机路径
    function initCircleCurveGroup(number){
        let curves = [];

        for (let i = 0; i < number; i++){
            let curve = new THREE.EllipseCurve(
                0,  0,            
                Math.random()*20+5, Math.random()*20+5,
                0, 2 * Math.PI,
                false,
                0
            );
            curves.push(curve);
        }
        return curves;
    }

以上函数将会生成多个随机半径在5-20之间的椭圆路径 长宽可能是不一样的

  • 生成随机颜色
function randomVec3Color(){
        return new THREE.Vector3(
            Math.random()*0.6 + 0.4,
            Math.random()*0.6 + 0.4,
            Math.random()*0.6 + 0.4
        )
    }

红绿蓝的通道都在0.4-1之间,稍微白一些的随机颜色;

  • 创建多个路径
let curves = initCircleCurveGroup(500);

for (let curve of curves){
    initFlyLine(curve,{
        speed: Math.random()*0.3+0.5,
        number: Math.floor(Math.random()*9+1),
        color: randomVec3Color(),
        size:4.0
    },2000)
}

将其放在渲染的前面

  • 动画
function render() {
        commonUniforms.u_time.value +=0.01;
        renderer.render( scene, camera );
    }

在渲染的时候将共用的时间uniform+=0.1; 如果你需要真实时间截则需要自己设定计时器获得,这里使用每一帧的时间。

这里是 角角兔
欢迎点赞评论哦
角角兔 towrabbit

  • 12
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
实现箭头流动效果可以通过three.js中的几何体和材质来完成,具体流程如下: 1. 创建箭头的几何体 箭头可以用CylinderGeometry和ConeGeometry两个几何体组合而成,其中CylinderGeometry用于表示箭头的身体,而ConeGeometry用于表示箭头的头部。在创建几何体时,需要设置几何体的大小和位置。 2. 创建箭头的材质 箭头的材质可以使用MeshBasicMaterial或MeshLambertMaterial,这里使用MeshBasicMaterial。在创建材质时,需要设置材质的颜色和透明度。 3. 将几何体和材质组合成Mesh对象 使用THREE.Mesh()函数将箭头的几何体和材质组合成Mesh对象,并设置其位置和旋转角度。 4. 添加箭头到场景中 使用THREE.Scene.add()函数将箭头添加到场景中。 5. 定义箭头的运动路径 箭头的运动路径可以使用THREE.Curve()函数定义,可以使用贝塞尔曲线或其他曲线来定义。 6. 定义箭头的运动动画 使用THREE.AnimationMixer()函数和THREE.AnimationAction()函数来定义箭头的运动动画,其中AnimationMixer用于管理动画,AnimationAction用于定义动画的播放方式。 7. 播放箭头的运动动画 使用AnimationAction.play()函数来播放箭头的运动动画,可以设置动画的循环次数和播放速度。 8. 渲染场景 使用THREE.WebGLRenderer()函数和THREE.Renderer.render()函数来渲染场景。 完整的代码示例: ``` // 创建场景 var scene = new THREE.Scene(); // 创建相机 var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 0, 10); // 创建渲染器 var renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 创建箭头的几何体 var cylinderGeometry = new THREE.CylinderGeometry(0.1, 0.1, 4, 32); var coneGeometry = new THREE.ConeGeometry(0.2, 1, 32); coneGeometry.translate(0, 2, 0); var arrowGeometry = new THREE.Geometry(); arrowGeometry.merge(cylinderGeometry); arrowGeometry.merge(coneGeometry); // 创建箭头的材质 var arrowMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.5 }); // 将几何体和材质组合成Mesh对象 var arrow = new THREE.Mesh(arrowGeometry, arrowMaterial); arrow.position.set(-5, 0, 0); arrow.rotation.y = Math.PI / 2; // 将箭头添加到场景中 scene.add(arrow); // 定义箭头的运动路径 var path = new THREE.Curve(); path.points = [new THREE.Vector3(-5, 0, 0), new THREE.Vector3(5, 0, 0)]; // 定义箭头的运动动画 var mixer = new THREE.AnimationMixer(arrow); var action = mixer.clipAction(new THREE.AnimationClip("move", -1, [new THREE.VectorKeyframeTrack(".position", [0, 1], [0, 0, 0, 5, 0, 0])])); action.loop = THREE.LoopOnce; action.timeScale = 2; // 播放箭头的运动动画 action.play(); // 渲染场景 function render() { requestAnimationFrame(render); mixer.update(0.01); renderer.render(scene, camera); } render(); ```
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值