4️⃣threejs学习笔记(四)
2026-3-12
| 2026-3-17
字数 5576阅读时长≈ 14 分钟
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 方法的解释:
在这里插入图片描述
在这里插入图片描述
最后,对新添加的变量使用
接管就完成了一个可控制样式的银河系了
在这里插入图片描述

完整代码

 
 
 
 
 
 
threeJs学习笔记(四)
  • Three.js
  • threejs学习笔记(三)threejs学习笔记(五)
    Loading...