richgel999 / bc7enc_rdo

State of the art RDO BC1-7 GPU texture encoders
Other
180 stars 32 forks source link

BC4/5 blocks with two values per channel are slightly off #17

Open cwoffenden opened 2 years ago

cwoffenden commented 2 years ago

If a BC4 block has only two values, and the search radius is greater than zero, then the second value is always interpolated, and since the BC4/5 interpolation is done with more than 8-bits in hardware (see findings here) the resulting value is slightly off.

If you start with the code here:

https://github.com/richgel999/bc7enc_rdo/blob/e6990bc11829c072d9f9e37296f3335072aab4e4/rgbcx.cpp#L2801

An example being: a block with values 126 and 127 and the default search radius of 3 will achieve a best_err of zero with endpoints 127 and 123 (with six interpolated values, MODE8 in the code). When interpolated as 8-bit, and with selectors of 0 and 2, this does correctly result in values of 126 and 127. But... since the hardware is using 14- or 16-bit interpolation then the resulting values are 126.43 and 127.0.

This is small, agreed, but when mixed with solid blocks of 126 we get block artefacts as the encoder flips between solid and multi-value blocks, breaking down to blocks of 126.0 and 126.43.

This came about because we have a normal map exported from 3D Coat with essentially a dimple noise texture on it. AFAIK it was worked on at 4k then exported at 2k, by which time the dimple texture end up being a few dots. Here's the result isolated with obvious block errors:

rgbcx

Ignoring there's clearly something amiss with the normal map, it does nicely highlight this issue. If this were BC3 alpha it wouldn't be affected, it's only BC4/5 with the extended interpolation bits.

The easy fix (which I've already tried) is to clamp the search radius to zero when there are only two values. I think, though, a better fix may be to interpolate with more range in bc4_block::get_block_values() so that it works for more than two values (which I'll try next).

cwoffenden commented 2 years ago

For reference, here's the original 3D Coat nmap:

Piano_Gold_nmap

And here's a cropped section I've been stepping through the code with:

problematic-piano-nmap

(Taken from the bottom left, cropped on the same 4x4 boundaries as the encoder)