Three.jsのパフォーマンスTips
はじめに
こんにちは。お弁当盛りつけ係のあんどうです。
📢 Calling all #threejs devs: Let's make a mega thread of performance/efficiency tips & tricks for three.js! Big or small, name something that has helped you boost rendering perf, load times, memory management, etc. Textures, materials, loaders, shaders, geometries, you name it.
— Jack Rugile (@jackrugile) 2018年2月21日
先月のことですが、ウェブ/ゲーム開発者のJack RugileさんのTwitterでの「Three.jsに関するパフォーマンスや効率化のためのTipsや工夫をみんなでまとめよう!」という呼びかけに対して、なんとThree.jsの作者であるmr.doobさんが「すぐに思いつく10のこと」を直接回答してくれていました。
せっかくなので簡単に説明しながら紹介したいと思います。
その1
1. Reuse geometries and materials whenever possible.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「できるだけGeometry
とMaterial
を再利用する」
同じ形状やテクスチャのメッシュが複数ある場合に、それぞれのメッシュ用にGeometry
やMaterial
をインスタンス化してしまうとその分メモリも食いますし、GLとのデータのやり取りや状態の変更が無駄に発生します。特にマテリアルについては
new THREE.Mesh(
geom,
new THREE.MeshPhoneMaterial({color:0xff0000})
);
みたいな感じで、ついメッシュ作成時に合わせて作成してしまうこともあるかと思いますが、パフォーマンスが気になるようならそういったことは避けましょう。
その2
2. Use *BufferGeometry instead of *Geometry if whenever possible.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「できるだけGeometry
ではなくBufferGeometry
を使用する」
Three.jsでパフォーマンスを気にする場合の鉄則です。BufferGeometry
を使用することでプロパティへのアクセスが若干面倒になりますが、そのかわりに内部的なGLとのデータのやり取りを大幅に減らすことができます。
その3
3. Use MeshBasicMaterial (unlit shading) MeshLambertMaterial (per-vertex shading) whenever possible.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「できるだけMeshBasicMaterial
(ライティングを考慮しないシェーディング)とMeshLambertMaterial
(頂点ごとのシェーディング)を使用する」
テクスチャで雰囲気を出せてライトの影響を考える必要がなければMeshBasicMaterial
を使いましょう。ライトの影響を考える必要があればMeshLambertMaterial
が一番軽量です。マットな質感で問題なければMeshLambertMaterial
を使いましょう。
その4
4. The less lights in the scene the better.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「シーン内のライトは少ないほどいい」
CGとは要するにライティングの計算です。少ないライトで済むならもちろん少なく済ませましょう。
その5
5. Bake scene lights to a texture and use them as lightmaps whenever possible.https://t.co/JSdxSXkQxe
— Ricardo Cabello (@mrdoob) 2018年2月22日
「できるだけシーンのライトをテクスチャに焼き込んでライトマップとして使用する」
その4と関係して、ではどうやってライトを減らすかという話ですが、可能なら事前に影を計算してテクスチャにし、そのテクスチャをマテリアルのライトマップに設定しましょう。
let lm = new THREE.TextureLoader().load('lm.png');
new THREE.MeshBasicMaterial({lightMap:lm});
その6
6. Ensure camera.near and camera.far are as tight to your scene as possible, also remember that 1 unit = 1 meter.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「camera.near
とcamera.far
はできるだけシーンぎりぎりになるように維持する。1単位が1メートルであることも覚えておく」
カメラには描画対象の領域を設定できます。描画対象領域が広くなるということは、その分レンダリングの負荷が増えるということです。近平面と遠平面はデフォルトのままにせず、シーン内部に含まれるオブジェクトを考慮した値を設定しましょう。
その7
7. Resize your textures as small as possible and to power of 2 sizes whenever possible.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「できるだけ小さい2のべき乗になるようにテクスチャをリサイズする」
小さな領域に設定するテクスチャは小さくしましょう。不必要に巨大なテクスチャはファイルダウンロード時間の面でもメモリの使用量の面でもGPUへの転送という面でも、全方位的に無駄です。またThree.jsでは任意のサイズの画像をテクスチャとして使用できますが、2のべき乗がもっともメモリ効率がよく利用にあたっての制限もないので、面倒臭がらずに事前にそのようにリサイズしておきましょう。
その8
8. Use material.alphaTest = 0.5 for transparency when possible.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「透明度を設定する場合はできるだけmaterial.alphaTest = 0.5
を使用する」
alphaTest
を設定すると、不透明度の値がそれ以下の場合は描画されなくなります。デフォルト値は0
ですが、この値を0.5
にすることで不透明度の低い要素が描画対象から外されます。0.5
の理由はよく分かりません。1/2
だから?株式会社カブクではこの疑問に答えられるメンバーを募集しています。
その9
9. Instead of increasing light.shadow.mapSize, use scene.add( new THREE.CameraHelper( light.shadow.camera ) ) to tighten the shadowmap.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「light.shadow.mapSize
を増やす代わりに、scene.add(new THREE.CameraHelper(light.shadow.camera))
を使用してシャドウマップを小さくする」
影の計算は負荷の大きな処理です。THREE.CameraHelper
を使用するとカメラの視錐台を画面に表示できますが、ここにlight.shadow.camera
を渡すことで影を計算する領域を表示できます。うまく利用して計算対象の領域をできる限り小さくしましょう。
その10
10. Use FXAAShader instead of antialias: true when possible.
— Ricardo Cabello (@mrdoob) 2018年2月22日
「できるだけ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ガチ勢を募集中です。
その他の記事
Other Articles
関連職種
Recruit