Сравнение FPS между экземпляром и вызовом слияния и нативным

Я тестирую FPS на своем ноутбуке с картой Intel(R) Iris(R) Plus Graphics 655. Чтобы протестировать пример threeJS с рендерингом Instance и рендерингом слиянием и отрисовкой.

Поэтому я использовал как модель QRCode_buffergeometry.json, так и модель suzanne_buffergeometry.json. для QRCode_buffergeometry.json: вершина: 12852, лицо: 4284 и для suzanne_buffergeometry.json: вершина: 1515 лицо: 967

Затем FPS для suzanne_buffergeometry с количеством 8000:

ИНСТАНЦИЯ: 36

ОБЪЕДИНЕНО: 43

NATIVE: с 23 до 35 по очереди

для модели QRCode_buffergeometry с количеством 8000:

ИНСТАНЦИЯ: 9

ОБЪЕДИНЕНО: 15-17

НОСИТЕЛЬНЫЙ: 17-19

Меня очень смущает этот спектакль. 1. Насколько я понимаю, независимо от того, использую ли я instance или merge-drawcall, drawcall фиксируется равным 1, а общее количество лиц для рисования одинаково, почему merged-drawcall лучше, чем instance? Поскольку номер грани и номер вершины одинаковы, я полагаю, что то, что произошло в вершинном шейдере для преобразования вершины, должно быть таким же, так почему же слияние происходит быстрее?

  1. Для модели QRCode_buffergeometry натив почти такой же, как слитый, и лучше, чем экземпляр, поэтому я предполагаю, что узким местом является не процессор, а графический процессор, однако окончательные данные рисования должны быть такими же, я имею в виду, что в конечном итоге номер лица должен быть отрисован должно быть таким же, почему родной быстрее? Разве экземпляр не должен быть лучшим способом? Я почти уверен, что камера далеко и близко достаточно велика, поэтому не должно быть никаких проблем с отбраковкой.

  2. Когда я пытаюсь оптимизировать какую-то большую сцену, когда мне следует выбрать слияние? когда выбирать экземпляр? а когда может лучше ничего не делать?

Любая помощь?

Большое спасибо~~~

Прикрепленный код для образца находится здесь

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 источник
comment
Насколько я знаю, большинство графических процессоров не выполняют аппаратное отрисовку экземпляров. Это оптимизация программного обеспечения. Вам нужно сделать всего один вызов в систему, чтобы нарисовать N вещей с помощью N вызовов. Драйвер внутренне по-прежнему настраивает GPU один раз для каждого экземпляра, поэтому эффективно выполняет N вызовов отрисовки. С объединенной геометрией вы делаете один вызов, драйвер выполняет одну настройку. Таким образом, слияние, как правило, будет быстрее. Что касается того, что родной быстрее, это звучит необычно. Особенно, если вы рисуете 8000 объектов. Разместите код в самом вопросе.   -  person gman    schedule 21.01.2020
comment
Спасибо за ответ, так что, как вы говорите, встроенная видеокарта, кажется, ничего не помогла в этом случае, я должен просто пропустить ее? Я прикрепил код, это просто простое обновление кода тестирования по умолчанию. действительно запрограммирован на выступление.   -  person caxieyou110    schedule 21.01.2020
comment
Взгляните на то, как код был сделан работоспособным для будущих вопросов.   -  person gman    schedule 21.01.2020


Ответы (1)


это только догадки

  1. Three.js по умолчанию отбрасывает, если что-то находится за пределами пирамиды видимости.

    Мы можем отключить это с помощью mesh.frustumCulled = false. Я не заметил разницы, и это должно отразиться на подсчете розыгрыша.

  2. Three.js по умолчанию сортирует непрозрачные объекты сзади наперед.

    Это означает, что при прочих равных условиях отсортированный будет работать быстрее, чем несортированный из-за проверки глубины. Если я установлю тест глубины всегда

    material.depthFunc = THREE.AlwaysDepth
    

    Тогда я, кажется, получаю немного более быстрый рендеринг с экземпляром по сравнению с нативным. Конечно все остальное не равно.

  3. Проблема в Хроме.

    Если я запускаю в Firefox или Safari, я получаю ожидаемые результаты. Объединенный > Экземпляр > Собственный

    Это может быть ошибка, или они могут решить проблему с драйвером или безопасностью, которой нет в других браузерах. Вам нужно будет спросить.

person gman    schedule 21.01.2020
comment
Кажется, как раз в этом случае, я имею в виду, что я пытался использовать firefox, тогда производительность кажется правильной. так это ошибка Chrome? Также я пытался использовать Chrome с дискретной видеокартой, похоже, что это тоже Merged › Instanced › Native. Так проводной - person caxieyou110; 21.01.2020