學(xué)習(xí)three.js四個月了权纤,最近終于利用shader粒子做出了我心心念念的蝌蚪狀飛線,這個網(wǎng)上也是查不到什么資料的乌妒,2Dcanvas的可以查得到汹想,3D的都藏著掖著很難找的到相關(guān)案例,最近我通過騰訊一個小姐姐發(fā)布幾個three.js 案例受到了一點啟發(fā)撤蚊,終于花了一個晚上做了出來古掏,也是第一次在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;
? ? ? }
`;
然后就是配置數(shù)據(jù)源钝计,數(shù)據(jù)源可以利用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葵蒂,整體思路就是取一段固定的粒子數(shù)目+加上Curve3的getPointAt取點函數(shù),不斷的取出固定數(shù)目并不斷前移的坐標重虑,直接修改bufferGeometry的position數(shù)據(jù)即可践付。
下面貼出完整代碼:
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;
? //粒子系統(tǒng)
? particleSystem: THREE.Points;
? //粒子數(shù)目
? pointNum:number;
? //粒子間的總距離
? distance: number;
? points: THREE.Vector3[];
? tween: any;
? /**
? * 創(chuàng)建粒子系統(tǒng)
? * @param points 粒子
? * @param size 粒子大小
? * @param num 粒子數(shù)目
? * @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;
? }
? //創(chuàng)建圓形精靈貼圖
? 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;
? }
}