Controlling Geometry Visibility and Compile Times: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
Line 210: Line 210:
See <code>sourcesdk_content\hl2\mapsrc\sdk_lightmaps.vmf</code> for examples of different lightmap scale values.
See <code>sourcesdk_content\hl2\mapsrc\sdk_lightmaps.vmf</code> for examples of different lightmap scale values.


==Sample maps referenced in this document==
== Sample maps referenced in this document ==
 
<code>sourcesdk_content\hl2\mapsrc\sdk_func_detail.vmf</code><br>
<code>
<code>sourcesdk_content\hl2\mapsrc\sdk_hints.vmf</code><br>
sourcesdk_content\hl2\mapsrc\sdk_func_detail.vmf
<code>sourcesdk_content\hl2\mapsrc\sdk_occluders.vmf</code><br>
 
<code>sourcesdk_content\hl2\mapsrc\sdk_prop_types.vmf</code><br>
sourcesdk_content\hl2\mapsrc\sdk_hints.vmf
<code>sourcesdk_content\hl2\mapsrc\sdk_lightmaps.vmf</code>
 
sourcesdk_content\hl2\mapsrc\sdk_occluders.vmf
 
sourcesdk_content\hl2\mapsrc\sdk_prop_types.vmf
 
sourcesdk_content\hl2\mapsrc\sdk_lightmaps.vmf
</code>


[[Category:Level Design]]
[[Category:Level Design]]

Revision as of 01:48, 28 August 2007

Merge-arrows.png
It has been suggested that this article or section be merged with Optimization (Geometry). (Discuss)


When a map is compiled with vvis.exe, a visibility set is created that controls what geometry is potentially visible at any given point when the map is loaded in the game engine. The size of this potentially visible set, or PVS, directly contributes to game performance. This document explains some of the tools Level designers have to help control visibility and improve performance, including areaportals, hint brushes, occluders, and detail brushes. Some of the same tools that are used to control the PVS can also dramatically decrease the time it takes for vvis to compile the level.

Leaves

A compiled map is split into sections, called a leaf or visleaf. Each leaf is a volume that contains a section of geometry that is drawn together. When any part of a leaf is visible from the current leaf, all of the geometry in the leaf is considered for rendering by the engine. You can view the current leaf in the engine by typing mat_leafvis 1 at the developer console. A red wireframe box will be drawn showing the current leaf. As you move around in the level, the box will redraw each time a new leaf is entered. Another way of visualizing leaves is to use the glview command-line tool.

For more information about leaves read visleafs

Glview

To use glview, first build the map by typing "%sourcesdk%\bin\vbsp -glview <map name>" on the command-line (dos command screen). This will run vbsp to generate a .gl file for your map. Next, type "%sourcesdk%\bin\glview -portals <map name>.gl" on the command-line, which brings up the glview application that visualizes all leaves in the map. You can now fly around using the W, S, A and D keys and view the leaves in the map.

Alternatively, you may start glview from hammer: Go into expert compile mode and enter these settings to make glview show you how your map is divided into visleafs

executable 1: $bsp_exe

parameters: -glview $path\$file

executable 2: <steamdirectory>\sourcesdk\bin\glview.exe

parameters: -portals $path\$file.gl


Just make a new configuration for glview, so you can just use this alternative if you want to use glview.

Hints

Hint brushes are non-solid brushes that manually create leaf divisions. Since the entire contents of a leaf are drawn when any part of it is visible, hint brushes can be used to give the compile tools 'hints' about how the areas should be split up into leaves.

A hint brush is a single brush with the material tools\toolshint on one or more of its faces. The other faces should have tools\toolsskip applied to them. The brush side(s) with toolshint will be used as a cutting plane for the hint brush. A leaf boundary will be created along that plane when the map is compiled. Multiple hint brushes can be used in a single map, and can even intersect.

Proper usage of hint brushes on large, open levels can dramatically reduce the time it takes for vvis to compile the level. Large numbers of leaves with lots of detail in them take a long time for the tools to compile. Use hint brushes to divide up the large sections of the level that have no detail from the areas that have lots of detail. A common example is an outdoor map with a canyon that opens to the sky. Placing a horizontal hint brush across the top of the canyon will create a large empty leaf in the sky, and separate it from the leaf with all of the detail (the canyon).

See sourcesdk_content\hl2\mapsrc\sdk_hints.vmf for examples of hint brush usage.

