ฉันกำลังทดสอบ FPS กับแล็ปท็อปโดยใช้การ์ด Intel(R) Iris(R) Plus Graphics 655 เพื่อทดสอบตัวอย่าง threeJS ด้วยการเรนเดอร์อินสแตนซ์และการเรนเดอร์แบบ Merge-drawcall
ดังนั้นฉันจึงใช้ทั้งโมเดล 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? เนื่องจากจำนวนใบหน้าและจุดยอดเท่ากัน ฉันคิดว่าสิ่งที่เกิดขึ้นในเชเดอร์จุดยอดสำหรับการแปลงจุดยอดก็ควรจะเหมือนกันด้วย แล้วเหตุใดการผสานจึงเร็วกว่า
สำหรับโมเดล QRCode_buffergeometry นั้น Native เกือบจะเหมือนกับการผสานและดีกว่าอินสแตนซ์ ดังนั้นฉันเดาว่า CPU ไม่ใช่คอขวด แต่เป็น GPU อย่างไรก็ตามข้อมูลการวาดสุดท้ายควรจะเหมือนกัน ฉันหมายถึงในที่สุดหมายเลขหน้าที่จะวาด ควรจะเหมือนกัน เหตุใดเนทิฟจึงเร็วกว่า อินสแตนซ์นั้นควรจะเป็นวิธีที่ดีที่สุดไม่ใช่หรือ ฉันค่อนข้างแน่ใจว่ากล้องทั้งระยะใกล้และไกลนั้นใหญ่เพียงพอ ดังนั้นจึงไม่น่าจะมีปัญหาในการคัดแยกใดๆ
เมื่อฉันพยายามปรับฉากใหญ่ๆ ให้เหมาะสม ฉันควรเลือกผสานเมื่อใด ควรเลือกอินสแตนซ์เมื่อใด และเมื่อบางทีการไม่ทำอะไรเลยจะดีกว่า?
ความช่วยเหลือใด ๆ ?
ขอบคุณมาก~~~
แนบรหัสสำหรับตัวอย่างอยู่ที่นี่
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>