96SEO 2026-05-05 08:16 2
还记得那个被产品经理嫌弃“不够震撼”的星空项目吗?起初一切dou在掌控之中,哪怕屏幕上飘着1000个小光点,帧率表依然稳稳地钉在60帧不动摇。那时候我觉得自己就是图形学的大神,随手一挥就是一片星河。

但现实hen快给了我一记响亮的耳光。当需求变成“再密一点,再亮一点”时噩梦开始了。胆子大一点,加到5000?勉强还Nengkan,掉到了50帧。再翻倍到一万?直接腰斩至30帧。等到两万个点挤在一起时浏览器彻底罢工,画面卡得像是在放幻灯片,风扇转得像直升机要起飞。
我盯着那满屏死掉的星星,陷入了深深的自我怀疑:为什么别人的Demo里动辄百万粒子还Neng流畅如丝,而我这几万个点就把显卡干趴下了?后来我才明白,这真不是显卡不行,纯粹是我的代码写法太“原始”了。
今天我就把这段从“卡顿”到“丝滑”的血泪史分享出来。不管你是刚入门的前端小白,还是想进阶的图形爱好者,kan完这篇,你也Neng搞懂如何用Three.js把十万级粒子玩出花来既养眼又不吃性Neng。
一、 探究卡顿的根源:CPU与GPU的博弈在Web开发里尤其是涉及到大量视觉元素时Zui大的性Neng杀手往往不是渲染本身,而是数据传输和计算逻辑的错位。
hen多新手习惯用JavaScript的循环去控制每一个粒子。比如想让粒子动起来就在`requestAnimationFrame`里写个`for`循环,遍历几万个数组元素,修改它们的坐标,然后告诉GPU:“嘿,画一下这个新位置”。
这就像什么呢?这就像你有一支拥有十万士兵的军队,但你这个指挥官却非要一个个士兵去喊口令:“张三,往前走一步;李四,往左跳一下。”哪怕你喊破喉咙,效率也极低。而且,JS和GPU之间的数据传输是有带宽限制的,每帧把几万个坐标的数据从内存扔到显存,本身就是一场灾难。
所以核心思路只有一条:把计算权移交给GPU,让CPU只负责发号施令,而不是事必躬亲。
二、 基础构建:Three.js中的粒子骨架在Three.js的世界里`Points`就是我们用来承载粒子的核心容器。它本质上是一种特殊的几何体,每一个顶点dou被渲染成一个方形的点。配合`PointsMaterial`,我们就Neng控制这些点的颜色、大小和贴图。
先来个Zui简单的“静态星空”热热身。这里的关键在于使用`BufferGeometry`,它比普通的Geometry性Neng高得多,因为它直接操作类型化数组,也就是显存里的数据块。
// 初始化几何体,准备存放顶点数据
const geo = new THREE.BufferGeometry;
// 假设我们要搞10000个点,每个点需要x,y,z三个坐标
const totalParticles = 10000;
const posArray = new Float32Array;
for {
// 随机散布在空间中
posArray = - 0.5) * 100;
}
// 把位置数据塞进几何体
geo.setAttribute);
// 创建材质,这里用个小圆点贴图
const mat = new THREE.PointsMaterial({
size: 0.5,
color: 0xffffff,
map: getCircleTexture, // 辅助函数生成圆形纹理
transparent: true,
alphaTest: 0.5 // 简单的透明度剔除
});
const starSystem = new THREE.Points;
scene.add;
这一步hen简单,对吧?但这时候的星星是死的,像撒了一把芝麻。想让它们“活”过来hen多新手会开始动JS循环的歪脑筋了——千万别!
三、 核心突破:将计算权移交给GPU着色器真正的工业级粒子特效,灵魂dou在`ShaderMaterial`里。着色器是运行在显卡上的小程序,它Neng并行处理成千上万个顶点,效率比JS高出几个数量级。
我们不再在JS里算坐标,而是把“时间”作为一个变量传给显卡,告诉显卡:“你自己根据时间算一下每个点该在哪。”
const shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uColor: { value: new THREE.Color }
},
// 顶点着色器:负责计算位置
vertexShader: `
uniform float uTime;
varying float vAlpha;
void main {
vec3 newPos = position;
// 让粒子在Y轴上根据时间Zuo正弦波动
// 加上position.x是为了让每个点的波动相位不同,显得自然
newPos.y += sin * 2.0;
vec4 mvPosition = modelViewMatrix * vec4;
gl_Position = projectionMatrix * mvPosition;
// 根据深度调整点的大小,近大远小
gl_PointSize = 40.0 * ;
// 把透明度传给片元着色器
vAlpha = 0.5 + 0.5 * sin;
}
`,
// 片元着色器:负责上色和画形状
fragmentShader: `
uniform vec3 uColor;
varying float vAlpha;
void main {
// 计算当前像素距离点中心的距离
vec2 coord = gl_PointCoord - vec2;
float dist = length;
// Ru果超出半径0.5,就丢弃,画出圆形
if discard;
// 加一点边缘柔化
float strength = 1.0 - ;
strength = pow;
gl_FragColor = vec4;
}
`,
transparent: true,
blending: THREE.AdditiveBlending // 叠加混合模式,让重叠的粒子geng亮
});
kan到没?一旦逻辑进了GPU,哪怕你把粒子数量加到10万,帧率可Nengdou不会掉多少。因为显卡Zui擅长的就是这种简单重复的并行计算。
四、 进阶优化:十万级粒子的生存法则虽然GPUhen强,但也不是无限的。当粒子数量级达到十万甚至百万时我们还需要一些“骚操作”来压榨性Neng。
1. 数据打包:BufferAttribute的艺术除了位置,粒子往往还有颜色、大小、随机偏移量等属性。不要创建多个BufferGeometry,而是要把所有属性打包进同一个Geometry的BufferAttribute里。
比如我们想让每个粒子颜色不同,大小也不同:
const count = 100000;
const geometry = new THREE.BufferGeometry;
const positions = new Float32Array;
const colors = new Float32Array;
const scales = new Float32Array; // 存放大小
for {
// 位置
positions = - 0.5) * 200;
positions = - 0.5) * 200;
positions = - 0.5) * 200;
// 颜色 - 随机生成一些偏蓝紫色的科幻感
colors = 0.5 + Math.random * 0.5; // R
colors = 0.2 + Math.random * 0.5; // G
colors = 1.0; // B
// 大小
scales = Math.random;
}
geometry.setAttribute);
geometry.setAttribute);
geometry.setAttribute);
然后在Shader里直接读取这些属性,GPU会自动把它们对应到每个顶点上。
2. 纹理图集:减少Draw Call的利器Ru果你想让粒子有的像星星,有的像雪花,有的像火星,千万别创建好几个Points对象。那样会导致Draw Call激增。
正确的Zuo法是画一张“大图”,把所有形状dou拼在一张图上。然后在Shader里通过`attribute`传一个索引,根据索引去算UV坐标,只显示图集的某一部分。
// 伪代码思路:在Vertex Shader里
// attribute float shapeIndex;
// varying vec2 vUv;
// void main {
// // 根据shapeIndex算出UV偏移
// vUv = gl_PointCoord;
// // ... 这里需要一些数学计算来映射到图集的对应区域
// }
3. 视距剔除:kan不见就不画
远处的粒子,其实只有几个像素大,甚至kan不清。这时候完全Ke以减少它们的计算量或者直接不画。在Shader里判断深度,Ru果距离太远,就把`gl_PointSize`设为0,或者透明度设为0。这Neng省下大量渲染资源。
五、 实战演练:构建流动的银河旋涡光说不练假把式。Zui后我们把上面学的全用上,搞一个既绚丽又流畅的“银河旋涡”。想象一下十万颗粒子围绕中心旋转,颜色随着半径渐变,大小忽大忽小,像是在呼吸。
const particleCount = 100000;
const galaxyGeo = new THREE.BufferGeometry;
const positions = new Float32Array;
const colors = new Float32Array;
const randomness = new Float32Array; // 用于动画的随机因子
const insideColor = new THREE.Color; // 核心颜色:暖色
const outsideColor = new THREE.Color; // 边缘颜色:冷色
for {
const i3 = i * 3;
// 银河半径
const radius = Math.random * 50;
// 旋转角度
const spinAngle = radius * 0.5; // 越远转得越慢
// 分支角度
const branchAngle = * / 3);
const randomX = Math.pow, 3) * <0.5 ? 1 : -1) * 2;
const randomY = Math.pow, 3) * <0.5 ? 1 : -1) * 2;
const randomZ = Math.pow, 3) * <0.5 ? 1 : -1) * 2;
positions = Math.cos * radius + randomX;
positions = randomY * ; // 中间厚,两边薄
positions = Math.sin * radius + randomZ;
// 颜色混合
const mixedColor = insideColor.clone;
mixedColor.lerp;
colors = mixedColor.r;
colors = mixedColor.g;
colors = mixedColor.b;
randomness = Math.random;
}
galaxyGeo.setAttribute);
galaxyGeo.setAttribute);
galaxyGeo.setAttribute);
// 银河材质
const galaxyMat = new THREE.ShaderMaterial({
depthWrite: false,
blending: THREE.AdditiveBlending,
vertexColors: true,
uniforms: {
uTime: { value: 0 }
},
vertexShader: `
uniform float uTime;
attribute vec3 color;
attribute float aRandom;
varying vec3 vColor;
varying float vDist;
void main {
vec3 newPos = position;
// 简单的旋转动画
float angle = uTime * 0.2 + aRandom;
float s = sin;
float c = cos;
// 绕Y轴旋转矩阵
float x = newPos.x * c - newPos.z * s;
float z = newPos.x * s + newPos.z * c;
newPos.x = x;
newPos.z = z;
vec4 mvPosition = modelViewMatrix * vec4;
gl_Position = projectionMatrix * mvPosition;
// 距离越远点越小
gl_PointSize = * ;
vColor = color;
vDist = -mvPosition.z;
}
`,
fragmentShader: `
varying vec3 vColor;
void main {
// 画圆
float r = distance);
if discard;
// 边缘发光效果
float glow = 1.0 - ;
glow = pow;
gl_FragColor = vec4;
}
`
});
const galaxy = new THREE.Points;
scene.add;
当你运行这段代码,kan着屏幕上那十万颗粒子缓缓旋转,中心炽热如火,边缘深邃如海,而且帧率依然坚挺在60帧时那种成就感是无与伦比的。这时候你才真正体会到,图形编程不是堆砌代码,而是指挥千军万马的艺术。
从Zui初的JS循环遍历,到后来的GPU Shader并行计算,再到Zui后的BufferAttribute数据打包,我们完成了一次从“菜鸟”到“高手”的思维蜕变。
粒子系统的精髓其实就一句话:别让CPU干苦力,把繁重的计算dou扔给GPU。
掌握了这些,无论是Zuo炫酷的网站背景,还是开发3D游戏里的技Neng特效,你douNeng游刃有余。当然Web图形学的坑还有hen多,比如后处理、物理碰撞等等,但那是后话了。
你在项目里Zuo过Zui疯狂的粒子效果是啥?有没有把浏览器搞崩过?欢迎在评论区留言,咱们一起聊聊那些年为了特效熬过的夜。
下篇预告: 既然粒子动起来了Neng不Neng让它们也受物理定律控制?比如碰到障碍物会反弹?教你让粒子拥有“实体感”。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback