-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Problem
Currently, Ambient Occlusion is subtracted from the calculated light value for a vertex, and light data directly influences vertex color. It causes data loss in shader, such that shadowed nodes, especially those in complete darkness, have no hardware coloring color data passed into the shader, or the data is severely scaled down and loses precision. Shader can't differentiate between shadow proper and ambient occlusion either. Of course this doesn't matter currently, but #14343 allows (that's its point) to additionally and uniformly light up everything.
The issue encountered there is that:
- Hardware coloring is often lost when applying Ambient Light, and it doesn't work at all for nodes that had been in complete darkness (this one can be solved by changing one step of light encoding).
- There's no Ambient Occlusion with bright enough Ambient Light (how bright exactly depends on how the colors are clamped), in particular none at all for nodes that had been in complete darkness.
See the discussion under the PR for screenshots showing both issues.
Solutions
The way the data is processed before passing to the shader has to change. Potentially, the way the data is passed to the shader may have to change too.
First of all, Ambient Occlusion is calculated in the function getSmoothLightCombined() at https://github.com/minetest/minetest/blob/58ea11c2b3101667e7a5ed118bf1cb2204f63586/src/client/mapblock_mesh.cpp#L148-L250 and right then and there it's baked into light_day and light_night, which are 8bit uints, which are then combined into a single u16 return value. A few other functions call it, but in the end its return value ends up either at https://github.com/minetest/minetest/blob/58ea11c2b3101667e7a5ed118bf1cb2204f63586/src/client/content_mapblock.cpp#L262 or at https://github.com/minetest/minetest/blob/58ea11c2b3101667e7a5ed118bf1cb2204f63586/src/client/content_mapblock.cpp#L472 and in both cases is unpacked from the u16 and put into a struct for further processing.
Stopping here for a moment, to decouple AO from shadows we'd need to either move the calculations out from that function, or just make it not bake the AO into its return value and return it separately. A few possible ways to do it:
- Make the return value an u32. This should give us plenty of space to pack whatever is needed, possibly at the cost of not using all the bits and increasing memory usage.
- Considering there are only 4 possible values of ambient occlusion in this function (no AO or one of the 3 values from the lookup table: https://github.com/minetest/minetest/blob/58ea11c2b3101667e7a5ed118bf1cb2204f63586/src/client/mapblock_mesh.cpp#L232-L236), another option would be to devote just 7 bits to
light_dayandlight_nighteach (instead of 8) and have remaining 2 bits encode the ambient occlusion. - Pass a pointer to the function to place the AO calculation outcome directly somewhere, where it'll possibly be used later.
Now, at a later point the processed light data ends up in the encode_light() function at https://github.com/minetest/minetest/blob/58ea11c2b3101667e7a5ed118bf1cb2204f63586/src/client/mapblock_mesh.cpp#L950-L976 where it gets made into a color (which later is multiplied with hardware coloring color). Currently, it's essentially preapplying shadows onto the hardware coloring color, while storing data about the artificial/natural color ratio in the alpha channel (the latter is later used by the shader to determine the final lighting color – artificial and natural light have subtly different hues). What we need is storing the maximum color value for that vertex in the color channels, that is, applying only ambient occlusion (and "directional" face shading) there, while storing the rest in the alpha channel. Ways to do the latter:
- Use 4 bits for the light value and 4 bits for the ratio.
- Use 4 bits for the day and 4 bits for the night light.
- Use 4 bits for the natural and 4 bits for the artificial light (requires significant changes in the previous parts of the pipeline).
- Expand the amount to full 32bit floats instead of just bytes.
So why are we using bytes here if the GPU is working with floats anyway? Well, OpenGL can take color data both in a 4-byte float per channel format, and 1-byte int per channel format. We're using the latter. 4th point right above means that we'd change it. Possible side effects? Not sure at which point the bytes get converted to floats, but it's on the OpenGL side. Some incredibly old hardware may fail doing this completely (not sure if we support such). Newer hardware probably won't have issues, because bottleneck lies elsewhere anyway.
Alternatives
- Do nothing at all about this. We're missing out on some standard graphical options.
- Refactor all this code and somehow make a cleaner way to implement it all along the way.
- Pass a second vertex color to the shader, as in Use a new two-color vertex type for mapblock meshes #14789 – not if this is needed, I think we'd have plenty of space to put all we need if we had the 4 floats available, instead of just 4 bytes.
Additional context
I'm willing to work on a PR for this, standalone or as part of #14343.