FPS Drop With Lower Object Count

Hi Experts,
I’ve designed a 3D model with Blender program. When I import my model to three js project I’m faced with low fps issue. I’ve checked the object count in scene and triangles. The object count seems to be 324. Why fps drops occur with this results ? The object count is too high or is there another wrong programming issue ? If you can informing me about this situation I would be very appreciated.

Here is my webgl-renderer and orbit controls code blocks;

createRenderer() {
      var _this = this;
      this.renderer = new THREE.WebGLRenderer({
        powerPreference: "high-performance",
        alpha: true,
        antialias: true
      });
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
      this.renderer.shadowMap.enabled = true;
      // this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      this.renderer.physicallyCorrectLights = true;
      // this.renderer.setClearColor(0xffffff);
      this.container.appendChild(this.renderer.domElement);
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.rotateSpeed = 1.5;
      this.controls.panSpeed = 1.5;
      this.controls.target.set(0, 0, 0);
      this.controls.zoomSpeed = 1.5;
      this.controls.minDistance = 0;
      this.controls.maxPolarAngle = 0.9 * Math.PI / 2;
      oPmremGenerator = new THREE.PMREMGenerator(this.renderer);
      oPmremGenerator.compileEquirectangularShader();
      this.controls.addEventListener("change", this.renderScene);
      this.renderer.dispose();
    },

renderScene() {
      this.renderer.render(this.scene, this.camera);
      // this.camera.updateMatrixWorld();
    },

Here is the image showing that the object count and fps value;

Thanks for your support.

When I zoomed out I can rotate very quickly around my model. But when I zoomed in the rotate process is being laggy.

This usually means your device is hitting limits in the fragment shader. PBR materials (MeshStandardMaterial, MeshPhysicalMaterial), the number of lights in the scene, and shadows all increase the cost of the fragment shader, and zooming in fills the screen with those expensive fragments.

To improve performance in this situation, you can do things like:

(a) reduce the number of lights
(b) bake lights
(c) use cheaper materials like MeshPhongMaterial, MeshLambertMaterial, or MeshBasicMaterial (unlit)

If you’re exporting glTF/GLB from Blender, you could get Standard, Physical, or Basic materials in three.js. To get Phong or Lambert you would have to traverse the objects and replace their materials.

4 Likes

Thanks for your help @donmccurdy. I’m exporting my model with GLB format. So, I’ve checked the all objects in to the model and every object’s material is consist of MeshStandard or MeshPhysical material. If these material types effect the performance badly I’m going to try replace all materials to the MeshBasicMaterial. But most of my material has a texture. How do I set the actual texture to the new MeshBasicMaterial ? Is it correct if I keep the textures on the actively used material in another variable and transfer it to the new material?
Thanks for your support.

I’m using only three lights in my scene (1 AmbientLight and 2 DirectionalLight) I’ve changed all material type to the MeshBasicMaterial and disabled shadows. But still occurs fps drops. When I removed the all lights from scene fps value is being very high. Where am I doing mistake ?

Here is my code representing override materials;

disposeScene() {
      this.scene.traverse(oObject => {
        if (!oObject.isMesh) return;
        oObject.geometry.dispose();
        if (oObject.material.isMaterial) {
          this.disposeMaterial(oObject, oObject.material);
        } else {
          // an array of materials
          for (const material of oObject.material)
            this.disposeMaterial(oObject, material);
        }
      });
    },
    disposeMaterial(oObject, oMaterial) {
      oObject.material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, color: new THREE.Color("#26A1FF") });
      oObject.material.needsUpdate = true;
      oObject.material.dispose();
      oMaterial.dispose();
      // Dispose Textures
      for (const key of Object.keys(oMaterial)) {
        const oValue = oMaterial[key];
        if (oValue && typeof oValue === "object" && "minFilter" in oValue) {
          oValue.dispose();
        }
      }
    },

Hm this part doesn’t look light, you don’t want to dispose the new material I think.

700K vertices is a fair amount, DoubleSide will make that slower, and every light adds cost too. It may also be that the vertex and object count is part of the problem here (i.e. when you zoom out, nothing can be frustum culled). If you want to post a demo others can probably debug better, I am just guessing based on screenshots here. :slight_smile:

1 Like

I suggest looking into scene graph complexity. Every frame, every node of your scene graph will visited and have it’s world transform matrix updated, this can get quite expensive.

Another thing is material/texture switching, you have only a “few” objects, but depending on the order in which they are drawn - three.js will have to switch materials and textures, in worst case - for every object. This gets expensive potentially as it may require actually re-uploading textures onto the GPU (depends on GPU memory) or at the very least cache problems, since you’re using different areas of memory all the time.

1 Like

Thanks for your help @donmccurdy. When I removed the shadows and used to MeshBasicMaterial the performance increase significantly. But I didn’t figure out why shouldn’t I use the dispose method for the new assigned material ? What does dispose method exactly ? I’m also using this.renderer.renderLists.dispose(); It can be effect the performance ?
Thanks a lot.

Thanks for your help @Usnul . In fact the textures that I used are not high quality images. I’m using 512x512px resolution for the textures. Do I need to decrease image resolution lower than 512px ?

The dispose() method should be used when you are completely done using a material, to destroy it and remove its resources from the GPU.

Do I need to decrease image resolution lower than 512px ?

It depends on the total volume of textures. If there are, say, less than 100 textures used - it should be fine at that resolution. Here’s a bit of math for you to help you figure out how much GPU memory you’re using:

(4 bytes per pixel in case of PNG)*(image width)*(image height)
so for 512x512 PNGs they take exactly 1 megabyte of GPU memory

if you use JPGs those are 3/4 of the size of PNG, as they don’t have alpha(transparency) channel of the PNG, so 3 bytes per pixel instead of PNG’s 4 bytes per pixel.

Now, let’s say you use MeshStandardMaterial, you have following textures per material:

  • diffuse
  • metalness
  • roughness
  • normal

You might have more or less in reality. This, with 512 textures is ~4Mb of textures per material. If you have 10 materials in the scene, it will take 40Mb, if you have 50 materials - that’s 200Mb and so on.

Personally I don’t think that texture size is an issue here, if the textures are indeed only 512x512. However, you have what looks to be like stacked boxes and light posts in the scene in fairly large quantity. Those use distinct materials. Here’s how three.js might be drawing those:

1: load box material
2: draw box
3: load light post material
4: draw light post
5: load box material
6: draw box
...

This is quite costly, because you’re doing a lot of material switching. And things would work much faster if you can avoid that, i.e. if you can forge three.js to draw objects that use same material all in a single batch.

Unfortunately I’ve heard this is often not the case, and that WebGL backends end up storing RGB as RGBA anyway. WebGL best practices - Web APIs | MDN

As is the case with many things in life :slight_smile:

But still, if performance is identical for your usecases - I would recommend using the designed API and ignoring the implementation detail. Who knows, maybe one day they will fix it? :thinking:

Either way, unless you’re on a mobile device with fairly low GPU memory, or are on an integrated GPU - memory is probably not the issue. Hard to say for sure without seeing the actual model though. Based on the screenshots, there are 36 textures, and at 1mb per texture that would only take 36Mb.

1 Like