第二十节:3D文本渲染 - 字体几何体生成与特效

第二十节:3D文本渲染 - 字体几何体生成与特效

TextGeometry深度解析与高级文字效果实现

1. 核心概念解析

1.1 3D文字渲染技术对比
技术 原理 优点 缺点
TextGeometry 将字体轮廓转换为3D网格 真实3D效果,支持材质 性能开销大,内存占用高
Canvas纹理 将文字渲染到纹理并应用到平面 性能好,支持复杂字体 无真实3D深度,边缘锯齿
Signed Distance Fields 使用距离场纹理渲染 缩放无损,效果锐利 需要预处理,特效有限
MSDF 多通道距离场技术 高质量,支持小字号 实现复杂,兼容性问题
1.2 3D文字生成流程

字体文件加载 轮廓解析 几何体生成 材质应用 场景添加 挤出深度 斜面处理 UV映射 基础材质 描边效果 发光特效


2. 基础文字几何体创建

2.1 字体加载与解析
javascript 复制代码
import { FontLoader } from 'three/addons/loaders/FontLoader';
import { TextGeometry } from 'three/addons/geometries/TextGeometry';

// 初始化字体加载器
const fontLoader = new FontLoader();
let font;

// 加载字体文件
fontLoader.load('/fonts/helvetiker_regular.typeface.json', (loadedFont) => {
  font = loadedFont;
  createText(); // 字体加载完成后创建文字
});

// 创建3D文字
function createText() {
  const textGeometry = new TextGeometry('Hello Three.js', {
    font: font,
    size: 1,          // 字体大小
    height: 0.2,      // 挤出深度
    curveSegments: 12, // 曲线分段数
    bevelEnabled: true, // 启用斜面
    bevelThickness: 0.03, // 斜面厚度
    bevelSize: 0.02,  // 斜面大小
    bevelOffset: 0,   // 斜面偏移
    bevelSegments: 5  // 斜面分段数
  });
  
  // 居中文字几何体
  textGeometry.computeBoundingBox();
  const centerOffset = -0.5 * (
    textGeometry.boundingBox.max.x - textGeometry.boundingBox.min.x
  );
  textGeometry.translate(centerOffset, 0, 0);
  
  // 创建材质
  const textMaterial = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    metalness: 0.5,
    roughness: 0.2
  });
  
  // 创建网格
  const textMesh = new THREE.Mesh(textGeometry, textMaterial);
  textMesh.castShadow = true;
  scene.add(textMesh);
}
2.2 文字几何体优化
javascript 复制代码
// 优化文字几何体性能
function optimizeTextGeometry(geometry) {
  // 合并顶点(减少draw calls)
  geometry.mergeVertices();
  
  // 计算法线(确保正确光照)
  geometry.computeVertexNormals();
  
  // 使用BufferGeometry提高性能
  const bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry);
  
  return bufferGeometry;
}

// 使用优化后的几何体
const optimizedGeometry = optimizeTextGeometry(textGeometry);
const textMesh = new THREE.Mesh(optimizedGeometry, textMaterial);

3. 高级文字特效

3.1 描边效果实现
javascript 复制代码
// 创建描边文字效果
function createOutlinedText() {
  const text = "Three.js";
  
  // 主文字几何体
  const mainGeometry = new TextGeometry(text, {
    font: font,
    size: 1,
    height: 0.3,
    bevelEnabled: true,
    bevelThickness: 0.03,
    bevelSize: 0.02
  });
  
  // 描边几何体(稍大一些)
  const outlineGeometry = new TextGeometry(text, {
    font: font,
    size: 1.05, // 比主文字稍大
    height: 0.3,
    bevelEnabled: true,
    bevelThickness: 0.03,
    bevelSize: 0.02
  });
  
  // 居中处理
  centerGeometry(mainGeometry);
  centerGeometry(outlineGeometry);
  
  // 创建材质
  const mainMaterial = new THREE.MeshStandardMaterial({
    color: 0x00ff88,
    metalness: 0.7,
    roughness: 0.2
  });
  
  const outlineMaterial = new THREE.MeshBasicMaterial({
    color: 0x000000,
    side: THREE.BackSide // 只渲染背面作为描边
  });
  
  // 创建组合网格
  const mainText = new THREE.Mesh(mainGeometry, mainMaterial);
  const outlineText = new THREE.Mesh(outlineGeometry, outlineMaterial);
  
  // 将描边稍微向后移动以避免z-fighting
  outlineText.position.z = -0.01;
  
  // 创建组并添加
  const textGroup = new THREE.Group();
  textGroup.add(mainText);
  textGroup.add(outlineText);
  
  scene.add(textGroup);
  return textGroup;
}

