一、整体思路
不用TextGeometry。文字多的时候性能太差
- 收集场景中需要绘制的所有文字信息
- 将所有的文字信息都绘制在一个canvas上并维护好,每个文字信息在canvas上的位置信息,包括,文字绘制在canvas上的第几行、在当前行的位置、文字长度等信息。将canvas生成 THREE.CanvasTexture
- 用THREE.Sprite绘制文字,使用步骤2生成的 CanvasTexture,这里注意,需要处理CanvasTexture的offset和repeat属性去匹配文字对应的文理坐标。将绘制的 THREE.Sprite进行适当的缩放
二、具体实现
1. 数据收集具体数据的收集,这里不做说明,简单的展示一下数据结构,方便看下面的代码
const texts = [{str: '90844',position: {x: -308922.01328395033,y: -674026.0824936354,z: 0.009999999776482582,},},...
]
2. 将所有文字信息绘制在canvas上,并维护好文字在canvas的位置信息
generateTextCanvas(texts: { str: string; position: THREE.Vector3 }[], color = '#fff') {// 设置canvas最大宽度为1000const maxWidth = 1000;let canvasWidth = 0;// 存储文字在canvas中的位置信息const positionItems: { start: number; lineNum: number; width: number;str:string }[] = [];// 记录当前绘制文字在的行数,从0开始let curLine = 0;// 记录当前绘制文字在当前行的起始位置let curStart = 0;const canvas = document.createElement('canvas');const context = canvas.getContext('2d');const lineHeight = 48;context.font = `${lineHeight / 2}px sans-serif`;for (let i = 0; i < texts.length; i += 1) {const measureText = context.measureText(texts[i].str);let { width } = measureText;width = Math.ceil(width);// 绘制的时候如果当前行绘制会超出,记得换行if (width + curStart < maxWidth) {positionItems.push({ start: curStart, lineNum: curLine, width,str: texts[i].str });canvasWidth = Math.max(canvasWidth, curStart + width,str: texts[i].str);curStart += width;} else {curLine += 1;positionItems.push({ start: 0, lineNum: curLine, width });curStart = width;canvasWidth = Math.max(canvasWidth, width);}}const canvasHeight = (curLine + 1) * lineHeight;canvas.width = canvasWidth;canvas.height = canvasHeight;context.fillStyle = color;context.font = `${lineHeight / 2}px sans-serif`;// 注意这里要设置为middle,并且这里的设置决定 fillText时的y的位置context.textBaseline = 'middle';context.textAlign = 'left';for (let i = 0; i < positionItems.length; i += 1) {const { start, lineNum } = positionItems[i];context.fillText(texts[i].str, start, lineNum * lineHeight + lineHeight / 2);}const texture = new THREE.CanvasTexture(canvas);return {texture,canvasWidth,canvasHeight,positionItems,};
}
3. 用THREE.Sprite绘制文字,并且设置好文理坐标
positionItems.forEach((item, index) => {const itemTexture = texture.clone();itemTexture.offset.set(positionItems[index].start / canvasWidth,1 - (positionItems[index].lineNum + 1) / lineLength,);itemTexture.repeat.set(positionItems[index].width / canvasWidth, 1 / lineLength);const material = new THREE.SpriteMaterial({map: itemTexture,transparent: true,// 需要设置,不然文字会有黑色的背景,会遮挡场景中的对象depthTest: false,});const textMesh = new THREE.Sprite(material);// 具体缩放多少按实际显示效果去看,但是这里的缩放比例很重要textMesh.scale.set(positionItems[index].width / 24, canvasHeight / lineLength / 24, 1);textMesh.position.set(item.position.x, item.position.y, item.position.z);scene.add(textMesh);
});
这里重点是offset和repeat的设置,scale缩放的时候也要注意,缩放值和实际的文字宽度和高度要一致,否则文字会变形。