学习Three.js--星环粒子(ShaderMaterial)
2026年2月2日 17:22
学习Three.js--星环粒子(ShaderMaterial)
前置核心说明
开发目标
基于Three.js的ShaderMaterial实现高性能10万粒子纯净白色星环效果,核心能力包括:
- 生成环形分布的大量粒子(10万级),形成内外半径固定的星环,粒子分布均匀协调;
- 借助GPU着色器实现粒子3D脉动动画,兼顾流畅效果与高性能,低配设备无明显卡顿;
- 实现纯净白色粒子,保证加法混合下明亮不返白、柔和有光晕,避免生硬刺眼;
- 实现圆形抗锯齿粒子,避免默认方形粒子的生硬边缘,提升视觉细腻度;
- 支持轨道交互(拖拽旋转、滚轮缩放),全方位查看3D星环的脉动与光晕效果。
![]()
核心技术栈
| 技术点 | 作用 |
|---|---|
THREE.ShaderMaterial |
自定义顶点/片元着色器,逻辑运行在GPU上并行处理,高效支撑10万级粒子(性能远超普通材质如PointsMaterial) |
自定义attribute(sizes/shift) |
向着色器传递每个粒子的独立数据(尺寸、脉动参数),实现粒子差异化效果(不同大小、不同脉动节奏) |
自定义uniform(uTime) |
向着色器传递全局统一数据(时间),驱动所有粒子的动画同步更新,保证脉动效果协调统一 |
圆柱坐标系(setFromCylindricalCoords) |
快速生成环形分布的粒子坐标,无需手动计算sin/cos三角函数,简洁高效且不易出错 |
模型视图/投影矩阵(modelViewMatrix/projectionMatrix) |
着色器中完成3D顶点的透视变换,将粒子的局部坐标转换为屏幕可显示的2D坐标,是3D渲染的必备步骤 |
| 透视缩放点大小 | 实现粒子大小随相机距离变化,模拟「近大远小」的真实透视效果,避免远处粒子过大/过小导致视觉失真 |
片元着色器圆形粒子+smoothstep
|
绘制圆形粒子,并用抗锯齿算法实现边缘渐隐,同时为白色粒子营造柔和光晕,提升视觉质感 |
THREE.AdditiveBlending |
粒子白色亮度叠加发光,让星环呈现更明亮、更有层次感的朦胧光晕,同时通过亮度控制避免返白 |
| 白色亮度安全控制(基底<1.0+尺寸限制) | 加法混合下的核心避坑点,保证白色粒子明亮通透且不出现过曝返白,是纯净白色星环的关键优化 |
分步开发详解
步骤1:基础环境搭建(场景/相机/渲染器/控制器)
1.1 核心代码
// 1. 场景初始化(纯黑背景,最大化衬托白色星环的光晕效果)
const scene = new THREE.Scene();
// 2. 透视相机(适配3D场景,兼顾星环整体查看与细节观察)
const camera = new THREE.PerspectiveCamera(
60, // 视角(FOV):60°视野适中,无场景变形
innerWidth / innerHeight, // 宽高比:适配浏览器窗口
1, // 近裁切面:过滤过近无效对象,提升性能
1000 // 远裁切面:保证星环完整处于可见范围
);
camera.position.set(0, 6, 100); // 高位侧视:既完整查看环形形态,又体现3D脉动层次感
// 3. 渲染器(抗锯齿,提升白色粒子边缘细腻度,避免光晕锯齿感)
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio); // 高清适配:Retina屏幕无模糊
document.body.appendChild(renderer.domElement);
// 4. 轨道控制器(支持拖拽旋转/滚轮缩放,便捷查看3D白色星环)
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼:拖拽旋转有惯性,交互更顺滑自然
controls.dampingFactor = 0.05; // 阻尼系数:惯性适中,兼顾精准度与流畅度
1.2 关键说明
-
相机位置:
(0, 6, 100)采用「高位+稍远」视角,既可以完整捕捉白色星环的环形形态,又能清晰观察粒子3D脉动的光晕变化,避免视角过近导致星环变形、光晕过曝。 -
渲染器
antialias: true:开启抗锯齿,配合片元着色器的smoothstep抗锯齿逻辑,让白色粒子的边缘和光晕更细腻,减少锯齿感,这对明亮的白色星环尤为重要。 - 控制器阻尼:启用阻尼后,交互体验更贴近真实3D场景,适合长时间查看星环的脉动效果,避免拖拽后瞬间停止的生硬感。
步骤2:粒子数据生成(环形坐标+自定义属性)
这是星环的基础,需要生成10万粒子的环形坐标,以及每个粒子的独立尺寸、脉动参数,为后续着色器提供数据支撑,同时保证粒子分布均匀,为白色光晕叠加打下基础。
2.1 核心代码
// 粒子系统核心参数(集中管理,方便调整,适配白色星环效果)
const count = 100000; // 总粒子数(10万,ShaderMaterial可高效处理,无明显卡顿)
const innerRadius = 10, outerRadius = 40; // 星环内外半径:决定环形大小与宽度
const pointsArr = []; // 粒子顶点坐标数组
const sizes = []; // 粒子尺寸数组(每个粒子独立尺寸,避免白色星环单调)
const shift = []; // 粒子脉动参数数组(每个粒子4个独立参数,实现差异化脉动)
const radii = []; // 粒子环形半径数组(备用,方便后续扩展)
// 辅助函数:向shift数组添加单个粒子的4个脉动参数
const pushShift = () => {
shift.push(
Math.random() * Math.PI, // 脉动参数1:初始相位(控制脉动起始位置,避免所有粒子同步脉动)
Math.random() * Math.PI * 2, // 脉动参数2:水平相位(控制水平方向脉动,增加3D层次感)
(Math.random() * 0.9 + 0.1) * Math.PI * 0.1, // 脉动参数3:脉动频率(控制脉动快慢,0.1倍PI保证舒缓流畅)
Math.random() * 0.7 + 0.05 // 脉动参数4:脉动幅度(控制脉动距离,0.05~0.75避免粒子跑出星环)
);
};
// 循环生成10万粒子数据
for (let i = 0; i < count; i++) {
// 幂次采样(Math.pow(Math.random(), 1.5)):让粒子更均匀分布在环形区域
// 避免直接使用Math.random()导致内侧粒子密集、外侧稀疏的问题
const rand = Math.pow(Math.random(), 1.5);
const radius = Math.sqrt(outerRadius * outerRadius * rand + (1 - rand) * innerRadius * innerRadius);
radii.push(radius); // 存储粒子环形半径(备用,方便后续扩展白色星环的密度变化)
// 从圆柱坐标系转换为直角坐标系,快速生成环形粒子坐标
pointsArr.push(
new THREE.Vector3().setFromCylindricalCoords(
radius, // 圆柱坐标系半径(对应星环环形半径,决定粒子在星环中的位置)
Math.random() * 2 * Math.PI, // 圆柱坐标系角度(0~2π,实现环形均匀分布)
(Math.random() - 0.5) * 2 // 圆柱坐标系高度(Z轴,-1~1):让星环有轻微厚度,提升3D立体感
)
);
// 生成粒子独立尺寸(0.3~1.5之间随机):实现白色粒子大小差异化,光晕叠加更自然
sizes.push(Math.random() * 1.2 + 0.3);
// 生成粒子独立脉动参数:每个粒子脉动节奏不同,避免白色星环脉动过于规则
pushShift();
}
2.2 关键技术点解析
-
10万粒子的高性能支撑:普通材质(如
PointsMaterial)处理10万粒子时,动画逻辑运行在CPU上,会出现明显卡顿;而ShaderMaterial的逻辑运行在GPU上,具备强大的并行处理能力,可轻松应对10万级甚至百万级粒子,这是实现高性能白色星环的核心基础。 -
圆柱坐标系(
setFromCylindricalCoords):Three.js内置的坐标转换方法,参数为「半径、角度、高度」,无需手动计算sin/cos来生成环形坐标,简洁高效且不易出错,是实现星环、圆柱等环形结构的最佳实践。 -
幂次采样(
Math.pow(Math.random(), 1.5)):如果直接使用Math.random(),粒子会在「内半径到外半径」的区间内均匀分布,导致星环内侧粒子密集、外侧稀疏,白色光晕叠加后会出现内侧过亮返白的问题;幂次采样后,粒子会更均匀地分布在环形区域,白色光晕叠加更协调,不易出现局部过曝。 -
自定义属性数组(
sizes/shift):-
sizes:存储每个粒子的独立尺寸,实现白色粒子大小的差异化,避免星环过于单调,同时让光晕叠加呈现自然的明暗变化; -
shift:每个粒子存储4个脉动参数,后续在着色器中用于计算3D脉动动画,让每个粒子的脉动效果不同,白色星环的脉动更贴近真实星尘的效果。
-
步骤3:构建BufferGeometry(绑定顶点+自定义attribute)
将步骤2生成的粒子数据绑定到BufferGeometry,并将自定义属性(sizes/shift/radii)添加到几何体中,让着色器能够访问这些数据,为实现差异化粒子效果和白色星环优化提供数据支撑。
3.1 核心代码
// 1. 构建BufferGeometry,绑定粒子顶点坐标(环形粒子的基础位置数据)
const pointsGeometry = new THREE.BufferGeometry().setFromPoints(pointsArr);
// 2. 添加自定义attribute:sizes(粒子尺寸,每个粒子1个值)
// 第二个参数「1」表示每个顶点的分量数为1,与sizes数组的每个元素一一对应
pointsGeometry.setAttribute(
'sizes',
new THREE.Float32BufferAttribute(sizes, 1)
);
// 3. 添加自定义attribute:shift(粒子脉动参数,每个粒子4个值)
// 第二个参数「4」表示每个顶点的分量数为4,与shift数组的每4个元素对应一个粒子
pointsGeometry.setAttribute(
'shift',
new THREE.Float32BufferAttribute(shift, 4)
);
// 4. 添加自定义attribute:radii(粒子环形半径,每个粒子1个值,备用扩展)
pointsGeometry.setAttribute(
'radii',
new THREE.Float32BufferAttribute(radii, 1)
);
3.2 关键技术点解析
-
BufferGeometry:高效的几何体类型,直接操作二进制数组存储数据,渲染时减少CPU与GPU之间的数据传输开销,适合大量粒子场景,性能远优于已被废弃的普通Geometry,是Three.js推荐的几何体类型。 -
自定义
attribute:这是向着色器传递「每个顶点/粒子独立数据」的核心方式,语法为geometry.setAttribute(属性名, BufferAttribute实例)。- 着色器中需要声明同名
attribute变量(如attribute float sizes;),才能访问对应数据; - 对于白色星环而言,通过
attribute传递的size参数,是实现粒子大小差异化、避免光晕均匀过曝的关键。
- 着色器中需要声明同名
-
Float32BufferAttribute:最常用的BufferAttribute类型,存储32位浮点型数据,兼顾精度与性能,适合传递粒子尺寸、脉动参数等数据。
步骤4:创建ShaderMaterial(核心!纯净白色星环的灵魂)
ShaderMaterial是本次实战的核心,通过自定义顶点着色器和片元着色器,实现粒子的纯净白色、3D脉动、圆形抗锯齿和柔和光晕,同时保证加法混合下不返白,所有逻辑在GPU上运行,保证高性能。
4.1 核心代码
// 构建ShaderMaterial,配置全局uniforms和着色器,实现纯净白色星环
const pointsMaterial = new THREE.ShaderMaterial({
// 1. 全局uniforms(向着色器传递全局统一数据,此处为时间和星环半径)
uniforms: {
uTime: { value: 0 }, // 全局时间:驱动所有粒子的动画同步更新
uInnerRadius: { value: innerRadius }, // 星环内半径(备用扩展)
uOuterRadius: { value: outerRadius } // 星环外半径(备用扩展)
},
// 2. 顶点着色器(处理粒子位置、颜色、大小,运行在每个顶点/粒子上)
vertexShader: `
uniform float uTime;
uniform float uInnerRadius;
uniform float uOuterRadius;
attribute float sizes; // 粒子尺寸(自定义attribute,每个粒子独立)
attribute vec4 shift; // 粒子脉动参数(自定义attribute,每个粒子4个值)
attribute float radii; // 粒子环形半径(备用扩展)
varying vec3 vColor; // 传递给片元着色器的白色(varying变量,实现平滑插值)
void main() {
// 步骤1:获取粒子原始位置
vec3 pos = position;
// 步骤2:设置纯净白色(核心避坑:不返白,留光晕叠加空间)
vColor = vec3(0.9, 0.9, 0.9); // 白色基底:0.9(<1.0),避免加法混合过曝返白
vColor *= 0.99; // 全局亮度微调:0.99(安全最大值),明亮且不返白,保留光晕感
// 步骤3:3D脉动动画(球面扰动,让粒子沿球面方向脉动,白色光晕更有层次)
float t = uTime;
// 计算粒子脉动相位(结合初始相位和时间,实现每个粒子不同的脉动节奏)
// 6.28318530718 = 2π,取模保证相位始终在0~2π之间,实现循环脉动
float moveT = mod(shift.x + shift.z * t, 6.28318530718);
float moveS = mod(shift.y + shift.z * t, 6.28318530718);
// 计算脉动偏移量(球面坐标转换,实现3D方向脉动,避免平面化)
vec3 offset = vec3(
cos(moveS) * sin(moveT),
cos(moveT),
sin(moveS) * sin(moveT)
) * shift.w; // 乘以脉动幅度,控制粒子脉动距离,避免跑出星环
pos += offset; // 叠加偏移量,更新粒子位置,实现脉动效果
// 步骤4:透视变换(将3D粒子坐标转换为2D屏幕坐标,3D渲染必备)
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0); // 模型视图矩阵:局部坐标→相机视角坐标
gl_Position = projectionMatrix * mvPosition; // 投影矩阵:相机视角坐标→屏幕裁剪坐标
// 步骤5:粒子大小计算(透视缩放+最大尺寸限制,避免白色光晕过曝)
gl_PointSize = 0.18 * sizes * (200.0 / -mvPosition.z); // 透视缩放:近大远小,视觉更真实
gl_PointSize = min(gl_PointSize, 5.0); // 限制最大尺寸:5.0,避免粒子过大导致光晕叠加返白
}
`,
// 3. 片元着色器(处理粒子像素颜色、形状,运行在每个像素上,实现圆形抗锯齿与柔和光晕)
fragmentShader: `
varying vec3 vColor; // 从顶点着色器传递过来的纯净白色
void main() {
// 步骤1:绘制圆形粒子(基于点坐标的UV计算,替代默认方形粒子)
vec2 uv = gl_PointCoord - 0.5; // 将点坐标从(0,0)~(1,1)转换为(-0.5,-0.5)~(0.5,0.5)
float d = length(uv); // 计算当前像素到粒子中心的距离
// 步骤2:圆形裁剪(丢弃超出圆心0.5范围的像素,形成圆形粒子)
if (d > 0.5) discard; // discard:丢弃当前像素,不渲染,实现圆形轮廓
// 步骤3:抗锯齿+柔和光晕(smoothstep实现渐隐,避免圆形边缘锯齿,提升白色光晕质感)
float alpha = smoothstep(0.5, 0.05, d); // 从0.5到0.05,alpha从0渐变到1,边缘渐隐
// 步骤4:设置最终像素颜色(纯净白色+渐变Alpha,实现柔和光晕)
gl_FragColor = vec4(vColor, alpha);
}
`,
// 4. 材质附加配置(提升白色星环视觉效果,核心是加法混合与透明设置)
transparent: true, // 启用透明:支持粒子边缘渐隐,实现柔和光晕效果
depthTest: false, // 关闭深度测试:允许白色粒子叠加,营造光晕层次感,避免粒子互相遮挡
blending: THREE.AdditiveBlending, // 加法混合:粒子白色亮度叠加,呈现朦胧光晕,提升星环质感
premultipliedAlpha: false // 关闭预乘Alpha:避免白色发灰,保持纯净通透,适配加法混合
});
// 5. 创建Points粒子对象,添加到场景(将几何体与材质结合,形成最终的白色星环)
const points = new THREE.Points(pointsGeometry, pointsMaterial);
scene.add(points);
4.2 关键技术点解析
(1)全局uniforms与着色器变量声明
-
uniform float uTime:全局时间变量,从JS端每帧更新,驱动所有粒子的动画同步,所有粒子共享该值,保证白色星环的脉动效果协调统一; -
attribute变量:声明自定义属性(sizes/shift/radii),每个粒子有独立的值,对应BufferGeometry中绑定的数据,是实现粒子差异化效果的核心; -
varying vec3 vColor:插值变量,用于在顶点着色器和片元着色器之间传递白色数据,Three.js会自动在顶点之间进行平滑插值,保证白色光晕的过渡自然,无明显断层。
(2)顶点着色器核心逻辑(纯净白色+3D脉动+透视优化)
-
纯净白色设置(核心避坑:不返白):
- 直接将
vColor赋值为vec3(0.9, 0.9, 0.9),白色基底值设为0.9而非1.0,为加法混合预留光晕叠加空间,避免10万粒子密集叠加后亮度溢出(>1.0)导致返白; - 保留
vColor *= 0.99的全局亮度微调,让白色更通透柔和,带有自然的光晕感,不会显得生硬刺眼,同时0.99是安全最大值,不会触发过曝返白; - 若觉得白色偏暗,可将基底值调整为
0.95,全局增益调整为0.99,切勿将任一值设为1.0及以上,否则会出现明显返白。
- 直接将
-
3D脉动动画(球面扰动,提升光晕层次感):
- 利用
mod函数计算脉动相位,保证相位始终在0~2π之间,实现循环流畅的脉动效果,避免粒子脉动出现断层; - 通过球面坐标转换(
cos/sin)计算偏移量offset,让粒子沿球面方向脉动,而非平面方向,白色星环更具3D立体感,光晕叠加也更有层次; - 乘以
shift.w(脉动幅度),控制每个粒子的脉动距离,实现差异化脉动效果,避免白色星环脉动过于规则,更贴近真实星尘的效果。
- 利用
-
透视变换与点大小缩放(视觉真实+避坑优化):
-
modelViewMatrix+projectionMatrix:Three.js内置的矩阵,完成3D顶点坐标到2D屏幕坐标的转换,是3D渲染的必备步骤,保证白色星环能够正确显示在屏幕上; -
gl_PointSize:设置粒子的屏幕尺寸,200.0 / -mvPosition.z实现「近大远小」的透视效果,让白色星环更具真实感,0.18为整体缩放系数,控制粒子的整体大小; -
min(gl_PointSize, 5.0):限制粒子的最大尺寸,避免远处粒子因透视缩放过大,导致白色光晕叠加过曝返白,这是白色星环的关键避坑点之一。
-
(3)片元着色器核心逻辑(圆形抗锯齿+柔和光晕)
-
圆形粒子绘制(替代默认方形,提升视觉质感):
-
gl_PointCoord:Three.js内置变量,代表当前像素在粒子中的坐标,范围为(0,0)到(1,1); - 转换为
(-0.5,-0.5)到(0.5,0.5)的坐标,计算到粒子中心的距离d,通过if (d > 0.5) discard丢弃超出圆心0.5范围的像素,形成圆形粒子,替代默认的方形粒子,让白色星环更细腻。
-
-
抗锯齿+柔和光晕(
smoothstep核心优化):-
smoothstep(a, b, x):GLSL内置平滑插值函数,当x <= a时返回0,x >= b时返回1,中间为平滑渐变; - 此处
smoothstep(0.5, 0.05, d),当d=0.5时返回0(完全透明),d=0.05时返回1(完全不透明),中间为平滑渐隐过渡,既实现了圆形粒子的抗锯齿,又为白色粒子营造了柔和的光晕,避免边缘生硬刺眼; - 若想让光晕更强,可将第二个参数调整为
0.0,若想让粒子边缘更锐利,可调整为0.1,根据需求灵活适配。
-
-
加法混合(
AdditiveBlending):- 粒子的白色亮度会与背景和其他粒子的亮度叠加,越密集的地方越亮,形成自然的朦胧光晕,提升白色星环的层次感和视觉冲击力;
- 配合
transparent: true和depthTest: false,保证白色粒子之间能够正常叠加,不会互相遮挡,光晕效果更连贯,同时避免白色发灰,保持纯净通透。
步骤5:动画循环(驱动动画+更新渲染)
每帧更新全局时间uTime,驱动粒子脉动动画,同时更新星环整体旋转和控制器阻尼,实现流畅的白色星环动画效果,保证视觉体验的连贯性。
5.1 核心代码
const clock = new THREE.Clock(); // 时钟:用于获取累计运行时间,不受帧率影响,避免动画累积误差
function animate() {
requestAnimationFrame(animate); // 绑定浏览器刷新率(通常60帧/秒),实现流畅无卡顿的动画
// 1. 获取累计运行时间,驱动着色器脉动动画(减慢速度,让白色星环脉动更舒缓)
const t = clock.getElapsedTime() * 0.5; // 乘以0.5:减慢时间流速,提升观察体验
pointsMaterial.uniforms.uTime.value = t * Math.PI; // 乘以PI:放大相位变化,让脉动更流畅
// 2. 星环整体旋转,增加场景活力(白色星环缓慢旋转,光晕效果更丰富)
points.rotation.y = t * 0.05;
// 3. 更新轨道控制器阻尼(必须在动画循环中调用,保证阻尼效果生效)
controls.update();
// 4. 渲染场景(将场景和相机的3D信息渲染为2D画布,呈现最终的白色星环效果)
renderer.render(scene, camera);
}
// 启动动画循环(开始运行白色星环的脉动与渲染)
animate();
5.2 关键说明
-
clock.getElapsedTime():获取从时钟启动到当前的累计运行时间(单位:秒),相比getDelta()(获取两帧之间的时间差)更适合驱动全局循环动画,避免动画因帧率波动出现累积误差,保证白色星环的脉动效果在不同设备上一致。 -
动画速度调节:乘以
0.5减慢时间流速,乘以Math.PI放大相位变化,让白色星环的脉动更舒缓、更易观察,可根据需求调整系数(如0.3更慢,1.0更快)。 -
星环整体旋转:
points.rotation.y让白色星环绕Y轴缓慢旋转,配合粒子的3D脉动,白色光晕的变化更丰富,避免场景过于静态,提升视觉体验。
步骤6:窗口适配(响应式调整)
保证白色星环在不同屏幕尺寸下都能全屏显示,且不会出现拉伸变形,适配桌面端、移动端等不同设备。
6.1 核心代码
window.addEventListener('resize', () => {
// 1. 更新相机宽高比(适配新的窗口尺寸,避免场景拉伸)
camera.aspect = window.innerWidth / window.innerHeight;
// 2. 更新相机投影矩阵(必须调用,否则宽高比修改不生效,场景会出现拉伸变形)
camera.updateProjectionMatrix();
// 3. 更新渲染器尺寸(适配新的窗口尺寸,保证白色星环全屏显示)
renderer.setSize(window.innerWidth, window.innerHeight);
});
6.2 关键说明
- 窗口大小变化时,同步更新相机宽高比和渲染器尺寸,保证白色星环在不同屏幕尺寸下都能全屏显示,且透视效果正常,不会出现拉伸变形。
-
camera.updateProjectionMatrix():相机参数(如宽高比)修改后,必须调用该方法更新投影矩阵,否则宽高比的修改不会生效,场景会出现明显的拉伸变形,影响白色星环的视觉效果。
核心参数速查表(快速调整白色星环效果)
| 参数名 | 当前取值 | 作用 | 修改建议 |
|---|---|---|---|
count |
100000 | 总粒子数,决定白色星环的密集程度与光晕细腻度 | 低配设备改为50000~80000,减少卡顿;高配设备改为200000,提升光晕细腻度 |
innerRadius/outerRadius
|
10/40 | 星环内/外半径,决定白色星环的大小和环形宽度 | 改为5/30:星环更小更窄,光晕更集中;改为20/60:星环更大更宽,光晕更分散 |
shift.w(生成时) |
0.05~0.75 | 粒子脉动幅度,决定白色粒子的移动距离与光晕变化 | 改为0.05 |
白色基底值 vec3(0.9, 0.9, 0.9)
|
0.9 | 白色基础亮度,决定星环的整体明亮度,核心避坑点 | 改为0.8~0.95:亮度适中,不易返白;切勿≥1.0,否则加法混合会过曝返白 |
全局亮度增益 * 0.99
|
0.99 | 白色整体增益,微调星环明亮度,保留光晕空间 | 改为0.9~0.99:安全范围,明亮且不返白;切勿>1.0,触发过曝返白 |
粒子整体缩放 0.18(gl_PointSize) |
0.18 | 白色粒子的整体尺寸缩放系数,决定粒子基础大小 | 改为0.15:粒子更小,光晕更细腻;改为0.25:粒子更大,光晕更明显(避免>0.3,易返白) |
粒子最大尺寸 5.0(min限制) |
5.0 | 白色粒子的最大尺寸限制,核心避坑点,防止光晕过曝 | 改为3.0 |
smoothstep(0.5, 0.05, d) |
0.05 | 白色粒子边缘渐隐起始值,决定光晕强弱与边缘细腻度 | 改为0.0:光晕更强,边缘更柔和;改为0.1:光晕更弱,边缘更锐利 |
clock.getElapsedTime() * 0.5 |
0.5 | 动画时间流速,决定白色星环的脉动与旋转速度 | 改为0.3:动画更舒缓,便于观察光晕细节;改为1.0:动画更快速,光晕变化更活跃 |
完整优化代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>星环粒子 - ShaderMaterial 纯净白色版</title>
<style>body { margin: 0; overflow: hidden; background: #000; }</style>
</head>
<body>
<script type="module">
// 导入Three.js核心库和轨道控制器
import * as THREE from 'https://esm.sh/three@0.174.0';
import { OrbitControls } from 'https://esm.sh/three@0.174.0/examples/jsm/controls/OrbitControls.js';
// ========== 1. 基础环境初始化(场景/相机/渲染器/控制器) ==========
const scene = new THREE.Scene();
// 透视相机:高位侧视,清晰观察白色星环的环形形态与3D脉动
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 6, 100);
// 渲染器:抗锯齿,提升白色粒子边缘与光晕的细腻度,适配高清屏幕
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// 轨道控制器:启用阻尼,实现顺滑的3D交互体验
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// ========== 2. 粒子数据生成(环形坐标+自定义属性,适配白色星环) ==========
const count = 100000; // 总粒子数:10万,ShaderMaterial高效支撑,无明显卡顿
const innerRadius = 10, outerRadius = 40; // 星环内外半径:决定环形大小与宽度
const pointsArr = []; // 粒子顶点坐标数组
const sizes = []; // 粒子尺寸数组:每个粒子独立,实现差异化光晕
const shift = []; // 粒子脉动参数数组:每个粒子4个值,实现差异化脉动
const radii = []; // 粒子环形半径数组:备用,方便后续扩展
// 辅助函数:生成单个粒子的4个脉动参数,控制脉动节奏与距离
const pushShift = () => {
shift.push(
Math.random() * Math.PI, // 脉动初始相位1:控制起始位置
Math.random() * Math.PI * 2, // 脉动初始相位2:控制水平方向脉动
(Math.random() * 0.9 + 0.1) * Math.PI * 0.1, // 脉动频率:控制快慢,舒缓流畅
Math.random() * 0.7 + 0.05 // 脉动幅度:控制距离,避免粒子跑出星环
);
};
// 循环生成10万粒子数据,保证环形分布均匀,为白色光晕叠加打基础
for (let i = 0; i < count; i++) {
// 幂次采样:让粒子均匀分布在环形区域,避免内侧密集、外侧稀疏导致光晕过曝
const rand = Math.pow(Math.random(), 1.5);
const radius = Math.sqrt(outerRadius * outerRadius * rand + (1 - rand) * innerRadius * innerRadius);
radii.push(radius);
// 圆柱坐标系→直角坐标系:快速生成环形粒子坐标,提升开发效率
pointsArr.push(
new THREE.Vector3().setFromCylindricalCoords(
radius,
Math.random() * 2 * Math.PI,
(Math.random() - 0.5) * 2 // 轻微高度:让星环有3D立体感,光晕更丰富
)
);
// 生成粒子独立尺寸:0.3~1.5,实现差异化大小,光晕叠加更自然
sizes.push(Math.random() * 1.2 + 0.3);
// 生成粒子独立脉动参数:实现差异化脉动,避免星环脉动过于规则
pushShift();
}
// ========== 3. 构建BufferGeometry(绑定顶点+自定义attribute,传递粒子数据) ==========
const pointsGeometry = new THREE.BufferGeometry().setFromPoints(pointsArr);
// 添加自定义attribute:sizes(粒子尺寸),着色器中实现差异化大小
pointsGeometry.setAttribute('sizes', new THREE.Float32BufferAttribute(sizes, 1));
// 添加自定义attribute:shift(粒子脉动参数),着色器中实现差异化脉动
pointsGeometry.setAttribute('shift', new THREE.Float32BufferAttribute(shift, 4));
// 添加自定义attribute:radii(粒子环形半径),备用扩展
pointsGeometry.setAttribute('radii', new THREE.Float32BufferAttribute(radii, 1));
// ========== 4. 创建ShaderMaterial(核心:实现纯净白色、3D脉动、柔和光晕) ==========
const pointsMaterial = new THREE.ShaderMaterial({
// 全局uniforms:传递时间与星环半径,驱动全局动画与扩展
uniforms: {
uTime: { value: 0 },
uInnerRadius: { value: innerRadius },
uOuterRadius: { value: outerRadius }
},
// 顶点着色器:处理粒子位置、白色、大小,实现3D脉动与透视优化
vertexShader: `
uniform float uTime;
uniform float uInnerRadius;
uniform float uOuterRadius;
attribute float sizes;
attribute vec4 shift;
attribute float radii;
varying vec3 vColor;
void main() {
vec3 pos = position;
// 纯净白色设置:基底0.9+增益0.99,明亮不返白,保留光晕叠加空间
vColor = vec3(0.9, 0.9, 0.9);
vColor *= 0.99;
// 3D脉动动画:球面扰动,实现流畅循环的差异化脉动,提升光晕层次感
float t = uTime;
float moveT = mod(shift.x + shift.z * t, 6.28318530718);
float moveS = mod(shift.y + shift.z * t, 6.28318530718);
vec3 offset = vec3(
cos(moveS) * sin(moveT),
cos(moveT),
sin(moveS) * sin(moveT)
) * shift.w;
pos += offset;
// 透视变换:3D坐标→2D屏幕坐标,保证白色星环正确显示
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
gl_Position = projectionMatrix * mvPosition;
// 透视缩放+最大尺寸限制:近大远小更真实,避免粒子过大导致光晕过曝返白
gl_PointSize = 0.18 * sizes * (200.0 / -mvPosition.z);
gl_PointSize = min(gl_PointSize, 5.0);
}
`,
// 片元着色器:处理粒子形状、抗锯齿、柔和光晕,实现纯净白色圆形粒子
fragmentShader: `
varying vec3 vColor;
void main() {
// 圆形粒子绘制:转换UV坐标,计算到粒子中心的距离
vec2 uv = gl_PointCoord - 0.5;
float d = length(uv);
// 圆形裁剪:丢弃超出圆心的像素,形成圆形轮廓,替代默认方形
if (d > 0.5) discard;
// 抗锯齿+柔和光晕:smoothstep实现边缘渐隐,提升白色光晕质感
float alpha = smoothstep(0.5, 0.05, d);
// 最终像素颜色:纯净白色+渐变Alpha,实现明亮柔和的光晕效果
gl_FragColor = vec4(vColor, alpha);
}
`,
// 材质配置:提升白色星环视觉效果,核心是加法混合与透明设置
transparent: true, // 启用透明,支持边缘渐隐与光晕叠加
depthTest: false, // 关闭深度测试,允许粒子叠加,光晕更连贯
blending: THREE.AdditiveBlending, // 加法混合,白色亮度叠加,呈现朦胧光晕
premultipliedAlpha: false // 关闭预乘Alpha,避免白色发灰,保持纯净通透
});
// 创建Points粒子对象,添加到场景,形成最终的纯净白色星环
const points = new THREE.Points(pointsGeometry, pointsMaterial);
scene.add(points);
// ========== 5. 动画循环(驱动脉动+更新渲染,实现流畅白色星环效果) ==========
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
// 更新全局时间,驱动着色器脉动动画,减慢速度提升观察体验
const t = clock.getElapsedTime() * 0.5;
pointsMaterial.uniforms.uTime.value = t * Math.PI;
// 星环整体旋转,增加场景活力,白色光晕变化更丰富
points.rotation.y = t * 0.05;
// 更新轨道控制器阻尼,保证顺滑交互
controls.update();
// 渲染场景,呈现最终的纯净白色星环效果
renderer.render(scene, camera);
}
// 启动动画循环,开始运行白色星环的脉动与渲染
animate();
// ========== 6. 窗口适配(响应式调整,适配不同屏幕尺寸) ==========
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
总结与扩展建议
核心总结
-
高性能核心:
ShaderMaterial将动画与渲染逻辑移至GPU并行处理,可轻松应对10万级甚至百万级粒子,性能远超普通JS驱动的粒子系统,这是实现流畅白色星环的基础。 -
数据传递核心:自定义
attribute传递粒子独立数据,uniform传递全局统一数据,varying实现着色器间数据平滑插值,这是Three.js着色器开发的核心范式,适用于所有复杂粒子场景。 -
白色星环核心避坑:
- 白色基底值设为
<1.0,全局增益设为≤0.99,为加法混合预留光晕叠加空间,避免过曝返白; - 限制粒子最大尺寸,避免远处粒子过大导致光晕叠加过曝;
- 幂次采样保证粒子均匀分布,避免局部密集导致光晕过亮。
- 白色基底值设为
-
视觉效果核心:
- 圆柱坐标系快速生成环形粒子,幂次采样保证分布均匀,为白色光晕叠加打下基础;
- 顶点着色器实现3D脉动,片元着色器实现圆形抗锯齿与柔和光晕;
-
AdditiveBlending加法混合实现白色亮度叠加,营造朦胧光晕,提升星环层次感。
- 透视优化核心:粒子大小随相机距离缩放,实现「近大远小」的真实透视效果,避免场景失真,提升白色星环的视觉真实感。
扩展建议
-
白色星环效果扩展:
- 动态调整白色亮度:通过
uniform传递白色增益值,让星环随时间实现「明暗呼吸」效果,提升场景张力; - 调整光晕强弱:通过修改
smoothstep的渐隐参数,实现光晕的「浓淡变化」,适配不同视觉风格; - 多环叠加:创建多个不同半径、不同脉动速度的白色星环,形成星系效果,提升场景复杂度。
- 动态调整白色亮度:通过
-
功能扩展:
- 交互增强:绑定鼠标位置,让白色星环跟随鼠标旋转、脉动,提升交互体验;
- 参数控制面板:提供可视化面板,允许用户调整星环半径、粒子数、脉动速度、白色亮度等参数,实时预览效果;
- 响应式优化:根据设备性能自动调整粒子数,低配设备减少粒子数,保证流畅性,高配设备增加粒子数,提升细腻度。
-
性能优化:
- 使用
InstancedBufferGeometry替代BufferGeometry,进一步减少DrawCall,支持更多粒子(百万级); - 开启渲染器的
powerPreference: "high-performance",优先使用高性能GPU,提升渲染效率; - 剔除不可见粒子:通过视锥体裁剪,剔除屏幕外的粒子,减少GPU渲染开销。
- 使用
-
视觉风格扩展:
- 添加轻微蓝色/银色调:在白色基底中加入少量蓝色(如
vec3(0.9, 0.9, 1.0)),营造冷色调科技感星环; - 添加辉光效果:结合
THREE.UnrealBloomPass后期处理,增强白色星环的辉光感,提升视觉冲击力。
- 添加轻微蓝色/银色调:在白色基底中加入少量蓝色(如