管道
本文将教大家如何使用 Three.js 创建并渲染一个在空间中旋转的“管道”形状。一个带有若干转弯、缓动上升的“螺旋体”在画面中缓慢旋转。
代码的核心功能如下:
- 设置渲染器、场景和相机,并将其添加到网页上。
- 通过随机数和简单的三角函数,生成一条用于创建管道的三维路径(使用
SplineCurve3
进行平滑插值)。 - 使用
TubeGeometry
来根据路径生成立体管道,然后搭配材质进行渲染。 - 在动画循环中对物体进行旋转,并实现窗口自适应调整。
搭建基础场景
在 script.js
文件中,先完成下面三个核心元素的初始化:渲染器、场景、相机。
// 1. 获取浏览器窗口大小
var width = window.innerWidth;
var height = window.innerHeight;
// 2. 创建渲染器并设置抗锯齿
var renderer = new THREE.WebGLRenderer({ antialias: true });
// 3. 创建场景
var scene = new THREE.Scene();
// 4. 创建透视相机
var camera = new THREE.PerspectiveCamera(75, width / height, 1, 10000);
// 设置渲染器背景色为白色
renderer.setClearColor(new THREE.Color(0xffffff), 1);
// 设置渲染区域尺寸并添加到页面
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
// 将相机加入场景
scene.add(camera);
// 设置相机位置并朝向
camera.position.z = -100;
camera.lookAt(new THREE.Vector3());
PerspectiveCamera
的四个参数分别是 视野角度(FOV)、宽高比、近裁剪面、远裁剪面。renderer.setClearColor
用于设置渲染器的背景色,这里指定为白色 (0xffffff
)。
生成曲线顶点
在创建 TubeGeometry
之前,需要有一条定义好的三维曲线。TubeGeometry 会依据该曲线生成管道。
以下代码完成了:
- 声明一个随机数参数
phi
,以及曲线段数l = 100
。 - 通过循环计算每个顶点的坐标。
- 把顶点位置存入
vertices
数组。
var l = 100;
var phi = Math.floor(Math.random() * 5) + 2; // 2 ~ 6 之间的随机整数
var radius = 50; // 基础半径
var vertices = [];
for (var i = 0; i < l; i++) {
var pct = i / (l - 1); // 当前段在 0 ~ 1 之间的比例
var theta = Math.PI * 2 * pct * phi; // 螺旋扭转的角度
// 让横向截面随 pct 进行缩放,制造一些锥形或渐变感觉
var taper = (Math.sin(pct) * radius) / 4;
// x, y, z 坐标计算
var x = taper * Math.cos(theta);
var y = radius * EasingQuadraticIn(seat(pct)) * 2 - radius;
var z = taper * Math.sin(theta);
// 推入数组
vertices.push(new THREE.Vector3(x, y, z));
}
关于缓动函数 zao
下面两个自定义函数是为了让形状在沿着 y 轴方向分布时有一个特定的插值效果:
function EasingQuadraticIn(k) {
// 传统的二次缓动函数:y = k^2
return k * k;
}
function seat(t) {
// 自定义三次曲线缓动后再映射到 0 ~ 1
return (Math.pow(2 * t - 1, 3) + 1) / 2;
}
读者也可自行替换不同的缓动或噪声函数,来获得别具一格的路径曲线。
创建管道并添加到场景
- 首先需要把离散的
vertices
转换为一条平滑曲线,SplineCurve3
可用于创建三维平滑样条曲线。 - 将曲线、切片数、管道的半径细分,以及旋转面数等参数一起传递给
TubeGeometry
。 - 为使其在场景中看起来更鲜艳,给物体加上
MeshPhongMaterial
以及光源PointLight
。
// 添加一个点光源
var light = new THREE.PointLight(0xffffff, 2, 200);
// 使用 SplineCurve3 将顶点数组转换为三维样条曲线
var spline = new THREE.SplineCurve3(vertices);
// 使用 TubeGeometry 生成管道,250 段细分,管道厚度为 4
var geometry = new THREE.TubeGeometry(spline, 250, 4, 32, false);
// 创建材质
var material = new THREE.MeshPhongMaterial({
color: "rgb(255, 150, 150)",
side: THREE.DoubleSide,
shininess: 2500,
emissive: new THREE.Color("rgb(255, 0, 0)"),
metal: false,
});
// 将几何体与材质组合成 Mesh
var mesh = new THREE.Mesh(geometry, material);
// 放大整体模型
mesh.scale.multiplyScalar(2);
// 将模型、相机、光源加入到场景
scene.add(mesh);
scene.add(camera);
camera.add(light);
这里把
light
放在相机上,是为了让灯光的位置随相机而动,从而更好地照亮物体。如果需要进一步控制光源位置或类型,可考虑使用其他灯光如DirectionalLight
、AmbientLight
等。
监听屏幕尺寸变化
为保证在浏览器调整大小时画面能自适应,需要监听 resize
事件并实时修改渲染器与相机的对应参数。
window.addEventListener("resize", resize, false);
function resize() {
width = window.innerWidth;
height = window.innerHeight;
// 更新渲染器
renderer.setSize(width, height);
// 更新相机
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
camera.aspect
用于设置相机的宽高比,务必在修改完后调用 updateProjectionMatrix()
来生效。
动画循环
最后要实现动画循环,让画面持续渲染并让管道不断旋转。这里借助浏览器原生的 requestAnimationFrame
方法在每帧都执行渲染。
loop();
function loop() {
// 请求下一帧
requestAnimationFrame(loop);
// 每一帧物体绕 y 轴微量旋转
mesh.rotation.y -= 1 / 30;
// 渲染场景
renderer.render(scene, camera);
}
如果想修改旋转速度,可以更改
mesh.rotation.y -= 1 / 30;
中的数值。也可以添加更多动画,如沿 x 或 z 轴旋转,或缩放、平移等。