web端的shader Threejs飞线

目标

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

当然你可以在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个单位,这样在整个函数 最后在外面需要乘以(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; 如果你需要真实时间截则需要自己设定计时器获得,这里使用每一帧的时间。

这里是 角角兔
欢迎点赞评论哦
web端的shader Threejs飞线_第1张图片

你可能感兴趣的:(three.js)