Webでできる表現を広げよう、PixiJS編

2021.11.29

2022.02.02

Fabeee社員ブログ


皆様初めまして。今年8月に入社しました、たまと申します。
今までは主にインフラとサーバーサイドのエンジニアとして働いてきた私ですが、
ありがたいことに今はフロント周りのお仕事もいただいております。
その中で私自身が魅了され極めたい!と思った技術がありましたのでそれについてご紹介させていただければと思います。
その技術というのは「WebGL」です。
WebGLとは、OSネイティブのアプリケーションで2D/3D表現を実現できるOpenGLのWeb版と呼ばれる技術でJavaScriptから利用することができるAPIです。
WebGLではウェブブラウザ上でプラグインなしで動作するためスマートフォンでも迫力のある表現を可能にします。
今回はそんなWebGLをPixiJSという2D表現を得意とするライブラリを使用して面白さを体感していけたらと思います。

PixiJSとは

PixiJSの概要

PixiJSは2D表現に特化した、レンダリングエンジンです。
PixiJSはCSSやSVGとは比にならない表現力とWebGLを活用した圧倒的なパフォーマンスを提供します。
活用事例として以下のようなものが挙げられます。
・ブラウザゲーム
・画像の操作や加工機能が求められるシミュレータ
・装飾や特殊なインタラクションによる近未来的なWeb表現の実現
など

上記の通り、PixiJSは2Dグラフィックを得意とし、3D表現も扱えなくもないですが、3Dモデルのレンダリングにはthree.jsやbabylon.jsなどが推奨されています。
多くのグラフィックライブラリでは、ステージ上に各オブジェクト(文字や画像などを表示したオブジェクト)を貼り付けて行くような感覚で描画していきます。
PixiJSの扱い方も同じで、他の描画ライブラリを利用した方であれば容易に理解することができると思います。

PixiJSの特徴

Fast

PixiJSの強みはスピードです。
2Dレンダリングに関しては、PixiJSが最速であると公式が謳っています。

Flexible

フレンドリーで機能豊富なAPIを有しており、基本的な処理をPixiJSが実現してくれるため開発者は高度な技術を必要としません。
また、あらゆるマルチプラットフォームに対応しています。

Free

PixiJSはオープンソースであり続け、大規模なコミュニティがその成長と進化を推進しています。

PixiJSを試す

ライブラリの用意

cdnの場合は以下で利用できます。

cdn経由

script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/6.2.0/browser/pixi.min.js"></script>
※執筆時点では6.2.0が最新です。

npm経由

npmの場合は以下のようにします。
インストール

npm install pixi.js

インポート

import * as PIXI from 'pixi.js'

レンダリング

画像を描画する

では、手始めに画像をレンダリングしてみたいと思います。
PixiJSには簡単に描画をサポートしてくれる「PIXI.Application」というコンポーネントがありますのでこちらを利用してみます。
・index.html(一部抜粋)
※htmlファイルは全て共通です。

<body>
 <div>
  <canvas id="2d"></canvas>
 </div>
</body>
・Javascript
const app = new PIXI.Application({
    width: 760,
    height: 400,
    view: document.getElementById('2d'),
    resolution: window.devicePixelRatio || 1,
    autoResize: true,
    backgroundColor: 0xafeeee,
});
const sprite = PIXI.Sprite.from('./snow_yukiotoko.png');
app.stage.addChild(sprite);

 

PixiJSではcanvas要素を介してレンダリングを実現します。また、PIXI.Applicationにいつくかのオプションを指定することができ、canvas要素のサイズやcanvas要素の指定、背景色など設定できます。

オプション値 意味
width 描画要素(canvas)の横幅
height(canvas) 描画要素の高さ
view 描画要素(canvas)の指定
resolution レンダラーの解像度/ピクセル密度
autoResize CSSでのwidth/heightの指定を適用するかどうか
backgroundColor 背景色。16進数で指定し、先頭は#ではなく0x

そして、画像は「PIXI.Sprite」コンポーネントでパスを指定できます。

