three.jsを使ってテキストを表示させる方法はいくつかあると思います。
-
TextGeometry
を使う - Bitmap Fontsを使う
- canvasにテキストを描画してテクスチャとして使う
大きくこの3つの方法があるのかと思いますが今回はcanvasにテキストを描画してテクスチャとして使う方法を紹介していきます。
デモはこちら
ソースコードはこちら
(本編の記事と内容が若干異なります)
テキストを表示させる大まかな流れ
- three.jsを描画するcanvasとは別にテキストを描画するcanvasを用意
- canvasにテキストを描画しテクスチャを生成
- テキストを描画したいメッシュを作成
- 作成したメッシュにテクスチャを送る
ざっとこんな流れ。あと、テクスチャに格納することをよくテクスチャに焼くなんて言います。
まずは下準備。今回は平面を描画するだけなのでパースのかからないOrthographicCamera
を使います。
export default class Canvas {
constructor() {
this.container = document.getElementById('CanvasContainer');
this.setConfig();
// レンダラを作成
this.renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: false,
});
this.renderer.setSize(this.Config.width, this.Config.height);
this.renderer.setPixelRatio(this.Config.dpr);
this.container.appendChild(this.renderer.domElement);
// 関数をthisでバインドして持っておく
this.resizeFunction = this.resize.bind(this);
this.updateFunction = this.update.bind(this);
// リサイズイベントを設定
window.addEventListener('resize', this.resizeFunction);
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, -1);
// レンダリング開始
// 初期化
this.init();
}
init() {
this.createMesh();
this.start();
}
createMesh() {
// 後述
}
setConfig() {
this.Config = {
width: window.innerWidth, // Canvasの幅
height: window.innerHeight, // Canvasの高さ
cameraZ: 1000, // カメラのz座標
dpr: 1, // device pixel ratio
aspectRatio: 1.0, // 画面アスペクト比
};
// 親要素のサイズを取得
const domRect = this.container.getBoundingClientRect();
const width = domRect.width;
const height = domRect.height;
this.Config.dpr = Math.min(window.devicePixelRatio, 2);
this.Config.width = width;
this.Config.height = height;
this.Config.halfWidth = this.Config.width / 2;
this.Config.halfHeight = this.Config.height / 2;
this.Config.aspectRatio = this.Config.width / this.Config.height;
}
resizeScene() {
this.renderer.setSize(this.Config.width, this.Config.height);
}
start() {
this.resize();
this.update();
}
resize() {
this.setConfig();
this.resizeScene();
}
update() {
// 最大60fpsでレンダリングをループ
requestAnimationFrame(this.updateFunction);
this.time = performance.now() * 0.001;
this.material.uniforms.time.value = this.time;
this.renderer.render(this.scene, this.camera);
}
}
canavasにテキストを描画してテクスチャを作る
canvasを使ってテクスチャを作るにはCanvasTexture
を使います。今回はmeasureText
を使って文字数に応じてcanvasの長さを可変にします。
/**
* 2D Canvasからテクスチャを作成する
* @param {Object} options
* @param {stirng} options.text 描画したい文字列
* @param {number} options.fontSize フォントサイズ
* @return {Object} テクスチャを返す。
* @memberof Canvas
*/
createTexture(options) {
// Canvas要素を作成
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// measureTextするためいったん設定
const fontFamily = 'monospace';
ctx.font = `bold ${options.fontSize * Config.dpr}px '${fontFamily}'`;
const textWidth = ctx.measureText(options.text); // 文字の横幅を取得
// dprに対応したサイズを計算
const width = textWidth.width;
const height = options.fontSize * Config.dpr * 0.8; // 文字に合わせて高さを調整。ここの高さは任意で
// 幅を指定
canvas.width = width;
canvas.height = height;
// 中央にテキストを描画
ctx.font = `bold ${options.fontSize * Config.dpr}px '${fontFamily}'`;
ctx.textAlign = 'left';
ctx.textBaseline = 'hanging';
ctx.fillStyle = 'rgba(255, 255, 255, 1.0)';
ctx.fillText(options.text, -5, 0); // 文字が途切れないように調整。数値はよしなに
// ↓canvasの文字を確認したいとき。テキストを描画したcanvasをbodyに追加しているだけです。
// document.body.appendChild(canvas);
// canvas.style.backgroundColor = '#933';
// canvas.style.position = 'relative';
// テクスチャを作成
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = false;
// ↓ここら辺の設定をしておかないとthree.jsでエラーが出る時がある
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBAFormat;
return texture;
}
テキストとcanvasのサイズについて
テキストぴったりのcanvasを作るのがなかなかうまくいかず、微調整をしています。フォントやテキストよってctx.fillText(options.text, -5, 0);
ここら辺の調整が必要になってきます。
// ↓canvasの文字を確認したいとき。テキストを描画したcanvasをbodyに追加しているだけです。
// document.body.appendChild(canvas);
// canvas.style.backgroundColor = '#933';
// canvas.style.position = 'relative';
こうやってテキストを描画したcanvasを表示させてcanvasを調整しているのですがもっとうまい方法ないのかなー・・・
メッシュを作る
メッシュを作ります。今回はただテキストを表示させるだけなので、ジオメトリにはPlaneBufferGeometry
を、マテリアルにはRawShaderMaterial
を使用します。
three.jsのShaderMaterialについての補足
three.jsのShaderMaterialには
- ShaderMaterial
- RawShaderMaterial
のふたつがあります。
ShaderMaterial
ではthree.jsビルトインのattributes
とuniforms
を自動で付加してくれます。自動で付加されたくない場合はRawShaderMaterial
を使います。
ビルトインattributes
とuniforms
はこちら。
// = object.matrixWorld
uniform mat4 modelMatrix;
// = camera.matrixWorldInverse * object.matrixWorld
uniform mat4 modelViewMatrix;
// = camera.projectionMatrix
uniform mat4 projectionMatrix;
// = camera.matrixWorldInverse
uniform mat4 viewMatrix;
// = inverse transpose of modelViewMatrix
uniform mat3 normalMatrix;
// = camera position in world space
uniform vec3 cameraPosition;
///////////////////////////////////////
// default vertex attributes provided by Geometry and BufferGeometry
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
詳しくは↓に書いてあります。
ShaderMaterial
あらためてこちらがメッシュを作る部分。
createMesh() {
const segment = 1;
this.geometry = new PlaneBufferGeometry(2, 2, segment, segment);
// テクスチャの作成 前述の「テクスチャを作る.js」を参照
this.texture = this.createTexture({
text: 'HOGE', // 描画したいテキスト
fontSize: 130, // フォントサイズ
});
// マテリアルの作成
this.material = new RawShaderMaterial({
uniforms: {
texture: { value: this.texture },
time: { value: 0.0 },
speed: { value: 1.0 },
resolution: { value: this.Config.aspectRatio },
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: false,
});
this.mesh = new Mesh(this.geometry, this.material);
this.scene.add(this.mesh);
}
シェーダー
シェーダーを見ていきます。今回はfract
関数を使いテキストをタイル状に表示させ左方向に流します。
precision mediump float;
uniform sampler2D texture;
uniform float time;
uniform float resolution;
uniform float speed;
varying vec2 vUv;
void main() {
float t = time * speed;
vec2 repeat = vec2(8.0, 8.0); // (行, 列)回繰り返し
vec2 uv = fract(vUv * repeat + vec2(t, 0.0));
vec3 color = texture2D(texture, uv).rgb;
gl_FragColor = vec4(color, 1.0);
}
vertexShaderは特に変わったことはしていません。
precision mediump float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
vec3 pos = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
あとは実行するだけです。
import Canvas from './Canvas/_index';
document.addEventListener('DOMContentLoaded', () => {
new Canvas();
});
まとめ
というわけでcanvasを使ってテキストを描画する方法を紹介しました。割と簡単な方法なのですが、スマホだったり高解像度ディスプレイで見るとテキストがかなりギザギザしてしまいます。もっと綺麗に描画したい!となったらBitmap Fontを使ったほうがいいかもしれません。
↓再掲
デモはこちら
ソースコードはこちら
(本編の記事と内容が若干異なります)
参考
techniques-for-rendering-text-in-threejs
Creating text
CanvasTexture