Threeのthree.js(webgl)パフォーマンスの最適化、フレームレートを改善するためのアイデア/方向性

Threeのthree.js(webgl)パフォーマンスの最適化、フレームレートを改善するためのアイデア/方向性

コンテンツ

Threeのthree.js(webgl)パフォーマンスの最適化、フレームレートを改善するためのアイデア/方向性

1.簡単な紹介

2.最適化の方向性

1.多数のオブジェクトを作成する場合、BufferGeometry(またはInstancedBufferGeometry)が作成されます

2.レンダリングメソッド.render()を適切に実行します

3.定期的なレンダリング機能で不要なコードの実行を減らします

4.モデル面の数を減らします。必要に応じて、モデルの詳細を追加して法線マップに置き換えることができます。

5.共有ジオメトリとマテリアル

6.レンダリングフレームレートの最適化は、実際には、いくつかの実用的なコードを使用して、レンダリングするための合理的な呼び出しです(補足ポイント2)。

7.グリッドマージ

8.材料と形状を可能な限り再利用します

9.モデルを削除するときは、メモリからマテリアルとジオメトリをクリアします

10.循環レンダリングで更新を使用しないでください(本当に更新する必要がある場合にのみ更新し、コードを含めて2を補足します)

11.インスタンス、マージパフォーマンスの比較

参照ブログ投稿


1.簡単な紹介

Three jsによって開発されたいくつかの知識は、後の期間に同様の問題に遭遇するのに便利であり、時間内に参照して使用することができます。

このセクションでは、three.js(webgl)のパフォーマンスの最適化を紹介し、一般的に使用される最適化の方法または方向を整理し、最適化の方向について誰もが考えられるようにします。 、メッセージを残すことを歓迎します。

ThreeJSを使用して大規模なモデルをロードすると、常にパフォーマンスの問題が発生します。パフォーマンスの最適化には、通常、ロードパフォーマンスの最適化、レンダリングフレームレートの最適化、およびメモリの最適化が含まれます。

CPUやグラフィックカードなどのハードウェアデバイスのパフォーマンスの特定の条件下で、より良いユーザーエクスペリエンスを実現するには、一般的なプロジェクト開発でThreejsプロジェクトコードのパフォーマンスを最適化して、スタッター現象を回避する必要があります。 threejsパフォーマンス最適化メソッドの簡単な紹介です。

モデルの面の数が比較的少ない場合、モデルをレンダリングするときにthreejsのパフォーマンスが高くなるだけでなく、面の数が少ないモデルもネットワークを介してロードされます。ファイルサイズが小さいため、ロード速度は当然です。速い。

2.最適化の方向性

1、创建多量物体时 ,BufferGeometry (或者InstancedBufferGeometry)创建

球、円柱など、Threejsが提供するジオメトリクラスを使用してジオメトリを作成する場合はBufferGeometry、ジオメトリクラスの代わりに基本クラスを使用することをお勧めしますGeometry

2.レンダリングメソッドの合理的な実行.render()

Threejsレンダラーのメソッドは、.render()実行するたびにCPUやGPUなどの多くのハードウェアリソースを呼び出す必要があるため、レンダリングパフォーマンスを向上させるために、実行.render()回数.render()を最小限に抑えることを検討できます。レンダリング効果に基づく実行の数.render()シーンにアニメーション効果がある場合は.render()、キャンバスイメージを定期的に更新する必要があります。製品、建物、機械部品の3Dモデルの表示など、シーンがアニメーションなしでデフォルトで静的である場合は、3Dモデルを回転およびズームするだけで済みます。マウスで実行をトリガー.render()します。つまり、マウスイベントがない場合は実行できません.render()

Threejsレンダラーのレンダリングフレームレートを制御せず、ブラウザが提供する機能により定期的なレンダリングを実現します。requestAnimationFrame()理想的には、requestAnimationFrame()レンダリングフレームレートは60FPSです。threejsがレンダリングする必要のあるシーンが複雑な場合、またはブラウザが配置されているデバイスが適切でない場合、デフォルトの実行効果が60FBSに達しない可能性があります。

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}
render();

アニメーションのあるシーンではrequestAnimationFrame()、デフォルトの60FBSを30FBSに設定するなど、関数が定期的にレンダリングを実行する回数を適切に制御できます。具体的な設定方法については、当サイトに掲載されている「Three.js Control Rendering Frame Rate(FPS)」の記事を参照してください。

一般的に静的なほとんどの3Dシーンでは、threejsレンダラーメソッドを定期的に実行することはできず、マウスでモデルを回転させたり、マウスイベントを介して実行をトリガーしたり、特定の期間にアニメーションを実行したりするなど.render()、必要に応じて実行することはできません。この期間は定期的に実行され、この期間の後、元の状態に復元されます。.render().render().render()