const sprite = PIXI.Sprite.from('./snow_yukiotoko.png');
最後に上記で生成した画像オブジェクト(PIXI.Sprite)をPIXI.Applicationに展開します。
app.stage.addChild(sprite);

これでPixiJSの機能を利用してレンダリングすることができます。


今回使用した画像サイズは380*400でして、表示位置(PIXI.Sprite.position)の座標はデフォルトで(0,0)ですので、描画要素のサイズを760*400に設定しているため左半分に画像が描画されていることがわかります。
以上のようにして、レンダリングの流れがわかったところで次はPixiJSのいくつかの機能を見て行きたいと思います。

PixiJS機能例

アニメーション

PixiJSの「PIXI.extras.AnimatedSprite」では、スプライトシートか、複数の静止画像を用いることで連続する2D表現を実現できます。
・script.js

const app = new PIXI.Application({
const app = new PIXI.Application({
    view: document.getElementById('2d'),
    resolution: window.devicePixelRatio || 1,
    autoResize: true,
    backgroundColor: 0x1099bb
});

const spriteWidth = 512;
const spriteHeight = 512;
// テクスチャの配列を用意する
const frames = [
    PIXI.Texture.from('img/Jump (1).png'),
    PIXI.Texture.from('img/Jump (2).png'),
    PIXI.Texture.from('img/Jump (3).png'),
    PIXI.Texture.from('img/Jump (4).png'),
    PIXI.Texture.from('img/Jump (5).png'),
    PIXI.Texture.from('img/Jump (6).png'),
    PIXI.Texture.from('img/Jump (7).png'),
    PIXI.Texture.from('img/Jump (8).png'),
    PIXI.Texture.from('img/Jump (9).png'),
    PIXI.Texture.from('img/Jump (10).png'),
    PIXI.Texture.from('img/Jump (11).png'),
    PIXI.Texture.from('img/Jump (12).png'),
    PIXI.Texture.from('img/Jump (13).png'),
    PIXI.Texture.from('img/Jump (14).png'),
    PIXI.Texture.from('img/Jump (15).png'),
];
// テクスチャの配列からAnimatedSpriteを作成する
const animSprite = new PIXI.extras.AnimatedSprite(frames);
animSprite.x = spriteWidth
animSprite.y = spriteWidth / 2;
animSprite.pivot.set(spriteWidth / 2, spriteHeight / 2);
animSprite.animationSpeed = 0.3;
// アニメーション再生開始
animSprite.play();
app.stage.addChild(animSprite);

インタラクション

PixiJSでは、表示オブジェクトにインタラクティブ性を持たせることも可能です。
また、「PIXI.Renderer」は描画機能を提供しており、PIXI.Applicationはこの機能を内容しているため
明示的に描画の命令を出す必要がありません。細かい制御等を行いたい時はPIXI.Rendererを活用すると良いでしょう。
そして、「PIXI.Container」は表示オブジェクトを集合体として管理できます。各ステージ(scene)ごとにオブジェクトをPIXI.Containerで管理しておけば表示の切り替えが容易になります。
・script.js

// Rendererの作成
const dragRenderer = new PIXI.Renderer({
    width: document.getElementById('gl').clientWidth,
    height: document.getElementById('gl').clientHeight,
    view: document.getElementById('2d'),
    autoDensity: true,
    forceFXAA: true,
    clearBeforeRender: true,
    preserveDrawingBuffer: true,
    resolution: window.devicePixelRatio || 1,
    backgroundColor: 0x1099bb,
});

// コンテナーの生成
const dragStage = new PIXI.Container();

// ローダーの生成
const dragLoader = new PIXI.Loader();
dragLoader.add('dragImg', './baseball_ball.png');

// ロードされたら描画
dragLoader.load((dragLoader, resources) => {
    dragLoader.reset();
    setup(resources);
});

function setup(resources) {
    //Create the `dragImg` sprite from the texture
    let dragImg = new PIXI.Sprite(resources.dragImg.texture);

    // インタラクティブ性を持たせる。
    // マウスやタッチイベントで操作できるように
    dragImg.interactive = true;
    // マウスオーバーしたら、アイコンを矢印から手に
    dragImg.buttonMode = true;
    // アンカーポイントの位置。中心なら0.5
    dragImg.anchor.set(0.5, 0.5);
    //拡大率
    dragImg.scale.set(0.3, 0.3);


    // イベントを設定
    dragImg
        .on('pointerdown', onDragStart)
        .on('pointerup', onDragEnd)
        .on('pointerupoutside', onDragEnd)
        .on('pointermove', onDragMove);

    // レンダリングの絶対位置
    dragImg.x = dragRenderer.width / 2;
    dragImg.y = dragRenderer.height / 2;


    //stageに画像を追加
    dragStage.addChild(dragImg);
    //レンダリング
    dragRenderer.render(dragStage);
}

function onDragStart(event) {
    this.data = event.data;
    this.alpha = 0.5;
    this.dragging = true;
    dragRenderer.render(dragStage);
}

function onDragEnd() {
    this.alpha = 1;
    this.dragging = false;
    this.data = null;
    dragRenderer.render(dragStage);
}

function onDragMove() {
    if (this.dragging) {
        const newPosition = this.data.getLocalPosition(this.parent);
        this.x = newPosition.x;
        this.y = newPosition.y;
    }
    dragRenderer.render(dragStage);
}

メッシュ操作

「PIXI.SimplePlane」ではテクスチャとして読み込んだ画像に対してメッシュを形成し、その頂点を操作することで複雑な変形を実現することができます。
・script.js

const app = new PIXI.Application({
    view: const area = document.getElementById('2d'),
    backgroundColor: 0x1099bb
});

const container = new PIXI.Container();
const texture = PIXI.Texture.from('./youkai_nurikabe.png');

texture.once('update', function () {

        // 頂点数
    const verticesX = 3;
    const verticesY = 3;
    const mesh = new PIXI.SimplePlane(texture, verticesX, verticesY);

    mesh.x = 200;
    mesh.y = 200;
    mesh.height = 0.7;
    mesh.width = 0.7;
    mesh.scale.x = 0.3
    mesh.scale.y = 0.3
    container.addChild(mesh);
    app.stage.addChild(container);

    app.ticker.add(function (delta) {
        const uvs = mesh.geometry.buffers[0].data;
        for (let i = 0; i < uvs.length; i++) {
            uvs[i] += Math.floor(Math.random() * Math.floor(3)) - 1;
        }
        mesh.geometry.buffers[0].update();
    })

});

3D

PixiJSでは高度に3Dオブジェクトを扱うことも可能で、その場合は以下のライブラリを導入することで実現できます。

<script src="https://cdn.jsdelivr.net/npm/pixi3d@1.0.0/dist/pixi3d.min.js"></script>

or

npm install pixi3d

・script.js

const app = new PIXI.Application({
    view: document.getElementById('2d'),
    antialias: true,
    backgroundColor: 0xffffff
});

let mesh = app.stage.addChild(PIXI3D.Mesh3D.createCube());

let light = Object.assign(new PIXI3D.Light(), {
    x: -1,
    z: +3
});
PIXI3D.LightingEnvironment.main.lights.push(light);

let rotation = 0;
app.ticker.add(() => {
    mesh.rotationQuaternion.setEulerAngles(0, rotation++, 0);
});

まとめ

いかがでしたでしょうか。PixiJSの可能性は無限大で、もっと多くのそして複雑な表現が可能です。また、2d/3dグラフィックは、Webエンジニアとして働く方々には今後避けては通れない技術になっていくものだと思いますのでこの機会に是非グラフィック表現に慣れておくのもありなのではないでしょうか!今回は2D表現に特化したライブラリを使用しましたが、3D表現を得意とするthree.jsなども今後は触れて行きたいと思います!

この記事の監修者:杉森由政

この記事の監修者:杉森由政

2018年2月Fabeee株式会社CTOに就任。現在は、新規事業開発部門の責任者を務めるともに、AIやブロックチェーンなどの先端技術を用いた研究開発も担当。また広島大学との連携研究においては、生体データを解析する人工知能および解析アルゴリズムの研究開発にも携わるなど、多岐に渡り活躍をしている。
>>プロフィール情報