Creating good-looking buildings can be a very labor-intensive task, as it usually involves placing a lot of individual meshes. The 'ProcBuilding' system in UE3 is designed to allow designers to very quickly create and edit good looking city buildings. It also automatically creates a low-LOD version of the building using a simple mesh and render-to-texture, to allow you to create large cities.
The tool is not designed to create an entire city with one click. It will not lay out streets and city blocks for you. The level designer is responsible for creating the shape and layout of buildings, and then this tool can be used to quickly apply meshes to that shape.
A speech on this system was given at GDC 2010, and the slides are available at the Unreal Technology site.
NOTE: This is still a work in progress! As such, this feature is unsupported at this time. Feedback is welcome!
ProcBuildings are just a special type of volume. So you place them like any other volume, by adjusting the builder brush, and then choosing ProcBuilding from the Volume list. Then you create a ProcBuildingRuleset in a package, using a new Kismet-like editor called Facade, which tells it how to apply meshes. Then you can try different rulesets on different buildings, change the shape of buildings etc. and it will update the meshing. To apply a ruleset you just need to set the Ruleset property of the volume, or simply drag-and-drop a ruleset from the ContentBrowser.
See the Procedural Buildings Tutorial page for a step by step example.
When working with a building that is not just a simple shape, it usually desirable to use multiple volumes. For performance reasons though, you should group these volumes together. This is done by choosing one volume to be the 'Base' volume, and then attaching other volumes to it using the 'Base' property. This can be made a lot easier using the Attachments Editor, or the 'ProcBuilding -> Group Buildings' option in the context menu. The 'Prefab Lock' button on the editor toolbar (the letter P) is also used to choose whether you are selecting entire ProcBuilding 'groups' or individual ProcBuilding volumes within a group. The 'Base' ProcBuilding volume in a group will show its wireframe in light green instead of the usual yellow, and can be quickly selected using 'ProcBuilding -> Select Base Building'.
You only need to set the Ruleset on the Base building, as child buildings will inherit this. You can set it on child Procbuilding volumes if you wish though, to override certain areas of the same ProcBuilding grouping. Note that all meshes that are used for a single building 'group' are owned by the 'Base' ProcBuilding volume, so if you click on them in the editor, you will find yourself selecting that volume.
The properties of a ProcBuilding volume are described below - more details on many of these are given later in this document.
| Ruleset || The ProcBuildingRuleset that should be applied to this volume |
| bGenerateRoofMesh || Whether a roof polygon should be generated for this building |
| bGenerateFloorMesh || whether a floor polygon should be generated for this building |
| bApplyRulesToRoof || If the Ruleset should be run on the roof of a building as well as the walls |
| bApplyRulesToFloor || If the Ruleset should be run on the floor of a building as well as the walls |
| bSplitWallsAtRoofLevels || If entire building group should be split horizontally at each roof level |
| bSplitWallsAtWallEdges || If building faces should be split vertically when another face intersects it |
| SimpleMeshMassiveLODDistance || How far away the building should transition to the low-LOD version |
| RenderToTexturePullBackAmount || When generating the render-to-texture for LOD, how far to pull back from building (and clip detail) |
| RoofLightmapRes || What resolution to use for lighting on the roof of the building |
| NonRectWallLightmapRes || What resolution to use for lighting on the non-rectangular wall polys |
| LODRenderToTextureScale || Controls the resolution of the LOD render-to-texture |
| BuildingMaterialParams || Additional parameters that are set on MaterialInstances applied to meshes in particular building. Can be used for tinting walls etc. |
| bBuildingBrushCollision || If collision should be enabled on the building volume itself |
The first thing that the building system does is extract a set of rectangular scopes from the designer-specified volume. These scopes form the basic working unit for the meshing system. Each scope has a location and rotation in space, and an X and Z dimension (X is across, Z is up). Obviously not all walls on the building are going to be rectangles however, and in those cases, the system extracts a rectangle by finding two parallel edges, and generating flat polygons to fill in the remaining areas.
Once a set of scopes have been found that describe the walls of the building, the ruleset works by doing one of two things:
In this way the ruleset is a graph of rules, where each 'top level' scope in turn is passed in on the left, broken down into smaller areas, and then meshes are placed to fill in those areas.
Rules are not normally executed on the roof of a building. This is just filled in with a textured poly (similar to the 'holes' in non-rectangular walls mentioned earlier).
- Break a scope into smaller scopes
- Place a mesh that takes up the space of the scope
Once you have created a new Ruleset (in the normal way, using the Content Browser), you can open it in the Facade editor. With no rule nodes selected, the property window will show the properties of the Ruleset itself:
The small black rectangle in Facade is the 'root' or 'top level' connector. You can now add new rule 'nodes' and connect them from here to break up each wall of the building and apply meshes. The green 'tick' button in Facade will regenerate meshing on all buildings which use the Ruleset being edited, allowing you to get a preview of how it looks.
The following is an explanation of each different rule that can currently be used.
| DefaultRoofMaterial || The material that should be applied to the roof polygon of the building |
| DefaultFloorMaterial || The material that should be applied to the floor polygon of the building |
| DefaultNonRectWallMaterial || The material that should be used on polygons created to fill in holes around wall scopes |
| RoofZOffset || Offset applied to the roof (at top of entire building) polygon along Z. Note that RoofEdgeScopeRaise is usually a better way to form walls around the roof. |
| NotRoofZOffset || Offset applied to roof (of any volume other than top of entire building) polygon along Z. Note that RoofEdgeScopeRaise is usually a better way to form walls around the roof. |
| FloorZOffset || Offset applied to the floor (at bottom of entire building) polygon along Z. |
| NotFloorZOffset || Offset applied to the floor (of any volume other than bottom of entire building) polygon along Z. |
| RoofPolyInset || How far to pull 'in' the vertices of the roof polygon |
| FloorPolyInset || How far to pull 'in' the vertices of the floor polygon |
| BuildingLODSpecular || Controls overall specular of the building low-LOD version |
| RoofEdgeScopeRaise || Extend any scopes that touch a roof up by this amount, forming short walls around the roof of the building |
| LODCubemap || Cubemap texture to use for cubemaps areas (normally glass) on the low-LOD version of the building |
| bLODOnlyRoof || If TRUE, a roof poly is only created for the low-LOD version of the building |
This is a very important rule, and actually adds a mesh to the building, using the size and location of the scope passed in. Meshes should always be authored so that the bottom-left corner is the origin, Z is up and X is right. You must specify how large a space you expect the mesh to take up. This is then used to adjust the size of the mesh to fill in the space that is provided for it. This allows you to have features (such as railings or trim) that extend outside the scope region that is passed in.
Each mesh node supports an array of specified StaticMeshes called BuildingMeshes, and it will pick randomly from that list. The following settings are available for each mesh in the array.
| Mesh || The StaticMesh to use |
| DimX || The X dimension (width) of Mesh |
| DimZ || The Z dimension (height) of Mesh |
| Chance || How likely this mesh is to be picked from the array. |
| Translation || An optional translation (or translation range) to apply to the mesh when placing |
| Rotation || An option rotation (or rotation range) in degrees to apply to the mesh when placing |
| bMeshScaleTranslation || If TRUE, the Translation specified is scaled by any scaling applied to the mesh |
| bOverrideMeshLightMapRes || If TRUE, use OverriddenLightMapRes instead of resolution set on the mesh |
| OverriddenMeshLightMapRes || Resolution to use for lighting on this mesh, if bOverrideMeshLightMapRes is TRUE |
| SectionOverrides || Allows you specify, for each section of this mesh, a range of possible materials to apply as random overrides |
Another important feature that a Mesh Rule performs is to test if the scope passed in is intersecting another part of the building. Consider the image below:
As you can see, the windows are intersecting with the roof of the building, and it looks bad. The Mesh Rule can determine if the scope is occluded, partially occluded, or not occluded. In the case where it is fully occluded (ie. entirely inside another part of the building), no mesh will be placed. If it is not occluded, one of the meshes specified in the BuildingMeshes array will be chosen. If it is partially occluded (like in the example above) the mesh specified by the PartialOccludedBuildingMesh option will be used instead.
If you wish you can disable this occlusion test by setting the bDoOcclusionTest option to FALSE, so a mesh from the BuildingMeshes array is always added. Note that occlusion tests are performed using 4 points just inside the corners of the scope.
By default, meshes used as part of a ProcBuilding do not have non-zero extent player collision (only zero-extent weapon collision). Player collision only happens with the volume itself (like a blocking volume) which has several advantages:
Occasionally though you do want player collision against a StaticMesh that is part of the building, and to do this you just set the bBlockAll flag in the rule node to TRUE.
- Collision is smooth and fast
- Collision does not change when the ProcBuilding's ruleset changes
This rule takes a scope and breaks it into a number of smaller scopes along an axis.
You first choose an axis (X or Z) using the RepeatAxis option and it will break up the scope along that axis into equal size pieces. It does this by making as many pieces as necessary to ensure no piece is larger than the size defined with RepeatMaxSize. This will generate a varying number of new scopes depending on the building size.
Below is an example of using a Repeat Rule along Z to break a large surface into a series of 'floors', and then a Repeat Rule along X to break each floor into a series of window scopes, and then a Mesh Rule to actually place the window mesh.
This node is similar to the Repeat Rule, as it takes a scope and breaks it into smaller scopes based on the defined SplitAxis. However, the Repeat Rule will generate a varying number of scopes depending on the input scope size, whereas the Split Rule will always (unless the input is too small) generate the same number of output scopes.
To define how the scope should be split up, the designer fills out the SplitSetup array. Each entry in the array will result in one output scope, as well as an additional output connector on the rule node. This allows you do to different things with each part of the scope. These are the properties for each entry in the array:
You should always have at least 1 entry in the SplitSetup set to expand (ie. bFixSize set to FALSE). Also, if the size of the scope passed in is larger than the size of all fixed entries, some pieces will not be generated as there is no room to fit them in.
| bFixSize || If TRUE, this output will be fixed to FixedSize. If FALSE, this output will expand as the input scope does. |
| FixedSize || Only used if bFixSize is TRUE, to define exactly how big the output scope should be |
| ExpandRatio || When multiple entries have bFixSize set to FALSE, this controls how they stretch. A larger number means more stretching as the input gets bigger |
| SplitName || This is used to label the output created on the rule node |
Some areas of buildings always start and end with the same feature, with another type of feature placed between. This is not easy to do with just Repeat and Split rules, so the Alternate Rule can be used.
As with both Repeat and Split rules, you need to specify an axis using the RepeatAxis property. There are two outputs from this rule, labeled A and B. In the default configuration, as the scope gets larger, the B regions will stretch, but the A region will not. You specify how large B should get before another one is added using the BMaxSize property, and how large A should always be using ASize.
There are some other options that give you additional control:
| bInvertPatternOrder || This option lets you start and end with a variable size B, instead of the fixed size A (ie. changing it from ABABA to BABAB) |
| bEqualSizeAB || If TRUE, forces A and B to be the same size |
As was mentioned in the Mesh Rule node, it is sometimes desirable to perform an occlusion test on a scope. This Rule Node allows you to perform this test on any scope, rather than just before placing a mesh. You may want to, for example, test a floor of a building before splitting it up to make room for a central door.
Because each scope extracted from the building volume is processed by the same set of rules, you can sometimes get odd-looking features on more complicated buildings. If your ruleset put shops in at the bottom of each scope for example, upper areas of the building may end up with shop fronts, when that does not make any sense. To fix this, the Top/Bottom Rule Node allows you to test whether the top or bottom of the scope is the same as the top or bottom of the entire ProcBuilding volume group. The example below helps illustrate this:
This node works by first seeing if the top of the input scope is the top of the entire building. If it is, it will remove split off the amount defined by ExtractTopZ and pass that scope out of the 'Top' output. If it is not, it will split off the amount defined by ExtractNotTopZ and pass that out of the 'Not Top' output. In this way you can, for example, have a larger trim at the top of the whole building than at the top of each step. A similar process is performed for the bottom of the building using the ExtractBottomZ and ExtractNotBottomZ values. Then the remainder of the scope is passed out of the 'Mid' output. If nothing is connected to one of the 'Top', 'Not Top', 'Bottom' or 'Not Bottom', then nothing will be split from the input scope in that case.
The Mesh Rule already allows you to select one mesh from a set, but sometimes you want control earlier on in your ruleset, for example choosing a totally different look for a particular floor or face of the building. In addition, you might sometimes want to choose more than one option from a set. The Random Rule lets you do this. You first indicate how many outputs you would like using the NumOutputs property. Then you decide how many of those outputs may fire using the MinNumExecuted and MaxNumExecuted properties. Using this rule you can 'stack' meshes on top of each other in random ways, for example choosing a different A/C unit, sign and light for a particular area of the building. The pictures below show an example of this:
Sometimes there are areas of a building where a flat quad mesh with a tiling material is the best solution, and this rule node allows you to do that. As well as adding a 2-triangle quad mesh to the building, this rule will also adjust the UVs to allow you to do tiling based on the size of the scope. This is controlled with the RepeatMaxSizeX and RepeatMaxSizeZ parameters, in a similar way to the Repeat Rule. Note that there is only one copy of the quad mesh in memory, and per-instance UV adjustment is performed using material instances. This is done automatically, but you must ensure that your base Material applied to the quad (using the Material parameter) supports 4 material instance parameters: U_Scale, U_Offset, V_Scale, V_Offset. There is a master material you can use as the basis for material instances applied to quad's called EngineBuildings.BuildingQuadMaterial. Below is an example of using a quad rule for one side of a building:
Note in this example the brick pattern is being tiled, but the dirt pattern is only happening once across the whole mesh. This can be easily set up in the material. If you want no tiling to be performed, you can set the bDisableMaterialRepeat to TRUE. You can also control the lightmap resolution on each quad mesh placed with the QuadLightmapRes property. You can also offset the quad mesh along Y (ie. into or out from the building) using the YOffset setting.
When working with Rulesets, it is possible to create some quite complicated graphs. Also, you sometimes create parts of a Ruleset that you wish to use in multiple other Rulesets (for example, a particularly detailed shop-front area). The help with this, it is possible to create a SubRuleset Node, and use the SubRuleset property to point to another Ruleset. Whatever scope is passed into the node will then be passed into the start of that Ruleset. In this way you can keep each Ruleset small, and avoid some unnecessary duplication. Note that you should be careful not to create 'cycles', where Rulesets refer to each other.
This offers you a simple choice based on the dimensions of a scope. You first pick the axis that you are interested in with the SizeAxis property, and then the size at which the decision will be made. If the scope dimension is small than this, then it will be passed out of the 'Less' output, otherwise it will be passed out of the 'Greater/Equal' output. The bUseTopLevelScopeSize option let you make this decision based on the 'top level' scope (ie. the entire building face size) instead of the scope passed in.
The Size Rule can be used to avoid meshes becoming too squished. In the example below, the 3-window mesh is being squished and does not look good. The designer then uses a Size rule to use a 2 or 1 window version of the mesh when the space is small. The same is done with the trim work.
This special node does not perform any action, but allows you to put a box around areas of the Ruleset and comment them, in the same way as Kismet.
By default the Ruleset applied to a ProcBuilding is applied to all faces. However, it is sometimes desirable to have a different appearance on different faces. To support this, you can use a Variation Rule. To add a 'variation' to a Ruleset, you first have to select the Ruleset itself in Facade (you do this by deselecting all nodes). Then you can add an element to the Variations array, giving a name for that variation (for example 'PlainBrickSide'). Then, whenever you place a Variation Rule node in Facade, it will have one output for each new variation you created for the Ruleset, plus a 'default' output at the top.
When the level designer wishes to use a particular variation on a face of the building, they simply do the following:
You can clear all variations assigned to a building by right-clicking on the building and choosing 'ProcBuilding -> Clear Face Variation Assignments'.
Here are some other examples of what can be done with a ruleset with many variations. This can be a very good way to create variety in a city, without creating a large number of unique rulesets.
- Go into geometry editing mode
- Select the face(s) of the building volume you wish to change
- Right click and choose the ProcBuilding menu
- Select the desired variation name from the list
Edge Mesh Rule
Is it sometimes possible to create a ruleset where the edges of each face are flat, and so corners just work naturally. An example of this is below:
However, this is not always the case. One approach for making corners look better when the meshes do not perfectly match is to place a mesh down the edge, at an angle half way between the faces. The Edge Mesh Rule does this. It will take the left edge of the scope passed in, and generate a new scope out of the 'Edge' output, angled half way between the two faces. The rest of the scope is slightly 'pulled in' from each vertical edge by the amount specified with the MainXPullIn property. If the angle between the faces is less than FlatThreshold degrees, no Edge scope is generated. The result looks like the example below:
To get the best looking corners on buildings, it is often desirable to create a custom corner mesh to handle different angles between faces. The Corner Rule node was designed to make this easy. When using the Corner Rule, each scope is designated as 'owning' its left-hand edge. It will split off a portion on the left and right of the scope passed in, and pass the scope it makes on the left out of the output that most closely matches the angle between faces. On the right it will just leave a gap, expecting that area to be filled in by the scope around the building to the right.
You can make a corner node support any number of angles (for example, 0, -90 and +90 degrees), by adding entries to the Angles array. You also control how much X space is taken up by the edge scopes using the CornerSize property. If you wish to override the amount of space to use for each angle, you can change the CornerSize in the Angles array entry to a non-zero value instead. Some other properties of the corner node are:
By default, the Corner node will remove the same amount on the left (for the corner mesh) and the right (for the gap). If you set the bUseAdjacentRulesetForRightGap property to TRUE however, the system will find the corner node used by the scope on the right, and use that corner size for the gap on the right. By doing this, variations with different corner styles can work nicely together, as seen in the picture below. Note that for this to work correctly, the corner sizes at all levels on the building need to be the same.
| FlatThreshold || If the angle between two scopes is less than this amount (in degrees), no mesh is added. |
| bNoMeshForConcaveCorners || If TRUE, no space is left or mesh inserted for concave corners |
One problem that you run into when using curved or beveled corners is that the roof polygon (generated based on the ProcBuilding volume faces) does not match the actual shape of the roof. The fix this, you can specify some additional information in the corner node, and this will be used to reshape the corners of the roof poly. Note that the system finds the highest corner node to get this information.
The properties in the Corner Rule node that control the roof corner shape are:
| CornerType || The shape of the corner - this can be EPBC_Default (the standard square corner), EPBC_Chamfer (diagonally cut corner) or EPBC_Round (rounded corner) |
| RoundTesselation || If the corner shape is EPBC_Round, this controls how much tessellation is used to round the corner |
| RoundCurvature || This controls the shape of the rounded corner |
| CornerShapeOffset || How far from start of curve mesh region to actually chamfer/round adjust roof poly corner |
In order to process a building, the system has to find which top-level scopes meet at each edge. This can be difficult when more than 2 edges meet at a single edge, as shown in the image below:
From looking at this, it is clear that it would not be possible to choose one corner mesh for the yellow edge and make the results look good. To fix this, the system will 'cut' all scopes at each different roof level in the ProcBuilding group. This can seen in this image:
You can disable this behavior if you wish by setting the bSplitWallsAtRoofLevels on the ProcBuilding volume to FALSE. This process does normally work quite well though, as many real building will continue trim at a roof level around the entire building, as can be seen here:
Here is an example of a ProcBuilding with and without that option turned on. As you can see, the building on the left has some holes, because it cannot do a good job of adding trim where the lower roof level meets the rest of the building.
All ProcBuilding groups will automatically generate a low-detail StaticMesh version of the building using a render-to-texture technique. This allows the engine to draw the entire low-LOD building with one draw call - all faces are atlased onto a single material. The geometry of the building is essentially the shape of the volume used, although features like RoofEdgeScopeRaise are still used on the LOD building, so small walls around the roof are still present in the LOD, so that the building silhouette does not change. Below is an example of a single-mesh LOD generated for the building:
The process of rendering the faces of the building into a texture is quite time-consuming, so it not performed as you edit the building. If you wish to generate the lowLOD textures, you can either rebuild lighting in your map (ensuring the 'Generate Building LOD Textures' option is checked), or select the ProcBuildings you want and choose 'Tools -> Generate Selected Buildings LOD Textures' from the main editor menu.
Buildings usually have glass windows, and this usually done with reflective cubemaps. As a result, the LOD needs a mask for the reflective windows or this will lose the reflection and create a very noticeable pop when transitioning. This is done automatically, but does require some setup on the meshes. The generate the low LOD textures, 3 rendering passes are made on the building. They are:
The following image demonstrates these 3 render states:
To make you meshes support 'Window Mode', they must first have a MaterialInstance (and not a Material) applied to them. The MaterialInstance should support a Scalar Parameter called ' RenderWindowCubemapMask'. This will be set to 1.0 when rendering the building in 'Window Mode'. There is also an optional parameter called 'RenderLOD' which is set to 1.0 during all 3 passes if, for example, you wish to disable some features of your material during LOD texture generation.
This process generates 2 textures . One is for the diffuse, unlit color, with a 1-bit 'window mask' stored in its alpha channel. The other texture is a lighting texture, which is usually much lower resolution. This textures are modulated together in the material on the low-LOD building mesh. The reason for 2 textures is because you need to apply lighting on top of the cubemap texture in the window areas. Below are the textures applied to the low-LOD building shown above:
- Unlit, to gather diffuse color
- Lighting Only, to gather lighting information
- Window Mode, where all windows are white and non-window areas are black
It is often desirable to have the high-detail building (made from many mesh instances) in a different sub-level from the low-LOD, single section mesh. This is automatically supported by the system. When you add a ProcBuilding to a streaming sub-level, it will automatically generate a new sublevel, with the same name as the 'high detail' level, but with the '_LOD' suffix. For example, if you add a ProcBuilding to CityBlock_01, a new streaming level called CityBlock_01_LOD will automatically be created and a ProcBuilding_SimpleLODActor (which is just a subclass of StaticMeshActor) will be added to it. The reason for having the low-LOD in its own sublevel instead of in the _P map is so that areas of the city can be worked on without requiring the _P map to be checked out. As you edit the high-detail ProcBuilding, the low detail is automatically updated. It is usually a good idea to check both the high and low detail version of a city block
Note that when adding a ProcBuilding to a map without sublevels, no LOD map will be added - the ProcBuilding itself will own the low-detail mesh.
When you attach a StaticMeshActor to a ProcBuilding (using the usual Base property), it will cause that mesh to be hidden when the building switches to its low-LOD version. This is usually desirable, as the low-LOD version will probably have a version of that static mesh rendered into its texture. When you place a StaticMeshActor (eg. an A/C unit) on top of a ProcBuilding, it will automatically attach itself to the building. This behavior can be disabled with the bDisableAutoBaseOnProcBuilding property on the StaticMeshActor.
Unreal Engine uses a dither approach to transitions/LOD. In order for this to function, however, you need to set the bUsedWithScreenDoorFade property to TRUE on any materials applied to building meshes (or meshes used on StaticMeshActors attached to the building).
There is a new tab in the editor which allows you to see stats for buildings in your city. There is one row for each building, and you can see stats for components, triangles and lighting and LOD texture memory usage. This can be very useful for spotting buildings that might need a simpler ruleset because they are very large, or Rulesets that may need their lighting settings adjusted or meshes simplified. Double-clicking on a row will take you to that building.
It is possible to set parameters that you have exposed in the building mesh Materials on a per-building basis. To do this, add elements to the BuildingMaterialParams array on the ProcBuilding, and fill in the name and desired color for that parameter. This can be a good way to add visual variety to a city without creating a large number of unique rulesets. Below is an example of a row of buildings using the same rulesets, but with the wall color changed using this feature.
You can override the material used on the generated roof, floor or non-rectangular wall polys on a per-ProcBuilding basis. To do this, enter geometry mode, select the Material you want to use instead, select the face(s), right click and choose 'ProcBuilding -> Apply Material (MaterialName) To Face'. You can clear all per-face Material overrides by right-clicking on a building and selecting 'ProcBuilding -> Clear Material Face Assignments'.
Meshing Roof & Floor
As mentioned earlier, by default meshing rules are not applied to roofs or floors of buildings. However, there are some cases where this is desirable (for example, adding supporting beams under a bridge-like building). There are two modes of doing this. Setting bApplyRulesToFloor or bApplyRulesToRoof on the ProcBuilding will actually enable a scope being extracted and rules being run on the roof and/or floor area. There is an additional option within the Ruleset variation settings called bMeshOnTopOfFacePoly. Enabling this will make the system generate the usual floor or roof poly, and then apply rules on top of this. An example of this can be seen here, on the underside of the walkway:
Technical Information (for Programmers)
This section is intended to give more detailed technical explanations for programmers working with the ProcBuilding system
ProcBuildings make extensive use of the InstancedStaticMeshComponent. This component allows the engine to render many instances of the same mesh in one draw call, thus increasing vertex performance and reducing draw calls. The system will try to create one ISMC per Procbuilding 'group' per mesh, but this may not be possible due to the way that meshes are grouped onto lightmaps, or if different materials or lighting options are used. The INSTCOMPCOUNT console command in the editor will print in the log a histogram of instances-per-component.