In total, the scene I sent to the render farm consisted of approximately 215000 objects. Most of these were instances of houses, trees, the glass roof, windows and other models. I used SideFX Houdini to lay out the points, and then copied the instances to them.
Glass roof
The glass roof was the setting stone for the instances underneath, as it set a proportion of how big the blocks of houses were. Naturally, I laid it out first. I created a tube sop, and rotated every second row of it to create a base point cloud for instancing. These instances were packed in the CopyToPoints SOP.
Base points used for instancing Instanced object Instanced object
Houses
There are 21 variants of the houses in four categories – small, medium, wide and large. They were imported from a kitbash in Maya. I used VEX to categorize them and to create a variant attribute, used when copying the houses to an appropriately scaled lot. The lots were created using Chain SOP, which deformed a simple grid on a hexagonal curve. The scale of the houses copied was determined by hand using groups, however I imagine this could have easily been set up procedurally.
Block variant Block variant node setup The result using multiple lots
These lots were projected on the ground plane using Ray SOP, and cleaned up of pieces that contained less than four points per primitive.
For randomizing which variant of the house from its respective category goes on its lot, I used this VEX snippet:
i@variant = fit(rand(@ptnum *chf("Seed")),0,1,0,6) ;
on the point groups I created earlier. This randomized the variant number, which the CopyToPoints SOP took as an input when instancing.


After a few iterations I was happy with the result.
Trees
I used a similar approach when loading the trees and categorized them based on their size. The only difference is that I used the AttributePaint SOP to create a mask for instancing.


A VEX snippet I found myself using repeatedly while instancing:
for (int i = 0; i < @numpt; i++) {
f@posy = @P.y; vector a = set(0,@posy,0);@N = normalize(a-@P);
}
This orients every point’s normal at X,Z origin.