How many geometries is too much?

Hi,


I've recently started prototyping my first SceneKit app, and I have a sphere which I want to render as a surface of hexagons (a truncated icoshedron).


Now, in order to get the hexagon resolution I need, there are around 4500 hexagons to be displayed. I managed to get this working by creating a SCNGeometry to represent each hexagon, and adding it to an SCNNode.


This works, however it's terribly slow, especially on older devices. On my iPhone 5s, the frame rate is just under 30fps, and on an iPad Mini it's around 4fps.


I suspect I can probably render it all as a single geometry however that (I think) means I lose the ability to animate individual hexagons, change their colours/materials, etc.


So for 4500 hexagons I'm looking at 27,000 vertices. That sounds like a lot, but I see and read about apps and games that seem to have much much larger vertice counts, so it seems to me that I must be doing something wrong.


At this point the materials are all just a basic colour; I'm not trying to do anything fancy. Below is the code that creates the geometry objects and adds them to the SCNNode.


Is there another way to be able to have individually addressable hexagons (facets) like this that is more efficient?


Thanks in anticipaton.


        UInt16 indices[] = {0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5};
      
        NSData *indiceData = [NSData dataWithBytes:indices
                                            length:sizeof(UInt16) * 12];

        SCNGeometryElement *element =
          [SCNGeometryElement geometryElementWithData:indiceData
                                        primitiveType:SCNGeometryPrimitiveTypeTriangles
                                       primitiveCount:12
                                       bytesPerIndex:sizeof(UInt16)];
      
        for (HSHex *hex in internalSphere.hexs) {
            SCNVector3 vertices[[hex boundaryLength]+1];
          
            for (NSUInteger index = 0; index < [hex boundaryLength]; index++) {
                vertices[index] = [hex boundaryPointAtIndex:index];
            }
          
            vertices[[hex boundaryLength]] = [hex boundaryPointAtIndex:0];
          
            SCNGeometrySource *verticeSource =
               [SCNGeometrySource geometrySourceWithVertices:vertices
                                                       count:([hex boundaryLength]+1)];
          
            SCNGeometry *hexGeometry = 
               [SCNGeometry geometryWithSources:[NSArray arrayWithObject:verticeSource]
                                       elements:[NSArray arrayWithObject:element]];


            hexGeometry.firstMaterial.diffuse.contents = [UIColor greenColor];
            hexGeometry.firstMaterial.doubleSided = YES;
              
            SCNNode *node = [SCNNode nodeWithGeometry:hexGeometry];
            [self addChildNode:node];
        }

Accepted Reply

There is a fundamental difference to grasp.


An SCNGeometry defines the relative position of vertices to one another in a model.

An SCNNode defines the transform (position, rotation, scale) of the geometry it displays in the world (or in its parent node if attached), relative to its pivot point.


The same geometry instance can be assigned to thousands of different nodes, and therefore displayed in many different locations.


Let's take an example. You create a car racing game.


You need an Ford GT model. You create the geometry for it in Maya. The model defines the relative position of vertices that make up the car, as well as its materials.


Now, you import the model in XCode. You can instantiate the same geometry n times (e.g 15 Ford GT cars). Each car will be assigned to a different SCNNode instance and therefore displayed at a different position (and also rotation on all three axes) on the race track. There are 5 racing teams, and each racing team has 3 cars. You need distinct materials for these 5 teams. The 3 cars in each team will share the same materials. You can copy the geometry so you can assign different materials to change the look of the cars.

Replies

Do you really need to create a different instance of SCNGeometry for each hexagon ?


Why not re-using the same instance (or a set of instances with different materials applied) ?


That may be the source of your performance problem.


A number of nodes can use the same geometry and that makes rendering quicker.


From the documentation:


"You can easily copy geometries and change their materials. A geometry object manages the association between immutable vertex data and a mutable assignment of materials. To make a geometry appear more than once in the same scene with a different set of materials, use its inherited

copy
method. The copy shares the underlying vertex data of the original, but can be assigned materials independently. You can thus make many copies of a geometry without incurring a significant cost to rendering performance."


You would only need to make as many copies of the SCNGeometry as you have different materials. Then, your hexagone nodes could be assigned e.g geometry1, geometry2, geometry3, geometry4. You could assign these 4 geometries to thousands of nodes, sharing the same geometries and materials.

As I understand it, each geometry represents a unique set of vertices, so if I reuse the same geometry, they will all use the same vertices, and be in the same position. I tried to improve this by reusing the SCNGeometryElement object for each geometry as the indices are always the same, but the fact that every hex has it's own unique position meant that I needed (I thought) to have a different geometry for each hex.