たとえば、マウスコントロールOrbitControlsは、OrbitControls3Dモデルがコントロールによって回転またはズームされると、レンダラーをトリガーしてレンダリングします。

// 渲染函数
function render() {
  renderer.render(scene, camera);
}
render();
var controls = new THREE.OrbitControls(camera);
//监听鼠标事件,触发渲染函数,更新canvas画布渲染效果
controls.addEventListener('change', render);

3.定期的なレンダリング機能で不要なコードの実行を減らします

threejsはrequestAnimationFrame()定期的にレンダリング関数render()を実行します。レンダリング関数では、レンダラー.render()メソッドに加えて、残りを可能な限りレンダリング関数の外側に配置する必要があります。内側に配置する必要がある場合は、次のように判断して追加できます。毎回ではなく、可能な限りレンダリング機能を実行すると、毎回不要なコードが実行されます。

4.モデル面の数を減らします。必要に応じて、モデルの詳細を追加して法線マップに置き換えることができます。

Threejsがシーン内のメッシュモデルをレンダリングするときに、メッシュモデルジオメトリの三角形の面の数または頂点の数が多いMesh場合Mesh、より多くのCPUとGPUの計算が必要になり、ジオメトリの頂点データが占めるメモリが増えます。毎回Threejsはレンダリングを実行する.render()ため、時間がかかります。三角形の面が多すぎると、レンダリングフレームレートが低下し、3Dモデルの操作時にマウスが動かなくなる可能性があります。

プロジェクトで使用される3Dモデルの場合、3Dアートは表面低減の最適化を実行することが多く、プログラマーは通常、特定の表面低減プロセスを考慮する必要はありません。

曲面の場合、表面の縮小が大きすぎると表示効果に影響を与える可能性があるため、表面の縮小の程度を適切に制御する必要があります。

サーフェスモデルの場合、法線マップを使用すると、表示品質に影響を与えることなくモデル面の数を効果的に減らすことができます。法線マップは、画像のピクセル値を通じてモデルサーフェスの幾何学的詳細を記録し、3Dアートのみで多くの幾何学的詳細を削減できます。モデルサーフェス。サーフェスの後で、法線マップをエクスポートし、プログラマーにロードするように提供します。簡単に言えば、3Dモデルの表面の豊かな幾何学的詳細を法線マップで表現できます。

5.共有ジオメトリとマテリアル

異なるメッシュモデルがジオメトリまたはマテリアルを共有できる場合は、共有方法を使用するのが最適です.2つのメッシュモデルがジオメトリまたはマテリアルを共有できない場合、当然、それらを共有する必要はありません。たとえば、2つのメッシュモデルのマテリアルカラーは次のとおりです。この場合、次に、通常、メッシュモデルのマテリアルオブジェクトを個別に作成します。

6.レンダリングフレームレートの最適化は、実際には、いくつかの実用的なコードを使用して、レンダリングするための合理的な呼び出しです(補足ポイント2)。

フレームレートの最適化の考え方は、必要な場合にのみレンダリングすることであり、操作がないときにrender()を呼び出さないことです。いつレンダリングを呼び出す必要がありますか?主に次の状況が含まれます。

  • シーン内のオブジェクトの追加、削除、および変更
  • オブジェクトが選択され、選択解除されます
  • カメラの位置、観測点の変更
  • レンダリング領域のサイズが変更されます
  • ..。

したがって、これらの変更をトリガーする操作、主に次の操作に注意する必要があります。

  • scene.add / removeメソッドが呼び出されます(モデルのロード、削除などの場合)
  • オブジェクトマテリアルの変更、位置の変更、スケーリング、回転、透明度など。
  • OrbitControlsへの変更
  • カメラの「変更」イベント
  • Mousedown / mouseup/mousemoveおよびその他のイベント
  • キーボード用のw/a / s / d / up / down / left/right矢印など

1)コードスニペットを使用する

this.controls.addEventListener('change', () => {
      this.enableRender()
    })

    window.addEventListener('keydown', (e: KeyboardEvent) => {
      // can also check which key is pressed...
      this.enableRender()
    }

      this.renderer.domElement.addEventListener('mousedown', (e) => {
        this.enableRender()
      })
      this.renderer.domElement.addEventListener('mousemove', (e) => {
        if (/* we can add more constraints here */) {
          this.enableRender()
        }
      })
      this.renderer.domElement.addEventListener('mouseup', (e) => {
        !this.mouseMoved && this.selectHandler(e)
        this.enableRender()
      })

2)その他のリファレンスパッケージ

/**
 * This class implemented setTimeout and setInterval using RequestAnimationFrame
 */
export default class RafHelper {
  readonly TIMEOUT = 'timeout'
  readonly INTERVAL = 'interval'
  private timeoutMap: any = {} // timeout map, key is symbol
  private intervalMap: any = {} // interval map