// 几何体居中函数
function centerGeometry(geometry) {
  geometry.computeBoundingBox();
  const center = new THREE.Vector3();
  geometry.boundingBox.getCenter(center);
  geometry.translate(-center.x, -center.y, -center.z);
}
3.2 发光文字效果
javascript 复制代码
// 创建发光文字效果
function createGlowingText() {
  const geometry = new TextGeometry('GLOW', {
    font: font,
    size: 1,
    height: 0.2,
    curveSegments: 24 // 更高分段数以获得更平滑的效果
  });
  
  centerGeometry(geometry);
  
  // 使用ShaderMaterial创建发光效果
  const material = new THREE.ShaderMaterial({
    uniforms: {
      time: { value: 0 },
      glowColor: { value: new THREE.Color(0x00ffff) },
      glowIntensity: { value: 1.5 }
    },
    
    vertexShader: `
      varying vec2 vUv;
      varying vec3 vNormal;
      
      void main() {
        vUv = uv;
        vNormal = normal;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `,
    
    fragmentShader: `
      uniform float time;
      uniform vec3 glowColor;
      uniform float glowIntensity;
      
      varying vec2 vUv;
      varying vec3 vNormal;
      
      void main() {
        // 基础颜色
        vec3 baseColor = glowColor;
        
        // 边缘发光效果(基于法线方向)
        float intensity = pow(0.7 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 4.0);
        
        // 添加脉动效果
        float pulse = sin(time * 2.0) * 0.5 + 0.5;
        intensity += pulse * 0.3;
        
        // 最终颜色
        vec3 finalColor = baseColor * intensity * glowIntensity;
        
        gl_FragColor = vec4(finalColor, 1.0);
      }
    `,
    
    side: THREE.DoubleSide,
    blending: THREE.AdditiveBlending,
    transparent: true
  });
  
  const textMesh = new THREE.Mesh(geometry, material);
  
  // 更新uniforms的动画
  function animate() {
    requestAnimationFrame(animate);
    material.uniforms.time.value += 0.01;
    renderer.render(scene, camera);
  }
  animate();
  
  return textMesh;
}
3.3 渐变文字效果
javascript 复制代码
// 创建渐变色彩文字
function createGradientText() {
  const geometry = new TextGeometry('GRADIENT', {
    font: font,
    size: 1,
    height: 0.3,
    bevelEnabled: true,
    bevelSegments: 3
  });
  
  centerGeometry(geometry);
  
  // 为每个顶点计算颜色
  const positionAttribute = geometry.getAttribute('position');
  const colorArray = new Float32Array(positionAttribute.count * 3);
  
  for (let i = 0; i < positionAttribute.count; i++) {
    const x = positionAttribute.getX(i);
    const y = positionAttribute.getY(i);
    
    // 基于位置计算颜色
    const r = (x + 2) / 4; // 从-2到2映射到0-1
    const g = (y + 1) / 2; // 从-1到1映射到0-1
    const b = 0.5;
    
    colorArray[i * 3] = r;
    colorArray[i * 3 + 1] = g;
    colorArray[i * 3 + 2] = b;
  }
  
  geometry.setAttribute('color', new THREE.BufferAttribute(colorArray, 3));
  
  // 使用顶点着色器实现渐变
  const material = new THREE.ShaderMaterial({
    uniforms: {
      time: { value: 0 }
    },
    
    vertexShader: `
      attribute vec3 color;
      varying vec3 vColor;
      
      void main() {
        vColor = color;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `,
    
    fragmentShader: `
      uniform float time;
      varying vec3 vColor;
      
      void main() {
        // 添加动态效果
        vec3 finalColor = vColor;
        finalColor.r += sin(time) * 0.1;
        finalColor.g += cos(time * 0.7) * 0.1;
        
        gl_FragColor = vec4(finalColor, 1.0);
      }
    `
  });
  
  return new THREE.Mesh(geometry, material);
}

4. 性能优化与大批量文字处理

