Membandingkan FPS antara instance dan menggabungkan drawcall dan native

Saya menguji FPS dengan laptop saya menggunakan kartu Intel(R) Iris(R) Plus Graphics 655. Untuk menguji contoh threeJS dengan rendering Instance dan rendering merge-drawcall.

Jadi saya menggunakan model QRCode_buffergeometry.json dan model suzanne_buffergeometry.json. untuk QRCode_buffergeometry.json: vertex:12852, face: 4284 dan untuk suzanne_buffergeometry.json: vertex:1515 face: 967

Lalu FPS untuk suzanne_buffergeometry dengan hitungan 8000:

INSTAN: 36

DIGABUNG: 43

ASLI: dari 23 menjadi 35 secara rotasi

untuk model QRCode_buffergeometry dengan jumlah 8000:

INSTAN: 9

DIGABUNG: 15-17

ASLI: 17-19

Saya sangat bingung dengan pertunjukan ini. 1. Sejauh pemahaman saya, tidak peduli apakah saya menggunakan instance atau merge-drawcall, drawcall tetap menjadi 1 dan total nomor wajah yang akan diundi sama, mengapa merge-drawcall lebih baik daripada instance? Karena nomor muka dan simpul keduanya sama, saya kira apa yang terjadi pada shader simpul untuk mengubah simpul juga harus sama, jadi mengapa penggabungan lebih cepat?

  1. Untuk model QRCode_buffergeometry, aslinya hampir sama dengan gabungan, dan lebih baik dari contoh, jadi saya kira CPU bukan leher botol tetapi GPU, namun data gambar akhir harus sama, maksud saya pada akhirnya nomor wajah yang akan digambar harusnya sama, mengapa yang asli lebih cepat?, bukankah itu yang seharusnya menjadi cara terbaik? Saya cukup yakin jarak jauh dan dekat kameranya cukup besar, jadi seharusnya tidak ada masalah pemusnahan apa pun.

  2. Saat saya mencoba mengoptimalkan beberapa adegan besar, kapan saya harus memilih penggabungan? kapan harus memilih contoh? dan kapan mungkin tidak melakukan apa pun lebih baik?

Ada bantuan?

Terima kasih banyak~~~

Terlampir kode untuk sampel ada di sini

body { margin: 0; }
<div id="container"></div>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
import Stats from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/libs/stats.module.js';
import {
  GUI
} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/libs/dat.gui.module.js';
import {
  OrbitControls
} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js';
import {
  BufferGeometryUtils
} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/utils/BufferGeometryUtils.js';
var container, stats, gui, guiStatsEl;
var camera, controls, scene, renderer, material;

// gui
var Method = {
  INSTANCED: 'INSTANCED',
  MERGED: 'MERGED',
  NAIVE: 'NAIVE'
};

var api = {
  method: Method.INSTANCED,
  mesh_number: 1,
  count_per_mesh: 1000
};

var modelName = 'suzanne_buffergeometry.json';
var modelScale = (modelName === 'suzanne_buffergeometry.json' ? 1 : 0.01);
var modelVertex = (modelName === 'suzanne_buffergeometry.json' ? 1515 : 12852);
var modelFace = (modelName === 'suzanne_buffergeometry.json' ? 967 : 4284);

//
init();
initMesh();
animate();

//
function clean() {
  var meshes = [];
  scene.traverse(function(object) {
    if (object.isMesh) meshes.push(object);
  });

  for (var i = 0; i < meshes.length; i++) {
    var mesh = meshes[i];
    mesh.material.dispose();
    mesh.geometry.dispose();
    scene.remove(mesh);
  }
}

