FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

誰でも簡単にWebGLで高速なアニメーションを作る!

こんにちは。菅野です。

突然ですが、WebGLというものをご存知でしょうか?
GPUを使用した3DグラフィックをHTMLページ内にガンガン描画できる仕組みです。

3Dだけしかできないという訳ではなく、2Dでの描画もすることが出来ます。
この場合もGPUの恩恵を受けられ、より高速に描画できます。

今回はHTMLのCanvasタグでWebGLで手軽に高速な2Dのアニメーションを作る方法について紹介したいと思います。

CanvasWebGLの対応状況

HTML5では自由にお絵描きが出来るCanvasタグというものがあり、ここに描画するための仕組みとして2dコンテキストがあります。
基本的にはCanvasタグではこれを使って2D画像を描画します。

さらにGPUをバリバリ使って描画が出来るwebglコンテキストがあるのですが、これが使用できるかどうかは環境に依ります。

Can I use... Support tables for HTML5, CSS3, etc
対応状況は上記のリンクを参照してください。

一昔前、canvasで2Dアニメーションを作った時にはWebGLはモバイル環境では全滅でした。
でも、現在はiOSならほぼ確実に使えて、Androidはもう少しで安心して使えるようになるといった状況だと思います。

CreateJS

直接2dコンテキストやwebglコンテキストを使用すると、大変すぎて爆死するのでもう少し楽にやる方法を紹介します。

createjs.com
CreateJSというライブラリを使用することで割と簡単に2Dアニメーションを作れます!

早速使ってみましょう。

とりあえず使う
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>canvas</title>
        <meta name="viewport" content="width=630px, user-scalable=0">
        <style>
            body {
                margin: 0;
            }
        </style>
        <script src="http://code.createjs.com/createjs-2015.11.26.combined.js"></script>
        <script>
            function run() {
                // Stageは全てのオブジェクトのルートになるオブジェクトでcanvasを保持している
                var stage = new createjs.Stage("canvas");
                var height = stage.canvas.height;
                var width = stage.canvas.width;
                // 画像はBitmapクラスを使う
                var logo = new createjs.Bitmap("http://www.septeni-original.co.jp/img/common/logo.gif");
                // 位置調整
                logo.regX = 110;
                logo.regY = 72;
                logo.x = 110;
                logo.y = 100;
                // Tweenというものを使うと、簡単にアニメーションっぽいのが作れる
                createjs.Tween.get(logo, {loop: true, paused: false})
                        .to({y: height}, 600, createjs.Ease.sineIn)
                        .to({y: 100}, 600, createjs.Ease.sineOut);
                createjs.Tween.get(logo, {loop: true, paused: false})
                        .wait(500)
                        .to({scaleY : 0.4}, 100, createjs.Ease.sineIn)
                        .to({scaleY : 1}, 100, createjs.Ease.sineIn)
                        .wait(500);
                createjs.Tween.get(logo, {loop: true, paused: false})
                        .to({x: width - 110}, 1370)
                        .to({x: 110}, 1370);
                // 描画したいオブジェクトは、Stageに配置すると描画される
                stage.addChild(logo);
                // フレームレートを指定する
                createjs.Ticker.framerate = 60;
                // 毎フレームごとの処理を扱うハンドラーを登録
                createjs.Ticker.addEventListener("tick", handleTick);
                function handleTick(event) {
                    // updateメソッドで、Stageの状態が描画させる。
                    // 毎フレームごとに状態を描画することでアニメーションが出来上がる!
                    stage.update();
                }
            }
        </script>
    </head>
    <body onload="run()">
        <canvas id="canvas" width="630" height="550"></canvas>
    </body>
</html>

いきなりサンプルの完成形を貼付けましたが、一つずつ解説していきます。

まず、CreateJSのライブラリを読み込みます。CDNで配信されているのでscriptタグを追加するだけです。
<script src="http://code.createjs.com/createjs-2015.11.26.combined.js"></script>

次に必要なのは、canvasタグです。適当に大きさを指定しておきます。また、bodyのonloadイベントハンドラにメイン処理を行う関数を指定しておきます。

<body onload="run()">
        <canvas id="canvas" width="630" height="550"></canvas>
</body>

