ThreeJS曲线动画:打造炫酷3D路径运动

(一) 效果呈现:

自定义路径运动

ThreeJS路径与动画部分技术点详细解析

以下是pathway.vue案例中路径创建与动画实现的核心技术点及具体属性:

一、路径创建与可视化

1. CatmullRom曲线定义
javascript 复制代码
const path = new THREE.CatmullRomCurve3([
    new THREE.Vector3(0, -4, 0),
    new THREE.Vector3(2, -2, 0),
    new THREE.Vector3(0, 0, 2),
    // ...更多点坐标...
    new THREE.Vector3(0,-4, 0),
]);
  • CatmullRomCurve3:创建平滑的三次样条曲线,通过一系列控制点生成连续的平滑路径
  • Vector3点数组:定义曲线路径的关键点,曲线会通过这些点并在点之间平滑过渡
  • 闭合路径:首尾点坐标相同(均为(0,-4,0)),确保路径形成一个闭合循环
2. 路径点标记
javascript 复制代码
const pointMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const pointGeometry = new THREE.SphereGeometry(0.1, 6, 6);
const points = path.getPoints(path.points.length - 1);
points.forEach(point => {
    const pointMesh = new THREE.Mesh(pointGeometry, pointMaterial);
    pointMesh.position.copy(point);
    scene.add(pointMesh);
});
  • getPoints():获取曲线上的点,此处获取原始控制点
  • SphereGeometry :用于创建标记点的小球几何体,参数含义:
    • radius: 0.1 - 小球半径
    • widthSegments: 6 - 水平分段数
    • heightSegments: 6 - 垂直分段数
  • position.copy():将路径点坐标复制到标记点的位置
3. 路径线条可视化
javascript 复制代码
const points1 = path.getPoints(50);
const geometryLine = new THREE.BufferGeometry().setFromPoints(points1);
const materialLine = new THREE.LineBasicMaterial({ color: 0x000fff });
const line = new THREE.Line(geometryLine, materialLine);
line.computeLineDistances();
scene.add(line);
  • getPoints(50):获取曲线上均匀分布的50个点,点数越多线条越平滑
  • BufferGeometry.setFromPoints():根据点数组创建线条几何体
  • LineBasicMaterial:基础线条材质,设置为蓝色(0x000fff)
  • computeLineDistances():计算线条中各点之间的距离,优化线条渲染效果

二、动画实现

1. 时间控制机制
javascript 复制代码
let time = 0;
function render() {
    const looptime = 20; // 完整循环所需时间,单位秒
    const t = (time % looptime) / looptime; // 归一化时间参数t,范围0到1
    // ...
    time += 0.01; // 增加时间参数
}
  • looptime:定义动画完整循环的时间(20秒)
  • 归一化时间t :通过(time % looptime) / looptime计算,确保t始终在[0,1]范围内
  • time增量:每次渲染增加0.01,控制动画速度
2. 沿路径移动
javascript 复制代码
const point = path.getPointAt(t); // 获取路径上对应t位置的点
sphere.position.copy(point); // 更新球体位置
  • getPointAt(t):根据归一化时间t获取曲线上的精确位置点
  • position.copy():将获取的点位置应用到球体上,实现沿路径移动
3. 朝向调整
javascript 复制代码
const tangent = path.getTangentAt(t).normalize();
const axis = new THREE.Vector3(0, 1, 0); // 假设Y轴为上方向
const radians = Math.acos(axis.dot(tangent)); // 计算旋转角度
const cross = new THREE.Vector3().crossVectors(axis, tangent).normalize(); // 计算旋转轴
sphere.quaternion.setFromAxisAngle(cross, radians); // 设置球体旋转
  • getTangentAt(t):获取路径上特定位置的切线方向
  • normalize():将向量归一化为单位向量
  • dot():计算两个向量的点积,用于确定夹角
  • acos():计算反余弦值,得到旋转角度(弧度)
  • crossVectors():计算两个向量的叉积,确定旋转轴
  • quaternion.setFromAxisAngle():通过轴角方式设置物体的四元数旋转
4. 场景整体旋转
javascript 复制代码
function animate() {
    requestAnimationFrame(animate);
    // 旋转整个场景
    scene.rotation.y -= 0.001;
    
    // 更新轨道控制器
    controls.update();
    // 调用渲染函数
    render()
    // 渲染场景
    renderer.render(scene, camera);
}
animate();
  • requestAnimationFrame():创建浏览器优化的动画循环
  • scene.rotation.y:使整个场景绕Y轴缓慢旋转,增强视觉效果
  • controls.update():更新轨道控制器状态,确保交互正常
  • renderer.render():执行场景渲染

