高级纹理技术
深入探讨 Three.js 的高级纹理应用,包括法线贴图、环境贴图、程序化纹理、视差贴图等技术。
概述
高级纹理技术能够显著提升 3D 场景的视觉质量,实现更真实的光照、反射和细节效果。本文档将深入讲解各类高级纹理技术的原理和应用。
法线贴图详解
法线贴图原理
法线贴图通过修改表面法线方向来模拟表面细节,无需增加几何体复杂度。每个像素存储一个法线向量,用于光照计算时产生凹凸效果。
plaintext
切线空间法线贴图颜色含义:
- RGB(128, 128, 255) = 法线朝上(平坦表面)
- RGB 值偏红:法线偏向 +X 方向
- RGB 值偏绿:法线偏向 +Y 方向javascript
import * as THREE from 'three';
const textureLoader = new THREE.TextureLoader();
// 加载法线贴图
const normalMap = textureLoader.load('normal.jpg');
const material = new THREE.MeshStandardMaterial({
color: 0x888888,
normalMap: normalMap,
normalScale: new THREE.Vector2(1, 1)
});
// 调整法线强度
material.normalScale.set(2, 2); // 增强效果
material.normalScale.set(0.5, 0.5); // 减弱效果法线空间
| 空间类型 | 常量 | 特点 | 适用场景 |
|---|---|---|---|
| 切线空间 | TangentSpaceNormalMap | 相对于表面,蓝色基调 | 动态物体、可复用贴图 |
| 对象空间 | ObjectSpaceNormalMap | 相对于模型,彩色 | 静态物体、特定造型 |
javascript
// 切线空间法线贴图(默认)
// 蓝色表示平坦表面
// RGB = (0.5, 0.5, 1) 表示向上法线
material.normalMapType = THREE.TangentSpaceNormalMap;
// 对象空间法线贴图
// 使用模型局部坐标系
material.normalMapType = THREE.ObjectSpaceNormalMap;生成法线贴图
从高度图实时生成法线贴图。
javascript
import * as THREE from 'three';
// 从高度图生成法线贴图
function generateNormalMapFromHeight(renderer, heightTexture, strength = 1.0) {
const size = heightTexture.image.width;
// 创建渲染目标
const renderTarget = new THREE.WebGLRenderTarget(size, size);
// 创建法线生成材质
const normalMaterial = new THREE.ShaderMaterial({
uniforms: {
heightMap: { value: heightTexture },
resolution: { value: new THREE.Vector2(1.0 / size, 1.0 / size) },
strength: { value: strength }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D heightMap;
uniform vec2 resolution;
uniform float strength;
varying vec2 vUv;
void main() {
// 采样周围像素
float center = texture2D(heightMap, vUv).r;
float left = texture2D(heightMap, vUv - vec2(resolution.x, 0.0)).r;
float right = texture2D(heightMap, vUv + vec2(resolution.x, 0.0)).r;
float top = texture2D(heightMap, vUv + vec2(0.0, resolution.y)).r;
float bottom = texture2D(heightMap, vUv - vec2(0.0, resolution.y)).r;
// 计算梯度
vec3 normal = normalize(vec3(
(left - right) * strength,
(bottom - top) * strength,
1.0
));
// 转换到 [0,1] 范围
normal = normal * 0.5 + 0.5;
gl_FragColor = vec4(normal, 1.0);
}
`
});
// 渲染到纹理
const quad = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
normalMaterial
);
const tempScene = new THREE.Scene();
const tempCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
tempScene.add(quad);
renderer.setRenderTarget(renderTarget);
renderer.render(tempScene, tempCamera);
renderer.setRenderTarget(null);
// 获取生成的纹理
const normalTexture = renderTarget.texture;
// 清理
normalMaterial.dispose();
quad.geometry.dispose();
return normalTexture;
}环境贴图
立方体贴图
立方体贴图由 6 张图片组成,分别对应立方体的 6 个面。
javascript
// 加载立方体贴图
const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMap = cubeTextureLoader.load([
'px.jpg', 'nx.jpg', // 正/负 X
'py.jpg', 'ny.jpg', // 正/负 Y
'pz.jpg', 'nz.jpg' // 正/负 Z
]);
// 作为环境贴图(用于反射)
scene.environment = envMap;
// 作为背景
scene.background = envMap;
// 材质使用
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 1.0,
roughness: 0.0,
envMap: envMap,
envMapIntensity: 1.0
});等距矩形投影贴图
使用单张 HDR 图像作为环境贴图,更方便制作和获取。
javascript
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
// 加载 HDR 环境贴图
const rgbeLoader = new RGBELoader();
rgbeLoader.load('environment.hdr', (texture) => {
// 设置为等距矩形投影
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
scene.background = texture;
});PMREMGenerator
PMREM(Prefiltered Mipmaped Radiance Environment Map)生成器,用于预处理环境贴图,优化实时反射效果和性能。
javascript
import * as THREE from 'three';
const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
// 从等距矩形贴图生成
rgbeLoader.load('environment.hdr', (texture) => {
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
scene.environment = envMap;
// 清理原始纹理
texture.dispose();
});
// 从场景生成环境贴图
const envMap = pmremGenerator.fromScene(scene).texture;
scene.environment = envMap;
// 记得在不需要时释放
pmremGenerator.dispose();自定义环境贴图
创建简单的程序化环境贴图。
javascript
// 创建渐变天空环境贴图
function createGradientEnvMap() {
const size = 256;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, 0, size);
gradient.addColorStop(0, '#87CEEB'); // 天空蓝
gradient.addColorStop(1, '#4169E1'); // 皇家蓝
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size, size);
const texture = new THREE.CanvasTexture(canvas);
texture.mapping = THREE.EquirectangularReflectionMapping;
return texture;
}
scene.environment = createGradientEnvMap();RoomEnvironment
使用 RoomEnvironment 快速创建室内照明环境。
javascript
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
const pmremGenerator = new THREE.PMREMGenerator(renderer);
const environment = new RoomEnvironment();
const envMap = pmremGenerator.fromScene(environment).texture;
scene.environment = envMap;
environment.dispose();视差贴图
视差贴图通过偏移纹理坐标来模拟深度效果,比普通法线贴图更加逼真。
基础视差映射
javascript
import * as THREE from 'three';
// 视差贴图材质
const parallaxMaterial = new THREE.ShaderMaterial({
uniforms: {
diffuseMap: { value: textureLoader.load('diffuse.jpg') },
heightMap: { value: textureLoader.load('height.jpg') },
normalMap: { value: textureLoader.load('normal.jpg') },
parallaxScale: { value: 0.05 },
bumpScale: { value: 1.0 }
},
vertexShader: `
varying vec2 vUv;
varying vec3 vViewDir;
varying vec3 vNormal;
varying vec3 vTangent;
varying vec3 vBitangent;
void main() {
vUv = uv;
vNormal = normalize(normalMatrix * normal);
vTangent = normalize(normalMatrix * tangent.xyz);
vBitangent = cross(vNormal, vTangent) * tangent.w;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewDir = normalize(-mvPosition.xyz);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform sampler2D diffuseMap;
uniform sampler2D heightMap;
uniform sampler2D normalMap;
uniform float parallaxScale;
uniform float bumpScale;
varying vec2 vUv;
varying vec3 vViewDir;
varying vec3 vNormal;
varying vec3 vTangent;
varying vec3 vBitangent;
void main() {
// 转换到切线空间
vec3 viewDir = normalize(vec3(
dot(vViewDir, vTangent),
dot(vViewDir, vBitangent),
dot(vViewDir, vNormal)
));
// 简单视差偏移
float height = texture2D(heightMap, vUv).r;
vec2 offset = viewDir.xy * (height * parallaxScale);
vec2 newUv = vUv - offset;
vec3 color = texture2D(diffuseMap, newUv).rgb;
gl_FragColor = vec4(color, 1.0);
}
`
});陡峭视差映射(Steep Parallax Mapping)
更精确的视差效果,通过多层采样实现。
javascript
const steepParallaxShader = {
uniforms: {
diffuseMap: { value: null },
heightMap: { value: null },
normalMap: { value: null },
parallaxScale: { value: 0.1 },
minLayers: { value: 8 },
maxLayers: { value: 32 }
},
vertexShader: `
varying vec2 vUv;
varying vec3 vViewDir;
varying vec3 vNormal;
varying vec3 vTangent;
varying vec3 vBitangent;
void main() {
vUv = uv;
vNormal = normalize(normalMatrix * normal);
vTangent = normalize(normalMatrix * tangent.xyz);
vBitangent = cross(vNormal, vTangent) * tangent.w;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewDir = normalize(-mvPosition.xyz);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform sampler2D diffuseMap;
uniform sampler2D heightMap;
uniform sampler2D normalMap;
uniform float parallaxScale;
uniform float minLayers;
uniform float maxLayers;
varying vec2 vUv;
varying vec3 vViewDir;
varying vec3 vNormal;
varying vec3 vTangent;
varying vec3 vBitangent;
vec2 steepParallaxMapping(vec2 uv, vec3 viewDir) {
// 根据视角动态调整采样层数
float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));
float layerDepth = 1.0 / numLayers;
float currentLayerDepth = 0.0;
// 计算每层的偏移量
vec2 deltaTexCoords = parallaxScale * viewDir.xy / viewDir.z / numLayers;
vec2 currentTexCoords = uv;
// 采样直到找到合适的层
float currentDepthMapValue = texture2D(heightMap, currentTexCoords).r;
while (currentLayerDepth < currentDepthMapValue) {
currentTexCoords -= deltaTexCoords;
currentDepthMapValue = texture2D(heightMap, currentTexCoords).r;
currentLayerDepth += layerDepth;
}
// 可以添加视差遮蔽(Parallax Occlusion Mapping)进行插值优化
return currentTexCoords;
}
void main() {
// 转换到切线空间
vec3 viewDir = normalize(vec3(
dot(vViewDir, vTangent),
dot(vViewDir, vBitangent),
dot(vViewDir, vNormal)
));
vec2 parallaxUv = steepParallaxMapping(vUv, viewDir);
vec3 color = texture2D(diffuseMap, parallaxUv).rgb;
gl_FragColor = vec4(color, 1.0);
}
`
};程序化纹理
Simplex 噪声纹理
javascript
import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
// 创建噪声纹理
function createNoiseTexture(size, scale) {
const simplex = new SimplexNoise();
const data = new Uint8Array(size * size * 4);
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const i = (y * size + x) * 4;
const noise = simplex.noise(x * scale, y * scale);
const value = (noise + 1) / 2 * 255;
data[i] = value;
data[i + 1] = value;
data[i + 2] = value;
data[i + 3] = 255;
}
}
const texture = new THREE.DataTexture(data, size, size);
texture.needsUpdate = true;
return texture;
}
const noiseTexture = createNoiseTexture(512, 0.02);Perlin 噪声
javascript
function createPerlinNoiseTexture(size) {
const data = new Uint8Array(size * size * 4);
// Perlin 噪声辅助函数
function fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
function lerp(a, b, t) {
return a + t * (b - a);
}
function grad(hash, x, y) {
const h = hash & 3;
const u = h < 2 ? x : y;
const v = h < 2 ? y : x;
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
}
// 排列表
const perm = [];
for (let i = 0; i < 256; i++) {
perm[i] = Math.floor(Math.random() * 256);
}
for (let i = 0; i < 256; i++) {
perm[256 + i] = perm[i];
}
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const i = (y * size + x) * 4;
const nx = x / size * 8;
const ny = y / size * 8;
const xi = Math.floor(nx) & 255;
const yi = Math.floor(ny) & 255;
const xf = nx - Math.floor(nx);
const yf = ny - Math.floor(ny);
const u = fade(xf);
const v = fade(yf);
const aa = perm[perm[xi] + yi];
const ab = perm[perm[xi] + yi + 1];
const ba = perm[perm[xi + 1] + yi];
const bb = perm[perm[xi + 1] + yi + 1];
const x1 = lerp(grad(aa, xf, yf), grad(ba, xf - 1, yf), u);
const x2 = lerp(grad(ab, xf, yf - 1), grad(bb, xf - 1, yf - 1), u);
const noise = (lerp(x1, x2, v) + 1) / 2;
const value = noise * 255;
data[i] = value;
data[i + 1] = value;
data[i + 2] = value;
data[i + 3] = 255;
}
}
const texture = new THREE.DataTexture(data, size, size);
texture.needsUpdate = true;
return texture;
}分形噪声(FBM)
叠加多层噪声实现更自然的效果。
javascript
function createFractalNoiseTexture(size, octaves, persistence, lacunarity) {
const simplex = new SimplexNoise();
const data = new Uint8Array(size * size * 4);
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const i = (y * size + x) * 4;
let noise = 0;
let amplitude = 1;
let frequency = 0.01;
let maxValue = 0;
for (let o = 0; o < octaves; o++) {
noise += simplex.noise(x * frequency, y * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence; // 振幅衰减
frequency *= lacunarity; // 频率增加
}
const value = ((noise / maxValue) + 1) / 2 * 255;
data[i] = value;
data[i + 1] = value;
data[i + 2] = value;
data[i + 3] = 255;
}
}
const texture = new THREE.DataTexture(data, size, size);
texture.needsUpdate = true;
return texture;
}
// 典型参数组合
const terrainNoise = createFractalNoiseTexture(512, 6, 0.5, 2.0);沃罗诺伊纹理
javascript
function createVoronoiTexture(size, numPoints) {
const data = new Uint8Array(size * size * 4);
// 生成随机点
const points = [];
for (let i = 0; i < numPoints; i++) {
points.push({
x: Math.random() * size,
y: Math.random() * size,
color: [Math.random() * 255, Math.random() * 255, Math.random() * 255]
});
}
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const i = (y * size + x) * 4;
// 找最近的点
let minDist = Infinity;
let closestPoint = points[0];
for (const point of points) {
const dx = x - point.x;
const dy = y - point.y;
const dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
closestPoint = point;
}
}
data[i] = closestPoint.color[0];
data[i + 1] = closestPoint.color[1];
data[i + 2] = closestPoint.color[2];
data[i + 3] = 255;
}
}
const texture = new THREE.DataTexture(data, size, size);
texture.needsUpdate = true;
return texture;
}
const voronoiTexture = createVoronoiTexture(256, 20);纹理混合
多纹理混合
使用顶点颜色或蒙版混合多种纹理。
javascript
// 使用顶点颜色混合纹理
const blendMaterial = new THREE.ShaderMaterial({
uniforms: {
texture1: { value: textureLoader.load('grass.jpg') },
texture2: { value: textureLoader.load('dirt.jpg') },
texture3: { value: textureLoader.load('rock.jpg') }
},
vertexShader: `
attribute vec3 blendWeights;
varying vec3 vBlendWeights;
varying vec2 vUv;
void main() {
vBlendWeights = blendWeights;
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D texture3;
varying vec3 vBlendWeights;
varying vec2 vUv;
void main() {
vec4 color1 = texture2D(texture1, vUv);
vec4 color2 = texture2D(texture2, vUv);
vec4 color3 = texture2D(texture3, vUv);
// 归一化混合权重
float totalWeight = vBlendWeights.x + vBlendWeights.y + vBlendWeights.z;
vec4 color = (color1 * vBlendWeights.x +
color2 * vBlendWeights.y +
color3 * vBlendWeights.z) / totalWeight;
gl_FragColor = color;
}
`
});高度图纹理混合(Splat Map)
javascript
const splatMaterial = new THREE.ShaderMaterial({
uniforms: {
splatMap: { value: splatTexture },
texture1: { value: texture1 },
texture2: { value: texture2 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D splatMap;
uniform sampler2D texture1;
uniform sampler2D texture2;
varying vec2 vUv;
void main() {
float splat = texture2D(splatMap, vUv).r;
vec4 color1 = texture2D(texture1, vUv);
vec4 color2 = texture2D(texture2, vUv);
gl_FragColor = mix(color1, color2, splat);
}
`
});视频纹理
将 HTML5 视频作为纹理应用到 3D 对象上。
javascript
// 创建视频元素
const video = document.createElement('video');
video.src = 'video.mp4';
video.loop = true;
video.muted = true; // 必须静音才能自动播放
video.playsInline = true;
// 创建视频纹理
const videoTexture = new THREE.VideoTexture(video);
videoTexture.minFilter = THREE.LinearFilter;
videoTexture.magFilter = THREE.LinearFilter;
videoTexture.colorSpace = THREE.SRGBColorSpace;
// 应用到材质
const material = new THREE.MeshBasicMaterial({
map: videoTexture
});
// 播放控制
async function playVideo() {
try {
await video.play();
} catch (err) {
console.error('视频播放失败:', err);
}
}
function pauseVideo() {
video.pause();
}
// 响应式尺寸
video.addEventListener('loadedmetadata', () => {
videoTexture.image = video;
videoTexture.needsUpdate = true;
});视频纹理完整示例
javascript
function createVideoMesh(url, width, height) {
const video = document.createElement('video');
video.src = url;
video.crossOrigin = 'anonymous';
video.loop = true;
video.muted = true;
video.playsInline = true;
const texture = new THREE.VideoTexture(video);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
const geometry = new THREE.PlaneGeometry(width, height);
const material = new THREE.MeshBasicMaterial({ map: texture });
const mesh = new THREE.Mesh(geometry, material);
// 添加控制方法
mesh.play = () => video.play();
mesh.pause = () => video.pause();
mesh.setVolume = (vol) => { video.muted = false; video.volume = vol; };
// 自动播放
video.play().catch(console.warn);
return mesh;
}3D 纹理
3D 纹理(体积纹理)用于体积渲染、医学成像等场景。
javascript
// 创建 3D 纹理
const size = 32;
const data = new Uint8Array(size * size * size * 4);
for (let z = 0; z < size; z++) {
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const i = (z * size * size + y * size + x) * 4;
// 创建球体形状
const dx = x - size / 2;
const dy = y - size / 2;
const dz = z - size / 2;
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
const value = distance < size / 2 ? 255 : 0;
data[i] = value;
data[i + 1] = value;
data[i + 2] = value;
data[i + 3] = value;
}
}
}
const texture3D = new THREE.Data3DTexture(data, size, size, size);
texture3D.format = THREE.RGBAFormat;
texture3D.minFilter = THREE.LinearFilter;
texture3D.magFilter = THREE.LinearFilter;
texture3D.needsUpdate = true;体积渲染着色器示例
javascript
const volumeMaterial = new THREE.ShaderMaterial({
uniforms: {
volumeTexture: { value: texture3D },
transferFunction: { value: null },
steps: { value: 128 },
alphaScale: { value: 1.0 }
},
vertexShader: `
varying vec3 vPosition;
void main() {
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler3D volumeTexture;
uniform sampler2D transferFunction;
uniform float steps;
uniform float alphaScale;
varying vec3 vPosition;
void main() {
vec3 rayDir = normalize(vPosition - cameraPosition);
vec3 rayPos = vPosition;
vec4 color = vec4(0.0);
for (int i = 0; i < 128; i++) {
if (i >= int(steps)) break;
vec4 sampleColor = texture(volumeTexture, rayPos * 0.5 + 0.5);
sampleColor.a *= alphaScale;
color += sampleColor * (1.0 - color.a) * (1.0 / steps);
if (color.a > 0.99) break;
rayPos += rayDir * (1.0 / steps);
}
gl_FragColor = color;
}
`,
transparent: true
});性能优化
纹理内存管理
javascript
// 监控纹理内存
function logTextureMemory() {
const info = renderer.info;
console.log(`纹理数量: ${info.memory.textures}`);
console.log(`几何体数量: ${info.memory.geometries}`);
}
// 正确释放纹理
function disposeTexture(texture) {
if (texture.image && texture.image instanceof HTMLVideoElement) {
texture.image.pause();
texture.image.src = '';
}
texture.dispose();
}
// 批量释放材质中的纹理
function disposeMaterialTextures(material) {
const textureProperties = [
'map', 'normalMap', 'roughnessMap', 'metalnessMap',
'aoMap', 'emissiveMap', 'alphaMap', 'bumpMap',
'displacementMap', 'envMap'
];
textureProperties.forEach(prop => {
if (material[prop]) {
material[prop].dispose();
}
});
}纹理流式加载
javascript
// 使用 ImageBitmap 实现异步加载
async function loadTextureAsync(url) {
const response = await fetch(url);
const blob = await response.blob();
const bitmap = await createImageBitmap(blob);
const texture = new THREE.Texture(bitmap);
texture.needsUpdate = true;
return texture;
}
// 纹理加载队列
class TextureLoadQueue {
constructor(maxConcurrent = 4) {
this.queue = [];
this.loading = 0;
this.maxConcurrent = maxConcurrent;
}
add(url, priority = 0) {
return new Promise((resolve, reject) => {
this.queue.push({ url, resolve, reject, priority });
this.queue.sort((a, b) => b.priority - a.priority);
this.processNext();
});
}
processNext() {
if (this.loading >= this.maxConcurrent || this.queue.length === 0) return;
const { url, resolve, reject } = this.queue.shift();
this.loading++;
textureLoader.load(url,
(texture) => {
resolve(texture);
this.loading--;
this.processNext();
},
undefined,
(error) => {
reject(error);
this.loading--;
this.processNext();
}
);
}
}LOD 纹理策略
javascript
// 根据距离动态切换纹理
class TextureLODManager {
constructor(renderer) {
this.renderer = renderer;
this.textures = new Map();
}
addLODLevel(baseName, distances) {
const levels = distances.map(d => ({
distance: d.distance,
texture: textureLoader.load(d.texture)
}));
this.textures.set(baseName, levels);
}
getTexture(baseName, object, camera) {
const levels = this.textures.get(baseName);
if (!levels) return null;
const distance = camera.position.distanceTo(object.position);
for (const level of levels) {
if (distance < level.distance) {
return level.texture;
}
}
return levels[levels.length - 1].texture;
}
}常见问题
Q: 法线贴图效果不明显?
A: 检查以下几点:
- 确保
MeshStandardMaterial有足够的光照 - 调整
normalScale值 - 检查法线贴图是否正确导入(切线空间 vs 对象空间)
javascript
// 确保几何体有切线
if (!geometry.attributes.tangent) {
geometry.computeTangents();
}Q: 环境贴图反射不正确?
A: 检查以下设置:
javascript
// 确保材质支持反射
material.envMapIntensity = 1.0;
material.metalness = 0.5; // 或更高
material.roughness = 0.0; // 光滑表面反射更明显
// 确保环境贴图已设置
scene.environment = envMap;Q: 视差贴图边缘有伪影?
A: 这是视差贴图的常见问题:
- 减小
parallaxScale值 - 使用视差遮蔽映射(Parallax Occlusion Mapping)
- 在边缘添加淡出效果
Q: 视频纹理有延迟?
A: 优化方案:
- 使用较低分辨率的视频
- 预加载视频
- 确保视频编码适合流式播放
javascript
// 预加载视频
video.preload = 'auto';
video.load();Q: 3D 纹理内存占用过大?
A: 解决方案:
- 使用压缩格式
- 降低分辨率
- 使用
Int8Array或Int16Array替代Uint8Array - 按需加载纹理块
API 参考
特殊纹理类型
| 类 | 说明 |
|---|---|
Texture | 基础纹理类 |
VideoTexture | 视频纹理 |
DataTexture | 数据纹理 |
Data3DTexture | 3D 数据纹理 |
CanvasTexture | Canvas 纹理 |
CubeTexture | 立方体纹理 |
CompressedTexture | 压缩纹理 |
DepthTexture | 深度纹理 |
纹理加载器
| 加载器 | 格式 |
|---|---|
TextureLoader | JPG, PNG, GIF, WebP |
CubeTextureLoader | 立方体贴图 |
RGBELoader | HDR (RGBE) |
DDSLoader | DDS 压缩格式 |
KTX2Loader | KTX2/Basis 压缩格式 |
EXRLoader | OpenEXR |
TGALoader | TGA |
纹理映射模式
| 常量 | 说明 |
|---|---|
UVMapping | 标准 UV 映射 |
CubeReflectionMapping | 立方体反射 |
CubeRefractionMapping | 立方体折射 |
EquirectangularReflectionMapping | 等距矩形反射 |
EquirectangularRefractionMapping | 等距矩形折射 |
最佳实践
- 合理选择贴图类型:根据视觉需求选择合适的贴图组合
- 优化纹理尺寸:平衡质量和性能,使用 2 的幂次方尺寸
- 使用压缩格式:生产环境使用 KTX2/Basis 减少内存和带宽
- 缓存纹理:避免重复加载相同纹理
- 及时释放:不再使用的纹理调用
dispose() - LOD 策略:远距离使用低分辨率纹理
- 异步加载:大纹理使用异步加载避免阻塞
- 内存监控:定期检查
renderer.info.memory.textures