使用 three.JS 创建大小不随着场景变化的文字, 需要以下两步:
1, 将文字绘制到画布上.
2, 创建着色器材质, 把文字放到三维场景中.
优点:
1, 跟用 html 实现文字相比, 这些文字可以被模型遮挡, 更具有三维效果.
2, 不会随着场景旋转缩放改变尺寸, 不存在远处看不清的情况, 适用于三维标注.
效果图:
示例代码 1:
示例代码 2:
实现方法
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 线条模糊的问题》: https://www.jianshu.com/p/c0970eecd843
在上面的代码中, 字体大小和线宽都是偶数, 不存在这个问题.
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 线条模糊的问题: https://www.jianshu.com/p/c0970eecd843
来源: https://www.cnblogs.com/tengge/p/11979854.html