这些技术点共同实现了平滑的路径定义、可视化和沿路径的动画效果,包括精确的位置控制和朝向调整,使球体能够自然地沿着预设路径运动。

(二)除了CatmullRomCurve3绘制曲线以外,还有以下几个属性可以绘制曲线:

ThreeJS中常用的曲线类型及属性

除了CatmullRomCurve3外,ThreeJS还提供了多种曲线类型用于不同场景的曲线创建。以下是常用的曲线类型及其主要属性:

1. LineCurve / LineCurve3 - 直线

javascript 复制代码
// 二维直线
const line2d = new THREE.LineCurve(new THREE.Vector2(0, 0), new THREE.Vector2(10, 10));

// 三维直线
const line3d = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(10, 10, 10));
  • 主要参数:起点和终点的Vector2/Vector3对象
  • 适用场景:创建简单的直线段、坐标轴等

2. QuadraticBezierCurve / QuadraticBezierCurve3 - 二次贝塞尔曲线

javascript 复制代码
// 二维二次贝塞尔曲线
const bezier2d = new THREE.QuadraticBezierCurve(
    new THREE.Vector2(0, 0),     // 起点
    new THREE.Vector2(5, 10),    // 控制点
    new THREE.Vector2(10, 0)     // 终点
);

// 三维二次贝塞尔曲线
const bezier3d = new THREE.QuadraticBezierCurve3(
    new THREE.Vector3(0, 0, 0),  // 起点
    new THREE.Vector3(5, 10, 5), // 控制点
    new THREE.Vector3(10, 0, 0)  // 终点
);
  • 主要参数:起点、单个控制点、终点
  • 适用场景:创建平滑的曲线过渡,如路径规划、动画曲线

3. CubicBezierCurve / CubicBezierCurve3 - 三次贝塞尔曲线

javascript 复制代码
// 二维三次贝塞尔曲线
const cubic2d = new THREE.CubicBezierCurve(
    new THREE.Vector2(0, 0),     // 起点
    new THREE.Vector2(2.5, 10),  // 控制点1
    new THREE.Vector2(7.5, -10), // 控制点2
    new THREE.Vector2(10, 0)     // 终点
);

// 三维三次贝塞尔曲线
const cubic3d = new THREE.CubicBezierCurve3(
    new THREE.Vector3(0, 0, 0),  // 起点
    new THREE.Vector3(2.5, 10, 5), // 控制点1
    new THREE.Vector3(7.5, -10, -5), // 控制点2
    new THREE.Vector3(10, 0, 0)  // 终点
);
  • 主要参数:起点、两个控制点、终点
  • 适用场景:需要更复杂曲率控制的曲线,如字体轮廓、复杂路径

4. EllipseCurve - 椭圆曲线

javascript 复制代码
const ellipse = new THREE.EllipseCurve(
    0, 0,          // 中心坐标 (x, y)
    10, 5,         // 半径 (xRadius, yRadius)
    0, Math.PI * 2, // 起始角度和结束角度
    false,         // 是否顺时针
    0              // 旋转角度
);
  • 主要参数:中心点坐标、x/y半径、起始/结束角度、方向、旋转角度
  • 适用场景:创建椭圆、圆形、圆弧等
  • 特别说明:当xRadius和yRadius相等时,就是正圆

5. CircleCurve - 圆弧曲线

javascript 复制代码
const circle = new THREE.CircleCurve(
    0, 0,      // 中心坐标 (x, y)
    10,        // 半径
    0, Math.PI // 起始角度和结束角度
);
  • 主要参数:中心点坐标、半径、起始/结束角度
  • 适用场景:创建圆弧段
  • 注意:这是EllipseCurve的特例,专门用于圆弧

6. ArcCurve - 圆弧曲线(更灵活的版本)

javascript 复制代码
const arc = new THREE.ArcCurve(
    0, 0,    // 中心坐标 (x, y)
    10,      // 半径
    0, Math.PI // 起始角度和结束角度
);
  • 主要参数:中心点坐标、半径、起始/结束角度
  • 适用场景:与CircleCurve类似,但实现方式略有不同

7. SplineCurve / SplineCurve3 - 样条曲线

javascript 复制代码
// 二维样条曲线
const spline2d = new THREE.SplineCurve([
    new THREE.Vector2(0, 0),
    new THREE.Vector2(2, 3),
    new THREE.Vector2(4, 0),
    new THREE.Vector2(6, 3),
    new THREE.Vector2(8, 0)
]);

// 三维样条曲线
const spline3d = new THREE.SplineCurve3([
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(2, 3, 1),
    new THREE.Vector3(4, 0, 2),
    new THREE.Vector3(6, 3, 1),
    new THREE.Vector3(8, 0, 0)
]);
  • 主要参数:Vector2/Vector3点数组
  • 适用场景:通过多个点创建平滑曲线
  • 注意:这是CatmullRomCurve的早期版本,功能类似

8. CatmullRomCurve3 - 三次样条曲线(案例中使用的类型)

javascript 复制代码
const curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(1, 1, 1),
    new THREE.Vector3(2, 0, 2),
    new THREE.Vector3(3, 1, 3)
], false, 'catmullrom', 0.5);
  • 主要参数
    • 点数组:定义曲线形状的控制点
    • closed:是否闭合
    • type:插值类型('centripetal', 'chordal', 'catmullrom')
    • tension:张力参数,控制曲线的曲率(0-1)
  • 适用场景:需要通过多个点创建平滑、可控的3D路径

9. TubeGeometry - 管状几何体

javascript 复制代码
const path = new THREE.CatmullRomCurve3([...点数组...]);
const tube = new THREE.TubeGeometry(
    path,  // 路径对象
    100,   // 分段数
    0.5,   // 半径
    12,    // 半径分段数
    false  // 是否闭合
);
  • 主要参数:路径对象、长度分段数、半径、半径分段数、是否闭合
  • 适用场景:创建沿曲线延伸的管状结构,如管道、隧道等

10. ParametricCurve - 参数化曲线基类

javascript 复制代码
// 自定义参数曲线
class CustomCurve extends THREE.ParametricCurve {
    constructor() {
        super();
    }
    getPoint(t, optionalTarget = new THREE.Vector3()) {
        // t从0到1
        const x = t * 10;
        const y = Math.sin(t * Math.PI * 2) * 5;
        const z = Math.cos(t * Math.PI * 2) * 5;
        return optionalTarget.set(x, y, z);
    }
}
const customCurve = new CustomCurve();
  • 主要方法:需要重写getPoint(t)方法来定义曲线形状
  • 适用场景:创建自定义数学函数定义的曲线

这些曲线类型可以根据具体需求选择使用,它们都提供了getPoints()、getPointAt()、getTangentAt()等方法来获取曲线上的点和切线,便于创建路径动画和几何形状。

(三)案例源码 vite + threeJs

js 复制代码
<!-- 曲线轨道 -->
<template>
    <div id="pathway_container"></div>
</template>

<script setup>
import { reactive, toRefs, ref, onMounted, nextTick } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
let scene, camera, renderer, controls, domWidth, domHeight;
function initScene() {
    // 场景
    scene = new THREE.Scene();
    // 相机
    camera = new THREE.PerspectiveCamera(75, domWidth / domHeight, 0.1, 1000);

    // 绘制一个圆球
    const geometry = new THREE.SphereGeometry(0.5, 28, 28);// 半径,宽度分段,高度分段
    // const material = new THREE.MeshBasicMaterial({ color: 0xf1a000 });
    // 设置球体为线框模式
    const material = new THREE.MeshBasicMaterial({ color: 0xf1a000, wireframe: true });
    const sphere = new THREE.Mesh(geometry, material);
    scene.add(sphere);
    // 定义路径 - CatmullRomCurve3 曲线--绘制一个围绕着Y轴螺旋向上的路径
    const path = new THREE.CatmullRomCurve3([
        new THREE.Vector3(0, -4, 0),
        new THREE.Vector3(2, -2, 0),
        new THREE.Vector3(0, 0, 2),
        new THREE.Vector3(-2, 2, 0),
        new THREE.Vector3(0, 4, -2),
        new THREE.Vector3(2, 6, 0),
        new THREE.Vector3(0, 8, 2),
        new THREE.Vector3(-2, 10, 0),
        new THREE.Vector3(0, 12, -2),
        new THREE.Vector3(0, 11, -2),
        new THREE.Vector3(-2, 8, 0),
        new THREE.Vector3(0, 6, 2),
        new THREE.Vector3(2, 4, 0),
        new THREE.Vector3(0,2, -2),
        new THREE.Vector3(-2, 0, 0),
        new THREE.Vector3(0, -2, 2),
        new THREE.Vector3(1, -4, 0),
        new THREE.Vector3(0,-4, 0),

    ]);
    // 给path的这几个点添加小球进行标记
    const pointMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
    const pointGeometry = new THREE.SphereGeometry(0.1, 6, 6);
    const points = path.getPoints(path.points.length - 1);
    // 给每个点添加一个小球
    points.forEach(point => {
        const pointMesh = new THREE.Mesh(pointGeometry, pointMaterial);
        pointMesh.position.copy(point);
        scene.add(pointMesh);
    });

    // 创建路径的几何体用于显示
    const points1 = path.getPoints(50);
    // 创建线条几何体和材质
    const geometryLine = new THREE.BufferGeometry().setFromPoints(points1);
    // 创建线条材质 
    const materialLine = new THREE.LineBasicMaterial({ color: 0x000fff });
    // 创建线条对象
    const line = new THREE.Line(geometryLine, materialLine);
    // 线条更平滑
    line.computeLineDistances();
    // 将线条添加到场景中
    scene.add(line);

    // 坐标轴辅助线
    const axesHelper = new THREE.AxesHelper(8);
    scene.add(axesHelper);
       // 相机位置调整:向右、向上、向后移动
    camera.position.set(5, 25, 25);
    // 设置相机看向原点上方一点,这样原点就会在视图下方
    camera.lookAt(0, 5, 0);
    renderer = new THREE.WebGLRenderer();
    // 设置渲染器大小
    renderer.setSize(domWidth, domHeight);
    document.getElementById('pathway_container').appendChild(renderer.domElement);

    // 轨道控制器
    controls = new OrbitControls(camera, renderer.domElement);
    // 启用阻尼效果
    controls.enableDamping = true;
    // 轨道控制器阻尼系数
    // 轨道控制器阻尼系数是一个0到1之间的小数,用于控制轨道控制器的惯性效果。
    // 当值为0时,轨道控制器没有惯性效果,当值为1时,轨道控制器有最大的惯性效果。
    controls.dampingFactor = 0.05;
    // 让geometry沿着路径移动
    let time = 0;
    function render() {

        // 计算沿路径的位置
        const looptime = 20; // 完整循环所需时间,单位秒
        const t = (time % looptime) / looptime; // 归一化时间参数t,范围0到1
        const point = path.getPointAt(t); // 获取路径上对应t位置的点
        sphere.position.copy(point); // 更新球体位置

        // 计算切线方向以调整球体朝向
        const tangent = path.getTangentAt(t).normalize();
        const axis = new THREE.Vector3(0, 1, 0); // 假设Y轴为上方向
        const radians = Math.acos(axis.dot(tangent)); // 计算旋转角度
        const cross = new THREE.Vector3().crossVectors(axis, tangent).normalize(); // 计算旋转轴

        sphere.quaternion.setFromAxisAngle(cross, radians); // 设置球体旋转

        time += 0.01; // 增加时间参数   
    }
    // 渲染循环
    function animate() {
        requestAnimationFrame(animate);
        // 旋转整个场景
        scene.rotation.y -= 0.001;

        // 更新轨道控制器
        controls.update();
        // 调用渲染函数
        render()
        // 渲染场景
        renderer.render(scene, camera);

    }
    animate();
}
onMounted(() => {
    nextTick(() => {
        domWidth = document.getElementById('pathway_container').clientWidth;
        domHeight = document.getElementById('pathway_container').clientHeight;
        initScene();
    })

})
</script>
<style scoped>
#pathway_container {
    width: 100%;
    height: 100vh;
    background-color: #f0f0f0;
}
</style>
相关推荐
西洼工作室2 小时前
项目环境变量配置全攻略
前端
阿珊和她的猫3 小时前
Webpack 优化:构建速度与包体积的双重提升
前端·webpack·node.js
阿珊和她的猫3 小时前
Webpack 打包体积优化:让应用更轻量、更高效
前端·webpack·状态模式
im_AMBER3 小时前
Vite + React 项目启动深度踩坑指南
前端·学习·react.js·前端框架
Hammer Ray3 小时前
前端开发基础概念(React)
前端·react.js·前端框架
Sunlightʊə5 小时前
2.登录页测试用例
运维·服务器·前端·功能测试·单元测试
Code Crafter6 小时前
ES6-ES14 新特性速查
前端·ecmascript·es6
Lhuu(重开版6 小时前
CSS从0到1
前端·css·tensorflow
不说别的就是很菜7 小时前
【前端面试】HTML篇
前端·html
前端一小卒7 小时前
生产环境Sourcemap策略:从苹果事故看前端构建安全架构设计
前端·javascript