var randomizeMatrix = function() {
  var position = new THREE.Vector3();
  var rotation = new THREE.Euler();
  var quaternion = new THREE.Quaternion();
  var scale = new THREE.Vector3();

  return function(matrix) {
    position.x = Math.random() * 40 - 20;
    position.y = Math.random() * 40 - 20;
    position.z = Math.random() * 40 - 20;
    rotation.x = Math.random() * 2 * Math.PI;
    rotation.y = Math.random() * 2 * Math.PI;
    rotation.z = Math.random() * 2 * Math.PI;
    quaternion.setFromEuler(rotation);
    scale.x = scale.y = scale.z = Math.random() * modelScale;
    matrix.compose(position, quaternion, scale);
  };
}();

function initMesh() {
  clean();

  console.time(api.method + ' (build)');
  for (var i = 0; i < api.mesh_number; i++) {
    // make instances
    new THREE.BufferGeometryLoader()
      .setPath('https://threejs.org/examples/models/json/')
      .load(modelName, function(geometry) {
        material = new THREE.MeshNormalMaterial();
        geometry.computeVertexNormals();

        switch (api.method) {
          case Method.INSTANCED:
            makeInstanced(geometry);
            break;
          case Method.MERGED:
            makeMerged(geometry);
            break;
          case Method.NAIVE:
            makeNaive(geometry);
            break;
        }
      });
  }
  console.timeEnd(api.method + ' (build)');
  var drawCalls = 0;
  switch (api.method) {
    case Method.INSTANCED:
    case Method.MERGED:
      drawCalls = api.mesh_number;
      break;
    case Method.NAIVE:
      drawCalls = api.mesh_number * api.count_per_mesh;
      break;
  }
  guiStatsEl.innerHTML = [
    '<i>GPU draw calls</i>: ' + drawCalls,
    '<i>Face Number</i>: ' + (modelFace * api.mesh_number * api.count_per_mesh),
    '<i>Vertex Number</i>: ' + (modelVertex * api.mesh_number * api.count_per_mesh)
  ].join('<br/>');
}

function makeInstanced(geometry, idx) {
  var matrix = new THREE.Matrix4();
  var mesh = new THREE.InstancedMesh(geometry, material, api.count_per_mesh);

  for (var i = 0; i < api.count_per_mesh; i++) {
    randomizeMatrix(matrix);
    mesh.setMatrixAt(i, matrix);
  }
  scene.add(mesh);
}

function makeMerged(geometry, idx) {
  var instanceGeometry;
  var geometries = [];
  var matrix = new THREE.Matrix4();
  for (var i = 0; i < api.count_per_mesh; i++) {
    randomizeMatrix(matrix);
    var instanceGeometry = geometry.clone();
    instanceGeometry.applyMatrix(matrix);
    geometries.push(instanceGeometry);
  }

  var mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
  scene.add(new THREE.Mesh(mergedGeometry, material));
}

function makeNaive(geometry, idx) {
  var matrix = new THREE.Matrix4();
  for (var i = 0; i < api.count_per_mesh; i++) {
    randomizeMatrix(matrix);
    var mesh = new THREE.Mesh(geometry, material);
    mesh.applyMatrix(matrix);
    scene.add(mesh);
  }
}

function init() {
  var width = window.innerWidth;
  var height = window.innerHeight;

  // camera
  camera = new THREE.PerspectiveCamera(70, width / height, 1, 100);
  camera.position.z = 30;

  // renderer
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);
  renderer.outputEncoding = THREE.sRGBEncoding;
  container = document.getElementById('container');
  container.appendChild(renderer.domElement);

  // scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff);

  // controls
  controls = new OrbitControls(camera, renderer.domElement);
  controls.autoRotate = true;

  // stats
  stats = new Stats();
  container.appendChild(stats.dom);

  // gui
  gui = new GUI();
  gui.add(api, 'method', Method).onChange(initMesh);
  gui.add(api, 'count_per_mesh', 1, 20000).step(1).onChange(initMesh);
  gui.add(api, 'mesh_number', 1, 200).step(1).onChange(initMesh);
  var perfFolder = gui.addFolder('Performance');
  guiStatsEl = document.createElement('li');
  guiStatsEl.classList.add('gui-stats');
  perfFolder.__ul.appendChild(guiStatsEl);
  perfFolder.open();
  // listeners
  window.addEventListener('resize', onWindowResize, false);
  Object.assign(window, {
    scene
  });
}