See NinjaGrinch's Pyramid Hint Brushes article for some advanced hinting techniques and discussion.

An additional 3rd party example [1]

Areaportals

An areaportal is a brush entity, func_areaportal, that can be used to 'seal' separate leaves and control visibility. Areaportals are like doorways that can only be fully open or closed. When an areaportal is closed, it blocks the visibility of the geometry behind it. When it is open, the geometry is visible again. Areaportals can be dynamically opened and closed while the engine is running, and are typically set to open and close via outputs from sets of trigger_multiple brushes.

A somewhat non-intuitive aspect of areaportals is that they are volumes, not single surfaces. Although areaportals can be any size, usually you want to make a very thin brush (1 unit wide). Make sure that all faces of an areaportal brush use the tools\toolsareaportal material, then use the 'toEntity' button (located in the right hand toolbar area by default) and select 'func_areaportal' in the 'Class' combo box. You must also make sure that the thin faces of the areaportal brush are covered by solid, non-detail brushes (displacement brushes will also not work to seal the areaportal). It is a common level design bug to not have areas completely sealed by areaportals. This condition is detected automatically by vbsp when you compile the map. Hammer provides a way of visualizing where the area portal "leak" is by allowing you to load the pointfile generated by vbsp. If you're having trouble finding the source of your leak, see the Leaks Explained for more information.

Areaportals can also be used in an always-open state. An always-open areaportal is created by setting the "Initial State" keyvalue on the func_areaportal entity to "Open". Areaportals (whether they're always-open, or triggered to open) have a behavior of culling geometry that is visible through the areaportal. Similar to looking through an open window of a house, only the leaves that are directly visible through the areaportal will be rendered by the engine. In this way, the outside geometry is roughly "culled" to the size of the window, decreasing the amount of geometry rendered, and increasing performance. Because of this behavior, always-open areaportals are sometimes used at the doorways to large outdoor areas. Simply placing an always-open areaportal at the end of the hallway that opens into a wide expanse can produce a substantial performance gain. While the player is inside, looking out of the doorway, only the geometry that is in the each leaf directly visible through the doorway will be drawn. A special type of areaportal entity, func_areaportalwindow, is often used in these cases. The func_areaportalwindow entity is a type of areaportal that can be open or closed based on a distance from the player instead of simply being triggered to open or close like standard func_areaportal brushes.

Although areaportals can give substantial performance benefits when used correctly, care must be taken with not to place too many areaportals where many of them would all be visible simultaneously. There is processing overhead required for each areaportal. If multiple portals are open and visible in the current view, and the portals are not culling (hiding) enough geometry, it's certainly possible that the overhead required to process the areaportals will be more costly to performance than if the portals were not there at all. Watch out for the "worst case scenario" in these situations.

One other tricky aspect of using areaportals is that they are not allowed to cross water boundaries. To accomplish this, you must manually make two func_areaportals, one that lies outside the water, and a second that lies inside the water. The two areaportal should meet at the water plane.

See func_areaportal and func_areaportalwindow in the entity documentation for more details.

Occluders

Occluders are similar to areaportals in that they are brush entities that control visibility of the geometry behind them. The main differences are that a) occluders only hide model geometry, not brush geometry, and b) occluders do not need to seal areas, they can be free standing. Similar to areaportals, occluders can be dynamically activated while the engine is running.

Occluders are created by making a brush entity func_occluder. The occluder should have tools\toolsoccluder material on the sides of the brush that need to block visibility, and tools\toolsnodraw on those sides of the brush which that will not block visibility. Brush sides that have the toolsoccluder material on unnecessarily will degrade the performance of the occluder.

A note of caution -- similar to areaportals, overuse of occluders can decrease performance. Each occluder that is placed should be checked to make sure that it actually hides enough geometry in the map. If the occluder blocks the visibility of more than a few expensive models, it should be effective in increasing performance. If not, the occluder itself may be more expensive that the geometry it hides. To determine whether the occluders are helping or hurting performance, you can bindtoggle a key to the console variable r_occlusion, which toggles the occlusion system on and off (for example, typing "bindtoggle o r_occlusion" at the developer console will do this). Then check the framerate (which can be done by typing "cl_showfps 2" at the console) while toggling the occlusion system on and off with the key you bound.

See func_occluder in the entity documentation for more details on this entity.

See sourcesdk_content\hl2\mapsrc\sdk_occluder.vmf for examples of a func_occluder is constructed.