  private run (type = this.INTERVAL, cb: () => void, interval = 16.7) {
    const now = Date.now
    let startTime = now()
    let endTime = startTime
    const timerSymbol = Symbol('')
    const loop = () => {
      this.setIdMap(timerSymbol, type, loop)
      endTime = now()
      if (endTime - startTime >= interval) {
        if (type === this.intervalMap) {
          startTime = now()
          endTime = startTime
        }
        cb()
        if (type === this.TIMEOUT) {
          this.clearTimeout(timerSymbol)
        }
      }
    }
    this.setIdMap(timerSymbol, type, loop)
    return timerSymbol
  }

  private setIdMap (timerSymbol: symbol, type: string, loop: (time: number) => void) {
    const id = requestAnimationFrame(loop)
    if (type === this.INTERVAL) {
      this.intervalMap[timerSymbol] = id
    } else if (type === this.TIMEOUT) {
      this.timeoutMap[timerSymbol] = id
    }
  }

  public setTimeout (cb: () => void, interval: number) {
    return this.run(this.TIMEOUT, cb, interval)
  }

  public clearTimeout (timer: symbol) {
    cancelAnimationFrame(this.timeoutMap[timer])
  }

  public setInterval (cb: () => void, interval: number) {
    return this.run(this.INTERVAL, cb, interval)
  }

  public clearInterval (timer: symbol) {
    cancelAnimationFrame(this.intervalMap[timer])
  }
}

7.グリッドマージ

ほとんどの場合、グループを使用すると、多数のメッシュを簡単に操作および管理できます。ただし、オブジェクトの数が非常に多い場合、パフォーマンスがボトルネックになります。グループを使用する場合、各オブジェクトは引き続き独立しており、個別に処理およびレンダリングする必要があります。THREE.Geometry.merge
()関数を使用すると、複数のジオメトリをマージしてユニオンを作成できます。

通常のグループを使用して20,000個の立方体を描画する場合、フレームレートは約15フレームです。マージしてから、20,000個の立方体を描画することを選択すると、パフォーマンスを低下させることなく20,000個の立方体を簡単にレンダリングできることがわかります。マージされたコードは次のとおりです。

//合并模型,则使用merge方法合并
var geometry = new THREE.Geometry();
//merge方法将两个几何体对象或者Object3D里面的几何体对象合并,(使用对象
的变换)将几何体的顶点,面,UV分别合并.
//THREE.GeometryUtils: .merge() has been moved to Geometry.
Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.
for(var i=0; i<gui.numberOfObjects; i++){
var cube = addCube();
cube.updateMatrix();
geometry.merge(cube.geometry, cube.matrix);
}
scene.add(new THREE.Mesh(geometry, cubeMaterial));

THREE.GeometryUtils.merge()は、このメソッドをTHREE.Geometryオブジェクトの先頭に移動しました。addCubeメソッドを使用して立方体を作成します。マージされたTHREE.Geometryオブジェクトの正しい位置と回転を保証するために、マージ関数に追加するだけTHREE.Geometryオブジェクトを提供し、オブジェクトの変換行列も提供します。この行列をマージ関数に追加すると、マージされた正方形が正しく配置されます。


メッシュマージの長所と短所

短所:グループは個々の個人を操作できますが、メッシュをマージすると、各オブジェクトを個別に制御できなくなります。ブロックを
移動、回転、または拡大縮小することはできません。
利点:パフォーマンスが低下することはありません。すべてのメッシュが1つにマージされるため、パフォーマンスが大幅に向上します。大きくて複雑なジオメトリを作成する必要がある場合。外部ソースからジオメトリを作成してロードすることもできます。
 

8.材料と形状を可能な限り再利用します

ここでは、例として材料と形状を取り上げます(頻繁に使用されます)

for (var i = 0; i < 100; i++) {
    var material = new THREE.MeshBasicMaterial();
    var geometry = new THREE.BoxGeometry(10, 10, 10);
    var mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
}


尽量替换为

var material = new THREE.MeshBasicMaterial();
var geometry = new THREE.BoxGeometry(10, 10, 10);
for (var i = 0; i < 100; i++) {
    var mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
}

9.モデルを削除するときは、メモリからマテリアルとジオメトリをクリアします

1. item.geometry.dispose(); //删除几何体
2. item.material.dispose(); //删除材质

10.循環レンダリングで更新を使用しないでください(本当に更新する必要がある場合にのみ更新し、コードを含めて2を補足します)

ここでの更新とは、現在のジオメトリ、マテリアル、テクスチャなどの変更を指します。これには、Three.jsがビデオメモリデータを再更新する必要があります。