4.1 实例化文字渲染
javascript 复制代码
// 使用InstancedMesh渲染大量文字
function createInstancedText() {
  const text = "A";
  const count = 1000; // 实例数量
  
  // 创建基础文字几何体
  const geometry = new TextGeometry(text, {
    font: font,
    size: 0.3,
    height: 0.05
  });
  
  centerGeometry(geometry);
  
  // 创建实例化网格
  const instancedMesh = new THREE.InstancedMesh(
    geometry,
    new THREE.MeshStandardMaterial({ color: 0xffffff }),
    count
  );
  
  // 创建变换矩阵和颜色
  const matrix = new THREE.Matrix4();
  const color = new THREE.Color();
  
  for (let i = 0; i < count; i++) {
    // 随机位置
    const x = (Math.random() - 0.5) * 50;
    const y = (Math.random() - 0.5) * 50;
    const z = (Math.random() - 0.5) * 50;
    
    // 随机旋转
    const rx = Math.random() * Math.PI;
    const ry = Math.random() * Math.PI;
    const rz = Math.random() * Math.PI;
    
    // 设置位置和旋转
    matrix.compose(
      new THREE.Vector3(x, y, z),
      new THREE.Quaternion().setFromEuler(new THREE.Euler(rx, ry, rz)),
      new THREE.Vector3(1, 1, 1)
    );
    
    instancedMesh.setMatrixAt(i, matrix);
    
    // 随机颜色
    color.setHSL(Math.random(), 0.7, 0.5);
    instancedMesh.setColorAt(i, color);
  }
  
  scene.add(instancedMesh);
  return instancedMesh;
}
4.2 文字LOD系统
javascript 复制代码
// 实现文字细节层次系统
class TextLODSystem {
  constructor() {
    this.lodLevels = new Map();
    this.currentLOD = 'high';
  }
  
  // 为文字创建不同细节级别
  createLODLevels(text, font) {
    const levels = {
      high: this.createTextGeometry(text, font, 0.5, 32),
      medium: this.createTextGeometry(text, font, 0.5, 16),
      low: this.createTextGeometry(text, font, 0.5, 8),
      verylow: this.createTextGeometry(text, font, 0.5, 4)
    };
    
    this.lodLevels.set(text, levels);
    return levels;
  }
  
  createTextGeometry(text, font, size, segments) {
    const geometry = new TextGeometry(text, {
      font: font,
      size: size,
      height: size * 0.1,
      curveSegments: segments,
      bevelEnabled: segments > 8,
      bevelThickness: size * 0.02,
      bevelSize: size * 0.01,
      bevelSegments: Math.floor(segments / 2)
    });
    
    centerGeometry(geometry);
    return geometry;
  }
  
  // 根据距离选择LOD级别
  updateLOD(cameraPosition, textPosition) {
    const distance = cameraPosition.distanceTo(textPosition);
    
    if (distance > 50) {
      this.currentLOD = 'verylow';
    } else if (distance > 25) {
      this.currentLOD = 'low';
    } else if (distance > 10) {
      this.currentLOD = 'medium';
    } else {
      this.currentLOD = 'high';
    }
  }
  
  // 获取当前LOD级别的几何体
  getCurrentLODGeometry(text) {
    const levels = this.lodLevels.get(text);
    return levels ? levels[this.currentLOD] : null;
  }
}

// 使用示例
const lodSystem = new TextLODSystem();
const textLevels = lodSystem.createLODLevels('LOD Text', font);

// 在渲染循环中更新LOD
function animate() {
  requestAnimationFrame(animate);
  
  lodSystem.updateLOD(camera.position, textMesh.position);
  const currentGeometry = lodSystem.getCurrentLODGeometry('LOD Text');
  
  if (currentGeometry) {
    textMesh.geometry = currentGeometry;
  }
  
  renderer.render(scene, camera);
}

5. 完整案例:动态文字展示系统

javascript 复制代码
class DynamicTextSystem {
  constructor(scene, font) {
    this.scene = scene;
    this.font = font;
    this.textMeshes = new Map();
    this.animations = [];
  }
  
  // 创建文字元素
  createText(text, options = {}) {
    const {
      size = 1,
      position = new THREE.Vector3(0, 0, 0),
      color = 0xffffff,
      bevelEnabled = true,
      animation = null
    } = options;
    
    const geometry = new TextGeometry(text, {
      font: this.font,
      size: size,
      height: size * 0.2,
      curveSegments: 12,
      bevelEnabled: bevelEnabled,
      bevelThickness: size * 0.03,
      bevelSize: size * 0.02,
      bevelSegments: 5
    });
    
    centerGeometry(geometry);
    
    const material = new THREE.MeshStandardMaterial({
      color: color,
      metalness: 0.3,
      roughness: 0.4
    });
    
    const textMesh = new THREE.Mesh(geometry, material);
    textMesh.position.copy(position);
    textMesh.userData.originalPosition = position.clone();
    textMesh.castShadow = true;
    
    this.scene.add(textMesh);
    this.textMeshes.set(text, textMesh);
    
    // 添加动画
    if (animation) {
      this.addAnimation(textMesh, animation);
    }
    
    return textMesh;
  }
  
