3D地球可视化教程 - 第3篇:地球动画与相机控制

难度 : ⭐⭐⭐☆☆ 中级
预计时间 : 40分钟
技术栈: Three.js + GSAP + 数学几何

📖 教程简介

在前两篇教程中,我们创建了基础地球渲染和夜晚纹理效果。现在,我们要让地球"动起来"!

本篇将深入学习动画系统的设计和实现,包括炫酷的地球入场动画、智能的相机控制,以及自动旋转功能。

本篇学习目标

  • 掌握GSAP动画库的使用
  • 理解贝塞尔曲线的数学原理
  • 实现复杂的相机动画序列
  • 学习自动旋转系统设计
  • 掌握动画时序控制

最终效果预览

完成本篇教程后,你将获得:

  • 地球入场动画
  • 弧形路径的平滑运动
  • 自动旋转功能
  • 动画时序控制

动画理论基础

为什么需要动画系统?

静态的3D场景虽然美观,但缺乏吸引力。动画系统的价值:

  • 吸引注意力 - 动态效果更容易抓住用户眼球
  • 提升用户体验 - 平滑的过渡比突变更舒适
  • 增强视觉层次 - 通过运动展示空间关系
  • 营造氛围 - 动画传达情感和风格

动画系统架构

复制代码
地球动画系统
├── GSAP时间线管理 → 控制动画序列和时序
├── 贝塞尔曲线路径 → 创建自然的运动轨迹
├── 相机动画控制 → 管理视角变化
└── 自动旋转系统 → 持续的背景动画

核心动画组件解析

1. GSAP时间线系统

GSAP (GreenSock Animation Platform) 是业界领先的动画库:

javascript 复制代码
// CameraAnimationController.js - GSAP时间线
const timeline = gsap.timeline({
  onStart: () => {
    this.isAnimating = true;
    if (onStart) onStart();
  },
  onComplete: () => {
    this.isAnimating = false; 
    if (onComplete) onComplete();
  },
});

// 旋转动画(立即开始)
timeline.to(animationData, {
  rotationProgress: 1,
  duration: duration / 1000,    // GSAP使用秒为单位
  ease: "power2.inOut",         // 缓动函数
  onUpdate: () => {
    // 四元数插值实现平滑旋转
    const currentQuaternion = new THREE.Quaternion();
    currentQuaternion.slerpQuaternions(
      startQuaternion, 
      targetQuaternion, 
      animationData.rotationProgress
    );
    earthGroup.rotation.setFromQuaternion(currentQuaternion);
  }
}, 0);

// 位移动画(延迟开始)
timeline.to(animationData, {
  positionProgress: 1,
  duration: duration / 1000,
  ease: "power2.inOut",
  onUpdate: () => {
    // 贝塞尔曲线插值实现弧形路径
    const currentPosition = this.calculateQuadraticBezier(
      startPosition, midPoint, targetPosition, 
      animationData.positionProgress
    );
    earthGroup.position.copy(currentPosition);
  }
}, positionDelay / 1000);  // 延迟500ms开始

GSAP的优势

  • 高性能 - 优化的渲染循环
  • 易用性 - 直观的API设计
  • 功能丰富 - 支持复杂的动画序列
  • 兼容性好 - 跨浏览器支持

贝塞尔曲线数学原理

二次贝塞尔曲线

地球的入场动画使用二次贝塞尔曲线创建弧形路径:

javascript 复制代码
/**
 * 二次贝塞尔曲线公式
 * B(t) = (1-t)²P₀ + 2(1-t)tP₁ + t²P₂
 * 
 * 其中:
 * - P₀: 起始点
 * - P₁: 控制点(决定弧形方向)
 * - P₂: 结束点
 * - t: 参数 [0, 1]
 */
calculateQuadraticBezier(p0, p1, p2, t) {
  const oneMinusT = 1 - t;
  const tSquared = t * t;
  const oneMinusTSquared = oneMinusT * oneMinusT;

  // 分别计算 x, y, z 坐标
  result.x = oneMinusTSquared * p0.x + 2 * oneMinusT * t * p1.x + tSquared * p2.x;
  result.y = oneMinusTSquared * p0.y + 2 * oneMinusT * t * p1.y + tSquared * p2.y;
  result.z = oneMinusTSquared * p0.z + 2 * oneMinusT * t * p1.z + tSquared * p2.z;

  return result;
}

路径控制参数