几何体:
geometry.verticesNeedUpdate = true; //顶点发生了修改
geometry.elementsNeedUpdate = true; //面发生了修改
geometry.morphTargetsNeedUpdate = true; //变形目标发生了修改
geometry.uvsNeedUpdate = true; //uv映射发生了修改
geometry.normalsNeedUpdate = true; //法向发生了修改
geometry.colorsNeedUpdate = true; //顶点颜色发生的修改

材质:
material.needsUpdate = true

纹理:
texture.needsUpdate = true;

それらが更新された場合、それをtrueに設定すると、Three.jsは判断を渡し、データをビデオメモリに再送信し、構成アイテムをfalseに再変更します。これは非常に効率的なプロセスであるため、必要な場合にのみ変更を試み、ループ設定のrender()メソッドには入れません。必要な場合にのみレンダリングする

操作がないときにループを常にレンダリングさせるのがリソースの無駄である場合は、必要な場合にのみレンダリングする方法を紹介します。
まず、円形レンダリングに判定を追加します。判定値がtrueの場合、円形レンダリングを実行できます。

var renderEnabled;
function animate() {
if (renderEnabled) {
    renderer.render(scene, camera);
}
    requestAnimationFrame(animate);
}
animate();

次に、遅延関数を設定します。各呼び出しの後に、renderEnabledをtrueに設定し、3秒間遅延して
、falseに設定できます。この遅延時間は、必要に応じて変更できます。

//调用一次可以渲染三秒
let timeOut = null;
function timeRender() {
//设置为可渲染状态
    renderEnabled = true;
//清除上次的延迟器
if (timeOut) {
    clearTimeout(timeOut);
}
    timeOut = setTimeout(function () {
    renderEnabled = false;
}, 3000);
}

次に、カメラコントローラーが更新された後のコールバックなど、必要なときにこのtimeRender()メソッドを呼び出すことができます

controls.addEventListener('change', function(){
    timeRender();
});

カメラの位置が変わると、コールバックがトリガーされて円形レンダリングが開始され、ページ表示が更新されます。

シーンにモデルを追加する場合は、re-renderを直接呼び出します。

scene.add(mesh);
timeRender();

最後に、重要な問題は、マテリアルのテクスチャが非同期であるため、画像が追加された後にコールバックをトリガーする必要があることです。幸い
、Three.jsはこれを考慮に入れています。Three.jsの静的オブジェクトTHREE.DefaultLoadingManagerのonLoadコールバックは、
各テクスチャ画像が読み込まれた後にコールバックをトリガーします。これにより、Three.jsのすべてのコンテンツを変更できます。再
レンダリングをトリガーすると、アイドル状態でのレンダリングが停止します。

//每次材质和纹理更新,触发重新渲染
THREE.DefaultLoadingManager.onLoad = function () {
    timeRender();
};

11.インスタンス、マージパフォーマンスの比較

1)インスタンスマルチインスタンスジオメトリ

同じ形状、同じ素材ですが、インデックスを使用して各個人のサイズや位置などを簡単に制御できます

let insGeometry = new THREE.BoxBufferGeometry(1, 1, 1);
//创建具有多个实例的实例化几何体
let insMesh = new THREE.InstancedMesh(insGeometry, material, total);
//修改位置
let transform = new THREE.Object3D();
for (let index = 0; index < total; index++) {
    transform.position.set(Math.random() * 2000, Math.random() * 2000, Math.random() * 2000);
    transform.scale.set(Math.random() * 50 + 50, Math.random() * 50 + 50, Math.random() * 50 + 50);
    transform.updateMatrix();
    //修改实例化几何体中的单个实例的矩阵以改变大小、方向、位置等
    insMesh.setMatrixAt(index, transform.matrix);
}
scene.add(insMesh);

2)マージジオメトリをマージします

異なるジオメトリ、同じ材料にはインデックスがなく、個別にマージでき、個別に制御するのは困難です

 let geometries = [];
 let transform = new THREE.Object3D();
 for (let index = 0; index < total; index++) {
     let geometry = new THREE.BoxBufferGeometry(Math.random() * 50 + 50, Math.random() * 50 + 50, Math.random() * 50 + 50);
     transform.position.set(Math.random() * 2000, Math.random() * 2000, Math.random() * 2000);
     transform.updateMatrix();
     geometry.applyMatrix4(transform.matrix);
     geometries.push(geometry);
 }
 let mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
 let mergedMesh = new THREE.Mesh(mergedGeometry, material2);
 scene.add(mergedMesh);

参照ブログ投稿

1.ThreeJSパフォーマンスの最適化-レンダリングフレームレートの最適化-知っている

2.Three.jsレンダリングパフォーマンスの最適化

3.ThreeJSのパフォーマンスの最適化

4. Threejsのパフォーマンスの最適化(マルチインスタンスのレンダリングとマージ)

おすすめ

転載: blog.csdn.net/u014361280/article/details/124285654
おすすめ