Skip to content

15. 实战:隧道穿梭

Published:

学完按照规律生成各种几何体,我们可以生成各种形状的管道:

2025-03-22 18.15.52.gif

这节我们来做个实战:隧道穿梭

创建项目:

mkdir tube-travel
cd tube-travel
npm init -y

image.png

进入项目,安装下 ts 类型:

npm install --save-dev @types/three

创建 index.html 和 index.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <script type="importmap">
    {
        "imports": {
            "three": "https://esm.sh/three@0.174.0/build/three.module.js",
            "three/addons/": "https://esm.sh/three@0.174.0/examples/jsm/"
        }
    }
    </script>
    <script type="module" src="./index.js"></script>
</body>
</html>

index.js

import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';

const scene = new THREE.Scene();

scene.add(mesh);

const pointLight = new THREE.PointLight(0xffffff, 200);
pointLight.position.set(80, 80, 80);
scene.add(pointLight);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000);
camera.position.set(200, 200, 200);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)

function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

document.body.append(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

创建 Scene、Light、PerspectiveCamera、Renderer

然后创建 mesh.js

import * as THREE from 'three';

const path = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-100, 20, 90),
    new THREE.Vector3(-40, 80, 100),
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(60, -60, 0),
    new THREE.Vector3(100, -40, 80),
    new THREE.Vector3(150, 60, 60)
]);

const geometry = new THREE.TubeGeometry(path, 100, 5, 30);

const material = new THREE.MeshBasicMaterial({
    color: new THREE.Color('orange'),
    side: THREE.DoubleSide
});

const mesh = new THREE.Mesh(geometry, material);

export default mesh;

用三维样条曲线 CatmullRomCurve3 创建穿过 6 个点的一条曲线。

然后用 TubeGeometry 创建管道几何体。

设置管道分段数 100,圆分段数 30,半径 5

跑一下:

npx live-server

image.png

2025-03-23 10.47.25.gif

这样,一条弯曲的管道就完成了。

我们找个图片作为纹理贴图:

stone.png

加载一下:

image.png

const loader = new THREE.TextureLoader();
const texture = loader.load('./stone.png')
texture.wrapS = THREE.RepeatWrapping;
texture.repeat.x = 20;

const material = new THREE.MeshBasicMaterial({
    map: texture,
    side: THREE.DoubleSide
});

x 方向重复 20 次

看下效果:

2025-03-23 10.52.33.gif

颜色不大对,改下 colorSpace:

image.png

texture.colorSpace = THREE.SRGBColorSpace;

2025-03-23 10.55.43.gif

现在颜色对了,但是凹凸不平的颗粒感还不大够。

再加一下受环境光影响的纹理贴图:

image.png

2025-03-23 10.59.21.gif

是不是好多了?

然后我们接下来就要进入管道内部了。

怎么做呢?

其实你用 OrbitControls 看下就知道了。

2025-03-23 11.03.14.gif

OrbitControls 可以控制相机 camera 的位置和 lookAt,让相机进入管道内部,然后不断向前即可。

所以说,我们只要改变 camera 位置和 lookAt 就能做到穿梭管道的效果。

但管道是弯曲的,如何确定 camera 的位置和方向呢?

其实也简单,从曲线上取一堆密一点的点,然后 camera.position 设置当前点,lookAt 看向下一个点就可以了。

image.png

我们取 1000 个均匀的点。

export const tubePoints = path.getSpacedPoints(1000);

然后在渲染循环里不断更新 camera 的 position 和 lookAt

image.png

image.png

import mesh, { tubePoints } from './mesh.js';
let i = 0;
function render() {
    if(i< tubePoints.length - 1) {
        camera.position.copy(tubePoints[i]);
        camera.lookAt(tubePoints[i + 1]);
        i += 1;
    } else {
        i = 0;
    }

    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

copy 就是复制传入的点的坐标。

看下效果:

2025-03-23 11.15.53.gif

现在就是穿梭隧道的感觉了,并且还会转弯。

我们还可以把它改成键盘控制:

image.png

document.addEventListener('keydown', (e) => {
    if(e.code === 'ArrowDown') {
        i += 10;
    }
})

按向下的键才会动。

2025-03-23 11.33.25.gif

案例代码上传了小册仓库

总结

这节我们做了一个穿梭隧道的实战,核心目的还是练习曲线 Curve、生成几何体(比如 TubeGeometry)的 API。

我们用三维样条曲线画了穿过 n 个点的三维曲线,然后用 TubeGeometry 生成管道。

给它设置了纹理贴图,调整颜色空间,设置 map、aoMap 之后,真实感就很强了。

然后通过改变 camera 的 position 和 lookAt 实现了镜头穿梭隧道的感觉。

相机的位置是通过 curve.getSpacedPoints 取的一堆均匀的点。

经过这个实战之后,相信你对曲线、生成几何体、相机运动等就有更深的理解了。

评论