これで準備は完了で、これからCreateJSを使っていきます。下記のようにしてStageオブジェクトを作成します。引数はcanvasタグのid属性の値です。
new createjs.Stage("canvas");
このStageオブジェクトがすべてのDisplayObjectのルート要素になり、この下に表示したい子要素を追加します。
何らかのGUIライブラリを使ったことがある方なら、よくありがちな、いつものノリで使えます。

new createjs.Bitmap("http://www.septeni-original.co.jp/img/common/logo.gif");
画像を追加するためにはBitmapクラスを使います。引数は画像のパスです。
作成したインスタンスはStageにaddChildします。
これでOKです。簡単ですね。サンプルでは見栄えのために位置調整もしています。

表示ですが、Stageオブジェクトのupdateメソッドを呼び出すと実際にcanvasタグに描画されます。
これを忘れると真っ白なままです。

動かないとつまらないと思う方が大多数だと思うので、それも解説します。
動かしたいオブジェクトのxとかy座標のプロパティを書き換えてupdateメソッドを呼ぶだけです!
が、滑らかに動かすのはコツがいるのでここはTweenというものを使います。

createjs.Tween.get(logo, {loop: true, paused: false})
                        .to({y: height}, 600, createjs.Ease.sineIn)
                        .to({y: 100}, 600, createjs.Ease.sineOut);

TweenのgetメソッドにTweenさせたい対象を渡してTweenオブジェクトを取得します。
取得したオブジェクトのtoメソッドをチエーンさせてアニメーションを作っていきます。
toメソッドの引数は、変更するプロパティと値、変化に要する時間、イージング(どんな風に滑らかに動かすか)の指定です。

これでy(上下方向)のプロパティはスムーズに変化するのですが、stageのupdateメソッドを呼び続けないと画面が更新されません。
これをどうにかするためにTickerを使います。

// フレームレートを指定する
createjs.Ticker.framerate = 60;
// 毎フレームごとの処理を扱うハンドラーを登録
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
    // updateメソッドで、Stageの状態が描画させる。
    // 毎フレームごとに状態を描画することでアニメーションが出来上がる!
    stage.update();
}

フレームレートを設定し、毎フレームごとに登録した関数を呼び出してくれる便利な仕組みです。
stageのupdateだけではなく、毎フレーム毎に行う計算ロジックを動作させるのにも使えます。

動かすとこんな感じになります。 http://zakknak.github.io/webgl-test/sample1

その2

画像だけではなく、図形も描画できます。
http://zakknak.github.io/webgl-test/sample2

ソースは以下参照。
https://github.com/zakknak/webgl-test/blob/gh-pages/sample2/index.html

ほかにいろいろなことをしようと思っても、CreateJSはドキュメントがしっかりしているので使用方法に困ることはありません。
これで2dコンテキストで、自由に描画できるようになりました!

で、WebGL

今までは2dコンテキストで描画をしていました。 滑らかに動いているのですが、いろいろ作り込むとどんどん動作が重くなってきます。(特に大量のオブジェクトを描画するとき)

そこでWebGLの力に頼ります。

CreateJSでwebglを使用するためには、Stageの代わりにSpriteStageを使います。
SpriteStageはStageを継承したクラスでStageと使い方は一緒です。ですが、webglで描画しやすいオブジェクトしかaddChildできないという制約があります。
Bitmapは普通にaddChild出来るので何の問題もありません。

このSpriteStageですが、CDNで配信されているものには含まれていないので別のjsファイルを読み込む必要があります。
どこにもホスティングされていなくて面倒ですが、cdn.rawgit.comを使うとgithubリポジトリから読み込めます。
<script src="//cdn.rawgit.com/CreateJS/EaselJS/0.8.2/lib/webgl-0.8.2.min.js"></script>

これらを使って簡単にWebGLの有り無しでの比較が出来るものを作ってみました。

Shower of Septeni Original Logo

圧倒的にWebGLの方が描画速度が速いのがわかると思います。
また、iPhone6で動作するのも確認しています。

ソース
https://github.com/zakknak/webgl-test/blob/gh-pages/webgl/index.html

ソースに関しての説明は割愛します。 見所は、SpriteStageの制約を避けるために一回Stageで描画したものをSpriteStageに貼付けているところとか、予めPhotoShopとかで作ればいいのにJS上でスプライトシートを作成しているところとか?

まとめ

このように、2DでWebGLを使うのは今すぐにでも始められるのでやってみよう。