As your quote says:


"A geometry object manages the association between immutable vertex data and a mutable assignment of materials."

There is a fundamental difference to grasp.


An SCNGeometry defines the relative position of vertices to one another in a model.

An SCNNode defines the transform (position, rotation, scale) of the geometry it displays in the world (or in its parent node if attached), relative to its pivot point.


The same geometry instance can be assigned to thousands of different nodes, and therefore displayed in many different locations.


Let's take an example. You create a car racing game.


You need an Ford GT model. You create the geometry for it in Maya. The model defines the relative position of vertices that make up the car, as well as its materials.


Now, you import the model in XCode. You can instantiate the same geometry n times (e.g 15 Ford GT cars). Each car will be assigned to a different SCNNode instance and therefore displayed at a different position (and also rotation on all three axes) on the race track. There are 5 racing teams, and each racing team has 3 cars. You need distinct materials for these 5 teams. The 3 cars in each team will share the same materials. You can copy the geometry so you can assign different materials to change the look of the cars.

Light bulb moment! Thank you so much. Now I think I can see what I need to do. So instead of those 4500 geometries, I need 1 geometry and 4500 nodes, positioned appropriately.


How does this become a smaller number of draws, or is this a question on the fundamental mechanism behind all this? Looking forward to tryign this out tonight.


I'll come back when I've played with it. (just a little excited about the potential of seeing this work)

>>> So instead of those 4500 geometries, I need 1 geometry and 4500 nodes, positioned appropriately.

Correct.

Interesting!


On a device that supports Metal (my 5s), the draw count drops from 4.36K to 8, and the frame rate stays above 32fps when the entire sphere is visible.


On a device that only supports OpenGL, there appears to be no benefit to this approach. The draw count remains at 4.36K, and the frame rate drops to 5fps.


I would have thought the behaviour would be more consistent.


The other complication of this technique is that I now need to orient each node so that it is at 90 degrees to the radial from the centre of the sphere to it's midpoint. I may also need to rotate each within that plane so that they appear to form a solid object.


Another thing I note is that when I zoom in far enough, causing some nodes to be off screen, scenekit seems to be smart enough to drop the draw count, and this improve the frame rate. Is there some way to tell the system, that when everything is on screen (all 4.36K nodes), but they are so small that they can't be rendered clearly, to reduce the draw count by simply no drawing nodes that can't possibly been seen?

>>> Is there some way to tell the system, that when everything is on screen (all 4.36K nodes), but they are so small that they can't be rendered clearly, to reduce the draw count by simply no drawing nodes that can't possibly been seen?


Yes, it's possible thanks to the

isNodeInsideFrustum:withPointOfView:
method.

Thanks for your help. I've marked your earlier response as the answer to the original question.


Working out how to orient the nodes is proving to be trickier than I'd thought, plus dealing with the OpenGL performance is something I'd hoped not to have to worry about.


Thanks

OK, new problem. Now that I've progressed a lot further with the tranformation that positions and orients each of the nodes (that share the same geometry), I've tried animating just one of them.


Animating via movement of the node itself works just fine however if I want to change the colour, or texture (material) of a specific node I can't because they all use the same SCNGeometry object, and the material is coupled to the geometry.


I should note that I've tried the advice from the documentation to use something like:


- (void)duplicateNode:(SCNNode *)node withMaterial:(SCNMaterial *)material {
   SCNNode *newNode = [node clone];
   newNode.geometry = [node.geometry copy];
   newNode.geometry.firstMaterial = material;
}


To do this, however as soon as I do, regardless of the device, OpenGL or Metal choice, the draw count shoots up from the 8 draws to the 4.35K draws again, and the frame rate drops to about 8. So that puts me at square one again.


Is there a way to share a geometry across multiple nodes whilst having control over the individual appearance of the nodes?


Thanks

How many distinct materials do you need ?


If you need only say 10 of them, create 10 geometries with copy, assign them 10 different materials, then just swap geometries as needed per hexagon.

Well, I was hoping to be able to support the ability to texture them individually, and there are 4300 odd in the sphere, but I've decided to get pragmatic today. I've tried exactly what you've suggested, and if I go for (say) 300 different materials, I still get 60fps on an iPhone 5s running Metal.


I still have a problem on devices that don't run Metal because SceneKit doesn't seem to give any performance boost by reducing the geometry count on OpenGL.


Thanks again for your help.