In the beginning, I definitely wasn’t planning to add a night mode, but with shaders in ModernGL, it turned out to be pretty easy. You just tint the pixels in the fragment shader.
For a quick proof of concept, I went ahead and tinted everything to see how it would look.

In the shader, when the tint flag is enabled, I simply multiply every pixel’s color by a tint value to darken the scene.

In the texture coordinate array, I include the tint flag as part of the data. It gets this value from the texture atlas, where each sprite can be configured to be tintable or not. For example, UI images that live in other layers of the atlas should have the tint flag set to False, so they stay unaffected by the night mode.

However, when I omitted certain textures – like the selection decal – I ran into a problem.

After cleaning up the pixels of each image, there was still something wrong with alpha blending – it wasn’t rendering as expected.
After a few days of debugging with help from the good folks on the ModernGL Discord server, we finally found the issue. Turns out, the order in which you enable and disable blending really matters – obvious in hindsight.
The fix was to enable blending for instance rendering, disable it when rendering to the color attachment, and then re-enable it for UI rendering. Simple, but easy to overlook.

With that fix in place, night mode now works smoothly – and importantly, it no longer tints UI components like selection decals or destination pointers.