11
\$\begingroup\$

In three.js we can simply merge geometry to limit the amount of draw calls and thus increase performance. In a simple test with one material, I could draw 50.000 cubes + shadows @ 60fps on my GTX660 GPU. Without geometry merging, 5.000 cubes already caused a problem.

I wonder how to preserve the benefits of rendering each cube mesh on it's own. For example, how to pick a cube mesh when everything is merged into one geometry? By default that's not possible of course.

Is there any common technique for this problem? After all I do have all the unmerged mesh objects even after merging. So there must be some way to utilize them for picking?

What I want to do in a nutshell

  • SimCity like game for learning purposes
  • Each house is a cube mesh
  • Want to render 50.000 houses and be able to add & remove houses
  • House-selection via mouse-cursor (picking) must be possible
\$\endgroup\$
1
  • \$\begingroup\$ I'm not sure whether this is of use to you, but I mention it for completeness. Simplygon has a royalty based pricing model for indie developers and can do a lot of this mesh merging and partitioning at design time. \$\endgroup\$
    – steeveeet
    Commented Dec 13, 2016 at 17:34

4 Answers 4

11
\$\begingroup\$

Ok I got it. After merging the whole geometry together, I still have the individual meshes in an array. So I can simply use these meshes for raycasting, even though they are not even rendered. Took me a while to realize this.

enter image description here

For the picking I use this octree implementation: http://threejs.org/examples/#webgl_octree_raycasting

This brings down the intersection tests per update down from 50.000 to ~500. Without the octree the fps will decrease heavily.

The orange picking hull you see is actually the now rendered mesh (+1 draw call) with a changed material and modified size.

enter image description here

So I guess the next step is to implement some kind of map partitioning. That is, break the merged geometry up into several pieces. The reason for this is that the merged geometry has a large amount of vertices. That means If I move the map 99% off screen, the graphics card still has to process all the vertices because the geometry is still in view, at least the 1% of it. So if it's broken up, only the pieces in view have to be rendered.

\$\endgroup\$
8
  • \$\begingroup\$ Thank you so much for this insight. I've been trying to find a way to do this as well, and i think your solution here is most excellent! Quick question though: For your local list of objects (i.e. Three.Object3D's), what kind of properties are required for the rayCaster.intersectObjects() to work? \$\endgroup\$ Commented Sep 26, 2014 at 0:38
  • \$\begingroup\$ Very nicely done. \$\endgroup\$ Commented Sep 26, 2014 at 1:18
  • \$\begingroup\$ I have a similar problem, but somehow cannot get the raycasting work with r70. Did you do anything special while creating and merging the boxes? I use the following code (pastebin.com/PStaAF3P) to create and merge the nodes, but maybe there is something missing in order to get the raycaster working? \$\endgroup\$
    – fhahn
    Commented Mar 5, 2015 at 10:12
  • \$\begingroup\$ Quick question: I am doing something very similar to you (a 3d map based on geoJSON data). With your method, whenever you rotate the camera do you need to also rotate the pre-existing building meshes? Or do you just add the individual boxes to the scene but don't render them? \$\endgroup\$
    – Spencer
    Commented Oct 19, 2015 at 21:01
  • \$\begingroup\$ @Spencer No don't need to rotate. I keep individual boxes in global array and only add merged geometry to scene. You can then do raycasting with the object in global array, because raycaster depends on camera matrix and each boxes position. Most likely not best way, this was just proof of concept for me. Doing no three.js anymore at this point. \$\endgroup\$
    – user990827
    Commented Oct 20, 2015 at 2:38
1
\$\begingroup\$

For picking you could also render ID's for each cube to another render target and just check what the ID value is at the cursor. The benefit is that the picking is pixel perfect and works efficiently also for more complex geometry.

If all the objects have the same geometry, then you could use instanced rendering. One stream defines the geometry, while another defines properties per instance (e.g. transformation). For frustum culling you would need to build the instance stream each frame based on the visibility test. If you have large amount of objects though you may want to place those objects into a loose octree or something to speed up the culling.

\$\endgroup\$
0
\$\begingroup\$

I'm not sure about the particulars of three.js, but two possible performance hogs come to mind in general OpenGL:

  1. Have you considered instancing? You will only need one draw call as well and use less VRAM.
  2. Have you taken a serious look at the picking algorithm? If your cubes have bounding volumes in a list instead of a tree for example that would explain a difference of that magnitude.
\$\endgroup\$
0
\$\begingroup\$

Another approach you can take is to build some vertex attribute into your geometry and put highlight logic into your fragment shader. This is extremely useful when you don't want to have two copies of data in memory, and you'll have more control of how highlight is implemented.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .