如何用Three.js高效渲染1000+个2D文本标签?
高效渲染大量带裁剪文本的2D网格方案
1. 批量SDF文本渲染+自定义着色器裁剪(最优方案)
- 核心思路:把所有文本的位置、内容、裁剪区域数据打包到单个
BufferGeometry中,用基于SDF(有向距离场)的自定义着色器批量渲染文本,同时在片元着色器内完成网格边界裁剪——全程GPU加速,将draw call降到1~2个。 - 高效原因:
- 彻底避免单个文本mesh带来的draw call爆炸,1000+元素仅需1次绘制调用。
- SDF字体渲染缩放不失真,计算量远低于
TextGeometry这类多边形文本方案。 - 裁剪逻辑在GPU并行处理,效率比CPU层面的遮罩、DOM裁剪高几个数量级。
- 实现步骤:
- 生成SDF字体:用
msdf-bmfont-xml工具将TTF字体转换成SDF纹理和字符数据(JSON格式,含字符UV、大小、间距等)。 - 构建BufferGeometry:创建属性数组存储所有文本数据,包括:
- 文本的基础位置(x,y)
- 每个字符的UV偏移、尺寸
- 对应网格的裁剪边界(如矩形的left/right/top/bottom)
- 文本颜色、字号等样式参数
- 自定义ShaderMaterial:
- 顶点着色器:传递裁剪边界、文本位置等数据到片元着色器。
- 片元着色器:根据SDF纹理采样值渲染字符形状,同时对比当前像素坐标与裁剪边界,超出部分用
discard指令直接丢弃。
- 生成SDF字体:用
- 适用场景:静态/动态文本均支持,动态更新时仅需修改BufferGeometry的对应属性。
2. 纹理图集预渲染+批量网格渲染(适合静态文本)
- 核心思路:提前把所有需要显示的文本渲染到一张大纹理图集里,每个网格对应图集中的一个子纹理区域;再用单个
BufferGeometry渲染所有网格,每个网格的材质调用对应子纹理,通过自定义着色器实现裁剪。 - 实现要点:
- 用Canvas API批量渲染所有文本到一张纹理,记录每个文本的UV坐标。
- 构建网格的BufferGeometry时,为每个网格传递对应的文本UV和裁剪边界。
- 片元着色器中采样纹理图集的对应区域,并执行裁剪判断。
- 优势:文本渲染提前完成,运行时GPU开销极低;缺点是文本动态修改时需重新生成纹理图集,仅适合内容固定的场景。
3. InstancedMesh结合SDF文本渲染(适合规则形状网格)
- 核心思路:如果网格是统一规则形状(如矩形),用
InstancedMesh渲染所有网格实例,同时在实例化着色器中集成SDF文本渲染和裁剪逻辑,每个实例对应一组文本与裁剪区域。 - 实现要点:
- 创建单个网格几何体(如矩形
ShapeGeometry),用InstancedMesh生成1000+实例,每个实例传递矩阵、文本数据、裁剪边界等实例化属性。 - 自定义着色器中,根据实例ID获取对应文本和裁剪参数,完成渲染与裁剪。
- 创建单个网格几何体(如矩形
- 优势:网格与文本渲染完全合并,draw call最少;缺点仅适用于形状统一的网格。
避坑总结
- 放弃
CSS2dRenderer:DOM元素叠加在Three.js画布上,1000+元素会导致浏览器频繁重排重绘,性能瓶颈在CPU而非GPU。 - 放弃
TextGeometry/单个troika文本mesh:每个文本对应独立mesh,draw call数量等于文本数量,1000+会直接导致性能崩溃。
内容的提问来源于stack exchange,提问作者Duba Duba
相关产品推荐
相关产品推荐