javascript 复制代码
// 动画路径配置
const pathConfig = {
  startPosition: new THREE.Vector3(3.8, 0, 17.4),    // 起始位置(远距离)
  targetPosition: new THREE.Vector3(-0.8, -5, -7),   // 目标位置(近距离)
  arcHeight: -20,                                     // 弧形高度(负值向下弯曲)
  midPointPosition: 0.2,                              // 中间点位置比例
  positionDelay: 500,                                 // 位移延迟(毫秒)
};

参数作用解释

  • arcHeight: 控制弧形的弯曲程度,负值创建向下的弧形
  • midPointPosition: 控制弧形的对称性,0.2表示偏向起点
  • positionDelay: 让旋转先开始,位移后开始,创建层次感

自动旋转系统

旋转控制逻辑

javascript 复制代码
// EarthMouseController.js - 自动旋转
update() {
  if (!this.isMouseDown) {
    // 🔄 自动旋转功能 ✅ 第3篇教程
    if (this.enableAutoRotate) {
      this.elementsGroup.rotation.y += this.autoRotateSpeed;
    }
  }
}

旋转参数配置

javascript 复制代码
// 旋转系统参数
const rotationConfig = {
  autoRotateSpeed: 0.001,     // 自动旋转速度 (弧度/帧)
  enableAutoRotate: true,     // 是否启用自动旋转
  rotationSpeed: 0.005,       // 手动旋转速度
  dampingFactor: 0.95,        // 阻尼系数 (第7篇教程)
};

速度对比

  • 0.001 弧度/帧3.6度/秒1圈/100秒
  • 这个速度既能看到旋转效果,又不会太快导致眩晕

动画序列设计

分层动画时序

我们的地球动画采用分层时序设计:

javascript 复制代码
// Scene.js - 动画触发时序
const animationSequence = {
  // 阶段1: 地球入场 (0% - 90%)
  earthAnimation: {
    rotation: "立即开始",      // 旋转动画
    position: "延迟500ms开始", // 位移动画
  },
  
  // 阶段2: 圆环效果 (90%时触发)
  ringAnimation: {
    trigger: "地球动画90%时",
    effect: "圆环从0缩放到1",
  },
  
  // 阶段3: 飞线效果 (90%时触发)  
  flightLineAnimation: {
    trigger: "地球动画90%时",
    effect: "飞线依次绘制",
  },
  
  // 阶段4: UI界面 (95%时触发)
  uiAnimation: {
    trigger: "地球动画95%时", 
    effect: "界面元素滑入",
  }
};

时序控制实现

javascript 复制代码
// Scene.js - 动画进度监控
onUpdate: (progress, easedProgress, data) => {
  // 🪐 90%时触发圆环动画
  if (!this.ringAnimationTriggered && progress >= 0.9) {
    this.ringAnimationTriggered = true;
    this.startRingScaleAnimation();
  }
  
  // ⚡ 90%时触发飞线动画
  if (!this.flightLineAnimationTriggered && progress >= 0.9) {
    this.flightLineAnimationTriggered = true;
    this.startFlightLineAnimations();
  }
  
  // 🎨 95%时触发UI动画
  if (!this.uiAnimationTriggered && progress >= 0.95) {
    this.uiAnimationTriggered = true;
    this.dispatchEvent("uiAnimationTrigger");
  }
}

相机动画系统

相机控制器设计

javascript 复制代码
class CameraAnimationController {
  constructor(camera, controls) {
    this.camera = camera;
    this.controls = controls;
    
    // 预设的相机位置
    this.presetPositions = {
      close: new THREE.Vector3(2.7, 19.4, 12.6),    // 近景位置
      far: new THREE.Vector3(6.6, 23.5, 33.8),      // 远景位置
    };
  }
  
  // 相机拉远动画
  zoomOutWithPresetPositions(options = {}) {
    const { duration = 3000, onComplete } = options;
    
    gsap.to(this.camera.position, {
      x: this.presetPositions.far.x,
      y: this.presetPositions.far.y,
      z: this.presetPositions.far.z,
      duration: duration / 1000,
      ease: "power2.out",
      onComplete: onComplete
    });
  }
}

核心技术实现

1. 四元数旋转插值

javascript 复制代码
// 使用四元数实现平滑旋转
const startQuaternion = new THREE.Quaternion().setFromEuler(startRotation);
const targetQuaternion = new THREE.Quaternion().setFromEuler(targetRotation);
const currentQuaternion = new THREE.Quaternion();

// SLERP (球面线性插值) 
currentQuaternion.slerpQuaternions(
  startQuaternion, 
  targetQuaternion, 
  progress
);

// 应用到地球组
earthGroup.rotation.setFromQuaternion(currentQuaternion);

四元数的优势

  • 避免万向锁问题
  • 插值结果更平滑
  • 计算效率更高
  • 旋转路径最短

