Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • React

  • Vue

  • JavaScript文章

  • 学习笔记

  • openlayers

  • threejs

    • threejs渲染3D字体介绍
    • MapboxGL

    • 工具

    • 源码合集

    • 前端
    • threejs
    东流
    2024-10-31
    目录

    threejs渲染3D字体介绍

    # 概述

    本文主要介绍如何通过 Three.js 生成 3D 文本。

    # 效果展示

    # 代码分析

    核心代码部分就是通过 Three.js 中的 FontLoader 和 TextGeometry 来加载字体并创建 3D 文本。

    核心代码如下:

    const loader = new FontLoader();
    
    loader.load(textFamily.value, function (font) {
      const geometry = new TextGeometry(text.value, {
        font: font, //字体
        size: 120, //文本字体大小
        height: 10, //文本厚度
        curveSegments: 4, //定义曲线细节的段数
        bevelEnabled: true, //启用斜面效果
        bevelThickness: 10, //控制斜面的厚度
        bevelSize: 8, //控制斜面的大小
        bevelSegments: 5, //控制斜面的段数
      });
      geometry.computeBoundingBox();
      const xOffset = (geometry.boundingBox.max.x - geometry.boundingBox.min.x) / 2;
      textMesh = new THREE.Mesh(geometry, materials);
      textMesh.position.set(-xOffset, 0, 0);
      scene.add(textMesh);
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    逐行分析如下:

    • FontLoader 实例化:
    const loader = new FontLoader(); //创建了一个 FontLoader 的实例,用于加载字体文件。
    
    1
    • 加载字体:
    loader.load(textFamily.value, function (font) {
    
    1

    使用 loader.load() 方法加载字体,textFamily.value 是字体文件的路径。当字体加载完成后,会调用回调函数,参数font是加载的字体对象。

    • 创建文本几何体:

    使用 TextGeometry 创建一个几何体。text.value 是要显示的文本内容,通过选项定义了文本的样式,各选项说明如上。

    • 计算边界框:
    geometry.computeBoundingBox(); //计算文本几何体的边界框,以便后续使用。
    
    1
    • 计算 X 偏移量:
    const xOffset = (geometry.boundingBox.max.x - geometry.boundingBox.min.x) / 2;
    
    1

    计算文本的 X 轴偏移量,使文本在场景中居中。boundingBox.max.x 和 boundingBox.min.x分别代表边界框的最大和最小 X坐标。

    • 创建网格并设置位置
    textMesh = new THREE.Mesh(geometry, materials);
    textMesh.position.set(-xOffset, 0, 0);
    
    1
    2

    创建一个 Mesh 对象,将文本几何体和材料应用到该网格上。然后将网格的位置设置为 (-xOffset, 0, 0),使其在 X 轴上居中。

    • 将网格添加到场景:
    scene.add(textMesh);
    
    1

    将 textMesh 添加到 scene 场景中,使其在渲染中可见。

    • 移除字体 如果需要移除字体,可以调用scene.remove(textMesh)

    # FontLoader

    FontLoader字体加载器并不是 Three.js 的核心实现,示例中的FontLoader引用如下 "three/addons/loaders/FontLoader.js"

    其代码如下:

    import { FileLoader, Loader, ShapePath } from "three";
    
    class FontLoader extends Loader {
      constructor(manager) {
        super(manager);
      }
    
      load(url, onLoad, onProgress, onError) {
        const scope = this;
    
        const loader = new FileLoader(this.manager);
        loader.setPath(this.path);
        loader.setRequestHeader(this.requestHeader);
        loader.setWithCredentials(this.withCredentials);
        loader.load(
          url,
          function (text) {
            const font = scope.parse(JSON.parse(text));
    
            if (onLoad) onLoad(font);
          },
          onProgress,
          onError
        );
      }
    
      parse(json) {
        return new Font(json);
      }
    }
    
    //
    
    class Font {
      constructor(data) {
        this.isFont = true;
    
        this.type = "Font";
    
        this.data = data;
      }
    
      generateShapes(text, size = 100) {
        const shapes = [];
        const paths = createPaths(text, size, this.data);
    
        for (let p = 0, pl = paths.length; p < pl; p++) {
          shapes.push(...paths[p].toShapes());
        }
    
        return shapes;
      }
    }
    
    function createPaths(text, size, data) {
      const chars = Array.from(text);
      const scale = size / data.resolution;
      const line_height =
        (data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness) *
        scale;
    
      const paths = [];
    
      let offsetX = 0,
        offsetY = 0;
    
      for (let i = 0; i < chars.length; i++) {
        const char = chars[i];
    
        if (char === "\n") {
          offsetX = 0;
          offsetY -= line_height;
        } else {
          const ret = createPath(char, scale, offsetX, offsetY, data);
          offsetX += ret.offsetX;
          paths.push(ret.path);
        }
      }
    
      return paths;
    }
    
    function createPath(char, scale, offsetX, offsetY, data) {
      const glyph = data.glyphs[char] || data.glyphs["?"];
    
      if (!glyph) {
        console.error(
          'THREE.Font: character "' +
            char +
            '" does not exists in font family ' +
            data.familyName +
            "."
        );
    
        return;
      }
    
      const path = new ShapePath();
    
      let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
    
      if (glyph.o) {
        const outline =
          glyph._cachedOutline || (glyph._cachedOutline = glyph.o.split(" "));
    
        for (let i = 0, l = outline.length; i < l; ) {
          const action = outline[i++];
    
          switch (action) {
            case "m": // moveTo
              x = outline[i++] * scale + offsetX;
              y = outline[i++] * scale + offsetY;
    
              path.moveTo(x, y);
    
              break;
    
            case "l": // lineTo
              x = outline[i++] * scale + offsetX;
              y = outline[i++] * scale + offsetY;
    
              path.lineTo(x, y);
    
              break;
    
            case "q": // quadraticCurveTo
              cpx = outline[i++] * scale + offsetX;
              cpy = outline[i++] * scale + offsetY;
              cpx1 = outline[i++] * scale + offsetX;
              cpy1 = outline[i++] * scale + offsetY;
    
              path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);
    
              break;
    
            case "b": // bezierCurveTo
              cpx = outline[i++] * scale + offsetX;
              cpy = outline[i++] * scale + offsetY;
              cpx1 = outline[i++] * scale + offsetX;
              cpy1 = outline[i++] * scale + offsetY;
              cpx2 = outline[i++] * scale + offsetX;
              cpy2 = outline[i++] * scale + offsetY;
    
              path.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, cpx, cpy);
    
              break;
          }
        }
      }
    
      return { offsetX: glyph.ha * scale, path: path };
    }
    
    export { FontLoader, Font };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154

    由上可知,FontLoader就是一个继承自Loader类,在其load方法中实例化 Three.js 的FileLoader类去加载字体文件,然后调用parse方法去解析字体文件。

    pares方法中也就是实列化Font类,在Font类中定义了一个generateShapes方法,用于绘制字体,该方法会在TextGeometry中去调用。

    # TextGeometry

    TextGeometry位于three/examples/jsm/geometries/TextGeometry.js,其实现如下:

    class TextGeometry extends ExtrudeGeometry {
      constructor(text, parameters = {}) {
        const font = parameters.font;
    
        if (font === undefined) {
          super();
        } else {
          const shapes = font.generateShapes(text, parameters.size);
    
          if (parameters.depth === undefined && parameters.height !== undefined) {
            console.warn(
              "THREE.TextGeometry: .height is now depreciated. Please use .depth instead"
            ); // @deprecated, r163
          }
    
          parameters.depth =
            parameters.depth !== undefined
              ? parameters.depth
              : parameters.height !== undefined
              ? parameters.height
              : 50;
    
          // defaults
    
          if (parameters.bevelThickness === undefined)
            parameters.bevelThickness = 10;
          if (parameters.bevelSize === undefined) parameters.bevelSize = 8;
          if (parameters.bevelEnabled === undefined)
            parameters.bevelEnabled = false;
    
          super(shapes, parameters);
        }
    
        this.type = "TextGeometry";
      }
    }
    
    export { TextGeometry };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    TextGeometry就是继承了ExtrudeGeometry类,该类是 Three.js 中用于创建三维几何体的类,主要通过对二维形状进行挤出(extrusion)来生成三维对象。而TextGeometry则就是调用Font类的generateShapes方法生成THREE.Shape对象。

    # ExtrudeGeometry

    主要特点

    • 基础形状:ExtrudeGeometry 通过一个二维的形状(Shape 对象)作为基础形状进行挤出。

    • 挤出方向:可以指定挤出的深度和方向,允许生成具有不同高度的三维对象。

    • 轮廓调整:可以在挤出过程中应用各种配置选项,比如切角、法线方向、凹凸等,使得生成的几何体更加丰富和复杂。

    • 曲面细分:支持细分,可以通过设置细分参数来增加几何体的细节。

    构造函数 ExtrudeGeometry 的构造函数通常如下:

    const geometry = new THREE.ExtrudeGeometry(shapes, options);
    
    1
    • 参数:
      • shapes: 一个或多个 THREE.Shape 对象,定义了挤出的二维形状。
      • options: 一个对象,包含以下可选属性:
        • depth: 挤出的深度。
        • bevelEnabled: 是否启用斜角。
        • bevelThickness: 斜角的厚度。
        • bevelSize: 斜角的大小。
        • bevelSegments: 斜角的细分数。
    编辑 (opens new window)
    上次更新: 2024/10/31, 03:00:59
    源码分析之Openlayers中的Collection类
    MapboxGL加载离线字体

    ← 源码分析之Openlayers中的Collection类 MapboxGL加载离线字体→

    最近更新
    01
    GeoJSON
    05-08
    02
    Circle
    04-15
    03
    CircleMarker
    04-15
    更多文章>
    Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式