{T}

高级纹理技术

深入探讨 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: 检查以下几点:

  1. 确保 MeshStandardMaterial 有足够的光照
  2. 调整 normalScale
  3. 检查法线贴图是否正确导入(切线空间 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: 这是视差贴图的常见问题:

  1. 减小 parallaxScale
  2. 使用视差遮蔽映射(Parallax Occlusion Mapping)
  3. 在边缘添加淡出效果

Q: 视频纹理有延迟?

A: 优化方案:

  1. 使用较低分辨率的视频
  2. 预加载视频
  3. 确保视频编码适合流式播放
javascript
// 预加载视频
video.preload = 'auto';
video.load();

Q: 3D 纹理内存占用过大?

A: 解决方案:

  1. 使用压缩格式
  2. 降低分辨率
  3. 使用 Int8ArrayInt16Array 替代 Uint8Array
  4. 按需加载纹理块

API 参考

特殊纹理类型

说明
Texture基础纹理类
VideoTexture视频纹理
DataTexture数据纹理
Data3DTexture3D 数据纹理
CanvasTextureCanvas 纹理
CubeTexture立方体纹理
CompressedTexture压缩纹理
DepthTexture深度纹理

纹理加载器

加载器格式
TextureLoaderJPG, PNG, GIF, WebP
CubeTextureLoader立方体贴图
RGBELoaderHDR (RGBE)
DDSLoaderDDS 压缩格式
KTX2LoaderKTX2/Basis 压缩格式
EXRLoaderOpenEXR
TGALoaderTGA

纹理映射模式

常量说明
UVMapping标准 UV 映射
CubeReflectionMapping立方体反射
CubeRefractionMapping立方体折射
EquirectangularReflectionMapping等距矩形反射
EquirectangularRefractionMapping等距矩形折射

最佳实践

  1. 合理选择贴图类型:根据视觉需求选择合适的贴图组合
  2. 优化纹理尺寸:平衡质量和性能,使用 2 的幂次方尺寸
  3. 使用压缩格式:生产环境使用 KTX2/Basis 减少内存和带宽
  4. 缓存纹理:避免重复加载相同纹理
  5. 及时释放:不再使用的纹理调用 dispose()
  6. LOD 策略:远距离使用低分辨率纹理
  7. 异步加载:大纹理使用异步加载避免阻塞
  8. 内存监控:定期检查 renderer.info.memory.textures

相关链接