Importing CAD model : how can I optimize draw calls?

Hi to all !.

I would like to create a 3D viewer for a proprietary cad format.
These cad models are often very large with many elements.
created a file containing the unique objects (mesh and submesh) converted into single glb files that are then dynamically imported into a ThreeJS scene duplicating each instance and applying rotation/translation matrix.
In this way the format on disk remains quite compact.

The basic functions that I would like to obtain are:

  • highlighting of one or more elements,
  • transparency of one or more elements,
  • show/hide of one or more elements
  • I don’t need to manage textures

In this first version I preload the “meshes” with GLTFLoader (DRACOLoader and MeshoptDecoder) putting them (the single gltf scene) in a javascript Map, then I add the different instances cloning the “Mesh” (.clone() of the original gltf scene) of the map and setting rotation and translation

Model example in legacy windows viewer:

Using the gtlf importer each single cad object is composed of a Threejs object3D and many meshes for each material:
it would be better to use the “BufferGeometry .groups” ?

Using the “instanced Meshes” does not seem to be feasible because then I would not be able to manage the transparency:
or am I wrong? I would like to make each single object transparent independently from the others

This is my first test:

Some points to consider:
1.
You _can modify InstancedMeshes to work with transparency. I’m not sure how useful instancing will be for your screenshotted workload, since there appears to be mostly unique geometry? (unless there are small pieces im not seeing like screws and the like, which could definitely benefit from instancing).
Even without modification, you can make hilighting work by just turning off a single instance(s), and dropping in a regular Mesh(es) of the selected bits during selection…
The other nice thing about instancing within the context of GLTF… gltf now supports an “instancing extension” which, if you save/export your scene, can be loaded in most modern gltf viewers and be instanced automatically. This means you can get tiny files that expand into much larger scenes if there is a lot of duplication. AFAIK this is unattainable by other means in a generic way.

  1. alternatively… (or combined with the instancing approach)
    You can merge all the geometries for the object into a single mesh or a view meshes, grouped by material, but still keep track of the start/end regions of the individual pieces within the single mesh… and then dynamically create individual meshes when you identify which ones you need to hilight, and create a mesh for hilighting it on the fly.
    Meshes can also use multiple materials for different "drawRange"es… by setting the mesh.material to an array of materials, and setting up the drawGroups to delineate which regions of the meshes geometry apply to which materials.

Which approach is more optimal to take depends on how much time you have to implement it, and how much complexity you can afford in your app, and the nature of the models themselves.

Generally you want to keep drawCalls down below about 1k, and total triangle count below ~2 million.

Thanks for your information, I will treasure it.

Regarding the “drawRange” I seem to have read that between an object3D with “N” children (each a mesh with a single material) and a mesh with drawRange and array of materials there would not be a big benefit of improvement for the drawcalls: is that right?

No reduction in draw calls when using geometry.groups, no, I don’t think that will help with performance for your app.

For what you describe, I think I would use BatchedMesh. You can show/hide or tint individual geometries with its APIs easily.

To make something transparent I would hide it in the original batch and activate a copy of that object in a transparent batch – trying to mix opaque and transparent objects in a single batch would be trouble I think.

1 Like

I was thinking about the current implementation…

  1. Currently I find myself with a mess of cad objects (think of a bolt for example) that can also have multiple materials:
    I understand that BatchedMesh manage a single material, correct?

  2. These CAD objects are saved individually as GLTF so at runtime I should evaluate those that have a single material, group them by material and manage them appropriately with “addGeometry” and “addInstance”?

Assuming I manage to achieve point 2, at this point in the scene I would find a few Object3D that are GLTF “scenes” and a few that are BatchedMesh so in the “show/hide or tint” phase I should manage the thing appropriately.

I’m missing this suggestion: “and activate a copy of that object in a transparent batch”: are you saying that I should create, for example, 2 BatchedMesh for “material X” that are the same in everything except for the transparency of the color?

Yes, and in general you cannot have fewer draw calls than materials. If you have more than 100 materials I would certainly look for ways to reduce that. Notably – if materials differ only in color then it’s trivial to override per-instance color while using a single material.

… are you saying that I should create, for example, 2 BatchedMesh for “material X” that are the same in everything except for the transparency of the color?

You could do that, yes, but I think it depends on the use case. If only a few objects will be drawn with transparency at a given time, then you could just create standalone meshes for those objects dynamically, too. But I would not mix opaque and transparent objects in a single draw call if I could avoid it.

Maybe I am using the term material improperly. In my case the material of the objects is always unique, the color changes: the only exception is for the glass/plexiglass components which are transparent.

Then I need features such as highlighting and transparency but only for issues related to the creation of a viewer.

In a complex CAD model I can also have more than 200 materials intended as different colors, and some objects combine different colors

For example:
This CAD object is a complex “mesh” made of 3 colors. I can have many instances of this object that only change by positioning/rotation

here are the “Materials” (Colors)

join all mesh in cad or in 3ds Max so its a single mesh, with this you minimize draw call number down to minimum

in three.js the base color of a surface is computed as the product of:

<color> = <material.color> x <material.map> x <vertex color> x <instance color>

So, ideally, you could set material.color to #FFFFFF (1, 1, 1) and then override instance colors on the BatchedMesh or InstancedMesh. This way you can have far more unique colors displayed, without having more THREE.Materials (and draw calls).

1 Like

Hi. I tried to use for example the InstancedMesh for those cad objects having only 1 material and repeated more than 1 time.
In this example they went from 57 to 29.

I have a positioning problem related perhaps to the glb format.
I load the single glb objects in a map and in another map the geometries that I can use in the InstancedMesh in this way:

let geometry=originalMesh.children[0].geometry;
let imesh = new THREE.InstancedMesh( geometry, MM1, MeshCnt );

originalMesh=> is the single glb file “scene” object from “gltfLoader”

In the test I position the single objects (cloning the “scene” of a glb) calling my SetPosAndRot function.

but if I use the same procedure for the InstancedMesh I get an incorrect result (see image below)

What can be the best way to apply the matrix to the geometry of the GLB file only and not to the scene containing the geometry?

originalMesh=_meshInstances.get(ndz.Mesh); <== 'glb object
let meshInst=_InstancedArray.get(ndz.Mesh); <== 'geometry map from glb object

 O1=originalMesh.clone();

 if (meshInst!=undefined) {
 meshInst.userData.buildIndex=meshInst.userData.buildIndex+1;
 SetPosAndRot(O1, ndz.Position, ndz.Quaternion, true);
 meshInst.setMatrixAt( meshInst.userData.buildIndex, O1.matrix );

 } else {

 O1.name = ndz.Name;
 SetPosAndRot(O1, ndz.Position, ndz.Quaternion, true);

}

function SetPosAndRot (o3d, P, Q) {
 if (Q==null) Q=[0,0,0,0];
 o3d.matrixAutoUpdate=false;
 o3d.translateX(P.x);
 o3d.translateY(P.y);
 o3d.translateZ(P.z);
 let Q1 = new THREE.Quaternion(Q[0], Q[1] , Q[2], Q[3]);
 o3d.setRotationFromQuaternion(Q1);
 o3d.updateMatrix();
 o3d.updateMatrixWorld(true);
}

…I don’t understand how I can reuse the mesh geometry of a glb file in an InstancedMesh / BatchedMesh .
Due to the compression/quantization of the vertices (I suppose) the positioning and scaling are not correct. How can I fix this?