Lighting
When rendering objects in 3D, the Source 2 engine uses a simulation of how light appears in real life - allowing surfaces to respond realistically to light sources, ambient lighting and suchlike, with both static and dynamic objects casting shadows from those light sources. To ensure both high visual fidelity and high performance, the Workshop Tools need to precalculate a lot of information - storing lighting data in various forms such as lightmap textures, light probe volumes and cubemaps.
But what are all these things, anyway?
Inputs into the lighting system can be broken down into the following:
- Lightmap textures on static geometry (Hammer meshes, static props)
- Light probe volumes and cubemaps (ambient lighting on dynamic objects, reflections)
- Light sources (environment, point, ortho and spot lights)
- Volumetric fog
To get a preview of how things will look in Hammer, you can use the 'Preview Baked Lighting' menu at the top. This will do a relatively low-quality preview of how the final lighting will look in VR - hugely helping with artistic direction, gameplay design and similar. It uses vertex lighting and many per-pixel dynamic light sources instead of lightmaps - requiring much less precalculation, but resulting in lower quality and being much more expensive to render at runtime.
You'll need to build lightmaps for full release-worthy lighting, but you can make do with this preview lighting for low-performance testing in VR. For anything beyond an extremely simple map, framerates are likely to be horrible, so don't release an addon this way!
Light source types
Light sources come in several different types, with different behaviors in how light dissipates. You can place light sources into your map with the Entity Tool in Hammer from the left-hand toolbar - conveniently, it has a stylized light bulb as an icon.
Once placed, select entities in the 3D view and set color, brightness, maximum distance etc. in 'Object Properties' to the bottom right/
- Environment Light - light_environment
- Virtual sunlight from sky - light source infinitely far away
- No falloff over distance
- Usually just one of these in a map
- Super-useful presets set up in workshop_examples/prefabs/environment_settings prefabs
- Point Light - light_omni
- Single all-directional light source
- Bit like a light bulb!
- Spot Light - light_spot
- Directional light source, from a particular point
- Like a real-world spotlight, amazingly enough!
- Ortho Light - light_ortho
- Need to select 'light_ortho' in entity class popup menu to place this
- Cross between an environment light and a spot light
- Light is parallel, but only exists within a particular cuboid
- As with environment light, no falloff over distance
- Great for limited skylights and other special uses etc.
Direct and Indirect Lighting
Light sources are effectively made of two components:
- Direct lighting - primary component based on line of sight to light source
- Indirect lighting - secondary component from light bouncing from one surface to another - can be fairly subtle but adds enormously to visual realism
How direct lighting gets rendered is the main choice here.
Default is Baked Light Indexing
- Selected with 'Direct Lighting' set to 'Baked' and with 'Baked Light Indexing' checked (the default)
- By default, indirect lighting gets baked into lightmaps (for static surfaces) and light probe volumes (for dynamic objects)
- Advantages:
- Direct lighting has specular (shiny) component
- Can cast shadows from dynamic objects (characters, physics props etc.)
- Realistic contact hardening on shadows from static geometry (using Light Source Radius / SunSpreadAngle)
- Direct lighting can flicker, strobe, turn on and off (give the light entity a targetname and tell it what to do via entity I/O)
- Relatively cheap to render!
- Disadvantages:
- Can have a maximum of four indexed lights shining on one surface at a time or weird things happen (best to keep it to one or two to make things cheaper to render)
- Preview with menu at top right of 3D view in Hammer - set 'Tools Visualization Mode' to 'Baked Lighting Complexity'
- Black is zero indexed lights on that surface, red one, orange two, yellow three, white four and cyan is DON'T DO THAT
- Light source cannot move
- Only one indexed light source can cast shadows from dynamic objects at any one time
- Indirect component can't be switched on or off - set it to 'None' if you want a fully switchable light
- There's a limit on the total number of indexed lights in a map, although it's pretty high and you're unlikely to reach it in normal usage
- Can have a maximum of four indexed lights shining on one surface at a time or weird things happen (best to keep it to one or two to make things cheaper to render)
Fully baked Direct Lighting
- Selected with 'Direct Lighting' set to 'Baked' and with 'Baked Light Indexing' unchecked
- Advantages:
- Basically free to render!
- Direct component gets stored in lightmaps and light probe volumes, the same as with the indirect component
- Realistic contact hardening on shadows from static geometry (using Light Source Radius / SunSpreadAngle)
- Basically free to render!
- Disadvantages:
- Direct lighting has no specular component
- Lit surfaces can look dry and crusty
- Don't get directionality from directional lightmaps
- Can't cast shadows from dynamic objects
- Light source cannot move
- Cannot switch light on or off, cannot flicker or strobe
- Direct lighting has no specular component
Per-Pixel Direct Lighting
- Selected with 'Direct Light' set to 'Per Pixel'
- Can be very expensive; usually only used for special occasions (e.g. player flashlight) or for very small light sources (glows on ammo clips, resin etc.)
- Only use these if you know what you're letting yourself in for, pretty much
- Look for examples in shipped maps for further details
- Advantages:
- Can have high-quality specular component (although specular would be switched off for small light sources as listed above)
- Can have fully dynamic shadows (again, would be switched off for those small light sources)
- Can move light source, as with player flashlight!
- Direct lighting can flicker, strobe, turn on and off
- Disadvantages:
- Can be expensive!
- No contact hardening (Light Source Radius / SunSpreadAngle do nothing)
- Indirect component is still baked into lightmaps etc. if enabled (switch to 'None' for most purposes)
- Outside of preview lighting, we only support
- A single light_spot or light_ortho with shadows enabled in view
- Up to 8 light_omnis with shadows and specular disabled, and fully linear falloff
Indirect lighting
For proper indirect lighting on dynamic objects, you want to place Light Probe Volume and Cubemap entities - env_combined_light_probe_volume
- You want the center of each to be a typical, average view in that particular volume, be it a room, corridor, outdoors scene etc.
- For a simple cuboid room, you'd place it right at the centre, about eye height
- Extend the volume bounds (red, green and blue 3D arrows) to fully enclose that volume
- Reflections are done with box-projected cubemaps - imagine a box with a texture on each face
- In that simple cuboid room, you'd want the volume bounds to meet the walls, floor and ceiling
- Reflections would thus be of a simplified version of that very room, with correct apparent depth and everything
- Volumes can be rotated and can overlap - can set the priority to make one override another (e.g. a dark player-accessible box surrounded by sunlit outdoors)
- Hammer geometry should be broken into separate meshes as necessary
- Each mesh can only receive reflections from one cubemap
- Don't worry about effectively adding extra geometry, vertices etc. in the process - all kinds of fancy mesh dissection stuff goes on behind the scenes with visibility and similar anyway.
- All accessible areas should be enclosed by light probe volumes - if you have dynamic objects suddenly flashing really bright or whatever, you may have missed a bit
Lightmap Density
Lightmap density describes how dense the lightmap texture is on a particular face. This is necessary to control because giving all faces a uniform density would be inefficient to calculate.
You can preview lightmap density in-game with the command mat_luxels 1
.
Lightmap Player Spaces
Lightmaps are textures containing precalculated lighting information for static surfaces. You want these to be as high resolution as possible for the best visual fidelity. You can ensure the compilation process automatically prioritizes player-accessible stuff by placing lightmap player space volumes - quite simply, these are Hammer meshes with materials/tools/toolslightmapres.vmat on them. The closer a surface is to one of these meshes, the more lightmap texels are assigned to it.
The general gist of it is - place these lightmap player space volumes within areas the player can get to. Inaccessible areas - things behind fences, high up on walls, distant buildings - will thus be assigned fewer and fewer lightmap texels depending on how far away they are.
The net result is apparently seamless lightmap detail!
Lightmap Resolution Bias
When editing a face, you can control the Lightmap Resolution Bias. This is a value that will influence how large the lightmap texture space is for that face relative to the rest of the map.
Bias scales by powers of 2. A lightmap bias of +1 will make the texture resolution 2x what it would be normally, +2 is 4x resolution, etc. Negative bias will lower the lightmap resolution.
Be wary of adjusting this option by accident, as a single face with a very large lightmap bias can effectively destroy the lightmap density for the rest of your map! If you have bad shadows and your console is reporting 'Mesh with material <x> is extremely large in lightmap (x.x%)' with a large value, this may be why. You can go to the coordinates listed in console to get an idea of where these faces may be, and use mat_luxels 1
in game to find suspiciously dense faces.
Basic summary of lighting:
- Use indexed lights for the key lights in your scene - sunlight, major light sources etc. that you want specular from, switchable / flickering lights etc.
- The 'Baked Lighting Complexity' visualization mode will show you how things are looking in terms of rendering cost
- Ideally, you want things black, red and a bit of orange and yellow
- Use fully baked lights for visually less important things - fill lights, things in background, dimmer light sources
- Use per-pixel lights only for very particular reasons - if in doubt, don't.
Volumetric Fog
Half-Life: Alyx uses a voxel-based system for having the atmosphere react to light sources - hazy light flares, glare, mist, shafts of light coming through windows - all are possible using this unified volumetric fog system.
To enable it in your map, place a env_volumetric_fog_controller entity. The default settings are generally okay.
You can set volume-specific fog with env_volumetric_fog_volume entities - drag out the volume bounds in a similar way to light probe volumes. For harder edges to a fog volume, select the fog volume entity and set 'Falloff Exponent' to a low value such as zero.
Next, you need to set light sources to contribute to volumetric fog. Select a light entity, then set 'Volumetric Fogging' to 'Baked'. You can tweak the fog intensity with 'Fog contribution strength'
One drawback is that for outdoors areas, sunlit things will suddenly look darker. By default, atmosphere will absorb light. Normally we'd fix this by enabling volumetric fogging on the sunlight, but unfortunately environment lights (such as that sunlight) can't contribute to volumetric fog, for various technical reasons.
It's possible to locally fake stuff with a fog volume set to a low fog strength (to minimize atmospheric darkening), then a light_ortho with volumetric fog enabled, a low brightness, high fog contribution strength, and angles and color set to the same as the sunlight light_environment.
You can have many light sources with volumetric fogging enabled - additional rendering cost per light source is relatively minor. For light_omnis, set as many as you can to 'Baked: No shadows' or graphics programmers will be sad.
Look at shipping maps for more examples!
Building Lighting
Preview Baked Lighting
For an in-Hammer preview of how your map will look when fully compiled, make use of the Preview Baked Lighting : Bake All Lighting menu. This will perform some lower-quality calculations which will help you iterate on the overall look and visual feel of your map - it'll let you continue working on the map beyond this point, but remember to re-bake preview lighting whenever you make any significant changes so that you'll get an up-to-date representation of how everything will look.
Building Lightmaps
Once you're happy with how your map will look, you can now compile the thing. Press F9 to bring up the Map Builder dialogue box. The default settings for Full Compile will generate 1k lightmaps - this should be relatively fast for a simple map, just taking a few minutes. To build cubemaps for proper reflections on metallic surfaces, remember to check the Build cubemaps on load checkbox.
For a release version of your map, select the Final Compile option. This will generate higher-quality lightmaps at 2k resolution. This may take a little while on a typical home PC!