Three.js案例
管道

管道

本文将教大家如何使用 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 会依据该曲线生成管道。
以下代码完成了:

  1. 声明一个随机数参数 phi,以及曲线段数 l = 100
  2. 通过循环计算每个顶点的坐标。
  3. 把顶点位置存入 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;
}

读者也可自行替换不同的缓动或噪声函数,来获得别具一格的路径曲线。

创建管道并添加到场景

  1. 首先需要把离散的 vertices 转换为一条平滑曲线,SplineCurve3 可用于创建三维平滑样条曲线。
  2. 将曲线、切片数、管道的半径细分,以及旋转面数等参数一起传递给 TubeGeometry
  3. 为使其在场景中看起来更鲜艳,给物体加上 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 放在相机上,是为了让灯光的位置随相机而动,从而更好地照亮物体。如果需要进一步控制光源位置或类型,可考虑使用其他灯光如 DirectionalLightAmbientLight 等。

监听屏幕尺寸变化

为保证在浏览器调整大小时画面能自适应,需要监听 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 轴旋转,或缩放、平移等。