Smarter Bi-linear Interpolation
Fri 01 Dec 2017
Author: Sam Pickett
First Light uses a block based lighting system. Each block in the world has RGB and sun light values. During the meshing process, each vertex is given a light value, which is the average of the four blocks surrounding that vertex in the plane of the face being calculated. Basically, the averaging creates a smooth lighting effect, similar to that seen in games like Minecraft.
If, when creating the chunk mesh, the lighting values are included for each vertex, then the shader can interpolate the values automatically. The problem with this approach is that the shaders use barycentric interpolation across the individual triangles, whereas we want to interpolate the lighting across each quad, which is two triangles. It still works, but the triangle divisions are visible.
Unfortunately there is no simple way to fix this. The solution is for each voxel face, i.e. each pair of triangles, to pack the lighting values for all four corners into each individual vertex. This means that each vertex has four times as much lighting data. Then, the lighting values are manually interpolated in the fragment shader, using bi-linear interpolation. Basically the lighting is interpolated linearly across two opposite edges, then the two results are interpolated between in the perpendicular direction. To perform the interpolation we also need to include or generate UV coordinates. This solution works perfectly, however it adds a lot of data to the VBOs and requires extra computation in the fragment shader to perform the interpolation.
The Better Solution
I thought about this issue for along time. I was sure there had to be a more efficient way, and I was right! And the answer was very simple. The improved method works exactly the same as the previous one, but requires only two lighting values per vertex, rather than four. Consider a voxel face facing the camera. There are two vertices on the left, and two on the right. Each of the two left vertices is given lighting data for both left vertices (i.e. topLight and bottomLight). As the values are the same for each left vertex, any interpolation between them makes no difference. Similarly, each of the two right vertices is given lighting data for both right vertices. The automatic interpolation between the left and right sides is effectively linear, as each side is constant. The result is an automatically interpolated topLight and bottomLight, which are then manually interpolated using a 't' variable (like UVs, but only one axis), which we can cleverly pack into the unused 4th value of the normal vector. So, this technique halves the amount of lighting data in the VBO, and uses two automatic interpolations and one manual interpolation, rather than three manual interpolations.