欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用three.js创建大小不随着场景变化的文字

程序员文章站 2022-05-31 22:04:29
使用three.js创建大小不随着场景变化的文字,需要以下两步: 1、将文字绘制到画布上。 2、创建着色器材质,把文字放到三维场景中。 优点: 1、跟用html实现文字相比,这些文字可以被模型遮挡,更具有三维效果。 2、不会随着场景旋转缩放改变尺寸,不存在远处看不清的情况,适用于三维标注。 效果图: ......

使用three.js创建大小不随着场景变化的文字,需要以下两步:

1、将文字绘制到画布上。

2、创建着色器材质,把文字放到三维场景中。

优点:

1、跟用html实现文字相比,这些文字可以被模型遮挡,更具有三维效果。

2、不会随着场景旋转缩放改变尺寸,不存在远处看不清的情况,适用于三维标注。

效果图:

使用three.js创建大小不随着场景变化的文字

 

示例代码1:https://github.com/tengge1/shadoweditor/blob/master/shadoweditor.web/src/object/text/unscaledtext.js

示例代码2:https://gitee.com/tengge1/shadoweditor/blob/master/shadoweditor.web/src/object/text/unscaledtext.js

 

实现方法

 

1、使用canvas绘制文字,先用黑色绘制描边,然后用白色绘制文字。黑色描边主要为了让文字在白色背景处能看清。

 

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);

 

 

2、 创建着色器材质,将文字正对屏幕,渲染到三维场景中。

 

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上绘制的文字边缘是半透明的,材质要设置成半透明才能实现文字边缘平滑效果。

 
unscaledtextvertexshader
 
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
    );
}

 

说明:

a、(0.0, 0.0, 0.0)是平面中心世界坐标,左乘modelviewmatrix和projectionmatrix后,得到屏幕坐标系中的坐标。

b、proj.x / proj.w + position.x * width / domwidth * 2.0的意思是把平板中心放到世界坐标系正确位置,让平板显示的宽度恰好等于屏幕上的像素数,避免文字缩放。

c、乘以2.0是因为three.js默认生成的平板宽度和高度是1,屏幕坐标系宽度和高度都是从-1到1,是2。

d、gl_position.w为1.0时,是正投影,模型大小不随着屏幕深度变化而改变。

 
unscaledtextfragmentshader
 
precision highp float;

uniform sampler2d tdiffuse;
uniform float width;
uniform float height;
 
varying vec2 vuv;
 
void main() {
    // 注意vuv一定要从画布整数坐标取颜色,否则会导致文字模糊问题。
    vec2 _uv = vec2(
        (floor(vuv.s * width) + 0.5) / width,
        (floor(vuv.t * height) + 0.5) / height
    );

    gl_fragcolor = texture2d( tdiffuse, _uv );
}

 

说明:

1、uv坐标一定要恰好对应画布上的像素点,否则会导致文字模糊问题。

 

文字模糊的解决方法

使用three.js或webgl绘制文字,很容易遇到文字模糊的问题,主要有以下几个方面的原因。
 
 
1、canvas上绘制线条,是从两个像素中心点画的。
 
在整数像素处绘制1px的线条,其实在1px线条两边,都有0.5px半透明的线条,实际绘制了2px。绘制时,一定要从(整数+0.5px)像素开始绘制。
 
具体参考《canvas画布解决1px线条模糊的问题》:
 
在上面的代码中,字体大小和线宽都是偶数,不存在这个问题。
 
 
2、根据uv坐标从贴图取色的时候,一定要恰好取到贴图上的整数像素,否则会进行颜色插值,导致模糊。
 
我被这个问题卡了很久,具体现象就是随着视角改变,文字有时候清晰,有时候模糊,一闪一闪的。
 
解决方法就是在片源着色器中对自动插值的uv坐标进行“取整”,恰好取到(整数+0.5像素)。为什么加0.5,看上面《canvas画布解决1px线条模糊的问题》的文章。
 
实现代码:
 
vec2 _uv = vec2(
    (floor(vuv.s * width) + 0.5) / width,
    (floor(vuv.t * height) + 0.5) / height
);

 

其中,width和height分别是贴图的宽度和高度。
 
3、gl_position.xy恰好对应屏幕上的像素点。
 
这是我猜测的一个原因,根据原因2进行修改后,文字不模糊了。所以,这个没有仔细测试。
 
 

参考资料

1、基于three.js的开源三维场景编辑器:https://github.com/tengge1/shadoweditor
2、canvas画布解决1px线条模糊的问题: