Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

three.jsでcanvasを使ってテキストを表示させる方法

three.jsを使ってテキストを表示させる方法はいくつかあると思います。

  1. TextGeometryを使う
  2. Bitmap Fontsを使う
  3. canvasにテキストを描画してテクスチャとして使う

大きくこの3つの方法があるのかと思いますが今回はcanvasにテキストを描画してテクスチャとして使う方法を紹介していきます。

qiita_cap.gif
デモはこちら
ソースコードはこちら
(本編の記事と内容が若干異なります)

テキストを表示させる大まかな流れ

  • 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の長さを可変にします。

テクスチャを作る.js
  /**
   * 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ビルトインのattributesuniformsを自動で付加してくれます。自動で付加されたくない場合はRawShaderMaterialを使います。

ビルトインattributesuniformsはこちら。

builtInVertexShader.glsl
// = 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

あらためてこちらがメッシュを作る部分。

メッシュを作る.js
  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関数を使いテキストをタイル状に表示させ左方向に流します。

frag.glsl
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は特に変わったことはしていません。

vert.glsl
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);
}

あとは実行するだけです。

main.js
import Canvas from './Canvas/_index';

document.addEventListener('DOMContentLoaded', () => {
  new Canvas();
});

まとめ

というわけでcanvasを使ってテキストを描画する方法を紹介しました。割と簡単な方法なのですが、スマホだったり高解像度ディスプレイで見るとテキストがかなりギザギザしてしまいます。もっと綺麗に描画したい!となったらBitmap Fontaを使ったほうがいいかもしれません。

↓再掲
デモはこちら
ソースコードはこちら
(本編の記事と内容が若干異なります)


参考

techniques-for-rendering-text-in-threejs
Creating text
CanvasTexture

mtoutside
フロントエンドエンジニアを3年ほど。 WebGL / GLSL / p5.js / React勉強中。メディア運営会社でサイト運営・企画・製作 → Web製作会社 → Web製作会社(広告寄り)
https://www.develooop.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away