VRAD and lightmaps

Hi all,

I’m writing a lightmap optimization guide and I want to be sure I understand how VRAD works with lightmaps to light the luxels in the map. In-game, I’m using mat_filterlightmaps 0, mat_fullbright 2, and mat_luxels 1 to identify the luxel faces and the light they receive easier. It’s confusing me though. Take a look at this screenshot of Lumberyard:

It looks like the sample points are actually at the corners of the luxels, not the center. But that doesn’t explain this:

Lumberyard has an issue where the bottom step of these stairs on the Blu side has its vertical face totally fullblack. This makes me think the sample must come from the center of the luxel, since the center of each of these luxels is underneath the displacement where it would not receive any light. But if you simply cut the bottom half of the brush off, making the step a total of 8HU tall, the problem is fixed.


Here’s another issue above a doorframe:

The luxels above the doorframe are totally fullblack and the luxel is cut in half by the brush being only 8HU tall.

And here’s one more:

The top faces of these stairs should all be relatively the same brightness, but they’re not. The luxels are all over the place. I can’t make sense of it.

So does anyone know specifically how the lightmaps are used with VRAD? Where the sample points come from etc.? This shit is confusing me.

If you have a bit of a math background, you can read up on how radiosity works:

A simplified (but not completely correct) explanation would be that light is calculated by tracing a ray from the light source to the face and then bounced 8-100 times (depending on vrad.exe flags) for diffuse lighting.

Dark or black patches are caused because the whole light patch isn’t visible to the light source directly or via a diffuse light bounce. This can be worked around by reducing the lightmap scale on the face, or by using an entity like a func_brush instead of a func_detail which doesn’t block radiosity by default. Both methods cause slight performance issues, but the former can be offset by using larger lightmaps elsewhere in the map where lightmap resolution is not as important (eg. a roof or terrain.)

I kind of know how it works and how to fix them. I know that increasing the luxel density would correct all of these issues, I just want to know where the lightmap samples are coming from. The intersections between each luxel? The centers of each luxel? Is there any interpolation between adjacent luxel samples? I just can’t figure out why the face of those stairs would be fullblack if the samples are at the intersections, and why the doorframe would be fullblack if the samples are at the centers. It just seems like they go back on themselves. Maybe the doorframe is influenced by ambient occlusion or something and it doesn’t have other samples to interpolate below it to tell it that it should not be fullblack?

I wouldn’t trust the luxel view ingame, in a number of the engine branches its broken. Valve in general has done a pretty bad job maintaining their debug views:


Each square represents a patch and each patch is processed as a whole, the lines just denote the edges of the patches.

Each patch is also given a view factor:

Which determines how much light is received/bounced based on the patch orientation relative to the light source. I don’t think there is interpolation between patches, otherwise it would cause strange lighting in sharp transitions in light.

If the whole patch is not visible to a light source, it will cause errant lighting on that patch. Another situation that can cause dark or black patches is if it wraps around a face like on a sharp corner.

But if I just halve the height of that stair brush, the problem resolves itself. The “patch” is still not completely visible above the displacement. More than a half of it is still below the displacement, as can be seen in one of the screenshots in the OP. If it were processed as a complete unit, not a point at the center or intersections, wouldn’t it still be broken? Someone else I was talking to theorized that VRAD had some kind of aggressive interpolation between luxels that caused the top samples of the luxel to be overridden by the bottom samples, causing the fullblack face.

An illustration:


“A” is the sample in question, and the circles are the adjacent samples that would be interpolated together with A to create the final brightness.

This is something I was writing up to try and explain to myself how VRAD was working, I guess this is actually totally incorrect?

To be completely sure what’s happening, we’d have to look at the source to vrad.exe. My statements are based on what I know about radiosity and how it should work. Valve could be doing hocus pocus under the hood, or something completely wrong which causes the problems.

I know their radiosity implementation has issues because vrad can’t calculate styles lights properly, and lights tend to have color banding at extreme distances from light sources.

I don’t understand how it could be treated as a unit. How would that allow for brightness variation inside the patch?