Three.jsで日本語テキストを表示する方法(TextGeometryを使わない軽量手法)
Three.jsで日本語テキストを表示したいけれど、TextGeometryではうまくいかない…
そんな悩みを持つ方は多いと思います。
Three.jsの一般的な方法は、FontLoaderでフォントJSON(typeface.json)を読み込み、
TextGeometry / TextBufferGeometry を使って3D文字を生成する手法です。
しかし、この方法には大きな問題があります。
・標準の helvetiker_regular.typeface.json などは英字のみで、日本語に非対応
・日本語フォントをJSON化すると数MB〜数十MBになり、読み込みが非常に重い
・文字数が増えるほどジオメトリが肥大化し、パフォーマンスが低下
・Webフォントをそのまま使えないため、実務では扱いづらい
そこで本記事では、TextGeometryを使わずに日本語テキストを表示する軽量で柔軟な方法を解説します。
表示したい文字列からJavaScriptで動的に透過PNGを生成し、その画像をテクスチャとして
平面ジオメトリに貼り付けることで、3D空間に美しい日本語テキストを表示できます。
実例
マウス左ドラッグで回転、右ドラッグでカメラの移動、ホイールで拡大縮小できます。
スワイプで回転、画面に2本指を触れてなぞるとカメラの移動、ピンチイン/アウトで拡大縮小できます。
ソースコード
・・・
//■文字を表示する処理
//文字を画像に変換
png=CreateTextPng("文字を表示","96px","#ffffff");
//文字の画像のテクスチャをロード
textureText = new THREE.TextureLoader().load( png.img );
//マテリアル(材質)の作成
materialText=new THREE.MeshBasicMaterial({
color:0xffffff, map:textureText ,side:THREE.FrontSide,
transparent:true, opacity:1.0,
});
//平面ジオメトリの作成
planeGeo=new THREE.PlaneGeometry(png.w/10, png.h/10,1,1);
//メッシュの作成
meshText=new THREE.Mesh(planeGeo,materialText);
meshText.position.set(0, 0, 10.1);
scene.add(meshText);
・・・
//(文字列, 文字サイズ, 文字色)を指定すると{img:png画像, w:幅, h:高さ}を返す
function CreateTextPng(text,size,color){
let can=document.createElement("canvas");
let ctx=can.getContext("2d", {willReadFrequently:true});
family=
'Verdana,Roboto,"Droid Sans","游ゴシック",YuGothic,"メイリオ",Meiryo,'+
'"ヒラギノ角ゴ ProN W3","Hiragino Kaku Gothic ProN","MS Pゴシック",sans-serif';
ctx.font=size+" "+family;
ctx.baseLine="top";
ctx.textAlign="left";
let measure=ctx.measureText(text);
can.width=measure.width;
can.height=measure.fontBoundingBoxAscent+measure.fontBoundingBoxDescent;
//幅高さを変更すると再設定が必要になる
ctx.font=size+" "+family;
ctx.baseLine="top";
ctx.textAlign="left";
//透明にする
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle="rgb(255,255,255)";
ctx.fillRect(0,0,can.width,can.height);
//通常描画にする
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle=color;
ctx.fillText(text,Math.abs(measure.actualBoundingBoxLeft),measure.actualBoundingBoxAscent);
let png=can.toDataURL('image/png');
return {img:png, w:can.width, h:can.height};
}
