はじめに
こんにちは。お弁当盛りつけ係のあんどうです。
先月のことですが、ウェブ/ゲーム開発者のJack RugileさんのTwitterでの「Three.jsに関するパフォーマンスや効率化のためのTipsや工夫をみんなでまとめよう!」という呼びかけに対して、なんとThree.jsの作者であるmr.doobさんが「すぐに思いつく10のこと」を直接回答してくれていました。
せっかくなので簡単に説明しながら紹介したいと思います。
その1
「できるだけ
Geometry
とMaterial
を再利用する」
同じ形状やテクスチャのメッシュが複数ある場合に、それぞれのメッシュ用に
Geometry
やMaterial
をインスタンス化してしまうとその分メモリも食いますし、GLとのデータのやり取りや状態の変更が無駄に発生します。特にマテリアルについてはnew THREE.Mesh(
geom,
new THREE.MeshPhoneMaterial({color:0xff0000})
);
みたいな感じで、ついメッシュ作成時に合わせて作成してしまうこともあるかと思いますが、パフォーマンスが気になるようならそういったことは避けましょう。
その2
「できるだけ
Geometry
ではなくBufferGeometry
を使用する」
Three.jsでパフォーマンスを気にする場合の鉄則です。
BufferGeometry
を使用することでプロパティへのアクセスが若干面倒になりますが、そのかわりに内部的なGLとのデータのやり取りを大幅に減らすことができます。その3
「できるだけ
MeshBasicMaterial
(ライティングを考慮しないシェーディング)とMeshLambertMaterial
(頂点ごとのシェーディング)を使用する」
テクスチャで雰囲気を出せてライトの影響を考える必要がなければ
MeshBasicMaterial
を使いましょう。ライトの影響を考える必要があればMeshLambertMaterial
が一番軽量です。マットな質感で問題なければMeshLambertMaterial
を使いましょう。その4
「シーン内のライトは少ないほどいい」
CGとは要するにライティングの計算です。少ないライトで済むならもちろん少なく済ませましょう。
その5
「できるだけシーンのライトをテクスチャに焼き込んでライトマップとして使用する」
その4と関係して、ではどうやってライトを減らすかという話ですが、可能なら事前に影を計算してテクスチャにし、そのテクスチャをマテリアルのライトマップに設定しましょう。
let lm = new THREE.TextureLoader().load('lm.png');
new THREE.MeshBasicMaterial({lightMap:lm});
その6
「
camera.near
とcamera.far
はできるだけシーンぎりぎりになるように維持する。1単位が1メートルであることも覚えておく」
カメラには描画対象の領域を設定できます。描画対象領域が広くなるということは、その分レンダリングの負荷が増えるということです。近平面と遠平面はデフォルトのままにせず、シーン内部に含まれるオブジェクトを考慮した値を設定しましょう。
その7
「できるだけ小さい2のべき乗になるようにテクスチャをリサイズする」
小さな領域に設定するテクスチャは小さくしましょう。不必要に巨大なテクスチャはファイルダウンロード時間の面でもメモリの使用量の面でもGPUへの転送という面でも、全方位的に無駄です。またThree.jsでは任意のサイズの画像をテクスチャとして使用できますが、2のべき乗がもっともメモリ効率がよく利用にあたっての制限もないので、面倒臭がらずに事前にそのようにリサイズしておきましょう。
その8
「透明度を設定する場合はできるだけ
material.alphaTest = 0.5
を使用する」
alphaTest
を設定すると、不透明度の値がそれ以下の場合は描画されなくなります。デフォルト値は0
ですが、この値を0.5
にすることで不透明度の低い要素が描画対象から外されます。0.5
の理由はよく分かりません。1/2
だから?株式会社カブクではこの疑問に答えられるメンバーを募集しています。その9
「
light.shadow.mapSize
を増やす代わりに、scene.add(new THREE.CameraHelper(light.shadow.camera))
を使用してシャドウマップを小さくする」
影の計算は負荷の大きな処理です。
THREE.CameraHelper
を使用するとカメラの視錐台を画面に表示できますが、ここにlight.shadow.camera
を渡すことで影を計算する領域を表示できます。うまく利用して計算対象の領域をできる限り小さくしましょう。その10
「できるだけ
antialias: true
の代わりにFXAAShader
を使用する」
WebGLRenderer
には描画をアンチエイリアシングするためのantialias
プロパティがありますが、それよりもFXAAShader
を使用したほうが負荷が少ないようです。FXAAShader
の使用例は以下にあります。まとめ
ということで、最後にもう一度10個のTipsをまとめておきます。
-
できるだけ
Geometry
とMaterial
を再利用する
-
できるだけ
Geometry
ではなくBufferGeometry
を使用する
-
できるだけ
MeshBasicMaterial
(ライティングを考慮しないシェーディング)とMeshLambertMaterial
(頂点ごとのシェーディング)を使用する
-
シーン内のライトは少ないほどいい
-
できるだけシーンのライトをテクスチャに焼き込んでライトマップとして使用する
-
camera.near
とcamera.far
はできるだけシーンぎりぎりになるように維持する。1単位が1メートルであることも覚えておく
-
できるだけ小さい2の累乗になるようにテクスチャをリサイズする
-
透明度を設定する場合はできるだけ
material.alphaTest = 0.5
を使用する
-
light.shadow.mapSize
を増やす代わりに、scene.add(new THREE.CameraHelper(light.shadow.camera))
を使用してシャドウマップを小さくする
-
できるだけ
antialias: true
の代わりにFXAAShader
を使用する
WebGLガチ勢にはThree.jsは冗長な処理が多くてパフォーマンスが悪いと思われがちで、正直そこは完全に否定できるものでもありません。しかしThree.jsもその特性を理解して使用することで、多くの場合は必要十分なパフォーマンスを実現できるものと考えています。
株式会社カブクではThree.jsアプリのパフォーマンスを改善してくれるメンバーとWebGLガチ勢を募集中です。