Search code examples
macosperformance3dtexturesscenekit

In SceneKit, how can one tile a texture on differently sized objects while keeping draw calls minimal?


To improve performance/fps in a SceneKit scene, I would like to minimise the number of draw calls. The scene contains a procedurally generated city, for which I generate houses of random heights (each an SCNBox) and tile them with a single, identical repeating facade texture, like so:

enter image description here

The proper way to apply the textures appears to be as follows:

let material = SCNMaterial()
material.diffuse.contents = image
material.diffuse.wrapS = SCNWrapMode.repeat
material.diffuse.wrapT = SCNWrapMode.repeat
buildingGeometry.firstMaterial = material

This works. But as written, it stretches the material to fit the size of the faces of the box. To resize the textures to maintain aspect ratio, one needs to add the following code:

material.diffuse.contentsTransform = SCNMatrix4MakeScale(sx, sy, sz)

where sx, sy and sz are appropriate scale factors derived from size of the faces in the geometry. This also works.

But that latter approach implies that every node needs a custom material, which in turn means that I cannot re-use a single material for all of the houses, which in turn means that every single node requires an extra draw call.

Is there a way to use a single texture material to tile all of the houses (without stretching the texture)?


Solution

  • Using a surface shader modifier (SCNShaderModifierEntryPointSurface) you could modify _surface.diffuseTexcoord based on scn_node.boundingBox.

    Since the bounding box is dynamically fed to the shader all the objects will be using the same shader and will benefit from instancing (reducing the number of draw calls).

    The SCNShadable.h header file has more details on that.