type
Post
status
Published
date
Mar 12, 2026
slug
summary
粒子
生成银河系
tags
Three.js
category
技术学习笔记
icon
password
粒子
主要通过
threejs 里的 Points 和 PointsMaterial 实现Material 里的 sizeAttenuation 属性,用于配置粒子的近大远小的效果(离摄像机越远则越小)此时,摄像机放大时,粒子看着也会大

假如
设为
,则无论怎么放大,粒子都是同一尺寸:

分散粒子
如果要实现分散式的粒子,可以自己创建一个
BufferGeometry ,通过循环简单的实现为:
在这里插入图片描述
粒子材质使用纹理
一个找粒子材质的免费分享网站:
https://kenney.nl/assets/particle-pack同样的,粒子材质也可以设置纹理,通过
或
等属性加载,不过通常会选择使用
进行加载,需要提供一个黑色背景,内容白色的图片:

比如下面的例子中,使用这张图片:

声明材质时,使用
alphaMap 和 transparent但是这样简单地设置还不够,可能会出现一些问题,比如下面这张图里,有的粒子图片的边缘还存在,会遮挡后面的粒子,有的粒子是正常的

在这里插入图片描述
原因:GPU在创建粒子的时候是按照顺序进行创建的,但
webGL 渲染器并不知道哪一个在前哪一个在后,即不知道他们的深度关系是怎么样的修复方式:
alphaTest
可以设置
之间的值,用于告诉渲染器使用的纹理中,
值低于该值的像素不进行渲染,
值从
代表从黑到白的渐变:

它的默认值是0,默认情况下表示只有像素
alpha 值 <=0 (纯黑色)的才会被忽略所以可以通过设置合适的比例来让其渲染出来,比如设置0.5,那么图中
alpha 值小于 0.5 的颜色都不会被渲染出来这样,就可以大概去除掉黑色的边框了:

注意,不要把这个和opcaity透明度混为一谈了
depthTest
depthTest 默认为 true ,当它启动时,意味着 webGL 渲染器会在渲染每个像素(物体就是若干个像素绘制出来的)时,计算已经绘制出来的像素与当前要绘制的像素的深度关系如果当前要绘制像素比已经存在于深度缓冲区中的像素更远(或更近,取决于深度函数),那么这个像素将不会被绘制。这样可以确保只有“可见”的部分才会被绘制到屏幕上,从而实现正确的遮挡效果,同时节约渲染开销
将其设为
false ,可以看到所有粒子的黑色边框都消除了
在这里插入图片描述
相比于
alphaTest ,它不需要试验出合适的值,但 alphaTest 相对会更 定制化 一些但是,如果场景中存在其他的物体,比如添加一个立方体,此时如果关闭了
,会出现下面的结果:

立方体后面的粒子也渲染了出来,预期应该是立方体遮挡其后方的粒子:

当然,除非你就想要类似”透视”的效果,你可以这么做
depthWrite
所有被
渲染出来的东西,它们的
值都会被存放在内存的一个
里

所以,当
depthWrite 设置为 true 时,物体将会更新深度缓冲区,使得后来渲染的对象可以根据新的深度值进行深度测试。如果设置为 false ,则物体不会更新深度缓冲区,这意味着之后渲染的对象可能会忽略这个物体的存在,并可能被错误地渲染在其之上。在当前的场景中,我们需要的就是
立方体后面的粒子被隐藏 ,所以可以使用 depthWrite
在这里插入图片描述
上述
alphaTest 、 depthTest 、 depthWrite 都属于解决方案,没有完美的解决方案,但是通常对于粒子的场景都会使用 DepthWrite: false ,也可以三者结合使用弄出更好的效果Blending
(颜色)混合模式,即设置场景中物体重合时的展现形式,可以参考
官方提供例子来理解,PS、PR等软件里也是有这种配置的,它们是同一个东西

在粒子的场景中,常用的是
THREE.NormalBlending 和 THREE.AdditiveBlending ,默认是 THREE.NormalBlending不同色的粒子
可以通过配置顶点颜色来实现不同色的粒子:
颜色可以用
RGB 控制,所以用一个 colors 数组用来存放RGB颜色值,每3个代表一个顶点的颜色,然后给几何体设置上顶点颜色的 attributes 即可注意这样子配置后,几何体还没有顶点颜色的效果:

在这里插入图片描述
需要在粒子的材质里加上
vertexColors: true :
在这里插入图片描述
可以看到,粒子的颜色正确地使用上了,现在粒子重叠时是遮挡+覆盖的效果,可以配置
blending: THREE.AdditiveBlending 来开启颜色叠加混合模式:这样,下面框选的部分就会自动进行颜色叠加,计算出叠加后的颜色并渲染

粒子动画
粒子使用
Points 创建,和 Mesh 一样继承自 Object3D ,所以也可以通过在每一帧中修改 rotation 、 position 等属性来实现动画,比如实现一个粒子的旋转:除此之外,也可以通过修改
BufferGeometry 的坐标来实现动画:创建粒子的
BufferGeometry 时是经过 count 次遍历计算出几何体的每一个顶点后生成的,通过下面的代码创建所以
就是顶点的Y轴坐标,设置完成后得到下面的效果:

它们没有动起来,这是因为直接修改
geometry 的几何坐标后还需要通知 webGL 对其进行更新,需要加上下面的代码:效果:

在这里插入图片描述
当前的代码是在每一帧给所有粒子的y轴都设置为
Math.sin(elapsedTime) ,所以效果是相同幅度的上下浮动,给他们加上各自的 x 轴位置就可以实现波浪的效果了:
在这里插入图片描述
但是显然,操作几何体的每一个坐标是很耗性能的,因为要在每一帧之内进行
count 次的遍历,所以尽量不要这么去实现动画,而是用“着色器”实现一些更好的动画生成银河系

在这里插入图片描述
银河系是一个螺旋状的形状,要生成银河系,首先要思考怎么生成螺旋的形状;而螺旋的形状实际上就是将多条直线从同一个起点出发并弯曲,以不同角度绕出的一个形状,下面这是最终可以生成的银河系效果:

生成直线
所以,我们需要先构造出4条直线,假设使用20000个粒子,那么应该每4个粒子是一轮计算,每轮计算中,每个粒子构成的角度应该是
360° / 4 = 90° ,但是由于在 threejs 里不使用角度作为度量单位,所以应该是 2Π / 4 = Π / 2 ;i % branches 意为当前粒子数对分支取余,假如 branches = 4 ,便可以始终得到 0,1,2,3 ,以此在若干趟遍历中控制只有 branches 个相同的值;除以 branches ,就可以获得比例如 0,0.25,0.5,0.75 ;最后再乘以 2Π 即为所得(可以想像成一个圆上每90°取1个点)按照上面的代码处理后会得到下面的效果,对应的4个点坐标为:
、
、
、

我们得到了角度值,但是仅仅是值,还没有将他和方向关联上,如果我们需要生成前后左右4个方向的直线,那他的值应该类似这样:
1,0,0 、 0,0,1 、 -1,0,0 、 0,0,-1
这些值实际上就是 x、z 轴值分别取余弦和正弦的结果:
cos(0),0,sin(0) 、 cos(Π / 2),0,sin(Π / 2) 、 cos(Π),0,sin(Π) 、 cos(3Π / 2),0,sin(3Π / 2)
所以代码应该改为:这样就可以获取到下面的效果:

接着就是要实现粒子随机分布在这4个方向上,实际上需要的就是给每个粒子设置一个
半径 的长度,这个长度可以使用 Math.random() 获取:这样就可以获得我们想要的效果了:

到此,就可以通过修改上面的变量和粒子数量、大小来调试出更多直线了,使用
lil-gui 加上参数的调制,并且在每次调制完成后,重新生成整个”银河系“比如调整分支数到17条,得到的效果就是:

到这一步为止的完整代码:
每次调用
generateGalaxy 方法会先销毁原本的几何体和材质并把原本的粒子全部移除,这样做既是为了重置画面,也为了节约内存开销,因为 threejs 里的材质、几何体都需要手动清除直线弯曲
接下来,需要对直线实现弯曲的效果;每条直线实际上是由若干个粒子
以不同的长度 构成的,直线弯曲实际上就是让直线上的点发生一定量的偏移即可,但是要注意每个点偏移的量不能相同,不然只是相当于修改了直线的角度而已,比如:
在这里插入图片描述
所以,每个粒子的偏移量应该由
Math.random 生成,我们已经有了粒子的半径 radius ,他就是一个随机量,每个粒子的半径都不同,所以代码可以写成:
在这里插入图片描述
这样,基本的旋转的效果就实现了,然后我们可以绑定一个
可控制的变量 到操作面板里,控制其螺旋的圈数:
在这里插入图片描述
颜色控制
要控制粒子的颜色,可以通过修改
geometry 的顶点颜色来实现,也可以通过给 PointsMaterial 设置 color 属性实现我们现在要实现的是给每个粒子设置不同的颜色,如果要通过
PointsMaterial 去设置,这就意味着每次循环都要执行一次 points = new Points(geomery, material) 才能实现,这样子对性能占用的比较严重,所以还是应该使用 顶点着色 的方法实现:
在这里插入图片描述
看着像是白色的一条线,但其实只是粒子太多且混合模式设置成了
THREE.AdditiveBlending 导致,把粒子数量减少,放大可以看到颜色是作用到了每个顶点(粒子)的
在这里插入图片描述
完善形状
目前螺旋形状还只是一个平面,因为
y 轴坐标还始终是0,给y轴坐标加上一个随机值让形状立体起来:
在这里插入图片描述
但是目前的样子还是存在一定的规律性,可以试着再添加一点随机性;银河系的中间通常是一个大光圈,越到延申出来的尾部时光线越离散,所以可以认为粒子在中心密度高,尾部密度低,接下来就来实现这个需求
越靠近中心,意味着离原点越近,假如对其值取平方、三次方,那么靠近0的值将无限趋近于0,可以参考
的函数图像来看:

所以,可以给粒子的
x、y、z 的偏移量加上取幂指数的计算,这样就能实现我们的需求:
在这里插入图片描述
颜色渐变
目前的颜色机械质感严重,银河系的颜色通常是从中间到尾部的渐变色,所以接下来我们试着实现渐变色需求
首先我们用
threejs 提供的颜色类创建一个中心颜色,再定义一个尾部的颜色,让 threejs 自动计算从中心到尾部的颜色渐变首先将
通过颜色类内置的
方法进行一次深复制,然后调用颜色类内置的
方法生成
到
的渐变;
是每个粒子随机的半径,用它除以我们设置的基准半径就是粒子当前粒子所在的位置比例(注意这里不能直接使用
)

threejs 中对颜色类提供的 lerp 方法的解释:
在这里插入图片描述
最后,对新添加的变量使用
接管就完成了一个可控制样式的银河系了
