Is there any solution to this? Is that (array, UBO) really the “standard-way” of handling multiple lights in a scene?
What you’re talking about has never been “standard” by any real definition of that term.
Indeed, nowadays, there is nothing you might consider “standard”. There are several options.
The first is to stop pretending that the number of lights you can render with in a single pass limits the number of lights you can use in a scene. AKA, render each object multiple times, once for each light that affects it (that last part is important. All of the lights in a scene do not necessarily affect every object. Figure out which lights affect which objects). You add the values together to compute the composite result for each object. This is the closest thing to “standard”, since it’s been done since the days of Quake.
The alternative that is becoming increasingly popular is deferred rendering. It’s a bit more complicated to explain.
In order to compute the reflected color from a point on a surface for a particular light, you need two things: information about the light and information about the surface. The light’s information (position, intensity, etc) is constant for each light. What changes is the information about the surface, commonly called the “material parameters”. These are always the same for each object.
Deferred rendering is an attempt to leverage that last fact. You don’t render lights anymore. You render the geometry, not to the screen, but to a set of off-screen buffers, commonly called G-buffers. The G-buffers contain the material parameters for the object in the scene at that point. G-buffer contents typically include a normal, a diffuse color (sampled from a texture), maybe a specular shininess or something similar, and so forth. Oh, and you keep the depth buffer around.
Then, for each light, you render a full-screen quad. The fragment shader reads each of the material parameters from the G-buffers, then computes the light intensity based on the current light that this full-screen quad is working with. It reconstructs the position in an appropriate space via math that I’m not going to get into. With the position and the normal, and the other material parameters, you have everything you need to do lighting.
Deferred rendering is more complex to set up. But it is a pretty nice system. It cuts down a lot on the number of shaders, because “forward rendering” (the first method) combines the material stuff with the lighting stuff.