使用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
相關文章
相關標籤/搜索