Three.js进阶技巧:打造大小恒定且能被模型遮挡的沉浸式三维文字标注

2024-06-21 开发技术 208 次阅读 0 次点赞
本文介绍了一种利用Three.js创建大小不随场景变化的三维文字标注方法,解决了传统HTML元素无法被模型遮挡以及文字贴图随相机距离缩放的问题。核心步骤包括:在Canvas上绘制带黑色描边的白色文字以确保清晰度,再通过自定义着色器材质将文字渲染到三维场景中。顶点着色器通过计算屏幕坐标固定文字大小,片元着色器通过UV坐标取整避免文字模糊。该方法具有真正的三维遮挡效果和恒定的阅读尺寸,适用于建筑标注、设备标签、游戏UI等场景。文章还提供了完整示例代码和解决文字模糊的实用技巧。

在三维场景开发中,文字标注是一个常见的需求。传统的HTML元素虽然可以实现文字显示,但无法被三维模型遮挡,缺乏沉浸感。而使用Three.js创建的文字贴图又会随着相机距离缩放,导致远处看不清、近处太大。

本文将介绍一种使用Three.js创建大小不随场景变化文字的方法,完美解决上述问题。

效果展示

Three.js实现三维文字

核心优点

  • 真正的三维效果:文字可以被模型遮挡,比HTML实现更具沉浸感
  • 恒定的阅读尺寸:不会随着场景旋转缩放改变尺寸,远处也能清晰可见
  • 适用于三维标注:特别适合建筑标注、物体标签等场景

实现原理

实现这一效果主要包含两个步骤:

  1. 将文字绘制到Canvas画布上
  2. 创建着色器材质,将文字渲染到三维场景中

具体实现

第一步:绘制文字到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项目中实现更完美的文字标注效果!

最后更新于2小时前
本文由人工编写,AI优化,转载请注明原文地址: Three.js进阶技巧:打造大小恒定且能被模型遮挡的沉浸式三维文字标注

评论 (0)

登录 后发表评论

暂无评论,快来发表第一条评论吧!