ThreeJS曲线动画:打造炫酷3D路径运动
(一) 效果呈现:
自定义路径运动
ThreeJS路径与动画部分技术点详细解析
以下是pathway.vue案例中路径创建与动画实现的核心技术点及具体属性:
一、路径创建与可视化
1. CatmullRom曲线定义
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. 路径点标记
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. 路径线条可视化
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. 时间控制机制
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. 沿路径移动
const point = path.getPointAt(t); // 获取路径上对应t位置的点
sphere.position.copy(point); // 更新球体位置
- getPointAt(t):根据归一化时间t获取曲线上的精确位置点
- position.copy():将获取的点位置应用到球体上,实现沿路径移动
3. 朝向调整
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. 场景整体旋转
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 - 直线
// 二维直线
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 - 二次贝塞尔曲线
// 二维二次贝塞尔曲线
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 - 三次贝塞尔曲线
// 二维三次贝塞尔曲线
const cubic2d = new THREE.CubicBezierCurve(new THREE.Vector2(0, 0), // 起点new THREE.Vector2(2.5, 10), // 控制点1new THREE.Vector2(7.5, -10), // 控制点2new THREE.Vector2(10, 0) // 终点
);// 三维三次贝塞尔曲线
const cubic3d = new THREE.CubicBezierCurve3(new THREE.Vector3(0, 0, 0), // 起点new THREE.Vector3(2.5, 10, 5), // 控制点1new THREE.Vector3(7.5, -10, -5), // 控制点2new THREE.Vector3(10, 0, 0) // 终点
);
- 主要参数:起点、两个控制点、终点
- 适用场景:需要更复杂曲率控制的曲线,如字体轮廓、复杂路径
4. EllipseCurve - 椭圆曲线
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 - 圆弧曲线
const circle = new THREE.CircleCurve(0, 0, // 中心坐标 (x, y)10, // 半径0, Math.PI // 起始角度和结束角度
);
- 主要参数:中心点坐标、半径、起始/结束角度
- 适用场景:创建圆弧段
- 注意:这是EllipseCurve的特例,专门用于圆弧
6. ArcCurve - 圆弧曲线(更灵活的版本)
const arc = new THREE.ArcCurve(0, 0, // 中心坐标 (x, y)10, // 半径0, Math.PI // 起始角度和结束角度
);
- 主要参数:中心点坐标、半径、起始/结束角度
- 适用场景:与CircleCurve类似,但实现方式略有不同
7. SplineCurve / SplineCurve3 - 样条曲线
// 二维样条曲线
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 - 三次样条曲线(案例中使用的类型)
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 - 管状几何体
const path = new THREE.CatmullRomCurve3([...点数组...]);
const tube = new THREE.TubeGeometry(path, // 路径对象100, // 分段数0.5, // 半径12, // 半径分段数false // 是否闭合
);
- 主要参数:路径对象、长度分段数、半径、半径分段数、是否闭合
- 适用场景:创建沿曲线延伸的管状结构,如管道、隧道等
10. ParametricCurve - 参数化曲线基类
// 自定义参数曲线
class CustomCurve extends THREE.ParametricCurve {constructor() {super();}getPoint(t, optionalTarget = new THREE.Vector3()) {// t从0到1const 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
<!-- 曲线轨道 -->
<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到1const 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>