Geometry construction techniques

The methods used to construct brush geometry can also have an effect on performance. The main goal in creating efficient geometry in Hammer is to create as few polygons 'splits' as possible. One effective technique is to use the 2D grid for alignment whenever possible. Extra splits are created when geometry does not line up evenly, creating additional places where vbsp has to cut up the surfaces to render properly. If the brushes are aligned evenly to the grid, there will be a fewer number of cuts. Having fewer brushes rotated on angles will also decrease the number of splits necessary.

It is also worth noting that vbsp automatically splits geometry ever 1024 units of world coordinates. Extra splits can be avoided if brushes terminate evenly at a 1024 grid line. In Hammer, go to the Options dialog under the Tools menu. In the 2D Views tab, choose Highlight every 1024 units. Enabling this will display red shaded grid lines in 1024 increments in the Hammer 2D views. You should not go "out of your way" to create geometry along these grid lines, but if you have a choice of building something at 1024 units instead of 1025, build it at 1024.

The next section discusses detail brushes, which are another valuable tool to eliminate unnecessary vbsp splits.

Detail brushes

Detail brushes are a class of brushes that are grouped to avoid unwanted face splits when small, detailed brush objects meet large surfaces. Detail brush geometry is made by creating a func_detail entity out of a set of brushes. The brushes that are included in the func_detail will not clip (or split) non-detail brushes. A common example is a cylinder brush (such as a pillar) touching a floor in a room. Normally the cylinder would split the floor face into many smaller faces where the two brushes meet. By making the cylinder detail geometry, no such splitting will occur. Any rotated piece of brush geometry is also a good candidate, and will cause fewer VBSP splits if it is made into func_detail.

Detail brushes do not block visibility and cannot be used to seal the world or areaportals. Since they do not block visibility, detail brushes have a side benefit of simplifying the visibility set. Proper use of detail brushes can greatly decrease the time it takes for VVIS to compile the level.

Brushes that are func_detail are an entity in the Hammer editor, but the entity information is discarded after the map is processed in VBSP. This occurs to reduce the memory requirements of the geometry. The entity information is no longer needed once VBSP has used it for reducing splits. In the engine, surfaces that were created with detail geometry are identical to all other solid brush geometry in the level.

Objects that are small, complex, and/or do not seal areas are good candidates for detail geometry. Examples include pillars, gates, small rocks, fences, debris, piers, etc. A good way to tell if you need to switch non-detail geometry to detail geometry is to use glview. In places where you see a high density of leaves in glview, you'll want to switch some of the geometry in those areas to func_detail.

See func_detail for more information.

See the example file sourcesdk_content\hl2\mapsrc\sdk_func_detail.vmf for an example of how detail geometry can be used to reduce face splits.

Water

There are a couple of tricks to use to get performance back on levels that have water in them. Water is a pretty expensive shader, and you oftentimes can be fillrate bound when you use it. This can often be seen by typing +showbudget at the developer console: if the Swap Buffers bar is very high, you're likely running into a fill rate problem. To reduce this problem, make sure to cut the water brushes to eliminate parts that lie within other brushes or displacements.

Another performance problem is that any object that is partially inside and partially outside of the water is rendered twice: once for the refraction, and once for the part of the scene that lies on the same side of the water plane as the player. You can see which objects are being rendered into the refraction by typing mat_showwatertextures 1 in the developer console. Also typing mat_wireframe 1 in the console can help to highlight the problem areas. Try moving any prop_static so they don't touch the water, and cut your water brushes so as few as possible models actually cross the water plane.

Model geometry vs. brush geometry

In general, brushes comprise the biggest chunk of the level and are used for terrain and very large and coarse objects. Brushes block visibility and are useful for such things as walls, terrain and hillsides (or anything else that occludes large portions of the screen). Big objects like bridges should be made from brushes as the use of lightmaps allows them to look much better than a single very large model.

The downside to brushes, and the reason you do not want to use them all the time, is that they hit performance because they consume more memory than props, are always drawn and cut the BSP making it less efficient. Brushes also cannot be put into hierarchies like props can.

Brushes should be used for the following:

  • Terrain
  • Hillsides/natural walls
  • Walls
  • Building cores (use props to decorate them with windows, etc.)
  • Large structures such as bridges where lighting accuracy and visibility blocking are important
  • Objects that will have limited uses in a level – performance hit is taken for every brush in the world.

