Three.jsで常に正面を向く長方形の文字ラベルを描画する

Tatsuya Asami
11 min readJul 12, 2020

【概要】

1ファイルで作った雑実装ですがレポジトリはこちら。

  • 下記のgifのようなかめらw常に正面を向くオブジェクトを作成する。
  • カメラを移動させても常に正面を向くオブジェクトを作る
  • オブジェクトの形を正方形以外にする。
  • オブジェクトに文字を載せ、文字を載せるオブジェクトの形を長方形にする

カメラを移動させても常に正面を向くオブジェクトを作る

常に正面を向くオブジェクトはSpriteを使って作成する。

一般的なthree.jsで作成するオブジェクトは、geometryとmaterialを組み合わせたMeshを作成し、それをsceneに加える。
画面中央の緑色の立方体の場合は下記のようになる。(余談だが、少し透明度を加えるとダサさがかなり軽減される。transparentとopacityはセットで記述する必要がある。)

const geometry = new THREE.BoxGeometry(30, 30, 30);
const material = new THREE.MeshLambertMaterial({
color: 'green',
transparent: true,
opacity: 0.7,
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

Spriteの場合は、描画したい画像等をtextureに載せ、そのtextureをmaterialにマッピングする。ここまではSpriteだけではなく、他のオブジェクトを作成する時と同じである。

const createTexture = (filePath) => {
return new THREE.TextureLoader().load(filePath);
};
const wideImageTexture = createTexture(
'https://dummyimage.com/200x100/4a9e62/fff.png'
);

Spriteを作成する場合は、これをmaterialに載せるだけで良い。

// spriteを作成し、sceneに追加
const createSprite = (texture, scale, position) => {
const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
const sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(scale.x, scale.y, scale.z);
sprite.position.set(position.x, position.y, position.z);
scene.add(sprite);
};
createSprite(
wideImageTexture,
{ x: 30, y: 30, z: 30 },
{ x: 20, y: 20, z: 20 }
);

だが、Spriteは形やサイズを指定する方法が存在しない。scaleで倍率を変えることしかできない。元の画像の比率に関わらず正方形が出来てしまう。

元の画像は200x100のサイズ。

scaleでしか形を変えることができないので、拡大率を調整する。この場合はx方向に2倍長いので、x方向のscaleを2倍にしてあげるとちょうど元の画像と同じように見える。ちなみにSpriteの場合zのscaleは影響しない。

createSprite(
wideImageTexture,
{ x: 60, y: 30, z: 30 }, // xのscaleを200/100倍する。
{ x: 70, y: 20, z: 20 }
);

強引なロジックだが、かなり綺麗に描画される。

Spriteに文字を載せる

まずthree.jsに文字を描画する方法は公式のドキュメントにあるように何種類か存在する。

  1. DOM + CSS
  2. canvasにテキストを描き、textureとして使う。
  3. 別の 3D作成アプリで作成し、three.jsで読み込む。
  4. text geometryを使う。

3は1つのアプリで完結しないとなると、動的に文字を変えたい場合に何も出来ない。1も一般的にthree.jsで使うwebGLRendererが使用できないっぽいので使えない。4は英語以外のフォントを用意するのに手間がかかるのと、そもそも容量が増えてしまうのが避けられなさそう。(参考)

消去法で2のcanvasを使う一択となる。これはSpriteに限らず文字を扱いたい場合は共通。

canvasに文字を描画し、そのcanvasをtextureとしてmaterialに載せる。使用するオブジェクトが変わると出来そうなことが出来なくなることがあるthree.jsで、canvasが自由に使える安心感はかなり大きい。

canvas要素を作成

ここは完全にcanvasの仕様通り。materialとして使用する際に、canvasのサイズが小さいと文字がぼやけるので、適度に大きくした方がいい。

const createCanvasForTexture = (canvasWidth, canvasHeight, text, fontSize) => {
// 貼り付けるcanvasを作成。
const canvasForText = document.createElement('canvas');
const ctx = canvasForText.getContext('2d');
ctx.canvas.width = canvasWidth; // 小さいと文字がぼやける
ctx.canvas.height = canvasHeight; // 小さいと文字がぼやける

// 透過率50%の青背景を描く
ctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
//
ctx.fillStyle = 'black';
ctx.font = `${fontSize}px serif`;
ctx.fillText(
text,
// x方向の余白/2をx方向開始時の始点とすることで、横方向の中央揃えをしている。
(canvasWidth - ctx.measureText(text).width) / 2,
// y方向のcanvasの中央に文字の高さの半分を加えることで、縦方向の中央揃えをしている。
canvasHeight / 2 + ctx.measureText(text).actualBoundingBoxAscent / 2

);
return canvasForText;
};

fillTextの第2, 3引数が文字を描画し始める位置を表しているが、文字の位置を揃えるのに、canvasnの measureText() を使うといい。

(canvasWidth - ctx.measureText(text).width) / 2

canvasWidth — ctx.measureText(text).width で余白のサイズを割り出している。その余白の半分を始点にするとちょうど真ん中になる。

canvasHeight / 2 + ctx.measureText(text).actualBoundingBoxAscent / 2

canvasHeight / 2 でキャンバスの高さ中央を算出、これに文字自体の大きさを考慮して、ctx.measureText(text).actualBoundingBoxAscent / 2 を加えるとちょうど中心になる。(actualBoundingBoxAscentはMDN参照)

生成されたcanvasをcanvasTextureに載せ、それをspriteMaterialにマッピングする。

const canvasTexture = new THREE.CanvasTexture(
createCanvasForTexture(500, 500, 'Hello World!', 40)
);
const scaleMaster = 70;
createSprite(
canvasTexture,
{
x: scaleMaster,
y: scaleMaster,
z: scaleMaster,
},
{ x: -70, y: 70, z: -70 }
);

Spriteの形を長方形にしたい。

canvasのサイズとscaleの倍率をうまく組み合わせると、長方形のSpriteが作成できる。

const canvasWidth = 500;
const canvasHeight = 140;
const canvasRectTexture = new THREE.CanvasTexture(
createCanvasForTexture(canvasWidth, canvasHeight, '寿司鯖あ、。カナカナ', 50)
);
createSprite(
canvasRectTexture,
{
x: scaleMaster,
// 縦方向の縮尺を調整
y: scaleMaster * (canvasHeight / canvasWidth),

z: scaleMaster,
},
{ x: 50, y: 50, z: 50 }
);

ご覧の通り綺麗に文字が描画された長方形のSpriteを、綺麗に作成することができた。日本語にも対応している。

scaleとcanvasのサイズを連動させているので、サイズを変えても文字は潰れたり伸びたりしない。

const canvasWidth = 500;
const canvasHeight = 50;
const canvasRectTexture = new THREE.CanvasTexture(
createCanvasForTexture(
canvasWidth,
canvasHeight,
'Hello World World World!',
40
)
);
createSprite(
canvasRectTexture,
{
x: scaleMaster,
// 縦方向の縮尺を調整
y: scaleMaster * (canvasHeight / canvasWidth),
z: scaleMaster,
},
{ x: 50, y: 50, z: 50 }
);

measureText を使えば、テキスト長さに合わせてcanvasのサイズを変えることも可能だと思われる。他のthreeオブジェクトの説明をするテキストなどをSpriteで作成すると、カメラ操作中も説明が読めるので、非常に使い勝手がいいと思う。

--

--