Skip to content

Procedural City

Kiryha edited this page May 29, 2025 · 78 revisions

Introduction

This project aims to design and implement a procedural workflow for city modeling in Houdini.

The high-level overview of the process:

Content

Mass Model Generation

This procedural system generates a rough polygonal building representation called Mass Model. The output of this system is a polygonal shape with a set of primitive attributes.

Mass Model Attributes

The Mass Model comes with all necessary primitive attributes for the next stages:

  • facade_orientation: Facade Orientation, which axis the facade is facing, F - front, B - back, S - side.
  • facade_scale: Facade Scale Factor, the width of the facade in relative units, 1 - narrow, 2 - medium, 3 - wide, etc.
  • building_style: Building Style, string attribute for a certain architectural style. Each style has its own JSON file.

Procedural Facade Segmentation with a Shape Grammar

Abstract

Introducing a procedural system to split a polygonal Mass Model of a building into smaller sections using shape grammar rules. According to the grammar, the approach treats the facade as an initial shape subdivided vertically into floors and then horizontally into modules — facade elements (e.g., walls, windows, and doors, etc.). This allows a Houdini setup where a single input quad is automatically partitioned floor-by-floor and window-by-window based on rules.

The grammar operates in two tiers:

  • Level grammar (vertical) cuts the wall into floors.
  • Bucket grammar (horizontal) cuts each floor into labelled slots.

This setup interprets the JSON, performs the cuts, and writes attributes. After splitting, each slot is tagged so that later passes can instance or generate geometry (utilizing a pre-modeled modules library or procedural assets).

The inputs to the system are: a building Mass Model with defined facade attributes, and a Shape Grammar File. The result is a Mass Model split into floors and modules, with module attributes assigned:

The Mass Model facade attributes include:

  • Building Style ("BDF", "HKG", etc.)
  • Facade Orientation ("F", "S", "B", etc.)
  • Facade Scale (0, 1, 2, etc.)

The Houdini HIP file with injected data and logic.

Workflow

The system workflow is divided into three main stages:

  • Creating shape grammar files to define building styles,
  • Parsing the Shape Grammar from a JSON definition,
  • Geometry Segmentation of the input polygon according to the parsed rules.

Below is the high-level process:

Shape Grammar Definition

The JSON describes the mass model hierarchical split rules. Each building in the city can have its own style. We store each Building Style in a separate Building Definition JSON file (BDF). We assume the artist authored this JSON manually.

In the JSON, we represent the facade decomposition in two tiers:

  • Vertical rules (Level Grammar): Defines how the facade is divided into levels and floors (vertical slices). Each entry might specify a floor height and a repetition count for repeated floors.

  • Horizontal rules (Facade Grammar per floor): For each floor type, defines how that floor’s rectangle is split along the width. It lists segments from left to right, each with a type label (like “Wall”, “Window”, “Entrance”) and a width. Widths can be absolute or relative; to allow flexibility, we can mark certain segments as “flex” (flexible) to absorb remaining space or to repeat a pattern. The grammar can also include shorthand for repeating patterns on a floor (for instance, a window module repeated multiple times with a separator between each).

We split the mass model into facades (vertical walls). We split the facade into levels, and each level can have one or several floors. If we repeat the level more than once (the "level_repeat" parameter), then we have several floors of the same level. Then, each floor is divided horizontally into a horizontal segmentation pattern.

The same style can be applied to the Mass Models of different sizes. To support height variations, the level_repeat parameter can be defined as * token, which means repeating the level several times to fill the height of the level.

To support the width variations, we are introducing a facade scale and facade orientation attributes (calculated and stored at the mass model level). Facade scale is an integer value for relative facade width (0, 1, 2, etc). Facade orientation defines the side of the building ("F" for front, "S" for side, "R" for the roof, etc). The facade scale combined with the facade orientation (F0, S0, F1, etc) gives us a floor rule.

Shape Grammar Example

The example facade is an N-floor building. First floor is a ground floor and then we have 4 similar foors.

