web端的shader Threejs飞线

本文介绍如何使用粒子系统在Three.js中实现前端大末端细的动态飞线效果,通过顶点着色器动态调整粒子大小,生成旋转椭圆路径并应用自定义shader材质。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目标

之前想用粒子来实现一下飞线的效果,看到很多大佬的代码发现使用粒子会是一个不错的选择,因为粒子的渲染比较省性能,之前看到有人使用圆形的粒子 后来发现其实普通的正方形的粒子就行,因为在线的粗度比较小的情况下,是看不太出来是圆形还是方形,下面为最终效果。
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

### Three.js 线效果插件及相关库 在实现基于 Three.js 的线效果时,可以考虑使用一些现有的插件或第三方库来简化开发过程并提升性能。以下是几个常用的工具和方法: #### 1. **TWEEN 库** 虽然 TWEEN 主要用于动画处理,但它可以通过控制对象的位置变化来模拟线的效果[^1]。通过定义起始点和终点位置,并应用平滑过渡函数,能够轻松创建动态路径。 ```javascript import * as THREE from 'three'; import { Tween } from '@tweenjs/tween.js'; // 创建场景、相机和其他基础设置... const lineGeometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(-5, 0, 0), // 起点坐标 new THREE.Vector3(5, 0, 0) // 终点坐标 ]); const material = new THREE.LineBasicMaterial({ color: 0xff0000 }); const line = new THREE.Line(lineGeometry, material); scene.add(line); new Tween({ progress: 0 }) .to({ progress: 1 }, 2000) .onUpdate(({ progress }) => { const points = []; for (let i = 0; i < Math.floor(progress * 10); i++) { points.push(new THREE.Vector3( -5 + ((i / 9) * 10), Math.sin(i / 9 * Math.PI), 0 )); } line.geometry.setFromPoints(points); }).start(); ``` --- #### 2. **THREE.Path 和 Curve 类** 利用 `THREE.CatmullRomCurve3` 或其他曲线类构建自定义轨迹,配合粒子系统渲染出流畅的线条[^2]。 ```javascript const curve = new THREE.CatmullRomCurve3([ new THREE.Vector3(-10, 0, 0), new THREE.Vector3(0, 5, 0), new THREE.Vector3(10, 0, 0) ]); function createFlyingLine(curveInstance) { const geometry = new THREE.Geometry(); for (let i = 0; i < curveInstance.points.length; i += 0.05) { geometry.vertices.push(curveInstance.getPointAt(i)); } return new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2 })); } scene.add(createFlyingLine(curve)); ``` --- #### 3. **GSAP 动画引擎** GreenSock Animation Platform 提供强大的时间轴管理功能,适合制作复杂的行动效[^3]。它支持与 Three.js 深度集成,允许开发者精确调整每帧的状态更新逻辑。 ```javascript gsap.to(mesh.position, { duration: 2, x: targetXPosition, y: targetYPosition, z: targetZPosition, ease: "power1.inOut" }); ``` --- #### 4. **Custom Shader 实现高级视觉特效** 如果追求更高质量的画面表现,则可通过编写 GLSL 着色器程序来自定义光线传播行为[^4]。这种方法灵活性最高但也相对复杂。 ```glsl void main() { vec3 direction = normalize(vWorldPosition - cameraPosition); float intensity = pow(dot(direction, vec3(0., 0., 1.)), 8.); gl_FragColor = vec4(vec3(intensity), 1.) * uColor; } ``` 上述片段展示了如何计算光源方向上的衰减因子以增强真实感。 ---
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值