Hello to all,
in am writing a quite big 3D app with three.js. So I hope I can copy out enough parts to support you information without posting these thousends of line of code…
First of all the scene, lights, objects aso… work just fine with all my test-objects.
Then I started to load data from a real project (from another system). So I read all the information and create three.js objects according to the data given.
displayObjectTree(tree , parentObjNr = 0) {
if(parentObjNr < 100) this.clearScene();
//todo find parentObjNr
this.m_iCountMeshes = 0;
let currentNode = this.m_rootNode;
this.createThreeSceneObject(currentNode , tree);
this.updateGroups(this.m_rootNode);
}
createThreeSceneObject(currentSceneNode , srvObjInformation) {
if(!srvObjInformation.children) {
console.error("NO SRV INFORMATION TO BUILD OBJECT!!");
}
for(let i = 0 ; i < srvObjInformation.children.length ; i++) {
let newSceneParent = null;
let child = srvObjInformation.children[i];
switch(child.id) {
case ClassID.eClass_ID_CCadObjTree:
case ClassID.eClass_ID_CCadObject:
newSceneParent = this.createThreeSceneObject_ClassTree(currentSceneNode , child);
break;
case ClassID.eClass_ID_CCadPunkt:
case ClassID.eClass_ID_CCadVPunkt:
case ClassID.eClass_ID_CCadAPunkt:
case ClassID.eClass_ID_CCadLEPunkt:
case ClassID.eClass_ID_CCadESPunkt:
case ClassID.eClass_ID_CCadLGPunkt:
case ClassID.eClass_ID_CCadVEPunkt:
case ClassID.eClass_ID_CCadRPunkt:
case ClassID.eClass_ID_CCadSPunkt:
case ClassID.eClass_ID_CCadW2Punkt:
newSceneParent = this.createThreeSceneObject_CadPunkt(currentSceneNode , child);
break;
case ClassID.eClass_ID_CCadFlaeche:
newSceneParent = this.createThreeSceneObject_CadFlaeche(currentSceneNode , child);
break;
case ClassID.eClass_ID_CCadLinie:
newSceneParent = this.createThreeSceneObject_CadFLinie(currentSceneNode , child);
break;
default:
err("unkown child.id: " + child.id);
}
if(newSceneParent) {
this.m_iCountMeshes++;
document.title = "# Meshes = " + this.m_iCountMeshes;
newSceneParent.name = child.objnr;
newSceneParent.userData.SIA = {};
newSceneParent.userData.SIA.guid = child.guid;
newSceneParent.userData.SIA.objnr = child.objnr;
newSceneParent.userData.SIA.parent = child.parent;
newSceneParent.userData.SIA.tag = child.tag;
newSceneParent.userData.SIA.type = child.type;
newSceneParent.userData.SIA.id = child.id;
newSceneParent.userData.SIA.flags = child.flags;
newSceneParent.userData.SIA.layer = child.layer;
newSceneParent.userData.SIA.select = child.select;
if("ref1" in child) newSceneParent.userData.SIA.ref1 = child.ref1;
if("ref2" in child) newSceneParent.userData.SIA.ref2 = child.ref2;
if("ref3" in child) newSceneParent.userData.SIA.ref3 = child.ref3;
if("ref4" in child) newSceneParent.userData.SIA.ref4 = child.ref4;
if("ref5" in child) newSceneParent.userData.SIA.ref5 = child.ref5;
if("vx" in child) newSceneParent.userData.SIA.vx = child.vx;
if("vy" in child) newSceneParent.userData.SIA.vy = child.vy;
if("vz" in child) newSceneParent.userData.SIA.vz = child.vz;
if("invers" in child) newSceneParent.userData.SIA.invers = child.invers;
if("inversX" in child) newSceneParent.userData.SIA.inversX = child.inversX;
if("inversY" in child) {
newSceneParent.userData.SIA.inversY = child.inversY;
}
if("inversZ" in child) newSceneParent.userData.SIA.inversZ = child.inversZ;
if((newSceneParent.userData.SIA.flags & ObjectFlags.OF_GRUPPE_AUS) == 0) {
newSceneParent.layers.enable(LAYER.OBJECT_IS_VISIBLE);
}else {
newSceneParent.layers.disable(LAYER.OBJECT_IS_VISIBLE);
}
if(newSceneParent.userData.SIA.select == false) {
newSceneParent.layers.disable(LAYER.OBJECT_IS_SELECTABLE);
}else {
newSceneParent.layers.enable(LAYER.OBJECT_IS_SELECTABLE);
}
this.createThreeSceneObject(newSceneParent , child);
}
}
}
creating the single objects looks like this
createThreeSceneObject_CadPunkt(parentNode, objProperties) {
parentNode = parentNode || this.m_rootNode;
let color = 0xff0000;
let opacity = 1.0;
let radius = 0.5; // 2*0.5 = 1 (m)
let fScale = 1.0 / 1000.0; //Skalieren von Meter auf MM
if("color" in objProperties) {
let tplC = this.kkpColorToColor(objProperties.color);
color = tplC[0];
opacity = tplC[1] / 255.0;
}
if("ptSize" in objProperties) {
fScale = (objProperties.ptSize / 1000.0);
}
let geometry = new THREE.SphereGeometry(2 * radius , 5 , 5);
let material = new THREE.MeshPhongMaterial({color: color, fog: false, specular: 0x858585 , shininess:48, transparent: true, opacity: opacity});
let node = new THREE.Mesh(geometry , material);
node.castShadow = true;
node.receiveShadow = true;
node.position.x = objProperties.vPos.x;
node.position.y = objProperties.vPos.y;
node.position.z = objProperties.vPos.z;
node.scale.setScalar(fScale);
parentNode.add(node);
return node;
return null;
}
createThreeSceneObject_CadFlaeche(parentNode, objProperties) {
parentNode = parentNode || this.m_rootNode;
let opacity = 1.0;
let color = 0xff0000;
if(!("coords" in objProperties)) return null;
const coords = [];
for(let i = 0; i < objProperties.coords.length ; i++) {
let coord = objProperties.coords[i];
coords.push(coord.x);
coords.push(coord.y);
coords.push(coord.z);
}
if("color" in objProperties) {
let tplC = this.kkpColorToColor(objProperties.color);
color = tplC[0];
opacity = tplC[1] / 255.0;
}
const vertices = new Float32Array(coords);
let geometry = new THREE.BufferGeometry();
geometry.setAttribute("position" , new THREE.BufferAttribute(vertices , 3));
geometry.computeVertexNormals();
let material = new THREE.MeshPhongMaterial({color: color, fog: false, specular: 0xc4c4c4 , shininess:69, transparent: true, opacity: opacity , side:THREE.DoubleSide});
let node = new THREE.Mesh(geometry , material);
node.castShadow = true;
node.receiveShadow = true;
parentNode.add(node);
return node;
return null;
}
createThreeSceneObject_CadFLinie(parentNode , objProperties) {
parentNode = parentNode || this.m_rootNode;
let opacity = 1.0;
let color = 0xff0000;
if("color" in objProperties) {
let tplC = this.kkpColorToColor(objProperties.color);
color = tplC[0];
opacity = tplC[1] / 255.0;
}
if(!("coords" in objProperties)) return null;
if(objProperties.coords.length < 2) return null;
const points = [];
let offset = null;
for(let i = 0; i < objProperties.coords.length ; i++) {
let coord = objProperties.coords[i];
offset = objProperties.coords[0];
coord = {x: coord.x - offset.x , y: coord.y - offset.y , z: coord.z - offset.z }
points.push(new THREE.Vector3(coord.x , coord.y, coord.z));
}
const geometry = new THREE.BufferGeometry().setFromPoints(points);
let material = new THREE.LineBasicMaterial({color: color});
let node = new THREE.Line(geometry , material);
if(offset) node.position.set(offset.x , offset.y , offset.z);
node.castShadow = true;
node.receiveShadow = true;
//parentNode.add(node);
//return node;
return null;
}
createLine(vA , vB) {
if(!vA) return;
if(!vB) return;
const points = [vA , vB];
const geometry = new THREE.BufferGeometry().setFromPoints(points);
let material = new THREE.LineBasicMaterial({color: 0xff0201});
let node = new THREE.Line(geometry , material);
return node;
}
createText(text , size = 0.01 /* m */ , color = 0x0) {
if(!this.m_font) return null;
let textGeom = new TextGeometry(text , {
font: this.m_font,
size: size,
height: 0.001,
curveSegments:10,
bevelThickness:0,
bevelSize:0,
bevelEnabled:false
});
textGeom.computeBoundingBox();
let material = new THREE.MeshBasicMaterial({color: color, side:THREE.DoubleSide});
let node = new THREE.Mesh(textGeom , material);
node.castShadow = true;
node.receiveShadow = true;
return node;
}
And as said, in general this works fine. But when it comes to a real project to load, I have to load about 3500 Spheres, 1500 Lines, and about 2500 thousend shapes.
By animation scene is quite simple
animateScene() {
let self = this;
requestAnimationFrame( () => {self.animateScene(); } );
self.m_controls.update();
//self.m_renderer.render( self.m_scene, self.m_camera );
this.m_composer.render();
}
I use outline effects, that’ s why I render through a composer. Also I do use a raycaster that works on mouse move event. I haven’t tried to switch off raycasting, to see performance results, Or only raycast on mouse-click. I don’t know if that makes THE big difference.
What I tried to far…
Only create spheres, set them to invisible. Don’t create anything else. Performance is good. Memory gets used as expected.
Creating spheres and add them visible to the scene. Don’t create anything else. I get around 4-10 fps.
Creating only lines, resolves in the same performance issues.
Creating only shapes resolves in the same performance issues.
Further more I found out when keeping the amount of meshes below something around 2000 - 2500 object, I get 120 fps. Up to 3500 objects I still get up to 60 fps but when I try to move the scene (orbit controls) the frame rate drops to something around 20 fps.
If I have around 5500 objects in my scene the fps is a desaster of a maximum of 10 fps and when I try to move the scene an release the mouse, the scene takes more than 20 seconds to calm down.
I can try instancing all the spheres since there are more less the sames, except position, color and eventually radius.
Lines can (maybe?) get instanced as well.
But the shapes are set from individual points. I can try to merge them, but only if I can find a way to still select them by the raycaster.
What really annoys me, the application the data come from uses OpenGL1.1 using a mesa driver (software renderer). And that application laughs about the “poor performance” three.js seems to deliver, though I am sure, three.js indeed is a very powefull engine.
So yeah, I hope you might give me some ideas, what I could try to change to keep the fps going.
Just in case this helps as well, here are some screenshots.
Ignoring the floor-grid this scene contains 5544 objects and the tab uses 219MB RAM.
This object is almost unhable, and it doesn’t even contain 25% of a standard object.