Three.js 纹理 #
纹理(Texture)是 3D 图形中用于增加物体表面细节的图像。通过纹理贴图,可以让简单的几何体呈现出丰富的外观效果。
纹理概述 #
text
┌─────────────────────────────────────────────────────────────┐
│ Three.js 纹理类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 基础纹理 │ │ 功能纹理 │ │ 高级纹理 │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ 漫反射贴图 │ │ 法线贴图 │ │ 环境贴图 │ │
│ │ 透明贴图 │ │ 凹凸贴图 │ │ 光照贴图 │ │
│ │ 自发光贴图 │ │ 粗糙度贴图 │ │ 位移贴图 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
加载纹理 #
TextureLoader #
javascript
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('texture.jpg');
const texture = textureLoader.load(
'texture.jpg',
(texture) => {
console.log('纹理加载完成');
},
(progress) => {
console.log('加载进度:', progress);
},
(error) => {
console.error('加载失败:', error);
}
);
加载管理器 #
javascript
const loadingManager = new THREE.LoadingManager();
loadingManager.onStart = (url, loaded, total) => {
console.log(`开始加载: ${url}`);
};
loadingManager.onProgress = (url, loaded, total) => {
console.log(`进度: ${loaded}/${total}`);
};
loadingManager.onLoad = () => {
console.log('所有资源加载完成');
};
loadingManager.onError = (url) => {
console.error(`加载错误: ${url}`);
};
const textureLoader = new THREE.TextureLoader(loadingManager);
const texture = textureLoader.load('texture.jpg');
基础纹理属性 #
纹理包裹 #
javascript
const texture = textureLoader.load('texture.jpg');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.wrapS = THREE.MirroredRepeatWrapping;
texture.wrapT = THREE.MirroredRepeatWrapping;
纹理重复 #
javascript
texture.repeat.set(2, 2);
texture.repeat.x = 4;
texture.repeat.y = 4;
纹理偏移 #
javascript
texture.offset.set(0.5, 0.5);
texture.offset.x = 0.5;
texture.offset.y = 0.5;
纹理旋转 #
javascript
texture.rotation = Math.PI / 4;
texture.center.set(0.5, 0.5);
texture.rotation = Math.PI / 4;
纹理过滤 #
javascript
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
各向异性过滤 #
javascript
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
纹理类型 #
漫反射贴图(Map) #
javascript
const texture = textureLoader.load('diffuse.jpg');
const material = new THREE.MeshStandardMaterial({
map: texture
});
法线贴图(Normal Map) #
javascript
const normalMap = textureLoader.load('normal.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
normalMap: normalMap,
normalScale: new THREE.Vector2(1, 1)
});
凹凸贴图(Bump Map) #
javascript
const bumpMap = textureLoader.load('bump.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
bumpMap: bumpMap,
bumpScale: 0.05
});
粗糙度贴图(Roughness Map) #
javascript
const roughnessMap = textureLoader.load('roughness.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
roughness: 1.0,
roughnessMap: roughnessMap
});
金属度贴图(Metalness Map) #
javascript
const metalnessMap = textureLoader.load('metalness.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
metalness: 1.0,
metalnessMap: metalnessMap
});
环境光遮蔽贴图(AO Map) #
javascript
const aoMap = textureLoader.load('ao.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
aoMap: aoMap,
aoMapIntensity: 1.0
});
geometry.setAttribute('uv2', geometry.getAttribute('uv'));
自发光贴图(Emissive Map) #
javascript
const emissiveMap = textureLoader.load('emissive.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
emissive: 0xffffff,
emissiveMap: emissiveMap,
emissiveIntensity: 1.0
});
透明贴图(Alpha Map) #
javascript
const alphaMap = textureLoader.load('alpha.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
alphaMap: alphaMap,
transparent: true
});
位移贴图(Displacement Map) #
javascript
const displacementMap = textureLoader.load('displacement.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
displacementMap: displacementMap,
displacementScale: 0.5,
displacementBias: 0
});
环境贴图 #
CubeTextureLoader #
javascript
const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMap = cubeTextureLoader.load([
'px.jpg',
'nx.jpg',
'py.jpg',
'ny.jpg',
'pz.jpg',
'nz.jpg'
]);
scene.background = envMap;
const material = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
metalness: 1.0,
roughness: 0.1,
envMap: envMap,
envMapIntensity: 1.0
});
HDR 环境贴图 #
javascript
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
const rgbeLoader = new RGBELoader();
rgbeLoader.load('environment.hdr', (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.background = texture;
scene.environment = texture;
});
PMREMGenerator #
javascript
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
const pmremGenerator = new THREE.PMREMGenerator(renderer);
const envMap = pmremGenerator.fromScene(new RoomEnvironment()).texture;
scene.environment = envMap;
程序化纹理 #
Canvas 纹理 #
javascript
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ff6b6b';
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = '#ffffff';
ctx.font = '48px Arial';
ctx.fillText('Hello', 50, 128);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.MeshBasicMaterial({ map: texture });
DataTexture #
javascript
const width = 256;
const height = 256;
const data = new Uint8Array(width * height * 4);
for (let i = 0; i < width * height; i++) {
const stride = i * 4;
data[stride] = Math.random() * 255;
data[stride + 1] = Math.random() * 255;
data[stride + 2] = Math.random() * 255;
data[stride + 3] = 255;
}
const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
texture.needsUpdate = true;
const material = new THREE.MeshBasicMaterial({ map: texture });
视频纹理 #
javascript
const video = document.createElement('video');
video.src = 'video.mp4';
video.loop = true;
video.muted = true;
video.play();
const texture = new THREE.VideoTexture(video);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
const material = new THREE.MeshBasicMaterial({ map: texture });
UV 映射 #
UV 坐标 #
text
UV 坐标系统:
(0,1) ─────────── (1,1)
│ │
│ │
│ │
│ │
(0,0) ─────────── (1,0)
U: 水平方向 (0-1)
V: 垂直方向 (0-1)
自定义 UV #
javascript
const geometry = new THREE.PlaneGeometry(2, 2);
const uvAttribute = geometry.getAttribute('uv');
uvAttribute.setXY(0, 0, 0);
uvAttribute.setXY(1, 1, 0);
uvAttribute.setXY(2, 0, 1);
uvAttribute.setXY(3, 1, 1);
uvAttribute.needsUpdate = true;
UV 变换 #
javascript
texture.matrixAutoUpdate = false;
texture.matrix.setUvTransform(
0.5,
0.5,
2,
2,
Math.PI / 4,
0,
0
);
纹理动画 #
纹理滚动 #
javascript
const texture = textureLoader.load('texture.jpg');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
function animate() {
requestAnimationFrame(animate);
texture.offset.x += 0.01;
texture.offset.y += 0.005;
renderer.render(scene, camera);
}
animate();
精灵动画 #
javascript
const texture = textureLoader.load('spritesheet.png');
texture.repeat.set(1 / 4, 1 / 4);
let currentFrame = 0;
const totalFrames = 16;
const framesPerRow = 4;
function animate() {
requestAnimationFrame(animate);
currentFrame = (currentFrame + 1) % totalFrames;
const col = currentFrame % framesPerRow;
const row = Math.floor(currentFrame / framesPerRow);
texture.offset.x = col / framesPerRow;
texture.offset.y = 1 - (row + 1) / (totalFrames / framesPerRow);
renderer.render(scene, camera);
}
animate();
纹理优化 #
纹理尺寸 #
javascript
// 推荐尺寸:2 的幂次方
const recommendedSizes = [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096];
// 非 2 的幂次方纹理会被自动调整
texture.generateMipmaps = false;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
纹理压缩 #
javascript
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
const ktx2Loader = new KTX2Loader(manager);
ktx2Loader.setTranscoderPath('jsm/libs/basis/');
ktx2Loader.detectSupport(renderer);
ktx2Loader.load('texture.ktx2', (texture) => {
material.map = texture;
});
纹理释放 #
javascript
texture.dispose();
纹理类型对比表 #
| 纹理类型 | 用途 | 影响属性 |
|---|---|---|
| map | 基础颜色 | color |
| normalMap | 表面细节 | 法线方向 |
| bumpMap | 凹凸效果 | 表面高度 |
| roughnessMap | 粗糙度 | roughness |
| metalnessMap | 金属度 | metalness |
| aoMap | 遮蔽 | 环境光 |
| emissiveMap | 自发光 | emissive |
| alphaMap | 透明度 | opacity |
| displacementMap | 位移 | 顶点位置 |
| envMap | 环境 | 反射 |
完整示例 #
javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(3, 3, 3);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
const textureLoader = new THREE.TextureLoader();
const diffuseMap = textureLoader.load('https://threejs.org/examples/textures/brick_diffuse.jpg');
const normalMap = textureLoader.load('https://threejs.org/examples/textures/brick_normal.jpg');
const roughnessMap = textureLoader.load('https://threejs.org/examples/textures/brick_roughness.jpg');
diffuseMap.wrapS = THREE.RepeatWrapping;
diffuseMap.wrapT = THREE.RepeatWrapping;
diffuseMap.repeat.set(2, 2);
normalMap.wrapS = THREE.RepeatWrapping;
normalMap.wrapT = THREE.RepeatWrapping;
normalMap.repeat.set(2, 2);
roughnessMap.wrapS = THREE.RepeatWrapping;
roughnessMap.wrapT = THREE.RepeatWrapping;
roughnessMap.repeat.set(2, 2);
const material = new THREE.MeshStandardMaterial({
map: diffuseMap,
normalMap: normalMap,
roughnessMap: roughnessMap,
roughness: 0.8,
metalness: 0.1
});
const geometry = new THREE.BoxGeometry(2, 2, 2);
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeMaterial = new THREE.MeshStandardMaterial({
color: 0x333333,
roughness: 0.9,
metalness: 0.1
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.position.y = -1;
scene.add(plane);
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
cube.rotation.y = elapsedTime * 0.5;
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
下一步 #
现在你已经掌握了纹理系统,接下来学习 着色器,创建自定义渲染效果!
最后更新:2026-03-28