  // 添加文字动画
  addAnimation(textMesh, type) {
    switch (type) {
      case 'float':
        this.animations.push(() => {
          textMesh.position.y = textMesh.userData.originalPosition.y + 
            Math.sin(Date.now() * 0.001) * 0.3;
        });
        break;
        
      case 'rotate':
        this.animations.push(() => {
          textMesh.rotation.y += 0.01;
        });
        break;
        
      case 'pulse':
        this.animations.push(() => {
          const scale = 1 + Math.sin(Date.now() * 0.002) * 0.1;
          textMesh.scale.set(scale, scale, scale);
        });
        break;
    }
  }
  
  // 更新所有动画
  update() {
    this.animations.forEach(animation => animation());
  }
  
  // 更改文字内容
  updateText(oldText, newText) {
    const textMesh = this.textMeshes.get(oldText);
    if (!textMesh) return;
    
    this.textMeshes.delete(oldText);
    this.scene.remove(textMesh);
    
    const newMesh = this.createText(newText, {
      size: textMesh.scale.x,
      position: textMesh.position,
      color: textMesh.material.color.getHex()
    });
    
    return newMesh;
  }
  
  // 批量创建文字
  createTextGrid(texts, rows, cols, spacing) {
    const group = new THREE.Group();
    const count = Math.min(texts.length, rows * cols);
    
    for (let i = 0; i < count; i++) {
      const row = Math.floor(i / cols);
      const col = i % cols;
      
      const x = (col - cols / 2) * spacing;
      const y = (rows / 2 - row) * spacing;
      
      const textMesh = this.createText(texts[i], {
        position: new THREE.Vector3(x, y, 0),
        size: spacing * 0.4
      });
      
      group.add(textMesh);
    }
    
    this.scene.add(group);
    return group;
  }
}

// 使用示例
const textSystem = new DynamicTextSystem(scene, font);

// 创建单个文字
textSystem.createText('Hello', {
  size: 1.5,
  position: new THREE.Vector3(0, 2, 0),
  color: 0xff6b6b,
  animation: 'float'
});

// 创建文字网格
const texts = ['One', 'Two', 'Three', 'Four', 'Five', 'Six'];
textSystem.createTextGrid(texts, 2, 3, 3);

// 在渲染循环中更新
function animate() {
  requestAnimationFrame(animate);
  textSystem.update();
  renderer.render(scene, camera);
}

6. 注意事项与重点核心

6.1 字体加载与性能

字体文件选择

  • 使用.typeface.json格式的字体文件
  • 避免使用中文字体(文件过大),或使用子集化字体
  • 考虑使用系统字体+Canvas纹理的方案替代

加载性能优化

javascript 复制代码
// 预加载字体
function preloadFonts(fontUrls) {
  const loader = new FontLoader();
  const promises = fontUrls.map(url => {
    return new Promise((resolve) => {
      loader.load(url, resolve);
    });
  });
  
  return Promise.all(promises);
}

// 使用示例
const fontUrls = [
  '/fonts/helvetiker_regular.typeface.json',
  '/fonts/gentilis_regular.typeface.json'
];

preloadFonts(fontUrls).then(fonts => {
  const [mainFont, secondaryFont] = fonts;
  // 字体加载完成后的初始化
});
6.2 内存管理

几何体清理

javascript 复制代码
// 正确释放文字几何体内存
function disposeTextGeometry(textMesh) {
  if (textMesh.geometry) {
    textMesh.geometry.dispose();
  }
  if (textMesh.material) {
    if (Array.isArray(textMesh.material)) {
      textMesh.material.forEach(material => material.dispose());
    } else {
      textMesh.material.dispose();
    }
  }
  scene.remove(textMesh);
}

// 批量清理
function cleanupTextMeshes() {
  textSystem.textMeshes.forEach((mesh, text) => {
    disposeTextGeometry(mesh);
  });
  textSystem.textMeshes.clear();
}
6.3 移动端适配

性能优化策略

  1. 减少曲线分段数 :设置curveSegments: 4-8
  2. 禁用斜面 :设置bevelEnabled: false
  3. 使用简单字体:避免复杂轮廓的字体
  4. 限制文字数量:同一场景不超过50个文字网格
  5. 使用Canvas纹理:对静态文字使用纹理替代几何体

触控交互优化

javascript 复制代码
// 为文字添加交互
function makeTextInteractive(textMesh) {
  textMesh.userData.interactive = true;
  
  // 射线检测
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObject(textMesh);
  
  if (intersects.length > 0) {
    // 文字被点击或悬停
    textMesh.material.color.set(0xff0000);
  }
}

下一节预告:环境贴图与PBR材质

第二十一节:HDRI光照与金属粗糙度工作流

核心内容

  • HDR环境贴图加载与处理
  • PBR材质原理与参数调节
  • 金属度/粗糙度工作流实战
  • 环境反射与折射效果实现

重点掌握:创建逼真的物理渲染材质,掌握现代3D渲染的核心技术。