Three.js 行星光晕粒子特效
本篇文章中,使用 Three.js 构建一个具有行星、光晕和粒子特效的 3D 场景。
我们模拟了一个小型“太空”环境,有以下几个主要元素:
- 行星:使用球体几何体和基础纹理、凹凸贴图、高光贴图来增强细节。
- 光晕(Shader):通过自定义着色器模拟行星周围的光晕。
- 粒子系统:通过大量随机分布的四面体几何来模拟星空背景。
- 光源:通过环境光、半球光和定向光来让场景更有层次感。
代码解析
1. 初始化渲染器(Renderer)
import * as THREE from "three";
const renderer = new THREE.WebGLRenderer({
antialias: true, // 启用抗锯齿,使边缘更平滑
alpha: true, // 启用透明背景
});
renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 1);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.autoClear = false;
renderer.setClearColor(0x000000, 0.0);
document.body.appendChild(renderer.domElement);
WebGLRenderer
:为我们提供了 3D 绘制的能力。antialias
:抗锯齿可以让图形边缘更加平滑。alpha
:开启透明背景,方便实现一些特效叠加。renderer.setPixelRatio(...)
:根据设备像素比设置更精细的渲染,适配视网膜屏等高分辨率设备。renderer.autoClear = false
:在每帧绘制之前不自动清除画面,方便后期叠加某些特效。setClearColor(0x000000, 0.0)
:将背景色设置为透明的黑色。
2. 创建场景(Scene)
const scene = new THREE.Scene();
Scene
:场景是 Three.js 中所有 3D 对象、光照等的容器。
3. 设置相机(Camera)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
scene.add(camera);
PerspectiveCamera
:透视相机,模拟人眼观察物体的视觉。- 第一个参数是视场角度(FOV,field of view),这里是 75°。
- 第二个参数是宽高比,用于修正因屏幕宽高比不同造成的图像变形。
- 第三个参数和第四个参数分别是近、远裁剪面,控制相机可见范围。
camera.position.z = 400
:将相机在 Z 轴上往后移动,保证能看到场景中的物体。
4. 创建主要 3D 容器对象
const circle = new THREE.Object3D();
const particle = new THREE.Object3D();
const halo = new THREE.Object3D();
const luminor = new THREE.Object3D();
const lights = [];
scene.add(circle);
scene.add(particle);
scene.add(halo);
scene.add(luminor);
- 这里定义了四个
Object3D
容器:- circle 用于存放行星;
- particle 用于存放粒子系统;
- halo 用于存放光晕;
- luminor 用于存放光源;
- 最后再把它们都加入到场景中,方便后续整体变换、管理。
5. 定义几何体(Geometry)
const geometry = new THREE.TetrahedronGeometry(1, 1); // 粒子使用的四面体
const geo_planet = new THREE.SphereGeometry(10, 64, 32); // 行星使用的球体
const geom3 = new THREE.SphereGeometry(16, 32, 16); // 光晕使用的球体
- TetrahedronGeometry:四面体几何,用于粒子星空的散点效果。
- SphereGeometry:球体几何,用于行星和光晕的基础模型。
6. 粒子材质(Material)
const material = new THREE.MeshPhongMaterial({
color: 0x111111,
shading: THREE.FlatShading,
});
- 使用
MeshPhongMaterial
实现基础的光照模型。 color: 0x111111
:粒子呈暗灰色。shading: THREE.FlatShading
:平面着色,更简约的视觉效果。
7. 创建粒子系统
for (let i = 0; i < 500; i++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
mesh.position.multiplyScalar(200 + Math.random() * 500);
mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2);
particle.add(mesh);
}
通过循环生成 500 个带有四面体几何的粒子,然后随机设置位置与旋转:
set(...)
:随机生成位置向量;normalize()
:将该向量规范化,分布在球面上;multiplyScalar(...)
:再通过一个随机数控制它们与场景中心的距离,范围在 200 ~ 700;- 随机设置旋转以打破呆板,让星空更自然;
- 将生成的粒子加入粒子容器
particle
。
8. 行星材质(MeshPhongMaterial)
const mat = new THREE.MeshPhongMaterial({
color: 0x000000,
emissive: 0x000000,
map: new THREE.TextureLoader().load(
"https://upload.wikimedia.org/wikipedia/commons/2/2c/Generic_Celestia_asteroid_texture.jpg"
),
bumpMap: new THREE.TextureLoader().load(
"https://upload.wikimedia.org/wikipedia/commons/2/2c/Generic_Celestia_asteroid_texture.jpg"
),
bumpScale: 0.025,
specularMap: new THREE.TextureLoader().load(
"https://upload.wikimedia.org/wikipedia/commons/2/2c/Generic_Celestia_asteroid_texture.jpg"
),
specular: new THREE.Color("grey"),
shininess: 3,
});
MeshPhongMaterial
提供了漫反射和高光反射,适合模拟天体的质感。map
:基础纹理贴图,用于给表面上色。bumpMap
和bumpScale
:凹凸贴图及其强度,使表面出现微小起伏,增加细节真实性。specularMap
:高光贴图,用于定义光照在表面产生的高光效果。specular
和shininess
:高光的颜色和强度。
9. 光晕材质(ShaderMaterial)
在这个示例中,我们使用了自定义着色器(Vertex + Fragment)。在你的项目里,可以把它们放在单独的文件 vertexShader.glsl
和 fragmentShader.glsl
。下面是示例中的 GLSL 代码:
vertexShader.glsl:
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
fragmentShader.glsl:
varying vec3 vNormal;
void main(){
float intensity = pow(.7 - dot(vNormal, vec3(0., 0., .5)), 4.);
gl_FragColor = vec4(.89, .82, .69, 1.) * intensity; // 自定义光晕色调
}
- 在顶点着色器
vertexShader.glsl
中,我们将法线进行归一化计算,并传给片段着色器。 - 在片段着色器
fragmentShader.glsl
中,通过与某个方向向量(此例中是vec3(0., 0., .5)
) 的点乘来控制光晕强度,再使用pow()
提升光晕的“扩散感”,最终输出半透明的发光效果。
接着,用 ShaderMaterial
把它们应用到我们的光晕几何上:
const mat3 = new THREE.ShaderMaterial({
uniforms: {},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.BackSide,
blending: THREE.AdditiveBlending,
transparent: true,
opacity: 1.5,
depthWrite: false,
});
side: THREE.BackSide
:渲染背面,用于做包围在里面的光效。blending: THREE.AdditiveBlending
:加法混合,让发光区域和背景色叠加时会变得更亮。depthWrite: false
:避免因为深度缓冲造成自己遮挡自己。
10. 创建行星与光晕
// 行星
const planet = new THREE.Mesh(geo_planet, mat);
planet.scale.x = planet.scale.y = planet.scale.z = 15;
circle.add(planet);
// 光晕
const ball = new THREE.Mesh(geom3, mat3);
ball.scale.x = ball.scale.y = ball.scale.z = 16;
halo.add(ball);
const ball2 = new THREE.Mesh(geom3, mat3);
ball2.scale.x = ball2.scale.y = ball2.scale.z = 12;
ball2.position.set(25, 5, 1);
halo.add(ball2);
- 将球体网格对象与材质绑定后,放大缩小以获得合适的尺寸,并把它们加到对应的容器内。
ball2
作为一个次级光晕,加上了相对于第一个光晕的偏移位置(25, 5, 1
),在视觉上会形成微妙的浮动、层叠感。
11. 添加光源
const ambientLight = new THREE.AmbientLight(0x111111);
scene.add(ambientLight);
const hemiLight = new THREE.HemisphereLight(0x777777, 0x222222, 15);
hemiLight.position.set(-1, -1, 2);
luminor.add(hemiLight);
lights[1] = new THREE.DirectionalLight(0x999999, 4);
lights[1].position.set(-1, 0, 0.5);
lights[2] = new THREE.DirectionalLight(0x999999, 4);
lights[2].position.set(1, 0, 0.5);
scene.add(lights[1]);
scene.add(lights[2]);
- 环境光(AmbientLight):为场景提供基础的漫射光照,让物体不会完全在阴影中。
- 半球光(HemisphereLight):模拟来自天空和地面的光照,颜色可以分别设置天空和地面部分。
- 定向光(DirectionalLight):像太阳光一样平行照射,让行星表面出现更逼真的明暗对比。
12. 窗口大小自适应(onWindowResize)
window.addEventListener("resize", onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
- 当浏览器窗口大小改变时,重新计算相机的宽高比并更新投影矩阵,同时调整渲染器大小,保持视觉上的正确显示。
13. 动画循环(animate)
function animate() {
requestAnimationFrame(animate);
particle.rotation.y -= 0.004;
circle.rotation.x -= 0.001;
circle.rotation.y -= 0.001;
halo.rotation.z -= 0.005;
luminor.rotation.z -= 0.005;
renderer.clear();
renderer.render(scene, camera);
}
animate();
- 粒子系统的旋转:通过
particle.rotation.y
实现星空的慢速旋转。 - 行星的旋转:让行星在
x
、y
方向同时缓慢旋转,模拟自转效果。 - 光晕和光源容器的旋转:让光晕也进行缓慢旋转以获得流动感。
renderer.render(scene, camera)
:使用给定的相机渲染出场景。
代码
https://github.com/calmound/threejs-demo/tree/main/huiguang (opens in a new tab)