一、效果展示

扩散波纹动画是一种以场景中心为起点,向外不断扩散的圆形波纹效果,常用于地图中心点、雷达扫描等场景。波纹呈现出柔和的发光效果,由中心向外不断循环扩散。
二、实现原理
2.1 整体架构
扩散波纹动画的实现分为三个核心部分:
- 平面载体 :使用
PlaneGeometry创建一个足够大的平面,作为波纹的渲染载体 - 自定义着色器 :通过
onBeforeCompile钩子注入 GLSL 代码,实现波纹的计算与渲染 - 动画驱动:使用 GSAP 动画库驱动 uniform 变量,实现波纹的循环扩散
2.2 着色器实现
2.2.1 顶点着色器修改
在顶点着色器中添加 vPosition 变量,将片元的位置信息传递到片元着色器:
glsl
// 在 void main() 之前声明 varying 变量
varying vec3 vPosition;
void main(){
vPosition = position; // 将顶点位置传递给片元着色器
// ... 原有代码
}
2.2.2 片元着色器修改
片元着色器是波纹计算的核心,主要逻辑包括:
- 计算波纹半径 :
r = uTime * uSpeed(当前波纹的前沿半径) - 计算片元距离 :
rDistance = distance(vPosition.xy, center)(片元到中心的距离) - 判断波纹范围 :
rDistance > r && rDistance < r + 2.0 * w - 平滑过渡 :使用
smoothstep函数实现波纹前后沿的渐隐效果 - 颜色混合 :使用
mix函数在原色和波纹色之间平滑过渡
glsl
// 声明 uniform 变量
uniform float uTime; // 时间控制
uniform float uSpeed; // 扩散速度
uniform float uWidth; // 波纹宽度
uniform vec3 uColor; // 波纹颜色
varying vec3 vPosition; // 顶点位置
void main(){
// 计算当前波纹前沿位置
float r = uTime * uSpeed;
// 波纹半宽
float w = uWidth;
// 波纹中心坐标
vec2 center = vec2(0.0, 0.0);
// 计算片元到中心的距离
float rDistance = distance(vPosition.xy, center);
// 判断是否在波纹范围内
if(rDistance > r && rDistance < r + 2.0 * w) {
float per = 0.0;
// 波纹前沿(从外向内过渡)
if(rDistance < r + w) {
float p = smoothstep(0.0, 1.0, (rDistance - r) / w);
p *= p; // 增强渐隐效果
outgoingLight = mix(outgoingLight, uColor, p);
}
// 波纹后沿(从内向外过渡)
else {
float p = smoothstep(0.0, 1.0, (rDistance - r - w) / w);
outgoingLight = mix(uColor, outgoingLight, p);
}
gl_FragColor = vec4(outgoingLight, diffuseColor.a);
}
// 不在波纹范围内,完全透明
else {
gl_FragColor = vec4(outgoingLight, 0.0);
}
}
2.3 混合模式配置
为了实现发光叠加效果,需要配置自定义混合模式:
javascript
material.blending = THREE.CustomBlending
material.blendEquation = THREE.AddEquation
material.blendSrc = THREE.DstColorFactor
material.blendDst = THREE.OneFactor
混合公式 :result = src_color * DstColorFactor + dst_color * OneFactor
这种混合模式让波纹颜色与背景颜色产生叠加发光效果。
2.4 动画驱动
使用 GSAP 驱动 uTime uniform 变量,实现波纹循环扩散:
javascript
gsap.to(shader.uniforms.uTime, {
value: 4, // 从 0 动画到 4
repeat: -1, // 无限循环
duration: 6, // 每次循环 6 秒
ease: 'power1.easeIn', // 缓动函数
})
三、完整 HTML 实现
以下是一个可独立运行的扩散波纹动画示例:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>扩散波纹动画</title>
<style>
body { margin: 0; overflow: hidden; background: #102736; }
canvas { display: block; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "https://esm.sh/three@0.161.0",
"three/addons/": "https://esm.sh/three@0.161.0/examples/jsm/"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script type="module">
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
// 1. 初始化场景
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x102736)
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 10, 20)
camera.lookAt(0, 0, 0)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(window.devicePixelRatio)
document.body.appendChild(renderer.domElement)
// 控制器
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.enablePan = false
// 2. 创建扩散波纹
function createDiffuse() {
// 2.1 创建平面载体
const geometry = new THREE.PlaneGeometry(200, 200)
// 2.2 创建基础材质
const material = new THREE.MeshBasicMaterial({
color: 0x000000,
depthWrite: false,
depthTest: false,
transparent: true,
blending: THREE.CustomBlending,
})
// 2.3 配置混合模式
material.blendEquation = THREE.AddEquation
material.blendSrc = THREE.DstColorFactor
material.blendDst = THREE.OneFactor
// 2.4 注入自定义着色器
material.onBeforeCompile = (shader) => {
// 添加 uniform 变量
shader.uniforms = {
...shader.uniforms,
uTime: { value: 0 },
uSpeed: { value: 8.0 },
uWidth: { value: 2.0 },
uColor: { value: new THREE.Color(0x71918e) },
}
// 顶点着色器 - 添加 varying 变量
shader.vertexShader = shader.vertexShader.replace(
'void main() {',
`
varying vec3 vPosition;
void main(){
vPosition = position;
`
)
// 片元着色器 - 声明 uniform 和 varying
shader.fragmentShader = shader.fragmentShader.replace(
'void main() {',
`
uniform float uTime;
uniform float uSpeed;
uniform float uWidth;
uniform vec3 uColor;
varying vec3 vPosition;
void main(){
`
)
// 片元着色器 - 替换片元颜色计算逻辑
shader.fragmentShader = shader.fragmentShader.replace(
'#include <opaque_fragment>',
`
#ifdef OPAQUE
diffuseColor.a = 1.0;
#endif
#ifdef USE_TRANSMISSION
diffuseColor.a *= material.transmissionAlpha;
#endif
// 计算波纹前沿半径
float r = uTime * uSpeed;
// 波纹半宽
float w = uWidth;
// 波纹中心
vec2 center = vec2(0.0, 0.0);
// 计算片元到中心的距离
float rDistance = distance(vPosition.xy, center);
// 判断是否在波纹范围内
if(rDistance > r && rDistance < r + 2.0 * w) {
float per = 0.0;
// 波纹前沿(从外向内过渡)
if(rDistance < r + w) {
float p = smoothstep(0.0, 1.0, (rDistance - r) / w);
p *= p;
outgoingLight = mix(outgoingLight, uColor, p);
}
// 波纹后沿(从内向外过渡)
else {
float p = smoothstep(0.0, 1.0, (rDistance - r - w) / w);
outgoingLight = mix(uColor, outgoingLight, p);
}
gl_FragColor = vec4(outgoingLight, diffuseColor.a);
}
// 不在波纹范围内,完全透明
else {
gl_FragColor = vec4(outgoingLight, 0.0);
}
`
)
// 保存 shader 引用用于动画
window.diffuseShader = shader
}
// 2.5 创建网格并添加到场景
const mesh = new THREE.Mesh(geometry, material)
mesh.rotation.x = -Math.PI / 2 // 旋转至水平
mesh.position.set(0, 0.21, 0) // 稍微抬高避免 z-fighting
mesh.renderOrder = 3
scene.add(mesh)
// 2.6 启动动画
setTimeout(() => {
gsap.to(window.diffuseShader.uniforms.uTime, {
value: 4,
repeat: -1,
duration: 6,
ease: 'power1.easeIn',
})
}, 1000)
}
createDiffuse()
// 3. 添加参考网格(可选)
const gridHelper = new THREE.GridHelper(20, 20, 0x1b4b70, 0x1b4b70)
gridHelper.material.opacity = 0.3
gridHelper.material.transparent = true
scene.add(gridHelper)
// 4. 渲染循环
function animate() {
requestAnimationFrame(animate)
controls.update()
renderer.render(scene, camera)
}
animate()
// 5. 响应窗口大小变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
</script>
</body>
</html>
四、参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
uTime |
float | 0 → 4 | 时间控制变量,通过 GSAP 驱动 |
uSpeed |
float | 8.0 | 波纹扩散速度,值越大波纹扩散越快 |
uWidth |
float | 2.0 | 波纹宽度,影响波纹的厚度 |
uColor |
Color | 0x71918e | 波纹颜色 |
duration |
float | 6 | 单次循环时长(秒) |
value |
float | 4 | uTime 动画的目标值,影响扩散范围 |
五、扩展应用
- 多波纹叠加 :可以创建多个平面,使用不同的
uTime初始值实现多层波纹 - 自定义形状 :修改
distance函数,实现矩形、三角形等形状的波纹 - 多颜色渐变 :在
mix中加入更多颜色,实现彩虹波纹效果 - 波纹衰减 :根据
rDistance添加衰减因子,实现远处波纹变暗的效果