Skip to content

16. uv 动画实战:无限时空隧道

Published:

上节我们实现了一个隧道效果:

2025-03-23 11.33.25.gif

但是隧道是有尽头的,到了尽头就得从头开始:

999 (1).gif

如何实现没有尽头的无限隧道呢?

这就得用前面学过的 uv 动画了。

我们可以相机位置不变,只改变隧道的 texture.offset,用 uv 动画来实现这种效果。

创建项目:

npx create-vite infinite-tunnel

这次用 create-vite 创建 vite 项目。

image.png

进入项目,安装依赖:

npm install
npm install --save three
npm install --save-dev @types/three

然后改下 src/main.js

import './style.css';
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 width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
camera.position.set(300, 300, 300);
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、Camera、Renderer。

改下 style.css

body {
  margin: 0;
}

然后来写下 mesh.js

import * as THREE from 'three';

const geometry = new THREE.CylinderGeometry( 30, 50, 1000);

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

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

export default tunnel;

跑一下:

npm run dev

image.png

可以看到,它是一个实心的圆柱:

2025-04-04 07.48.28.gif

我们把它设置成空心:

image.png

32 是分段数,第六个参数是是否空心,设置为 true。

然后材质设置双面可见。

const geometry = new THREE.CylinderGeometry( 30, 50, 1000, 32, 32, true);

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

看下效果:

2025-04-04 07.52.52.gif

这样就可以了。

然后我们贴上颜色贴图:

storm.png

保存在 public 目录下。

image.png

const loader = new THREE.TextureLoader();
const texture = loader.load('./storm.png');
texture.colorSpace = THREE.SRGBColorSpace;

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

加载纹理图片,设置颜色空间,然后设置到材质的颜色贴图。

看下效果:

2025-04-04 08.00.50.gif

现在就有时空隧道的感觉了。

其实并不需要双面可见,因为相机只需要看到内部。

我们设置为反面可见,然后改下相机位置:

image.png

相机位置还是通过 OrbitControls 来调试:

image.png

controls.addEventListener('change', () => {
  console.log(camera.position);
});

2025-04-04 08.05.23.gif

调好之后设置到代码里:

camera.position.set(0.9, -520, 6.5);

现在一刷新就是在隧道内部了:

2025-04-04 08.06.23.gif

并且只设置了内部一个面的纹理:

2025-04-04 08.08.53.gif

设置竖直方向重复两次:

image.png

竖直方向重复,就要设置 wrapT。

image.png

这次我们不改变相机位置 camera.position 了,而是让纹理 offset 动。

用 uv 动画来实现穿梭隧道的效果。

image.png

mesh.material.map.offset.y += 0.01;

看下效果:

2025-04-04 08.15.47.gif

这样就有无限隧道的感觉了。

其实每次改变的数值还有另一种写法:

image.png

const clock = new THREE.Clock();
function render() {
    const delta = clock.getDelta();

    mesh.material.map.offset.y += delta * 0.5;
    mesh.rotation.y += delta * 0.5;

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

用 clock.getDelta 来拿到每次渲染的时间间隔,用它作为改变的数值。

这里顺便再让圆柱体旋转下。

2025-04-04 08.19.35.gif

不过现在还是太单调了,我们可以让它定期改变颜色。

首先,我们不把它设置为颜色贴图了,而是作为 alpha 贴图:

image.png

它是用贴图的颜色来控制不透明度的。

image.png

const material = new THREE.MeshBasicMaterial({ 
    transparent: true,
    alphaMap: texture,
    side: THREE.BackSide
});

这里也要改一下:

image.png

2025-04-04 08.38.49.gif

可以看到,现在颜色就消失了,只剩下了不透明度的改变。

然后我们改变下 color 的色相就好了

image.png

这里用 HSL 来改变颜色。

let H = 0;
H += 0.002;
if (H > 1) { H = 0; }

mesh.material.color.setHSL(H, 0.5, 0.5);

hsl 就是色相、饱和度、亮度,它是颜色的一种表示方法。

这个是色相环:

image.png

美术生应该很熟悉这种色相环,什么相差 60 度是邻近色、相差 180 度是互补色等等。

所以取值范围是 0 到 360 也就是共 360 度。

有 RGB 不就好了么?为啥还要搞个 HSL?

红绿蓝是计算机存储颜色的方式,它喜欢这种表示法,可以直接用来显示颜色。

但是对人来说,是不是还是明暗关系、色彩饱和度更容易理解一点?

所以选色的时候都是基于色相、饱和度、亮度这些东西,但存储的时候使用 RGB,最后屏幕显示颜色也是基于 RGB的。

我们用 HSL 标识颜色,然后改变色相的位置就好了。

看下效果:

2025-04-04 08.46.10.gif

现在就更有无限隧道的感觉了。

案例代码上传了小册仓库

总结

这节我们用 uv 动画实现了无限隧道的效果。

首先画了一个圆柱体,内部空心,设置反面的透明度贴图 alphaMap。

透明度贴图是用贴图的颜色来设置透明度的,需要同时设置材质的 transparent 为 true。

通过改变 texture.offset.y 来实现 uv 动画,用 clock.getDelata 来计算每次 offset.y 改变的数值。

然后通过 hsl 标识颜色,并且改变色相 h 的方式来实现了颜色的改变。

这算是一个比较综合的小实战,用到了 alphaMap + HSL + uv 动画。

评论