The bug
This has been an issue for a while. It's especially noticeable at level 3 of the side of grass. See the screenshots, where I placed a row of grass on a row of dirt. With mipmaps, half of the grass block is practically black at the 'optimal' distance where the 2x2 mipmap or further is used.
How to reproduce
Open this world
[media]with these settings
[media] and look straight ahead without moving.
❌ Observe the dark patches with the side of the grass.
Proposed solution
Using Yarn mappings for 1.14.4, replace this method:
net.minecraft.client.texture.Sprite
private static int blendPixels(int int_1, int int_2, int int_3, int int_4) {
return SRGBAverager.average(int_1, int_2, int_3, int_4);
}
using these classes provided by @unknown:
[media][media][media]Original analysis
I've looked it up in MCP 9.30/9.31 (corresponding to Minecraft 1.10.2). In net.minecraft.client.renderer.texture.TextureUtil
, generateMipmapData
is responsible for mipmap generation. It turns out not to use proper weighting by the alpha channel. Anything that isn't fully transparent is given the full weight, and anything that is is treated as black. That's where the darkness comes from.
The correct model is most natural when using colors premultiplied by alpha: just average everything. If you don't use premultiplied colors, you must multiply colors by alpha before averaging and divide the resulting color by the resulting alpha afterward.
Premultiplied also has the advantage that any blending steps you don't control directly work correctly, like texture filtering and frame buffer blending. Minecraft won't really experience either the advantages or the disadvantages, but it's the cleaner model that would've prevented problems like this bug from the start. That's something to consider.
Adding to the problem is that Minecraft tries to apply gamma correction to the alpha channel. That blows up the opacity, only emphasizing exactly those texels that are too dark. Gamma correction of the alpha channel doesn't make sense anyway because colors can deviate either way depending on what's blended with what. The general standard—which is also what you'll get natively if you enable frame buffer and texture sRGB—is to use sRGB (which is a little more involved than x^2.2) for colors and keep the alpha channel linear.
Related issues
is duplicated by
Attachments
Comments


