Three.js扩散波纹动画实现

一、效果展示

扩散波纹动画是一种以场景中心为起点,向外不断扩散的圆形波纹效果,常用于地图中心点、雷达扫描等场景。波纹呈现出柔和的发光效果,由中心向外不断循环扩散。

二、实现原理

2.1 整体架构

扩散波纹动画的实现分为三个核心部分:

  1. 平面载体 :使用 PlaneGeometry 创建一个足够大的平面,作为波纹的渲染载体
  2. 自定义着色器 :通过 onBeforeCompile 钩子注入 GLSL 代码,实现波纹的计算与渲染
  3. 动画驱动:使用 GSAP 动画库驱动 uniform 变量,实现波纹的循环扩散

2.2 着色器实现

2.2.1 顶点着色器修改

在顶点着色器中添加 vPosition 变量,将片元的位置信息传递到片元着色器:

glsl 复制代码
// 在 void main() 之前声明 varying 变量
varying vec3 vPosition;

void main(){
    vPosition = position;  // 将顶点位置传递给片元着色器
    // ... 原有代码
}

2.2.2 片元着色器修改

片元着色器是波纹计算的核心,主要逻辑包括:

  1. 计算波纹半径r = uTime * uSpeed(当前波纹的前沿半径)
  2. 计算片元距离rDistance = distance(vPosition.xy, center)(片元到中心的距离)
  3. 判断波纹范围rDistance > r && rDistance < r + 2.0 * w
  4. 平滑过渡 :使用 smoothstep 函数实现波纹前后沿的渐隐效果
  5. 颜色混合 :使用 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 动画的目标值,影响扩散范围

五、扩展应用

  1. 多波纹叠加 :可以创建多个平面,使用不同的 uTime 初始值实现多层波纹
  2. 自定义形状 :修改 distance 函数,实现矩形、三角形等形状的波纹
  3. 多颜色渐变 :在 mix 中加入更多颜色,实现彩虹波纹效果
  4. 波纹衰减 :根据 rDistance 添加衰减因子,实现远处波纹变暗的效果
相关推荐
犀利的毛豆豆1 小时前
vue和react的响应式有什么区别?
前端
南雨北斗1 小时前
vue3 下拉菜单
前端
赤壁小虾1 小时前
【渲染流水线】[逐片元阶段]-[透明度测试]以UnityURP为例
java·前端·数据库
Bigger2 小时前
我给 Han Design 做了 6 套很值钱的中国风配色主题
前端·设计·视觉设计
幸福摩天轮2 小时前
function Calling工具系统怎么实现
前端
Jul1en_3 小时前
【Claude Code Compact】源码级别的学习上下文压缩
java·前端·学习·github·ai编程
windliang3 小时前
Claude Code 源码分析(五):一次 Tool 调用怎样通过权限检查
前端·人工智能·面试
fengci.3 小时前
Microweber CMS 未授权路径穿越漏洞(CVE-2026-65694)
android·开发语言·前端·学习·php
Deryck_德瑞克3 小时前
【Nginx】配置差异分析
服务器·前端·nginx