{
  "levels": {
    "0": {                                  // Definition of the first level: one static floor
      "level_index": 0,                     // Number of current level
      "floor_height": 4.0,                  // Height of the floors in current level
      "floor_repeat": 1,                    // Level 0 divided into 1 floor
      "floor_rule": {
        "F0": [ "C|(A)*|E|(A)*|C" ],        // Rule of the building front ("F" attribute of a Masss Model)
        "S0": [ "C|(A)*|C" ]                // Rule for the building sides
      }
    },

    "1": {                                  // Definition of the second level: N floors
      "level_index": 1,
      "floor_height": 3.0,                  // Height of the floor in second level
      "floor_repeat": "*",                  // Repeat floor N times to fit facade height
      "floor_rule": {
        "F0": [ "C|(A)*|C" ],               // Module "A" repeated between corner modules "C", "A" can be scaled
        "S0": [ "C|(A)|C" ]                 // Same pattern, the width of "A" is static
      }
    },

    "2": {                                  // Definition of the last level: two top floors
      "level_index": 1,
      "floor_height": 5.0,                  // Height of the top floor
      "floor_repeat": 2,                    // Repeat floor 2 times
      "floor_rule": {
        "F0": [ "C|(W)*|C" ],               
        "S0": [ "C|(W)*|C" ]                 
      }
    }
  },

  "modules": {                              // Reusable façade pieces
    "C": { "width": 0.5 },                  // Corner pier
    "A": { "width": 1.0 },                  // Wall/pier between windows
    "W": { "width": 2.0 },                  // Window bay
    "E": { "width": 2.5 }                   // Entrance door
  }
}

levels — The vertical split of the facade. The "0", "1", "2", etc. keys tell how many levels each facade has. One level can have several floors (if level_repeat > 1)

modules — module library, definitions of each module.

"floor_repeat": "*" — If floor repeat is set to "*" instead of an integer number, repeat the floor to fit the mass model height.

F0, F1, S0, etc — Vertical split, front (F) and side (S) facades definitions. Those primitive attributes come from the mass model.

"F0": ["C|(W)C"] — Front orientation "F", size bucket "0":

  • "F0" = front‑facade rule,
  • The list holds one rule‑string, so that exact string is applied when the system sees a face tagged F0.

F0, F1, F2 … — Same face orientation, different size classes.

  • All strings are written for the front orientation, but with increasing nominal length.
  • At run‑time, the solver measures the strip length, expands each grammar, computes the leftover gap, and picks the first rule that fits (or best fit).
    • Short façade → F0
    • Medium → F1
    • Long → F2, etc.
  • If you have only one size to support, one entry (e.g., F0) is enough.

["C|(A)|C", "C|(B)|(C-D)*|(B)|C"] — Several strings inside one list = alternative patterns for the same size class.

  • Both strings are valid F0 layouts.
  • The generator picks one:
    • deterministic (first that fits some secondary constraint), or
    • stochastic (random choice / weighted choice).
  • Useful for subtle variation without changing size category.

Shape Grammar Semantics

  • | — Module bucket boundary. A bucket can be filled with a single module, repeated modules, or a macro pattern of modules
  • C, W, A, ... — Module. A key in the BDF file modules dictionary. Each level in the BDF has it's own module dictionary,
  • (...) — Repeat module/modules in parentheses unless it fits in the facade length.
  • [...]n — Repeat module in square brackets "n" times
  • * — The buckets module can be scaled to fit the facade.

Segmentation Process

Vertical Segmentation (Floors/Levels)

We loop through each facade primitive. On a primitive level, we get Mass Model attributes.
According to the attributes and BDF data, we calculate the "levels data" necessary for vertical segmentation. Within another loop (by number of floors), we split the facade into levels and floors, sort the floor primitives, and assign attributes to floor primitives.

Note: one floor of the level should be set as repeatable ("floor_repeat": "*") to support different mass model heights. The system will calculate how many repeatable floors we can fit into the facade height without scaling. As a result, when pre-molded modules are placed on the facade, the final building can be smaller than the mass model.

Horizontal Segmentation (Tiles/Windows)

