做旅游网站一年能挣多少营业推广的方式
THREE.js粒子效果-将模型转成粒子
视频教程地址
Three.js粒子变换特效
关于单个模型用粒子组成可参考使用粒子效果动画组成模型[自定义shader实现]
本文介绍多模型切换技巧
思路
- 获取需要切换的几个模型的数据就是每个点的坐标在
geometry.attributes.position
中array
即时构成mesh的数据。
例:const targetMesh = g.scene.getObjectByProperty("type", "Mesh"); if (!targetMesh) throw new Error("获取目标物体失败"); const { array} = targetMesh.geometry.attributes.position;
- 将数据保存到一个列表中供切换数据使用
- 切换模型数据实现思路
粒子的动画应该使用自定义shader实现,如果直接更新attributes 几千个点还好,数量达到几万就卡顿严重,无法使用,上述案例使用9万多点,运行时无丝毫卡顿不影响页面其他功能,实为上策。
为了让shader计算每个点在某一时刻应该在什么位置需要提供几个属性参与计算:变换前的位置,变换后应该的位置,变换的时长,当前的时间,如果需要颜色渐变可以额外提供之前和之后的颜色,这些数据通过geometry.attributes
传递给shader,在自定义shader中设置对应属性接收、计算。
例:this.particlesGeometry = new BufferGeometry(); this.color = new Float32Array(this.numberOfPoints * 3); this.particlesGeometry.setAttribute("color", new BufferAttribute(this.color, 3)); this.particles = new Points(this.particlesGeometry, PointsShaderMaterial);
每次切换模型数据 将原先的属性替换为新的模型数据 然后告诉shader需要更新这一属性,这样shader才会接受到新的有效的数据
this.particlesGeometry.attributes.color.needsUpdate = true;
shader中的计算和控制粒子变换的代码请看下面代码区域
顶点着色器
//决定片元着色器的颜色
varying vec3 vColor;
uniform float time;
uniform float size;
attribute vec3 color;
attribute vec3 oldColor;
attribute vec3 toPositions;
attribute vec3 oldPositions;
attribute float toPositionsDuration;
// attribute float scale;void main() {vec3 dispatchPos = toPositions;vColor = color;//顶点位置移动//当前时间在点的运行时间中占比float percent = time / toPositionsDuration;if(percent <= 1.) {dispatchPos.x = oldPositions.x + percent * (toPositions.x - oldPositions.x);dispatchPos.y = oldPositions.y + percent * (toPositions.y - oldPositions.y);dispatchPos.z = oldPositions.z + percent * (toPositions.z - oldPositions.z);vColor.x = oldColor.x + percent * (color.x - oldColor.x);vColor.y = oldColor.y + percent * (color.y - oldColor.y);vColor.z = oldColor.z + percent * (color.z - oldColor.z);}vec4 viewPosition = modelViewMatrix * vec4(dispatchPos, 1.0);gl_Position = projectionMatrix * viewPosition;//PointsMaterial材质的size属性和每个粒子随机的缩放 大小不一效果// gl_PointSize = size * scale;gl_PointSize = size;//近大远小效果 值自己调节gl_PointSize *= (120. / -(modelViewMatrix * vec4(dispatchPos, 1.0)).z);
}
片元着色器
varying vec3 vColor;void main() {gl_FragColor = vec4(vColor, 1.);
}
粒子控制器
/** @Author: hongbin* @Date: 2022-12-14 10:48:40* @LastEditors: hongbin* @LastEditTime: 2022-12-16 17:53:08* @Description:使用shader计算粒子位置 减小开销*/
import { BufferAttribute, BufferGeometry, Color, Material, Points, TextureLoader } from "three";
import starMap from "../assets/img/star_09.png";
// import starMap from "../assets/img/box.jpg";
import { PointsShaderMaterial } from "./shader/points.material";export interface IColor {left: THREE.Color;right: THREE.Color;dividingLine: number;
}interface IPointsModelParams {positions: ArrayLike<number>;duration: { min: number; max: number };box3: THREE.Box3;color: IColor;
}export class PointsControlClass {numberOfPoints: number;particles: Points<BufferGeometry, Material>;// particlesMaterial: PointsMaterial;particlesGeometry: BufferGeometry;toPositions: Float32Array;toPositionsDuration: Float32Array;oldPositions: Float32Array;color: Float32Array;defaultPointsColor = { left: new Color("#fff"), right: new Color("#51f"), dividingLine: 0.5 };modelsList: { [key: number]: IPointsModelParams; current: number; length: number } = { current: -1, length: 0 };oldColor: Float32Array;positions: Float32Array;/*** 粒子控制器* @param {number} numberOfPoints 数量必须大于所有模型中最大的点数量 不然显示不全*/constructor(numberOfPoints: number) {this.numberOfPoints = numberOfPoints;this.particlesGeometry = new BufferGeometry();//顶点着色器需要 position属性来定位this.positions = new Float32Array(numberOfPoints * 3);//顶点着色器的变更前的坐标this.oldPositions = new Float32Array(numberOfPoints * 3);//顶点着色器要前往的目标位置this.toPositions = new Float32Array(this.numberOfPoints * 3);//顶点着色器要前往的目标位置的时间this.toPositionsDuration = new Float32Array(this.numberOfPoints);//要变换的颜色this.color = new Float32Array(this.numberOfPoints * 3);//变换之前的颜色this.oldColor = new Float32Array(this.numberOfPoints * 3);this.particlesGeometry.setAttribute("color", new BufferAttribute(this.color, 3));this.particlesGeometry.setAttribute("oldColor", new BufferAttribute(this.oldColor, 3));this.particlesGeometry.setAttribute("position", new BufferAttribute(this.positions, 3));this.particlesGeometry.setAttribute("oldPositions", new BufferAttribute(this.oldPositions, 3));this.particlesGeometry.setAttribute("toPositions", new BufferAttribute(this.toPositions, 3));this.particlesGeometry.setAttribute("toPositionsDuration", new BufferAttribute(this.toPositionsDuration, 1));const textureLoader = new TextureLoader();PointsShaderMaterial.uniforms.textureMap = {value: textureLoader.load(starMap.src),};this.particles = new Points(this.particlesGeometry, PointsShaderMaterial);this.init();// console.log(new Color("#f00"));// console.log(new Color("#2e00fe"));}/*** 前往列表中的某个形态*/toIndex(index: number) {const { positions, color, box3, duration } = this.modelsList[index];const { length } = positions;const pointCount = length / 3;const { dividingLine, left, right } = color;const { abs } = Math;const {min: { x: minX },max: { y: maxX },} = box3;// 颜色的差值const disColor = {r: left.r - right.r,g: left.g - right.g,b: left.b - right.b,};const width = abs(box3.min.x - box3.max.x);//颜色分界const dividingLineValue = box3.min.x + width * dividingLine;console.log(`小:${box3.min.x} 大:${box3.max.x} 宽${width},分界线:${dividingLineValue}`);for (let i = 0, realCount = 0; i < this.numberOfPoints; i++, realCount++) {//模型的点和生成的点数量未必相等 多的则重合前面的位置realCount = realCount % pointCount;const i3 = i * 3;const r3 = realCount * 3;//设置给顶点着色器//保存起点this.oldPositions[i3] = this.toPositions[i3];this.oldPositions[i3 + 1] = this.toPositions[i3 + 1];this.oldPositions[i3 + 2] = this.toPositions[i3 + 2];//设置终点const x = positions[r3];this.toPositions[i3] = x;this.toPositions[i3 + 1] = positions[r3 + 1];this.toPositions[i3 + 2] = positions[r3 + 2];//设置颜色 从左到右过渡//当前位置坐标占比const percent = abs(x <= dividingLineValue? ((minX - x) / (dividingLineValue - minX)) * dividingLine * dividingLine: ((x - dividingLineValue) / (maxX - dividingLineValue)) * (1 - dividingLine) + dividingLine);//设置变换前的颜色this.oldColor[i3] = this.color[i3];this.oldColor[i3 + 1] = this.color[i3 + 1];this.oldColor[i3 + 2] = this.color[i3 + 2];//设置最终颜色const r = left.r - percent * disColor.r;const g = left.g - percent * disColor.g;const b = left.b - percent * disColor.b;this.color[i3] = r;this.color[i3 + 1] = g;this.color[i3 + 2] = b;//设置运动时间const useDuration = duration.min + Math.random() * (duration.max - duration.min);this.toPositionsDuration[i] = useDuration;}}/*** 向系统添加数据*/append(params: IPointsModelParams, index: number) {if (this.modelsList[index]) {console.log("已经有这项了");}this.modelsList[index] = params;this.modelsList.length = Object.keys(this.modelsList).length - 2;}/*** 下一个形态*/next() {const current = this.modelsList.current;const toIndex = current + 1;if (toIndex < this.modelsList.length && toIndex >= 0) {this.toIndex(toIndex);this.updateAttributes();this.modelsList.current++;return true;}return false;}/*** 上一个形态*/prev() {const current = this.modelsList.current;const toIndex = current - 1;if (toIndex < this.modelsList.length && toIndex >= 0) {this.toIndex(toIndex);this.updateAttributes();this.modelsList.current--;return true;}return false;}/*** 更新属性 同步到shader*/updateAttributes() {this.particlesGeometry.attributes.color.needsUpdate = true;this.particlesGeometry.attributes.oldColor.needsUpdate = true;this.particlesGeometry.attributes.toPositions.needsUpdate = true;this.particlesGeometry.attributes.oldPositions.needsUpdate = true;this.particlesGeometry.attributes.toPositionsDuration.needsUpdate = true;}/*** 更新粒子系统// * @param {number} progress 动画进度* @param {number} time 当前时间*/update(time: number) {// PointsShaderMaterial.uniforms.progress.value = progress;PointsShaderMaterial.uniforms.time.value = time;}/*** 初始化粒子系统 随机方形排布* @param {number} range 范围*/init(range: number = 1000) {for (let i = 0; i < this.numberOfPoints; i++) {const i3 = i * 3;const x = (0.5 - Math.random()) * range;const y = (0.5 - Math.random()) * range;const z = (0.5 - Math.random()) * range;this.toPositions[i3] = x;this.toPositions[i3 + 1] = y;this.toPositions[i3 + 2] = z;const c = Math.random();this.color[i3] = c;this.color[i3 + 1] = c;this.color[i3 + 2] = c;}this.particlesGeometry.attributes.toPositions.needsUpdate = true;}
}
自定义shader
/** @Author: hongbin* @Date: 2022-11-10 10:54:21* @LastEditors: hongbin* @LastEditTime: 2022-12-16 17:55:11* @Description:粒子材料*/
import vertexShader from "./points.vt.glsl";
import fragmentShader from "./points.fm.glsl";
import { AdditiveBlending,ShaderMaterial } from "three";export const PointsShaderMaterial = new ShaderMaterial({uniforms: {time: { value: 0 },//弥补自定义shader没有PointsMaterial材质的size属性size: { value: 8 },},blending: AdditiveBlending,// side: 2,transparent: true,vertexShader,//弥补自定义shader没有PointsMaterial材质的sizeAttenuation属性fragmentShader,alphaTest: 0.001,// depthTest: false,depthWrite: false,
});
初始化控制器
/*** 生成粒子系统控制器 传入粒子数量*/const PointsControl = new PointsControlClass(90686);scene.add(PointsControl.particles);
加载模型 填充数据
td.loadGltf(url).then((g) => {const targetMesh = g.scene.getObjectByProperty("type", "Mesh") as Mesh;if (!targetMesh) throw new Error("获取目标物体失败");const { array: positions} = targetMesh.geometry.attributes.position;targetMesh.geometry.computeBoundingBox();const color = { left: new Color("#f00"), right: new Color("#5f1"), dividingLine: 0.65 };const params = {duration: { min: 1000, max: 3000 },color,positions,box3: targetMesh.geometry.boundingBox!,};PointsControl.append(params, 0);});