Okay, I now have at least some understanding of what GClements is saying about dFdx() and dFdy(). I read up on that stuff a bit more. When you call texture(sampler, texcoord), it’s the same as calling textureGrad(sampler, texcoord, dFdx(texcoord), dFdy(texcoord)). If you compute the output of dFdx() and scale it, you can see the discontinuities that GClements is talking about:
I could be off on this next part. The relationship between mipmap level and derivative is non-linear ( https://www.opengl.org/discussion_boards/showthread.php/177520-Mipmap-level-calculation-using-dFdx-dFdy ), so if you try to bias it linearly you might exaggerate any discontinuities. That might be the problem I’ve been seeing, but I would need to dig into it more to really understand better.
If that really is the problem, you would probably need to generate your own per-vertex derivatives and interpolate them for use in textureGrad() in the fragment shader. However, textureGrad() doesn’t even work on my Intel HD 4000 graphics under Linux. So unless there’s a newer driver that fixes that problem I’m not going to go any further.
Fortunately for me, Dark Photon’s suggestion about textureLod works fine for what I’m doing. You might get some shimmering on the edges with textureLod if you use high enough mipmap levels, but I’m not seeing anything like that in practice.
Anyway, thanks for the help everyone. Here’s the final lighting with some nice, warm ambient and subtle fresnel reflections. The part that I never mentioned in my original post was that this texture problem looked really bad when combined with environment mapped bump mapping, so it’s good to have that fixed too.
Oh, and to answer and earlier question, the mipmaps are generated with glGenerateMipmap(). You can see how that looks pretty bad in the background imagery. Something I’ll need to work on later is blurring the mipmaps a little.