three.js+shader 制作粒子飞线,小蝌蚪那种。

学习three.js四个月了,最近终于利用shader粒子做出了我心心念念的蝌蚪状飞线,这个网上也是查不到什么资料的,2Dcanvas的可以查得到,3D的都藏着掖着很难找的到相关案例,最近我通过腾讯一个小姐姐发布几个three.js 案例受到了一点启发,终于花了一个晚上做了出来,也是第一次在three.js中使用shader。

下面是效果图:

先说说思路,第一步是制作一个蝌蚪状的粒子束,其实很简单就是让粒子一个一个从小到大排列就好,这部分主要利用shader处理,代码如下:

const vs:string = `

      attribute float size; // 顶点尺寸

      attribute vec4 colors; //顶点颜色

      varying float opacity; // 控制透明度

      varying vec3 vexColor; // 顶点颜色

      void main(){

          vexColor.x = colors.r;

          vexColor.y = colors.g;

          vexColor.z = colors.b;

          //w分量为透明度

          opacity = colors.w;

          vec4 mvPosition = modelViewMatrix * vec4(position,1.0); //?这里模型矩阵,坐标向量,和投影矩阵都是three给你注入的好像。

          gl_PointSize = size;

          gl_Position = projectionMatrix * mvPosition;

      }

`;

然后就是配置数据源,数据源可以利用three.js 给的curve3组件中的getPoints取得,代码如下

this.spline = new THREE.CatmullRomCurve3(vecs);

    this.pointNum = num;

    this.distance = this.spline.getLength();

    //初始化粒子

    this.points = this.spline.getPoints(num);

    const colorsLen = this.points.length * 4;

    const sizeLen = this.points.length;

    const colors:Float32Array = new Float32Array(colorsLen);

    const sizes:Float32Array = new Float32Array(sizeLen);

    this.geometry = new THREE.BufferGeometry().setFromPoints(this.points);

    for(let i=0,z=0;i<colorsLen;i+=4,z++){

      //color

      colors[i] = color.r;

      colors[i+1] = color.g;

      colors[i+2] = color.b;

      // opacity

      colors[i+3] = (i+3)/sizeLen;

      // size从小到大

      sizes[z] =size*(z/sizeLen);

    };

    this.geometry.addAttribute('colors',new THREE.BufferAttribute(colors,4));

    this.geometry.addAttribute('size',new THREE.BufferAttribute(sizes,1));


第二部分就是如何让粒子运动起来,这里我借助了tween.js,整体思路就是取一段固定的粒子数目+加上Curve3的getPointAt取点函数,不断的取出固定数目并不断前移的坐标,直接修改bufferGeometry的position数据即可。


下面贴出完整代码:

import * as THREE from "three";

import TWEEN from "@tweenjs/tween.js";

const fs:string = `

      uniform sampler2D texture;

      varying float opacity;

      varying vec3 vexColor;

      void main(){

          gl_FragColor = vec4(vexColor,opacity);

          gl_FragColor = gl_FragColor * texture2D(texture,gl_PointCoord);

      }     

`;

const vs:string = `

      attribute float size;

      attribute vec4 colors;

      varying float opacity;

      varying vec3 vexColor;

      void main(){

          vexColor.x = colors.r;

          vexColor.y = colors.g;

          vexColor.z = colors.b;

          //w分量为透明度

          opacity = colors.w;

          vec4 mvPosition = modelViewMatrix * vec4(position,1.0);

          gl_PointSize = size;

          gl_Position = projectionMatrix * mvPosition;

      }

`;

/**

* 粒子飞线

*/

export default class PointsFlyLine{

  //粒子位置

  geometry: THREE.BufferGeometry;

  //曲线

  spline: THREE.CatmullRomCurve3;

  //粒子系统

  particleSystem: THREE.Points;

  //粒子数目

  pointNum:number;

  //粒子间的总距离

  distance: number;

  points: THREE.Vector3[];

  tween: any;

  /**

  * 创建粒子系统

  * @param points 粒子

  * @param size 粒子大小

  * @param num 粒子数目

  * @param color 粒子颜色

  */

  constructor({ vecs, num, size, color }: { vecs: THREE.Vector3[]; num: number; size: number; color: THREE.Color; }){

    this.spline = new THREE.CatmullRomCurve3(vecs);

    this.pointNum = num;

    this.distance = this.spline.getLength();

    //初始化粒子

    this.points = this.spline.getPoints(num);

    const colorsLen = this.points.length * 4;

    const sizeLen = this.points.length;

    const colors:Float32Array = new Float32Array(colorsLen);

    const sizes:Float32Array = new Float32Array(sizeLen);

    this.geometry = new THREE.BufferGeometry().setFromPoints(this.points);

    for(let i=0,z=0;i<colorsLen;i+=4,z++){

      //color

      colors[i] = color.r;

      colors[i+1] = color.g;

      colors[i+2] = color.b;

      // opacity

      colors[i+3] = (i+3)/sizeLen;

      // size从小到大

      sizes[z] =size*(z/sizeLen);

    };

    this.geometry.addAttribute('colors',new THREE.BufferAttribute(colors,4));

    this.geometry.addAttribute('size',new THREE.BufferAttribute(sizes,1));

    const uniforms:object = {

      texture: {

        value: new THREE.CanvasTexture(this.createSpriteCanvas(size)),

      }

    };

    const shaderMaterial:THREE.ShaderMaterial = new THREE.ShaderMaterial({

      uniforms,

      vertexShader:vs,

      fragmentShader:fs,

      transparent:true,

      depthTest:false

    });

    this.particleSystem = new THREE.Points(this.geometry,shaderMaterial);

  }

  //飞线开始

  start(){

    const max = this.distance*10;

    const end:number = this.pointNum;

    const m = {start:0,end};

    this.tween = this.tweenAnimate(m,{start:max-end,end:max},2000,null,()=>{

      let pointArr:number[] = [];

      let s = Math.round(m.start),e = Math.floor(m.end);

      for (let i = s; i <= e && i<=max; i++) {

        pointArr = pointArr.concat(this.spline.getPointAt(i/max).toArray());

      }

      this.geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(pointArr),3);

    });

    this.tween.repeat(Infinity).start();

  }

  stop(){

    this.tween.stop();

  }

  tweenAnimate(current:object, target:object, interval:number, animation?:TWEEN.Easing, onUpdate?:Function, complete?:Function) {

    var animate = animation ? animation : TWEEN.Easing.Linear.None;

    let tween = new TWEEN.Tween(current).to(target, interval).easing(animate);

    onUpdate && tween.onUpdate(() => onUpdate());

    complete && tween.onComplete(() => complete());

    return tween;

  }

  //创建圆形精灵贴图

  createSpriteCanvas(size:number){

    const canvas = document.createElement('canvas');

    canvas.width = canvas.height = size;

    const context = canvas.getContext('2d');

    if(context!=null){

      context.fillStyle='rgba(255,255,255,.0)';

      context.beginPath();

      context.arc(size/2,size/2,size/2,0,Math.PI*2);

      context.fillStyle = 'white';

      context.fill();

      context.closePath();

    }

    return canvas;

  }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容