//
function onWindowResize() {
  var width = window.innerWidth;
  var height = window.innerHeight;
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  renderer.setSize(width, height);
}

function animate() {
  requestAnimationFrame(animate);
  controls.update();
  stats.update();
  render();
}

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

//
function getGeometryByteLength(geometry) {
  var total = 0;
  if (geometry.index) total += geometry.index.array.byteLength;
  for (var name in geometry.attributes) {
    total += geometry.attributes[name].array.byteLength;
  }
  return total;
}
// Source: https://stackoverflow.com/a/18650828/1314762
function formatBytes(bytes, decimals) {
  if (bytes === 0) return '0 bytes';
  var k = 1024;
  var dm = decimals < 0 ? 0 : decimals;
  var sizes = ['bytes', 'KB', 'MB'];
  var i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
</script>


person caxieyou110    schedule 20.01.2020    source sumber
comment
AFAIK sebagian besar GPU tidak melakukan instance drawing pada perangkat keras. Ini adalah optimasi perangkat lunak. Anda hanya perlu melakukan satu panggilan ke sistem untuk menarik N hal melalui N panggilan. Pengemudi secara internal masih menyiapkan GPU satu kali untuk setiap instance sehingga secara efektif membuat panggilan N draw secara internal.. Dengan gabungan geometri, Anda membuat satu panggilan, driver membuat satu pengaturan. Jadi penggabungan umumnya akan lebih cepat. Sedangkan untuk penutur asli menjadi lebih cepat, kedengarannya tidak biasa. Apalagi jika Anda menggambar 8000 objek. Posting beberapa kode dalam pertanyaan itu sendiri.   -  person gman    schedule 21.01.2020
comment
Terima kasih atas balasannya, jadi seperti yang Anda katakan, kartu grafis Terintegrasi sepertinya tidak ada yang membantu, sebaiknya saya lewati saja? Saya telah melampirkan kodenya, ini hanya pembaruan sederhana untuk kode pengujian default. benar-benar siap untuk kinerjanya.   -  person caxieyou110    schedule 21.01.2020
comment
Lihatlah bagaimana kode dibuat agar dapat dijalankan untuk pertanyaan selanjutnya   -  person gman    schedule 21.01.2020


Jawaban (1)


Ini hanya dugaan saja

  1. Three.js secara default menyisihkan jika ada hal-hal di luar frustum.

    Kita dapat mematikannya dengan mesh.frustumCulled = false. Saya tidak melihat perbedaannya dan ini akan terlihat dalam hitungan undian.

  2. Three.js secara default mengurutkan objek buram dari belakang ke depan.

    Ini berarti segala sesuatunya dianggap sama, yang diurutkan akan berjalan lebih cepat daripada yang tidak disortir karena uji kedalaman. Jika saya mengatur tes kedalaman ke selalu

    material.depthFunc = THREE.AlwaysDepth
    

    Lalu sepertinya saya mendapatkan rendering yang sedikit lebih cepat dengan instance vs asli. Tentu saja segalanya tidak sama.

  3. Masalah di Chrome.

    Jika saya menjalankan Firefox atau Safari saya mendapatkan hasil yang diharapkan. Digabung > Diinstankan > Asli

    Ini mungkin bug atau mungkin mereka sedang mengatasi masalah driver atau keamanan yang tidak dimiliki browser lain. Anda harus bertanya.

person gman    schedule 21.01.2020
comment
Sepertinya hanya kasus ini saja, maksud saya saya mencoba menggunakan firefox, lalu performanya sepertinya pas. jadi apakah ini bug Chrome? Saya juga mencoba menggunakan Chrome dengan kartu grafis Diskrit, sepertinya juga Digabung › Instance › Asli. Sangat berkabel - person caxieyou110; 21.01.2020