Each floor band is then subdivided vertically (along the facade’s width) into smaller segments. According to the grammar, these segments could represent walls, windows, doors, etc., arranged across the floor​. For instance, a ground floor might be split into an entrance region and wall segments, while an upper floor is split into a series of window panels separated by wall columns.

After these splits, the facade is composed of many smaller polygonal pieces, each corresponding to a logical facade element (e.g. a window region or wall panel). At this stage, the segmentation is complete. The next step will be to instantiate detailed pre-modeled geometry.

Segmentation Attributes

To propagate building data to the next stages of procedural modeling, we rely on point, primitive and detail attributes of geometry.

Segmented Mass Model Attributes

After segmentation is done, we get a splitter facade model with such primitive attributes:

  • facade_height: The vertical size of the facade primitive. Used to calculate repeatable floors to fit the facade height.
  • level_index: Level number of current primitive
  • floor_index: Floor number of current primitive
  • floor_scale: Vertical scale of all modules in the level
  • floor_height: Vertical size of the floor from BDF
  • module_name: Module Name for current primitive
  • module_scale: Horizontal scale of a current module
  • module_width: Horizontal size of a module from BDF
  • X, Y, Z: Local axis of current primitive, Z is a facade Normal
  • P0, P1: A floor baseline's start and end point coordinates.

Assembly of Building

The example of procedural facade modelling: JSON Building Definition File (HKG Style), Mass Model, Segmented Mass Model with attributes, assembled from procedural asset building.

The segmented mass model is a blueprint for facade detailing. We have two ways to replace the mass model with the final geometry for the rendering:

  • Utilize pre-modeled modules (e.g. building scans or handmade pieces)
  • Copy procedural assets

The first option will allow the instancing of a highly detailed geometry at the scale of the city. Memory efficient.

Using procedural assets allows infinite variations on the building level for the cost of prformance.

The Asset Library

  • Fixed‑width meshes for any element that can’t stretch: windows, doors, corner piers, balcony boxes. Width variants are cheap: same mesh cloned at 1 m, 1.2 m, 1.5 m, 2 m…
  • Scalable meshes for plain surfaces: blank wall, trim, spandrel panel. Author with clean UVs along X so stretching doesn’t show.
  • Tile‑able meshes: repeating grille, louvre, vent strip; just repeat until fit.
  • Transition pieces: left and right “caps” that bluntly hide mismatches (Matrix used WallCapL, WallCapR).

Nothing stops you from generating the mesh procedurally instead of pre‑modeling, but the film/game pipelines usually go with pre‑modeled modules + instancing because it’s fast and art‑directable.

Procedural Assets

Substitution of modules with procedural assets provides full control of the building detailing. We can change parameters of a window individually for each copy, making each copy unique.

Solaris and USD

Import USD to Solaris

  • From "Geo" context: Scene Import (All) or Sop Import nodes.
  • From USD File: Sublayer or Asset Reference nodes

Unpack USD

  • The SOP Modify node, Unpack USD Primitives to Polygons check box
  • The SOP Modify node, go inside, and create the Unpack USD node.

Export USD from Solaris

The USD ROP node, set Save Style to Flatten Stage

Geometry

In the geometry context, all meshes will be brought into Solaris as a single mesh. Introduce the "name" attribute to split mesh into different pieces.

Material assignment

Create material: Quick Surace Material and then Assign Material or Material Linker. Add node Name to geometry nodes to get different objects in USD

Subdivide Geometry

Create Mesh Edit node, turn ON Subdivision Scheme, drag and drop mesh primitive to the Primitives field. To control the quality of subdivision add Render Geometry Settings, set Dising > Dising Quality

USD/Solaris geometry prep: define material assignment by Houdini groups (create a group for each material on geometry), then if you add a "Name" node after groups, USD file will contain mesh divided by groups and in Solaris, you will be able to assign materials by those groups.

Remove internal edges of a polygon: Divide SOP > Remove Shared Edges

References

[1] The Matrix Awakens: Creating a World | Tech Talk | State of Unreal 2022. https://www.youtube.com/watch?v=xLVJP-o0g28

Clone this wiki locally