Three.js进阶技巧:打造大小恒定且能被模型遮挡的沉浸式三维文字标注
在三维场景开发中,文字标注是一个常见的需求。传统的HTML元素虽然可以实现文字显示,但无法被三维模型遮挡,缺乏沉浸感。而使用Three.js创建的文字贴图又会随着相机距离缩放,导致远处看不清、近处太大。
本文将介绍一种使用Three.js创建大小不随场景变化文字的方法,完美解决上述问题。
效果展示

核心优点
- 真正的三维效果:文字可以被模型遮挡,比HTML实现更具沉浸感
- 恒定的阅读尺寸:不会随着场景旋转缩放改变尺寸,远处也能清晰可见
- 适用于三维标注:特别适合建筑标注、物体标签等场景
实现原理
实现这一效果主要包含两个步骤:
- 将文字绘制到Canvas画布上
- 创建着色器材质,将文字渲染到三维场景中
具体实现
第一步:绘制文字到Canvas
首先,我们需要创建一个Canvas元素,并在上面绘制文字。为了确保文字在各种背景下都清晰可见,我们采用黑色描边加白色填充的方式:
let canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
let context = canvas.getContext('2d');
context.imageSmoothingQuality = 'high';
context.textBaseline = 'middle';
context.textAlign = 'center';
context.lineWidth = 4;
let halfWidth = canvas.width / 2;
let halfHeight = canvas.height / 2;
// 绘制黑色描边
context.font = `16px "Microsoft YaHei"`;
context.strokeStyle = '#000';
context.strokeText(text, halfWidth, halfHeight);
// 绘制白色文字
context.fillStyle = '#fff';
context.fillText(text, halfWidth, halfHeight);
这种绘制方式确保文字在亮色和暗色背景下都能清晰可读。
第二步:创建着色器材质
接下来,我们需要创建一个特殊的着色器材质,让文字始终面向相机且保持固定大小:
let geometry = new THREE.PlaneBufferGeometry();
let material = new THREE.ShaderMaterial({
vertexShader: UnscaledTextVertexShader,
fragmentShader: UnscaledTextFragmentShader,
uniforms: {
tDiffuse: {
value: new THREE.CanvasTexture(canvas)
},
width: {
value: canvas.width
},
height: {
value: canvas.height
},
domWidth: {
value: renderer.domElement.width
},
domHeight: {
value: renderer.domElement.height
}
},
transparent: true
});
let mesh = new THREE.Mesh(geometry, material);
注意:由于Canvas上绘制的文字边缘是半透明的,材质需要设置transparent: true才能实现平滑边缘效果。
顶点着色器
顶点着色器是实现固定大小的关键:
precision highp float;
uniform float width;
uniform float height;
uniform float domWidth;
uniform float domHeight;
varying vec2 vUv;
void main() {
vUv = uv;
vec4 proj = projectionMatrix * modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
gl_Position = vec4(
proj.x / proj.w + position.x * width / domWidth * 2.0,
proj.y / proj.w + position.y * height / domHeight * 2.0,
proj.z / proj.w,
1.0
);
}
核心要点说明:
(0.0, 0.0, 0.0)是平面的中心世界坐标proj.x / proj.w将中心点转换到屏幕坐标系position.x * width / domWidth * 2.0确保平面显示的实际像素数与Canvas像素数一致gl_Position.w = 1.0实现正交投影效果
片元着色器
片元着色器需要特别注意纹理采样的精度问题:
precision highp float;
uniform sampler2D tDiffuse;
uniform float width;
uniform float height;
varying vec2 vUv;
void main() {
// 确保从画布整数坐标取颜色,避免文字模糊
vec2 _uv = vec2(
(floor(vUv.s * width) + 0.5) / width,
(floor(vUv.t * height) + 0.5) / height
);
gl_FragColor = texture2D(tDiffuse, _uv);
}
解决文字模糊问题
在使用Three.js或WebGL绘制文字时,经常遇到文字模糊的问题。以下是几个关键的解决方法:
1. Canvas绘制精度问题
Canvas绘制线条时,1px的线条实际会占用2px的宽度。解决方案是从(整数+0.5px)的位置开始绘制。在我们的代码中,字体大小和线宽都使用偶数,避免了这个问题。
2. UV坐标采样精度
这是最容易被忽视的问题。当UV坐标不是恰好对应纹理的整数像素时,GPU会进行颜色插值,导致文字模糊。具体表现为:文字随着视角改变而时清晰时模糊,呈现闪烁效果。
解决方案是在片元着色器中对UV坐标进行取整:
vec2 _uv = vec2(
(floor(vUv.s * width) + 0.5) / width,
(floor(vUv.t * height) + 0.5) / height
);
3. 屏幕坐标对齐
确保gl_Position.xy恰好对应屏幕像素点,这有助于进一步提升文字清晰度。
完整示例代码
以下是一个完整的使用示例:
function createUnscaledText(text, renderer) {
// 创建Canvas纹理
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.font = 'Bold 20px "Microsoft YaHei"';
ctx.lineWidth = 4;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// 描边
ctx.strokeStyle = '#000000';
ctx.strokeText(text, centerX, centerY);
// 填充
ctx.fillStyle = '#ffffff';
ctx.fillText(text, centerX, centerY);
// 创建材质和网格
const geometry = new THREE.PlaneGeometry(1, 1);
const material = new THREE.ShaderMaterial({
uniforms: {
tDiffuse: { value: new THREE.CanvasTexture(canvas) },
width: { value: canvas.width },
height: { value: canvas.height },
domWidth: { value: renderer.domElement.clientWidth },
domHeight: { value: renderer.domElement.clientHeight }
},
vertexShader: UnscaledTextVertexShader,
fragmentShader: UnscaledTextFragmentShader,
transparent: true,
depthTest: true,
depthWrite: false
});
return new THREE.Mesh(geometry, material);
}
应用场景
这种技术特别适合以下场景:
- 建筑信息标注:在建筑信息模型(BIM)中标注房间名称、面积等信息
- 设备标签:在工业三维场景中标注设备编号、状态
- 游戏UI:在3D游戏中显示角色名称、血条等
- 数据可视化:在三维图表中显示数据标签
总结
通过Canvas绘制文字并结合自定义着色器材质,我们成功实现了大小不随场景变化的文字标注。这种方法既保持了文字的清晰度,又实现了真正的三维遮挡效果,是三维场景中文字标注的理想解决方案。
在实际项目中,你可以根据需要调整文字样式、大小和颜色,灵活应用于各种三维场景。希望本文能帮助你在Three.js项目中实现更完美的文字标注效果!