墨烟
let pointCloud: any
const PARTICLE_COUNT = 2000
const geometry = new THREE.BufferGeometry()
// 初始化三维粒子属性
const positions = new Float32Array(PARTICLE_COUNT * 3)
const directions = new Float32Array(PARTICLE_COUNT * 3)
const lifeParams = new Float32Array(PARTICLE_COUNT * 2)
for (let i = 0
// 初始位置在原点
positions[i * 3] = 0
positions[i * 3 + 1] = 0
positions[i * 3 + 2] = 0
// 生成三维随机方向(球面均匀分布)
const theta = Math.random() * Math.PI * 2
const phi = Math.acos(2 * Math.random() - 1)
const r = Math.random() * 0.5
directions[i * 3] = r * Math.sin(phi) * Math.cos(theta)
directions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta)
directions[i * 3 + 2] = r * Math.cos(phi)
lifeParams[i * 2] = Math.random()
lifeParams[i * 2 + 1] = Math.random() * 0.3 + 0.5
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
geometry.setAttribute('aDirection', new THREE.BufferAttribute(directions, 3))
geometry.setAttribute('aLifeParams', new THREE.BufferAttribute(lifeParams, 2))
const texture = new THREE.TextureLoader().load('https://threejs.org/examples/textures/sprites/disc.png')
// 三维喷射Shader材质
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uTexture: { value: texture },
uGravity: { value: -0.8 }, // 重力强度
uTurbulence: { value: 0.15 } // 湍流强度
},
vertexShader: `
uniform float uTime
uniform float uGravity
uniform float uTurbulence
attribute vec3 aDirection
attribute vec2 aLifeParams
varying float vLife
varying vec3 vPosition
void main() {
// 生命周期计算
float lifeOffset = aLifeParams.x
float speed = aLifeParams.y
float life = mod(uTime * speed + lifeOffset, 1.0)
vLife = life
// 基础运动
vec3 pos = position
// 主喷射方向(Y轴)叠加随机三维方向
vec3 moveVec = vec3(
aDirection.x * uTurbulence,
1.0 + aDirection.y * 0.3,
aDirection.z * uTurbulence
)
// 运动轨迹计算
pos += moveVec * life * 5.0
pos.y += uGravity * pow(life, 2.0)
// 湍流效果
pos.x += sin(uTime * 10.0 + lifeOffset * 10.0) * 0.1 * life
pos.z += cos(uTime * 8.0 + lifeOffset * 10.0) * 0.1 * life
// 保存位置供片段着色器使用
vPosition = pos
// 投影计算
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0)
gl_PointSize = 8.0 * (1.0 - life) * (1.0 / length(mvPosition.xyz))
gl_Position = projectionMatrix * mvPosition
}
`,
fragmentShader: `
uniform sampler2D uTexture
varying float vLife
varying vec3 vPosition
void main() {
// 颜色渐变(从蓝色到透明)
vec3 baseColor = mix(
vec3(0.3, 0.5, 1.0),
vec3(0.8, 0.9, 1.0),
smoothstep(0.0, 0.5, vLife)
)
// 贴图混合
vec4 texColor = texture2D(uTexture, gl_PointCoord)
float alpha = texColor.a * (1.0 - smoothstep(0.7, 1.0, vLife))
// 深度衰减效果
float depthFactor = 1.0 - smoothstep(0.0, 5.0, abs(vPosition.z))
alpha *= depthFactor
gl_FragColor = vec4(baseColor, alpha)
}
`,
transparent: true,
depthWrite: false,
blending: THREE.AdditiveBlending
})
pointCloud = new THREE.Points(geometry, material)
pointCloud.rotateX(Math.PI / 2)
pointCloud.position.setY(0.3)
pointCloud.scale.set(50, 50, 50)
scene.add(pointCloud)
function animate() {
requestAnimationFrame(animate)
pointCloud.material.uniforms.uTime.value = performance.now() / 1000
}
animate()