For block/item models, only use texture sizes where width and height are multiples of 16, and be careful when selecting regions that do not align with the 16x16 grid to be displayed on a face of the model.
A comparison of using no mipmapping versus default mipmapping (enlarge image for more obvious results):
In computer graphics, the goal is to simulate all the light passing through a particular screen pixel into your eye. That comes with a problem though: A screen pixel has a certain size, while naive rendering works by calculating a single coordinate at which a texture should be looked up at - so it will sample the texture only at one location, while the whole screen pixel actually covers a region of it. Ideally, you would want to average the color of all the area that the screen pixel covers and then output that.
A diagram comparing the pixel shown without mipmapping to the region that should be visible through (and should affect the color of) the screen pixel:
Mipmapping creates multiple versions of your texture, with each version (also called "level") being downscaled by a factor of 2 when compared to the previous level. So if your full texture is 16x16, mipmapping will create one version at 8x8, one at 4x4, one at 2x2 and one at 1x1, so in total, 4 different levels of downscaling in addition to the original texture. This way, a square of four adjacent pixels in one mipmap level will become averaged into just one pixel in the next mipmap level.
Then, when rendering the texture, you just need to calculate how many texture pixels are covered by the current screen pixel, and choose a mipmap level according to that area. Effectively, the mipmap texture acts as a pre-calculated average over the different regions of your texture.
For example, this is the mipmap that the described procedure yields when applying it to just the stone texture:
A texture can't be downscaled by a factor of 2 if its size is not a multiple of 2 - and this also includes trying to downscale the other mipmapping levels to get an even higher level.
Therefore, a texture's size must be divisible by a power of 2 corresponding to the desired mipmapping level. Because Minecraft uses a mipmapping level of 4, you need to use a multiple of 16 as your texture size so that mipmapping can be fully applied to the texture.
Furthermore, because Minecraft adds all block and item textures to the same atlas and only then creates a mipmap for that whole atlas, using a bad texture size for any block or item texture will also have the effect of lowering the mipmap level for the entire atlas, in other words, for all other blocks and items.
Just changing the texture size to 16x16 sounds good and all, but maybe you still want to make a block that looks like it has a 9x9 texture without the mipmap level being dropped to 0.
A simple way to solve that is to still make the texture file itself 16x16, but just change the respective block model to only use a 9x9 section of the texture, like this:
{
"parent": "block/block",
"textures": {
"particle": "#all",
"all": "block/red_wool"
},
"elements": [
{ "from": [ 0, 0, 0 ],
"to": [ 16, 16, 16 ],
"faces": {
"down": { "uv": [0, 0, 9, 9], "texture": "#all", "cullface": "down" },
"up": { "uv": [0, 0, 9, 9], "texture": "#all", "cullface": "up" },
"north": { "uv": [0, 0, 9, 9], "texture": "#all", "cullface": "north" },
"south": { "uv": [0, 0, 9, 9], "texture": "#all", "cullface": "south" },
"west": { "uv": [0, 0, 9, 9], "texture": "#all", "cullface": "west" },
"east": { "uv": [0, 0, 9, 9], "texture": "#all", "cullface": "east" }
}
}
]
}
The block atlas gets the 16x16 texture that it wants, and from the outside, the block looks just like as if it had a 9x9 texture.
However, when doing this trick, you can still run into issues:
(See the red lines that appear on the edges of the grey blocks in the middle of the screen)
You need to be careful, because the texture will still have mipmapping applied to it as if it was a 16x16 texture, and so, at the highest mip level, it will take the average over the entire texture - including the part that the model says should never be displayed, so you have to make sure to fill that area with a color that matches the surrounding texture.
In the broken example above, I filled the rest of the texture that is not selected to be shown in the model to be red, which is what is breaking the mipmapping:
In this case, I can fix it by instead filling the remaining area with a color that will not stand out when mixed with the rest of the image:
After applying this texture, it returns to looking good at all distances and angles:
In the explanation of this problem, I just used the highest mipmap level that averages over the entire 16x16 area as an extreme example that shows that all pixels in that area can still be shown in some cases, but the same of course also counts for the lower mip levels, so keep in mind that the individual 8x8, 4x4 and 2x2 regions that the texture is composed of also each correspond to one mip level.