I do not calculate lighting in a direct way, which would be:
"for every vertex check it's distance to every light source"
because this approach has 2 major disadvantages: It does not scale with many lightsources, and that approach does not include light coming in from the outside.
What I do is raycasting per block. For every block that is air but has neighbors that are solid I send out rays in every direction. Does a ray collide with a solid block, the sender-block will add occlusion. Does the ray collide with a light source, I add a gather value (depending on it's depth).
At the end I add those two values (occlusion and gather) together, transfer it from the light block per side to it's neighbors, and normalize over every block -> every vertex is averaged from the surrounding blocks to make it smooth. The good thing is, that you only have to do that once, when the segment is loaded, and only if the segment has changed.