phina.jsでレイヤーと仲良くなろう

phina.js Advent Calendar 2015の10日目です。

グループ管理の基本テクニック by alkn203さん←前 次→physicalクラスを使ってみよう by alkn203さん

レイヤー!

プログラマーたるものたくさんのレイヤーと仲良くしていきたいものです。複数のレイヤーをとっかえひっかえというのも夢のある話です。

シーンは自分用のCanvas要素を持つ

ご存知の通り、phina.jsの前身にあたるライブラリにtmlib.jsというものがあります。

tmlib.js

tmlib.jsと比べてphina.jsでは、SceneクラスがCanvasSceneクラスという名前に変わり、Canvas APIを利用するアプリケーションへ特化した形に再構成されています。

このCanvasSceneは内部にCanvas要素を持っています。

以前(tmlib.js時代)はカレントのシーンがアプリケーション固有のCanvasへ直接描き込んでいたのに対し、phina.jsではシーンが自前のCanvasに描画した後、その結果をアプリケーションのCanvasへ転写します。

  • tmlib.jsの描画方式
  • phina.jsの描画方式

phina.display.ThreeLayer

この「自分固有のCanvas要素を持ち、それへの描画をしたのち、本命のCanvas要素へ転写する」という方式。実はCanvasScene特有のものではなく、どんな要素でも実現することが出来ます。

このしくみをうまく応用しているのがphina.display.ThreeLayerです。

ThreeLayerサンプル

ThreeLayerはThree.jsとphina.jsをゆるく連携する機能です。Three.jsを使って固有のCanvasへの描画を行い、その描画結果を親要素のCanvasに転写しています。

  • phina.display.ThreeLayerのソース(抜粋)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
init: function(params) {
...
// Three.jsのレンダラー
this.renderer = new THREE.WebGLRenderer();
...
// 毎フレームでThree.jsの描画処理を行う
this.on('enterframe', function() {
this.renderer.render( this.scene, this.camera );
});
},
draw: function(canvas) {
// Three.jsによって描画された結果をcanvasに転写
var domElement = this.renderer.domElement;
canvas.context.drawImage(domElement, 0, 0, domElement.width, domElement.height);
},

自分でレイヤーを作ってみる

このようなレイヤーを独自に実装することも出来ます。

ポイントは以下の2つ。

phina.display.CanvasRendererクラス

要素ツリーをスキャンし、各要素をCanvasに描く機能を持ったクラスです。

HTMLCanvasElementを内包しています。

phina.display.CanvasSceneクラスやphina.display.Layerクラスはこのクラスのオブジェクトをメンバに持っています。

renderChildBySelfプロパティ

「子孫要素の描画は自身で行う」という宣言です。

trueが設定されている場合、CanvasRendererはその要素の子孫を描画しません。

デフォルトではfalseが設定されています。

描画スキップレイヤー - 描画スキップによる高速化

大量の子孫要素を持つ要素の場合、描画処理を数フレームに1回だけ行うなどしてアプリケーション全体のフレームレートを改善できる場合があります。

自前の非表示Canvasへの描画結果をキャッシュしておき、描画処理を行わないフレームではキャッシュを使用します。

手元の環境では上記「スキップなし」で20FPSほどであるのに対し、「スキップあり」版では30FPSほどに向上させることが出来ました。

もちろん、描画を省略するぶん見た目がカクカクしてしまうわけですが、弾幕系シューティングゲームの背景など特にプレイヤーが注意を払う必要のないものについては検討する価値があるかもしれません。

画像フィルタ処理レイヤー - WebGLを使った画像処理

phina.display.GLFilterLayer

子孫要素を非表示Canvasに描画した後、WebGLのテクスチャとして使用して画像にフィルタをかけ、その結果をシーンのCanvasに転写する実装です。

まとめ

特殊な描画処理をレイヤーとして切り出すことで、汎用的なエフェクト機能として実装することが出来ます。