首页 > web前端 > js教程 > 正文

Three.js 高性能渲染千级 2D 文本标签:实例化几何体与纹理图集实践

DDD
发布: 2025-11-26 16:54:01
原创
745人浏览过

Three.js 高性能渲染千级 2D 文本标签:实例化几何体与纹理图集实践

在 three.js 中渲染大量 2d 文本标签常面临性能瓶颈。本教程提供一种高效解决方案,利用实例化几何体(instancedbuffergeometry)显著减少 draw call,并结合纹理图集(texture atlas)将所有文本预渲染至一张纹理,通过着色器在每个实例上选择性采样,从而实现千级以上 2d 文本标签的流畅渲染,同时保持良好的视觉效果和可扩展性。

引言:大规模 2D 文本渲染的挑战

在 Three.js 等 3D 渲染引擎中,当需要显示成百上千个 2D 文本标签时,传统的渲染方法往往会遇到严重的性能问题。常见的尝试包括:

  • THREE.TextGeometry:为每个文本生成独立的 3D 几何体,导致几何体数量庞大,CPU 和 GPU 开销剧增。
  • troika-three-text:虽然提供了更优化的文本渲染,但在处理上千个独立文本时,仍然可能因其内部的批处理和更新机制而面临性能瓶颈。
  • CSS2DRenderer 或 CSS3DRenderer:利用 DOM 元素渲染文本,虽然渲染质量高且易于样式控制,但每个文本对应一个 DOM 元素,大量的 DOM 操作和浏览器布局计算会显著拖慢性能,尤其是在 3D 场景中需要频繁更新位置时。

这些方法在小规模应用中表现良好,但在需要渲染如楼层平面图上千个房间名称等场景时,其性能瓶颈便凸显出来。核心问题在于,每渲染一个文本,都会产生额外的 Draw Call、几何体处理或 DOM 操作开销。

核心方案:实例化几何体与纹理图集

为了高效渲染大量 2D 文本标签,我们可以采用实例化几何体(InstancedBufferGeometry)结合纹理图集(Texture Atlas)的策略。

  1. 实例化几何体(InstancedBufferGeometry)
    • 原理:允许使用一个 Draw Call 渲染多个具有相同几何结构但不同属性(如位置、旋转、颜色、纹理偏移等)的实例。
    • 优势:极大地减少了 Draw Call 数量,从而降低了 CPU 与 GPU 之间的通信开销,显著提升渲染性能。
  2. 纹理图集(Texture Atlas)
    • 原理:将所有需要显示的文本预先绘制到一张大的纹理图像上。每个文本占据图集中的一个特定区域。
    • 优势:避免了为每个文本单独加载和绑定纹理,减少了纹理切换的开销。在着色器中,通过计算每个实例在图集中的 UV 坐标偏移,可以采样到对应的文本图像。

这种组合方案将文本渲染的重担从 CPU 转移到 GPU,利用 GPU 的并行处理能力,实现高性能的大规模文本显示。

实现步骤与代码解析

下面我们将通过一个 Three.js 示例来详细阐述如何实现这一方案。

1. HTML 与 CSS 基础设置

首先,准备一个基本的 HTML 页面和一些 CSS 样式来确保 Three.js 渲染器能正确显示。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js 高性能 2D 文本标签</title>
    <style>
        body {
            overflow: hidden;
            margin: 0;
        }
    </style>
</head>
<body>
    <script type="module">
        // Three.js 核心代码将在此处
    </script>
</body>
</html>
登录后复制

2. Three.js 场景初始化

导入 Three.js 模块,并设置基础的场景、相机、渲染器和轨道控制器。

小艺
小艺

华为公司推出的AI智能助手

小艺 549
查看详情 小艺
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

// 场景、相机、渲染器设置
let scene = new THREE.Scene();
scene.background = new THREE.Color(0xface8d);
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(3, 5, 8).setLength(40);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

// 窗口大小调整事件
window.addEventListener("resize", () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

// 轨道控制器
let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 灯光
let light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));

// 网格辅助线
scene.add(new THREE.GridHelper());
登录后复制

3. 动态生成纹理图集

创建一个函数 getMarkerTexture,它使用 HTML Canvas 动态生成一张包含所有文本的纹理图集。

/**
 * 生成包含多个文本的纹理图集
 * @param {number} size 纹理图集的边长(例如 4096)
 * @param {number} amountW 图集宽度方向上的文本数量
 * @param {number} amountH 图集高度方向上的文本数量
 * @returns {THREE.CanvasTexture} 生成的 Three.js 纹理
 */
function getMarkerTexture(size, amountW, amountH) {
  let c = document.createElement("canvas");
  c.width = size;
  c.height = size;
  let ctx = c.getContext("2d");

  // 填充背景色
  ctx.fillStyle = "#fff";
  ctx.fillRect(0, 0, c.width, c.height);

  // 计算每个文本单元的尺寸
  const stepW = c.width / amountW;
  const stepH = c.height / amountH;

  // 设置文本样式
  ctx.font = "bold 40px Arial";
  ctx.textBaseline = "middle"; // 垂直居中
  ctx.textAlign = "center";   // 水平居中
  ctx.fillStyle = "#000";     // 文本颜色

  let col = new THREE.Color();
  let counter = 0;

  // 遍历图集单元格,绘制文本和边框
  for (let y = 0; y < amountH; y++) {
    for (let x = 0; x < amountW; x++) {
      // 计算文本绘制中心点
      // 注意:y轴方向的计算 (amountH - y - 1) 是为了匹配Three.js纹理的UV坐标系
      // Three.js的UV坐标原点在左下角,Canvas的Y轴向下
      let textX = (x + 0.5) * stepW;
      let textY = ((amountH - y - 1) + 0.5) * stepH;
      ctx.fillText(counter.toString(), textX, textY); // 绘制文本

      // 绘制随机颜色边框
      ctx.strokeStyle = '#' + col.setHSL(Math.random(), 1, 0.5).getHexString();
      ctx.lineWidth = 3;
      ctx.strokeRect(x * stepW + 4, y * stepH + 4, stepW - 8, stepH - 8);

      counter++;
    }
  }

  // 创建 Three.js 纹理
  let ct = new THREE.CanvasTexture(c);
  ct.colorSpace = THREE.SRGBColorSpace; // 设置颜色空间
  return ct;
}
登录后复制

此函数创建了一个 Canvas,将多个文本(在此示例中是数字)绘制到其不同的子区域中。amountW 和 amountH 定义了图集网格的尺寸,stepW 和 stepH 定义了每个文本单元的大小。通过调整 textX 和 textY,确保文本在每个单元格内居中。

4. 创建实例化几何体

使用 THREE.InstancedBufferGeometry 来创建大量的平面,每个平面将显示一个文本标签。

// 创建一个 PlaneGeometry 作为实例的原型
let ig = new THREE.InstancedBufferGeometry().copy(new THREE.PlaneGeometry(2, 1));
ig.instanceCount = Infinity; // 设置实例数量为无限,或指定具体数量

const amount = 2048; // 渲染的文本标签数量
let instPos = new Float32Array(amount * 3); // 存储每个实例的位置

// 随机生成每个实例的位置
for(let i = 0; i < amount; i++){
  instPos[i * 3 + 0] = THREE.MathUtils.randFloatSpread(50); // X
  instPos[i * 3 + 1] = THREE.MathUtils.randFloatSpread(50); // Y
  instPos[i * 3 + 2] = THREE.MathUtils.randFloatSpread(50); // Z
}
// 将位置数据作为实例化属性添加到几何体
ig.setAttribute("instPos", new THREE.InstancedBufferAttribute(instPos, 3));
登录后复制

这里我们创建了一个 PlaneGeometry 作为每个文本标签的显示面。InstancedBufferGeometry 复制了这个平面几何体,并通过 setAttribute 添加了一个名为 instPos 的实例化属性,用于存储每个文本标签在 3D 场景中的世界坐标。

5. 编写着色器材质

自定义 THREE.ShaderMaterial 来利用实例化属性和纹理图集。

let im = new THREE.ShaderMaterial({
  uniforms: {
    quaternion: {value: new THREE.Quaternion()}, // 用于使文本面向相机
    markerTexture: {value: getMarkerTexture(4096, 32, 64)}, // 纹理图集
    textureDimensions: {value: new THREE.Vector2(32, 64)} // 图集网格尺寸 (amountW, amountH)
  },
  vertexShader: `
    uniform vec4 quaternion; // 相机旋转四元数的逆
    uniform vec2 textureDimensions; // 纹理图集网格的宽度和高度(单元格数量)

    attribute vec3 instPos; // 每个实例的世界坐标

    varying vec2 vUv; // 传递给片元着色器的 UV 坐标

    // 四元数旋转函数
    vec3 qtransform( vec4 q, vec3 v ){ 
      return v + 2.0*cross(cross(v, q.xyz ) + q.w*v, q.xyz);
    } 

    void main(){
      // 将平面顶点旋转以面向相机,然后平移到实例位置
      vec3 pos = qtransform(quaternion, position) + instPos;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.);

      // 根据 gl_InstanceID 计算当前实例在纹理图集中的 UV 偏移
      float iID = float(gl_InstanceID); // 当前实例的 ID
      float stepW = 1. / textureDimensions.x; // 每个单元格
登录后复制

以上就是Three.js 高性能渲染千级 2D 文本标签:实例化几何体与纹理图集实践的详细内容,更多请关注php中文网其它相关文章!

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号