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

使用three.js建立大小不随着场景变化的文字,须要如下两步:html

一、将文字绘制到画布上。git

二、建立着色器材质,把文字放到三维场景中。github

优势:canvas

一、跟用html实现文字相比,这些文字能够被模型遮挡,更具备三维效果。dom

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

效果图:测试

 

示例代码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.jsspa

 

实现方法

 

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

 

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

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

 

说明:

一、uv坐标必定要刚好对应画布上的像素点,不然会致使文字模糊问题。

 

文字模糊的解决方法

使用three.js或WebGL绘制文字,很容易遇到文字模糊的问题,主要有如下几个方面的缘由。
 
 
一、canvas上绘制线条,是从两个像素中心点画的。
 
在整数像素处绘制1px的线条,其实在1px线条两边,都有0.5px半透明的线条,实际绘制了2px。绘制时,必定要从(整数+0.5px)像素开始绘制。
 
具体参考《canvas画布解决1px线条模糊的问题》: https://www.jianshu.com/p/c0970eecd843
 
在上面的代码中,字体大小和线宽都是偶数,不存在这个问题。
 
 
二、 根据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分别是贴图的宽度和高度。
 
三、 gl_Position.xy刚好对应屏幕上的像素点。
 
这是我猜想的一个缘由,根据缘由2进行修改后,文字不模糊了。因此,这个没有仔细测试。
 
 

参考资料

一、基于three.js的开源三维场景编辑器: https://github.com/tengge1/ShadowEditor
二、canvas画布解决1px线条模糊的问题: https://www.jianshu.com/p/c0970eecd843
相关文章
相关标签/搜索