Whenever possible, model mesh geometry (created in software such as Softimage|XSI) should be used instead of brush geometry created in Hammer. Model geometry, or ' props', render faster due to the batching capabilities of the engine. They also have lower memory requirements and benefit from multiple levels of detail to decrease the amount of geometry rendered. Models are made up of arbitrary sets of triangles, so they have few shape restrictions than brush geometry. Level designers can use any of the large numbers of model props that are available in the game directory to populate their levels.

Model geometry does not block visibility and cannot be used to seal the world or area portals. Additionally, although models have the ability to cast shadows, they do not use lightmaps for shadows cast upon them like brushes do. They use a simpler vertex-based lighting model for increased speed.

Objects that are small, complicated, and do not seal areas are good candidates for model prop geometry. Examples include furniture, light fixtures, junk, equipment, pipes, vehicles, gutters and other building details, etc.

Props should be used for the following:

  • Smaller objects in the world (pop cans, furniture, computer equipment, doors etc.).
  • Larger objects that require higher levels of detail – consider placing brushes inside the props to solve visibility/performance issues (brush should be a similar but smaller outline of static prop).
  • Anything that needs to move or be physically simulated (i.e. picked up, thrown, bumped etc).
  • Non-collidable organics like foliage (grass, plants etc.).
  • Objects that will appear many times in the level.

The Overview of Prop Types explains the different varieties of model prop entities in the Source engine.

Geometry fade distances

Model props have minimum and maximum fade distances that can be set in their entity properties. This allows the level designer to set distances or screen area when the prop will fade out to help increase performance. On machines running DirectX level 7 or lower, the props will fade earlier than the values set in the entity to further improve rendering speed. This can be controlled on a per-MOD basis by creating a dxsupport.cfg file for your mod and specifying the values for the console variables cl_detaildist and cl_detailfade for the various dx support levels.

See prop_static, prop_dynamic, or prop_physics in the entity documentation for more details on how to set fade parameters.

Clip and fog distance

Use of distance clipping can decrease the number of visible objects and improve rendering speed. Level designers can control the distance at which objects are 'clipped' or not drawn. Visible fog can be enabled to help hide the edge of the clipping. Fog and Far Z clip distances are set by including a env_fog_controller entity in your map.

See env_fog_controller in the entity documentation for more details on how to set the fog and clip distances.

Material choices and rendering performance

When working with materials, there are a couple performance bottlenecks to keep in mind: fillrate (which usually affects all DirectX levels), and reducing vertex processing (which only is a bottleneck on DirectX7). As a level designer, the only real choice that you've got to combat any of these performance bottlenecks is to either reduce the amount of a particular expensive material on screen, or to turn off some of the expensive features in the material (like bumpmapping, for example). Note that you can make different choices about which features to use at each separate DirectX support level.

One other important aspect of dealing with performance bottlenecks is that you're going to have to measure framerates to see how well you're doing. For Half-Life 2, we used 3 target machines (determined based on looking at our hardware survey stats) for measuring performance at various DirectX levels. These were:

DX (DirectX) Level Minimum Platform
DX9 ATI 9800, 1 GB, P4 3.0 GHz
DX8 NVidia GeForce4 Ti4600, 512 MB, P4 2.0 GHz
DX7 NVidia GeForce2 MX, 256MB, P4 1.2 GHz

Here's how you specify different material parameters for different dx support levels: Each shader has a specific fallback shader it uses if the shader determines it can't run on the particular DX level on the card currently being run. Within the definition of your material, you add a field with the same name as the fallback shader and a sub-block of fields which are the values of those fields that you want to use if that fallback shader is being used. A list of the most commonly used shaders and their fallback shaders is shown below.

Here's an example:

"LightmappedGeneric"
{
	"$basetexture" "Metal/metalwall063a"
	"$surfaceprop" "metal"
	"$envmap" "env_cubemap"

	"LightmappedGeneric_DX9"
	{
		"$bumpmap" "metal/metalwall063a_normal"
		"$normalmapalphaenvmapmask" 1
	}

	"LightmappedGeneric_DX8"
	{
		"$basetexture" "metal/citadel_metalwall063a"
		"$basealphaenvmapmask" 1
	}

	"LightmappedGeneric_DX6"
	{
		"$fallbackmaterial" "metal/metalwall063b"
	}
}

In this example, the $envmap field is used no matter what DirectX level the game is being run at. The $basetexture field metal/metalwall063a is used for every fallback shader except LightmappedGeneric_dx8 (used for running under DX8), which uses metal/citadel_metalwall063a for its $basetexture. Also under DX8 only, we add a field specifying that the special base texture used by dx8 has an envmap mask in its alpha channel. When using the LightmappedGeneric_DX9 shader (used by DX9), we add a bumpmap and specify that the envmap mask is in the alpha channel of the bumpmap. Note that it's also possible to cause the material system to use a completely different material with a totally different set of shaders by specifying a $fallbackmaterial in a fallback block, which is done in this example when falling back to the LightmappedGeneric_DX6 shader (used by DX7 and DX6).

For a listing of all of the fallback shaders used in Half-Life 2, see the document Half-Life 2 Shader Fallbacks.

Dealing with fillrate issues

The materials which are most expensive from a fillrate perspective are the water and refract shaders, and any material which uses bumpmaps. The way you can tell you've got a fillrate problem if you see a big bar for swapbuffers when using +showbudget. We'll talk here about solutions for a couple of the most common offenders for fillrate: refraction, water, and bumpmapped shaders.

For refractive shaders, the only real thing you can do is to use the $fallbackmaterial to make the shader not be refractive at the dx level where it's causing you fillrate issues. For water shaders, the fillrate cost of these shaders goes up when using refraction and reflection. You can turn both off by specifying $forcecheap 1 in the .VMT. You can also disable refraction by not specifying a $refracttexture in the .VMT, and you can disable reflection by not specifying a $reflecttexture. If you've got fillrate and CPU to burn, you can forcibly make the water do local reflections (regardless of video config settings) by specifying $forceexpensive 1, and you can forcibly make the water reflect entities by specifying $reflectentities 1.

For bumpmapping, usually the shader that's the fillrate offender is LightmappedGeneric. There are a couple things you can do to improve performance. Specifying $nodiffusebumplighting 1 turns off diffuse bumpmapping, but specular bumpmapping still is active. Turning off specular bumpmapping will also improve performance, although there is a pretty big visual quality hit you take for turning it off. One feature we added to LightmappedGeneric to help turn off bumpmapping on those DX8 cards that just weren't up to snuff was by introducing a fallback called LightmappedGeneric_NoBump_dx8. This fallback is automatically used on DX8 machines which are specified as using mat_reducefillrate 1 in the dxsupport.cfg file. In some materials used by the Citadel in Half-Life 2, we use this to allow us to specify the use of bumpmaps under dx9 and the normal dx8 path, but not under the low-fillrate DX8 path.

Low-end cards (DX7) also get both hosed from a fill-rate perspective and also from a vertex processing perspective by using unbumped specularity. As a result, by default, specularity is turned off on DX7 cards. To reactivate specularity for these materials under DX7, add $multipass 1 to the .VMT. The LightmappedGeneric_dx6, VertexLitGeneric_dx6, VertexLitGeneric_dx7, and UnlitGeneric_dx6 shaders will all respond to this material parameter.

Lightmap resolution

The Source engine uses lightmap data to simulate light and shadow on solid brush geometry. Increased lightmap density provides sharper cast shadows, at the cost of higher memory requirements. The density and quality of lightmaps can be altered to improve performance on a per face basis.

Lightmap scale settings are set using the Texture Application tool. Lightmap scale is measured in luxels per world grid unit. Lower numbers equal higher resolution. The default scale is 16 luxels per grid unit.

Higher resolution lightmaps increase the time it takes for VRAD to process the level. You can help reduce VRAD compile times and decrease memory requirements by lowering lightmap resolution on faces that don’t need a lot of shadow detail. Good candidates for higher lightmap scale values are surfaces that are completely in shadow, fully lit (with no shadows falling on them), or away from where players can get near them.

See Texture Application tool in the Hammer documentation for more detail on how to set lightmap scale.

See sourcesdk_content\hl2\mapsrc\sdk_lightmaps.vmf for examples of different lightmap scale values.

Sample maps referenced in this document

sourcesdk_content\hl2\mapsrc\sdk_func_detail.vmf
sourcesdk_content\hl2\mapsrc\sdk_hints.vmf
sourcesdk_content\hl2\mapsrc\sdk_occluders.vmf
sourcesdk_content\hl2\mapsrc\sdk_prop_types.vmf
sourcesdk_content\hl2\mapsrc\sdk_lightmaps.vmf