0

I am testing the FPS with my laptop using the Intel(R) Iris(R) Plus Graphics 655 card. To test the threeJS example with Instance rendering and merge-drawcall rendering.

So I used both the QRCode_buffergeometry.json model and the suzanne_buffergeometry.json model. for the QRCode_buffergeometry.json: vertex:12852, face: 4284 and for the suzanne_buffergeometry.json: vertex:1515 face: 967

Then the FPS for the suzanne_buffergeometry with 8000 count:

INSTANCE: 36

MERGED: 43

NATIVE: from 23 to 35 by rotation

for the QRCode_buffergeometry model with 8000 count:

INSTANCE: 9

MERGED: 15-17

NATIVE: 17-19

I am very confused with this performance. 1. As far as my understanding, with no matter if i use instance or merge-drawcall, the drawcall is fixed to be 1 and the total face number to draw is same, why merged-drawcall is better than instance? Since the face and vertex number are both same, I suppose what happened in the vertex shader for transform the vertex should be same too, so why merged is faster?

  1. For the QRCode_buffergeometry model, native is almost same as merged, and better than instance, so I guess the CPU is not the bottle neck but the GPU is, however the final drawing data should be same, i mean eventually the face number to be draw should be same, why native is faster?, isn't that the instance is supposed to be the best way? I am pretty sure the camera's far and near is big enough, so there should not be any culling issue.

  2. When I am trying to optimize some big scene, when should I pick merge? when to pick instance? and when maybe no doing anything is better?

Any help?

Thanks a lot~~~

Attached the code for the sample is here

body { margin: 0; }
<div id="container"></div>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.112.1/build/three.module.js';
import Stats from 'https://cdn.jsdelivr.net/npm/three@0.112.1/examples/jsm/libs/stats.module.js';
import {
  GUI
} from 'https://cdn.jsdelivr.net/npm/three@0.112.1/examples/jsm/libs/dat.gui.module.js';
import {
  OrbitControls
} from 'https://cdn.jsdelivr.net/npm/three@0.112.1/examples/jsm/controls/OrbitControls.js';
import {
  BufferGeometryUtils
} from 'https://cdn.jsdelivr.net/npm/three@0.112.1/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>
gman
  • 100,619
  • 31
  • 269
  • 393
caxieyou110
  • 517
  • 1
  • 4
  • 8
  • AFAIK most GPUs do not do instanced drawing in hardware. It's a software optimization. You only have to make one call into the system to draw N things via N calls. The driver internally still sets up the GPU one time for each instance so effectively internally making N draw calls.. With merged geometry you make one call, the driver makes one setup. So merged will generally be faster. As for native being faster that sounds unusual. Especially if you're drawing 8000 objects. Post some code **in the question itself**. – gman Jan 21 '20 at 03:00
  • Thanks for the reply, so as your saying, the Integrated graphics card seems like nothing helped to the instance, i should just all skip it? I've attached the code, it's just a simple update for the default testing code. really wired for the performance. – caxieyou110 Jan 21 '20 at 06:31
  • Take a look at how the code was made runable for future questions – gman Jan 21 '20 at 06:58

1 Answers1

1

This is only guesses

  1. Three.js by default culls if things are outside the frustum.

    We can turn this off with mesh.frustumCulled = false. I didn't notice a difference and this should show up in the draw count.

  2. Three.js by default sorts opaque objects back to front.

    This means everything else being equal, sorted will run faster than unsorted because of the depth test. If I set the depth test to always

    material.depthFunc = THREE.AlwaysDepth
    

    Then I seem to get slightly faster rendering with instanced vs native. Of course everything else is not equal.

  3. An issue in Chrome.

    If I run in Firefox or Safari I get the expected results. Merged > Instanced > Native

    It could be a bug or it could be they're working around a driver or security issue that the other browsers are not. You'd have to ask.

gman
  • 100,619
  • 31
  • 269
  • 393
  • It seems like just this case, i mean i tried to use firefox, then the performance seems to be right. so is this a Chrome bug? Also I tried to use the Chrome with a Discrete graphics card, it seems like it's Merged > Instanced > Native too. So wired – caxieyou110 Jan 21 '20 at 11:17