Skip to content

11. uv 坐标和 uv 动画

Published:

上节学了材质的颜色、颜色贴图(或者叫纹理贴图),这节继续来学下纹理相关的 uv 坐标。

uv 坐标

什么是 uv 坐标呢?

我们知道,画一个平面几何体 PlaneGeometry 需要 6 个顶点索引,2 个三角形:

image.png

那如果现在有一张图:

image.png

把它作为材质的纹理贴图加到这个平面上。

那怎么加呢?

是不是应该这样:

image.png

每个顶点对应图片的一个角。

我们给它个坐标系:

image.png

这样每个顶点渲染图片哪个位置的颜色,就直接看这个 0,0 到 1,1 的坐标就可以了。

这就是 uv 坐标。

为啥 material.map 叫颜色贴图呢?

其实就是根据 uv 坐标来取纹理图片中的对应位置的颜色。

各种几何体都有默认的 uv 坐标,也就是如何来贴图。

这个 uv 坐标也是可以自定义的。

我们来试一下:

mkdir texture-uv
cd texture-uv
npm init -y

image.png

创建项目,安装 ts 的类型包:

npm install --save-dev @types/three

创建 index.html

<!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 axesHelper = new THREE.AxesHelper(200);
scene.add(axesHelper);

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);

引入 Three.js,创建 Scene、Camera、Renderer。

相机设置在 0、0、200 的位置,看向 0、0、0。

然后写下 mesh.js

import * as THREE from 'three';

const geometry = new THREE.PlaneGeometry(200, 100);

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

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

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

console.log(mesh);

export default mesh;

创建一个 PlaneGeometry,用 MeshBasicMaterial 材质,加载纹理贴图,然后设置下颜色空间

看下效果:

npx live-server

image.png

image.png

可以看到,纹理图片贴到了网格模型上。

那它是如何贴的呢?

看下 uv 就知道了:

image.png

可以看到,geometry.attributes.uv 是和顶点一一对应的 uv 坐标。

也就是 4 个顶点分别对应了 4 个 uv 坐标。

那这些顶点构成的三角形也就知道取什么位置的图片颜色来渲染了。

我们改下试试:

image.png

const uvs = new Float32Array([
    0, 0.5,
    0.5, 0.5,
    0, 0,
    0.5, 0
]);

geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2);

BufferAttribute 设置每 2 个为一个坐标。

我们把 uv 坐标限定在了 0,0 到 0.5,0.5 的区域。

image.png

可以看到,这样就只取了一部分纹理图片来贴图。

当你有类似需求的时候,就可以自定义 uv 坐标。

uv 动画

纹理对象 Texture 有个 offset 属性,可以让纹理贴图在 x、y 方向做一些偏移。

这相当于改变了 uv 坐标,所以这种改变 texture.offset 的动画叫做 uv 动画。

比如这样:

2025-03-19 19.11.52.gif

不断改变纹理贴图的 offset,让它动起来。

我们来试一下:

创建 mesh2.js

import * as THREE from 'three';

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

const geometry = new THREE.SphereGeometry(50);

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

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

export default mesh;

和之前的地球差不多,这次是木星的纹理贴图:

muxing.jpg

引入然后去掉 AxesHelper:

image.png

看下效果:

2025-03-19 19.17.48.gif

然后我们让 texture.offset.y 动一下:

image.png

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

uv 坐标的范围是 0 到 1,每帧改变 0.01 已经不少了。

2025-03-19 19.25.11.gif

可以看到,纹理贴图确实动了,但是后面就没有贴图了。

我们让它重复一下:

image.png

这里是竖直方向重复,所以设置 wrapT

texture.wrapT = THREE.RepeatWrapping;

看下效果:

2025-03-28 15.00.35.gif

这样就是一个纹理贴图无限滚动的效果了,也就是 uv 动画。

案例代码上传了小册仓库

总结

这节我们学了 uv 坐标和 uv 动画。

纹理贴图如何映射到网格模型上是通过 uv 坐标决定的,也就是 geometry.attributes.uv,它和顶点一一对应。

我们可以修改不同顶点的 uv 坐标来实现纹理贴图的裁剪、旋转等效果。

texture.offset 可以让纹理贴图偏移一段距离,相当于改变了 uv 坐标,所以修改 texture.offset 的动画也叫做 uv 动画。

texture.offset 的偏移需要结合 texture.wrapT、textrue.wrapS 来设置,实现纹理贴图无限滚动的效果。

各种几何体都有默认的顶点 uv 坐标,当你需要对纹理贴图做一些处理的时候,可以自定义 uv 坐标。

评论