Do you think this is also causing the fire animation of a burning mob to get black / dark at the edges when you move away?
@unknown: Yes, looks like it! Good catch. If I end up making a proof of concept mod, I'll include a burning mob in the screenshots which should demonstrate it nicely.
Here's a proof of concept based on MCP 9.37/Minecraft 1.11.2. Put the Java files in example/jonathan2520 (next to Start.java) as indicated by the package. In net.minecraft.client.renderer.texture.TextureUtil, change the line
aint2[i1 + j1 * j] = blendColors(aint1[k1 + 0], aint1[k1 + 1], aint1[k1 + 0 + l], aint1[k1 + 1 + l], flag);
to
aint2[i1 + j1 * j] = example.jonathan2520.SRGBAverager.average(aint1[k1 + 0], aint1[k1 + 1], aint1[k1 + 0 + l], aint1[k1 + 1 + l]);
Full changes:
Proper weighting by alpha channel, obviously.
Proper sRGB.
According to the new model, if all four inputs are entirely transparent, the color is undefined. (Kind of like the Invisible Pink Unicorn. No way to tell from its appearance that it's supposed to be pink.) I've defined it by using an unweighted average in that case. Minecraft originally made it black. Doesn't really matter, I think, but there it is.
Removed the special case where if at least one texel in the entire texture/atlas was fully transparent, alpha values under 96 would be cut to 0. Might serve some purpose but I don't see sense in it and it's inconvenient. It can be re-added if necessary.
Although I haven't benchmarked, it should be significantly faster. For starters, all 4 Math.pow calls are gone. I came up with a table-based sRGB encoding scheme that's simple, fast and exact.
Indeed, it fixes the black edges around fire as well.
Some more observations:
Minecraft uses interpolation between mipmaps (GL_NEAREST_MIPMAP_LINEAR). That's what I remembered but the hard transitions to darkness on the side of grass threw me for a loop. What even causes those? Anyway, that interpolation sans premultiplication can cause further artifacts. The hard pixel art edges in Minecraft's textures make that as bad as it can be. It's also a case where my preservation of invisible colors can reduce the ensuing artifacts, if source images are authored with sensible invisible colors which they generally aren't. I guess it may be better to process the mipmap pyramid in reverse afterward, using the color of the lower-res mipmap to replace invisible colors. That will catch the worst of it regardless of authoring. You could actually do it in the forward pass because you only need to propagate back one level.
I don't know the full pipeline that well. If I did I might've been able to experiment with some more stuff like premultiplication. (Very invasive, requiring adjustments to all blend functions and colors passed to OpenGL. Would also have to check for pure alpha testing without blending which doesn't play nicely with it.) Oh well. Not like it or the work I've done so far is going to accomplish much anyway until Mojang sees it, which could take a while.
The sRGB constant I called alpha is really called a. What's in a name?
I did just try to make Minecraft use premultiplication. Turns out OpenGL is abstracted away enough to get away with a few hooks to adjust colors and blend functions. For the most part things look okay. But there's one major problem: fixed-function fog can't deal with premultiplication. Transparent stuff in the distance (water, clouds, stained glass, …) turns bright white. The general solution is to do the right calculation per fragment, which requires a fragment shader. So probably not going to happen for now.
An observation with my change to mipmap generation (and without the premultiplication stuff from above) is that leaves keep some holes in the distance even with mipmaps enabled. Normally they become darker and entirely opaque with mipmaps, for the same reason the uglier problems with mipmaps occur. Opacification is a side effect of gamma correction of the alpha channel, pushing it over the alpha test reference more easily. I explicitly took that out because it's nonsense.
Although the transparency introduced by my patch would be expected for transparent leaves, I must admit I liked the shimmer-free look of opaque leaves. I've never been able to decide what's better. If you want to, you can reintroduce the opacifying behavior on top of my mipmap generation by sRGB-decoding the reference alpha passed to glAlphaFunc. Where 0.5 is currently passed to render stuff like leaves, you'd pass ((0.5+0.055)/(1+0.055))^2.4 ≈ 0.214. The result will be virtually identical, except all the other problems like darkening and blend opacification will be gone.
I think this will be the final comment for now. Definitely enough analysis. The minimally invasive patch that I posted solves most of the problem with minimal regression potential. I'm putting my money on that.
Possibly related: since playing on 1.14, I keep seeing this sort of thing on grass on mipmap levels=4:
[media]This issue also affects 1.14.4 Pre-Release 6.
And this bug made its way into the official 1.14.4 release.
Still occurs 1.16.1
This effect has become very noticable in snapshot 21w10a
Confirmed RX 580 Graphic card AMD
I made a fabric mod for 1.16.5 that uses @unknown's classes and a very simple mixin. The mod works really well, however before uploading it anywhere I want to ask @unknown for permission to do so. I'll make sure to credit him since 90% of the mod is literally his code.

Can confirm in 1.17.1.

In 21w37a

In 21w38a

In 21w42a
[media]
In 21w43a
@jonathan2520 Under what license is the code? And may I use it in my own project?

Still in 1.18 pre-1, it gets really bad sometimes for me. Using level 4 mipmaps
[media]
In 1.18 Pre-7. Is there any computer settings perhaps that I can do it make this less noticeable? I fear it may not be fixed for next release

Still in 1.18 Pre-8

In 1.18.1 Pre-1

Still in 1.18.1 Release Candidate 1

In 22w05a

Still in 22w13a

Still in 22w15a

In 22w18a

In 22w19a

In 1.19 Pre-1

Can confirm for 1.19 Pre-2

In 1.19 Pre-3

In 1.19 Pre-4

Can confirm for 1.19 Pre-5

In 1.19

In 1.19.1 Release Candidate 1

In 1.19.1 Pre-5

In 22w44a

In 22w46a

In 1.19.3 Pre-1
Fixed if MipMap levels are set to 0
Because that literally disabled mipmapping, and the issue is with mipmapping...
Its going to be like permanently stuck in the game, because Mojang doesnt see it or something, i mean its been in the game for 4 years
I found out how to fix this. Take every pixel of a broken texture that has completely opaque and transparent pixels, and blur all of the pixels together in an image editing program to get the average color. Use the color picker on that pixel, erase the pixel, then replace every transparent pixel with that averaged color, at 49% or less opacity. Pixels under 50% opacity are invisible (unless they're on blocks like water or stained glass), but will be blurred with the mipmapping. Here's the overlay texture on the side of a grass block after I fixed it:
[media]The reason you should use 49% opacity is because the lower the opacity, the lower the color quality will be. If you use 1% opacity, it'll probably get rounded to RGB(127,127,127). 49% is the highest opacity that will still be invisible but have the highest quality.

Sodium has this bug fixed since 0.5.0:
— Mip-mapping has been greatly improved so that terrain no longer has a black outline around certain textures when viewed at extreme angles. (Note: This has the consequence that some textures, such as those used for leaves, may seem slightly brighter than they did before. However, we think this generally looks more accurate to the original texture work, and believe the original behavior was a bug.)
[media][media]
(Don't pay attention to the touch buttons, I tested this fix on PojavLauncher)
Can confirm in the latest MC version, by the way. Mipmaps generation is still broken in vanilla Minecraft.

Can confirm 1.21.4 Pre-Release 3