For most objects like the leaves in trees, this isn't a serious problem, most people won't even notice. But for important, fine grained stuff like wire mesh fence as above, this is certainly not wanted. There are some simple workarounds to this problem:
- Edit the image and make the alpha channel broader and sharper. But this probably makes your texture look much uglier.
- Adjust the alpha testing reference value for your material. This might work, but if you are unlucky, it doesn't look that nice.
- Disable mipmapping. This looks usually quite nice, but it impacts performance quite a lot
The result could probably be adjusted a bit, but for me, it looks ok for now. There is no performance impact when rendering this and it is also not slower to generate the mipmaps at all.
For a very simple and badly filtered mipmap, I was originally calculating a pixel for the next level like this:
a /= 4; r /= 4; g /= 4; b /= 4; newColor.set(a, r, g, b);
But now I'm doing it this way:
const float refValue = 0.65f; a = (int)( (amax*(1.0f - refValue)) + ((a / 4.0f) * refValue) ); r /= 4; g /= 4; b /= 4; newColor.set(a, r, g, b);
Maybe I'll add this feature into my game engine as well, so that other people will profit from this as well in the future.
four comments, already:
While it does look pretty decent in the place the wire mesh first switches mipmap levels it does look a bit overemphasized further away, likely because the repeated computation accumulates alpha in even lower mip-map levels. I guess this could be fixed by changing the refValue depending on the mip-level generated without incurring cost there.
Another approach might be using a slightly larger downsampling kernel, kind of like (1 3 3 1)2, or some other more bicubic-like variant (as this will likely be similar in effect to max, but possibly more well-behaved for iterated application). (1 3 3 1)2 would have a sum of 64, so could be computed without divisions (and even without multiplications) likewise.
xaos - 23 01 17 - 10:14
and i just realized I should have previewed the comment, as the markup behaves somewhat unusual
xaos - 23 01 17 - 10:15
But with that formatting, it looks quite sophisticated :)
niko - 23 01 17 - 12:34
Unless you actually read it
Just for fun: (33, -54, 164, 164, -54, 33)/256 gives apparently a relatively well-behaved 1d filter (some ringing of course, the truncation not helping, but preserves detail pretty well). It needs clamping of course (and will overshoot somewhat for small transients, but max kind of does that as well)...
Btw. another problem with max is (at least when alpha is premultiplied, which it often is) that mip-levels will tend to become progressively darker when using it solely for alpha. Compensating for that is somewhat cumbersome; I think to stay true to your scheme you would have to basically sort the 4 pixels (or find the one with maximum opacity at least), then blend both color and alpha of the average with the maximum. This then will shift the problem to textures which are inverted forms (so small holes instead of small non-holes), as these would likely become completely opaque in mipmapping (or at least much more opaque than they should be, this is kind of what happens at the mip-level boundary further away).
If building the mipmaps offline it might be worthy to take gamma correction into account (at least usually a value of 128 does not correspond to a real luminance of 0.5, but rather somewhere around 0.75), and use a larger filtering kernel. I would expect your empirical formula to achieve a similar effect in certain cases more by accident, as the problems kind of cancel each other. (Yet another approach would be stochastic sampling maybe, in particular together with alpha-testing, and ideally generating all mip-levels from the source image directly then, with larger and larger sampling areas. You might also check out blue noise and green noise dithering which are roughly related.)
xaos - 23 01 17 - 13:35