A Blender script to generate 3D cities procedurally.
We created a CGA system, rule set and primitives to generate a low-poly city. The original inspiration was Santorini (half inspiried by the real Greek city, half by the board game), although our style somehow evolved into a mix of ancient roman city with islamic architecture and full of colors.
Our primitives are more detailed than what one would expect from CGA systems (e.g. one set of columns, instead of constructing it procedurally too), but this allows us to focus on the larger rules without having to spend too much time modeling the details.
Our approach is based on the work by Müller et al., which define a shape grammar for constructing procedural 3D buildings, which can be extended to generate cities. However, we made some adaptations to the rules, to fit our particular case. Below we list the main differences.
Ideas proposed by Müller et al. which we did not implement:
- Component split: all our scopes are 3-dimensional.
- Textures: we only deal with RGB colors.
Things we do differently:
- Split, repeat, etc. only work 1 axis at a time. But we can write two or three rules and achieve the same result.
- Rotation limited to the Z axis, since that is the only one that we really need (and we don’t have to worry about 3D rotation issues). Also, we only allow rotation when primitives are being spawned, not for a whole scope.
- Translation and scale are used as separate rules. I.e. we need a specific rule to translate the scope. This implies having longer rule sets, but we don’t miss any feature from the original paper and with the ideas from the following section, we think it’s more flexible.
New syntax rules defined by us:
- We allow many mathematical expressions in the rule parameters, such as:
Subdiv("X", (2*R)/scope_size_z + scope_origin_x, (R+1) 2rel){...}
, where “R” is a random number, and we have access to the scope properties. This allows for non-deterministic rules and actions based on the scope attributes. - We use conditions to determine if rules should be executed or not. Such as:
rule1 [scope_size_x > 2,rule2] -> Subdiv("X" , R , 1rel){rule3 , rule1}
,rule2 -> [...]
,rule3 -> [...]
. This means that rule1 will be executed if the current scope size in the X axis is larger than 2, otherwise, rule2 is executed instead. Moreover, we realized that with this feature we can call rule1 recursively, and perform random splits on the X axis until the limit is reached (essentially, we build a “while” loop instead of the “for” loop that the Repeat call provides). - We allow for additional parameters in the Instantiate rule. Other than the name of the obj model, we allow specifying a rotation (as explained before, but we also added the possibility of snapping to a multiple of some angle), flips (i.e. scale by a -1 factor) in the X and Y axis, and an RGB color. As with anything else, these can also be determined by expressions consisting partly of random numbers.
- A Choice rule, that will call any children rule with a specified probability. The same result could be achieved with conditions, but this simplifies the rule syntax.
- As said before, materials can be set through the instancing rule, but also loaded from the obj file. In some cases, only some material of the object is set through the rules, while the other comes from the obj (e.g. in fountains, the cement color is random through the rules, but the water material comes from the obj).
We provide the assets and rules for generating the city shown in the pictures, but note that the script would work with any other set of assets and rules to generate any procedural structure.