2. 弧形路径算法

javascript 复制代码
// 计算弧形中间点
const midPoint = new THREE.Vector3();
midPoint.lerpVectors(startPosition, targetPosition, midPointPosition);
midPoint.y += arcHeight;  // 向上/向下偏移创建弧形

// 贝塞尔曲线插值
const currentPosition = calculateQuadraticBezier(
  startPosition,  // P₀: 起点
  midPoint,       // P₁: 控制点
  targetPosition, // P₂: 终点
  progress        // t: [0, 1]
);

3. 动画时序管理

javascript 复制代码
// 复杂动画的时序控制
const timeline = gsap.timeline();

// 同时开始的动画
timeline.to(target1, { /* 动画1 */ }, 0);
timeline.to(target2, { /* 动画2 */ }, 0);

// 延迟开始的动画
timeline.to(target3, { /* 动画3 */ }, 0.5);  // 延迟0.5秒

// 动画完成后的动画
timeline.to(target4, { /* 动画4 */ }, "+=0.2"); // 上个动画完成后0.2秒

动画参数详解

地球动画配置

javascript 复制代码
// Scene.js - 地球动画参数
this.earthAnimationParams = {
  arcHeight: -20,                    // 弧形高度偏移量
  midPointPosition: 0.2,             // 中间点位置比例
  positionDelay: 500,                // 位移动画延迟时间(ms)
  duration: 2500,                    // 动画持续时间(ms)
  
  // 其他动画触发时机
  ringAnimationTriggerProgress: 0.9,      // 90%时触发圆环
  flightLineAnimationTriggerProgress: 0.9, // 90%时触发飞线
  uiAnimationTriggerProgress: 0.95,       // 95%时触发UI
};

参数调节技巧

  • arcHeight < 0: 向下弯曲的弧形,更有重力感
  • midPointPosition < 0.5: 偏向起点的弧形,加速感更强
  • positionDelay > 0: 先旋转后位移,层次感更好

自动旋转配置

javascript 复制代码
// EarthMouseController.js - 旋转参数
const rotationParams = {
  enableAutoRotate: true,       // 启用自动旋转
  autoRotateSpeed: 0.001,       // 旋转速度 (弧度/帧)
  rotationSpeed: 0.005,         // 手动旋转速度
  
  // 旋转限制
  enableVerticalLimit: true,    // 启用垂直限制
  maxVerticalAngle: Math.PI / 3, // 最大角度 (60°)
  minVerticalAngle: -Math.PI / 3, // 最小角度 (-60°)
};

视觉效果分析

入场动画效果

  1. 阶段1 (0-500ms): 地球开始旋转

    • 从倾斜角度旋转到正常角度
    • 四元数插值保证平滑过渡
  2. 阶段2 (500-2500ms): 地球位移

    • 从远处弧形飞入到目标位置
    • 贝塞尔曲线创造自然的飞行轨迹
  3. 阶段3 (2250ms): 触发后续效果

    • 90%进度时启动圆环和飞线
    • 95%进度时启动UI动画

自动旋转效果

javascript 复制代码
// 持续的背景动画
if (this.enableAutoRotate) {
  this.elementsGroup.rotation.y += this.autoRotateSpeed;
}

视觉特点

  • 持续旋转 - 保持画面活力
  • 适中速度 - 既明显又不眩晕
  • 可中断 - 用户交互时自动停止

实现技术详解

1. 事件驱动架构

javascript 复制代码
// Scene.js - 事件系统
class Scene {
  // 事件监听器管理
  addEventListener(event, callback) {
    if (!this.eventListeners.has(event)) {
      this.eventListeners.set(event, []);
    }
    this.eventListeners.get(event).push(callback);
  }
  
  // 事件分发
  dispatchEvent(event, data) {
    if (this.eventListeners.has(event)) {
      this.eventListeners.get(event).forEach(callback => {
        callback(data);
      });
    }
  }
}

2. 动画状态管理

javascript 复制代码
// 动画状态标记
this.ringAnimationTriggered = false;
this.flightLineAnimationTriggered = false; 
this.uiAnimationTriggered = false;

// 防止重复触发
if (!this.ringAnimationTriggered && progress >= 0.9) {
  this.ringAnimationTriggered = true;
  this.startRingScaleAnimation();
}

3. 缓动函数选择

javascript 复制代码
// 不同阶段使用不同的缓动函数
const easingFunctions = {
  "power2.inOut": "平滑的加速和减速",
  "power2.out": "快速开始,慢慢结束",
  "elastic.out": "弹性效果",
  "back.out": "回弹效果",
};

动画调节技巧

获得最佳动画效果

