How to improve three.js performance, with react-three-fiber?

Hello, I know there’s quite a few questions on this forum on performance, but I tried to follow most guidelines and I want to know if I am doing anything wrong in particular with how I am using react-three-fiber. I also don’t really know what to make of my performance stats:

image

I read that three.js should be able to handle 400 geometries just fine, but I get very low fps. Also I have no idea what to make of the other numbers, is 808 calls bad?

I read this page which has a lot of helpful tips. And one of them is to avoid disposing of geometries, as opposed to making them invisible. As well as avoiding recreating geometries and vectors and instead use the same ones that were already created. I tried to do these things, but I get confused how they can be implemented with r3f. For example, I have an array in zustand that I am using to render my geometries, it gets updated from time to time, so I am using react’s framework to my advantage and update the scene as the array changes like this:

const Boxes = (props) => {
     const shapes = useShapes(state => state.shapes) // my zustand store, which is just an array of points

     const onClickHandler = (i) => {//.... on click handler changes for each mesh

     return (
          <>
          {shapes.map( (shape, i) => (
               <mesh onClick={() => onClickHandler(i)}>
                   <extrudeGeometry args={[new THREE.Shape(shape), {depth: 1}]}
                   <meshStandardMaterial/>
                   <Outlines .../>
                   <Edges .../> // Drei outline and edges
               </mesh>
          ))}
          </>
     )
}

In my mind this seems to be the most intuitive way of using r3f for an array of geometries, but maybe that would dispose of these geometries when the array changes, would it? In addition, regarding is this part:

new THREE.Shape(shape)

Is this something I’d want to avoid? As I am creating new shape objects each time it runs?

I also thought of using instanced meshes but each of my meshes have different click handlers

Do you identify anything that seems indicative of problems in my current use of three.js? Or am I probably looking at the wrong place entirely? I am not entirely sure where the issue could be, if it is in my use of three.js, in my use of r3f, or somewhere else!

Thank you!!

yes 808 draw calls is bad. 427 geometries is bad. you double and tripple these numbers up by drawing outlines and edges.

you think of threejs as fast because it is backed by a gpu but it’s not free. you must absolutely watch your draw calls, number of vertices, materials, etc. instance as much as you possibly can, use blender to merge stuff etc.

and there’s a bug, too

<extrudeGeometry args={[new THREE.Shape(shape), {depth: 1}]}

this is the same as new ExtrudeGeometry(new THREE.Shape(shape), {depth: 1}) and since these are constructor arguments it’s going to re-create the geometry every time the component renders. you forgot the key as well. if some of the shapes are the same you might be able to re-use them.

const material = new MeshStandardMaterial()
function Foo() {
...
  const shapes = useMemo(() => points.map(pointArr => new THREE.Shape(pointArr)), [points])
  const config = useMemo(() => ({ depth: 1 }), [])
...
  {shapes.map((shape, i) => (
    <mesh key={i} onClick={() => onClickHandler(i)} material={material}>
      <extrudeGeometry args={[shape, config]}>

still bad because that’s still going to be a lot of draw calls. at least it won’t re-do the whole thing from scratch every render.

2 Likes

ps for so many draw calls it might make more sense to add the outlines in postpro in a single pass. this would already save you 400+ calls. each <Edges> and each <Outlines> renders the object again with a simple custom shader. it’s a nice trick and often useful, but i wouldn’t use it for hundreds of objects.

2 Likes

Thank you so much! That’s super helpful!! So assuming normal three.js behavior, is 808 draw calls for 427 geometries not what one would expect? Does that indicate that there is an error beyond my three.js design, such as in my zustand store maybe?

As for the outlines and edges, they are really crucial for my app, so I assume I’ll have to stick with them. I tried using post-processing outlines, but I can’t for the life of me to get it to work. For some reason the post processing library makes my entire app behave weirdly (things like shadows, transparent objects, and depth buffer all get impacted in ways I don’t understand: as in this question) so using Drei’s outlines had been lifesaving so far.