着色器动画:用uTime实现动态效果

着色器动画是Three.js中实现高性能视觉效果的核心技术,通过GPU并行计算处理大量顶点或像素,能创造出流畅的动态效果。


一、核心原理:时间驱动的着色器动画

着色器动画的本质是通过时间变量驱动图形的顶点位置、颜色等属性随时间变化。在Three.js中,我们通过uniform变量将CPU的时间数据传递给GPU(着色器),让着色器根据时间计算动态效果。

关键概念:


二、实现步骤

1. 编写着色器代码示例

着色器分为顶点着色器(处理顶点位置)和片段着色器(处理像素颜色),两者通过varying变量传递数据。

顶点着色器:实现波浪起伏

const vertexShader = `
    // 声明变量
    varying vec2 vUv;          // 传递UV坐标给片段着色器
    varying float vElevation;  // 传递波浪高度给片段着色器
    uniform float uTime;       // 时间变量(从CPU传入)
    uniform float amplitude;   // 波浪振幅(可调节)
    
    void main() {
        vUv = uv; // 传递UV坐标
        
        // 核心:根据时间计算波浪高度
        // sin函数创建周期性波动,position.x控制水平方向波动,uTime控制动画速度
        float elevation = sin(position.x * 3.0 + uTime) * amplitude;
        vElevation = elevation; // 保存高度用于颜色计算
        
        // 计算新的顶点位置(y轴叠加波浪高度)
        vec3 newPosition = vec3(position.x, position.y + elevation, position.z);
        
        // 转换为最终屏幕坐标
        gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
    }
`;

片段着色器:随波浪变色

const fragmentShader = `
    precision mediump float; // 精度设置
    varying vec2 vUv;       // 接收顶点着色器的UV坐标
    varying float vElevation; // 接收波浪高度
    uniform float opacity;    // uniform传递的透明度参数
    
    void main() {
        // 基础颜色(基于UV坐标)
        vec3 color = vec3(vUv.x, vUv.y, opacity);
        
        // 根据波浪高度调整亮度(波峰更亮,波谷稍暗)
        // (vElevation是从顶点着色器传递过来的波浪高度值,范围大约在 -0.3 到 0.3 之间(因为 amplitude = 0.3))
        // 将波浪高度值向上平移 1.0 个单位:
        // 最小值:-0.3 + 1.0 = 0.7
        // 最大值:0.3 + 1.0 = 1.3
        // 这样就将原来的 [-0.3, 0.3] 范围映射到了 [0.7, 1.3],确保结果总是正数
        float brightness = vElevation + 1.0;
        vec3 finalColor = color * brightness;
        
        // 输出最终颜色
        gl_FragColor = vec4(finalColor, 1.0);
    }
`;

2. 创建着色器材质并应用到几何体

// 创建着色器材质
const shaderMaterial = new THREE.ShaderMaterial({
    vertexShader: vertexShader,   // 顶点着色器
    fragmentShader: fragmentShader, // 片段着色器
    uniforms: {
        uTime: { value: 0 },      // 时间变量(初始值0)
        opacity: { value: 0.5 },  // 透明度
        amplitude: { value: 0.3 } // 波浪振幅
    }
});

// 创建平面几何体
const geometry = new THREE.PlaneGeometry(10, 10, 100, 100);
const plane = new THREE.Mesh(geometry, shaderMaterial);
plane.rotation.x = -Math.PI / 2; // 平面旋转90度
scene.add(plane);

3. 动画循环:更新时间变量

通过requestAnimationFrame创建动画循环,不断更新uTime的值,驱动着色器动画:

function animate() {
    // 核心:每帧更新uTime(时间递增,控制动画速度)
    shaderMaterial.uniforms.uTime.value += 0.01;
    
    // 持续渲染
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}

三、关键知识点解析

  1. uTime的作用

    它是连接CPU和GPU的时间桥梁。CPU通过动画循环不断更新uTime的值(如每帧+0.01),GPU(着色器)根据这个值计算实时动画状态,实现周期性变化。

  2. 波浪数学原理

    sin(position.x * 3.0 + uTime)中:

  3. 性能优势

    着色器动画直接在GPU上运行,即使平面有100x100=10000个顶点,也能流畅运行(传统CPU修改顶点位置会很卡顿)。

    这种方法可以扩展到更复杂的效果,如水流、火焰、粒子动画等。


提示

通过时间变量驱动的着色器动画,是实现复杂动态效果的关键技术。掌握uTime的使用,可以让你的3D场景变得更加生动和富有表现力。