自然的入场动画
javascript 复制代码
{
  duration: 2500,           // 不急不慢的时长
  arcHeight: -20,           // 向下弧形,符合重力
  midPointPosition: 0.2,    // 偏向起点,加速感
  positionDelay: 500,       // 先转后移,层次感
}
舒适的自动旋转
javascript 复制代码
{
  autoRotateSpeed: 0.001,   // 1圈约100秒
  enableAutoRotate: true,   // 默认开启
}

实践操作

查看动画效果

刷新页面 (http://localhost:5174),你会看到:

  1. ** 入场动画** (前2.5秒)

    • 地球从右上方弧形飞入
    • 同时进行旋转调整
    • 平滑过渡到最终位置
  2. ** 自动旋转** (动画完成后)

    • 地球开始缓慢自动旋转
    • 鼠标拖拽时暂停自动旋转
    • 释放鼠标后恢复自动旋转

动画参数实验

你可以尝试修改参数观察效果:

javascript 复制代码
// 在Scene.js中修改这些值
this.earthAnimationParams = {
  arcHeight: -30,        // 尝试 -10, -20, -30
  midPointPosition: 0.3, // 尝试 0.1, 0.2, 0.5
  duration: 3000,        // 尝试 2000, 3000, 4000
  positionDelay: 800,    // 尝试 300, 500, 800
};

技术深度分析

1. 动画性能优化

javascript 复制代码
// 使用requestAnimationFrame确保流畅度
animate() {
  this.animationId = requestAnimationFrame(this.animate.bind(this));
  
  // 只在需要时更新
  if (this.lights) this.lights.update();
  if (this.earthNight) this.earthNight.update(lightPos);
  
  this.controls.update();
  this.renderer.render(this.scene, this.camera);
}

2. 内存管理

javascript 复制代码
// 动画完成后的清理
onComplete: () => {
  this.isAnimating = false;
  
  // 重置动画状态
  this.ringAnimationTriggered = false;
  this.flightLineAnimationTriggered = false;
  this.uiAnimationTriggered = false;
}

3. 数学优化

javascript 复制代码
// 预计算常用值
const oneMinusT = 1 - t;
const tSquared = t * t;
const oneMinusTSquared = oneMinusT * oneMinusT;

// 避免重复计算
result.x = oneMinusTSquared * p0.x + 2 * oneMinusT * t * p1.x + tSquared * p2.x;

本篇总结

** 核心知识点**

  1. GSAP动画库

    • 时间线管理
    • 缓动函数
    • 动画序列控制
  2. 数学几何

    • 贝塞尔曲线原理
    • 四元数旋转插值
    • 弧形路径计算
  3. 动画设计

    • 分层时序控制
    • 视觉层次营造
    • 用户体验优化
  4. 系统架构

    • 事件驱动设计
    • 状态管理
    • 性能优化

🎨 视觉效果成果

  • 震撼入场 - 地球从天而降的视觉冲击
  • 自然运动 - 符合物理直觉的弧形路径
  • 持续活力 - 自动旋转保持画面生动
  • 智能交互 - 用户操作时自动暂停

🚀 下一篇预告

在第4篇教程中,我们将学习:

☁️ 云层系统与纹理动画

  • 动态纹理UV动画
  • 透明度渐变效果
  • 多层渲染技术
  • 大气散射模拟

预期效果

  • ☁️ 缓慢飘动的云层
  • 🌫️ 真实的大气效果
  • 🎨 丰富的视觉层次

🎉 恭喜完成第3篇教程!

在下一篇中,我们将添加云层效果,让地球更加真实。


本文是《3D地球可视化教程系列》的第3篇。下一篇:《云层系统与纹理动画》

相关推荐
叫我少年3 小时前
Vue3 集成 VueRouter
vue.js
披萨心肠3 小时前
Vue单向数据流下双向绑定父子组件数据
vue.js
小小前端_我自坚强3 小时前
2025WebAssembly详解
前端·设计模式·前端框架
用户1412501665273 小时前
一文搞懂 Vue 3 核心原理:从响应式到编译的深度解析
前端
正在走向自律3 小时前
RSA加密从原理到实践:Java后端与Vue前端全栈案例解析
java·前端·vue.js·密钥管理·rsa加密·密钥对·aes+rsa
我是天龙_绍3 小时前
Lodash 库在前端开发中的重要地位与实用函数实现
前端
LuckySusu3 小时前
【vue篇】Vue 数组响应式揭秘:如何让 push 也能更新视图?
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 性能优化神器:keep-alive 深度解析与实战指南
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 核心机制揭秘:为什么组件的 data 必须是函数?
前端·vue.js