Mipmap Bleeding

November 21

现象

比如我想在图片的左上角显示tex1, 右下角显示tex2, 两张材质都开启了Triliner采样, 采样位置不同, 代码如下。

void main() { vec4 color = vec4(0.0); vec2 st = uv; if (uv.x < uv.y) { st -= 0.5; // Cause of mipmap bleeding color = texture(tex1, st); } else { color = texture(tex2, st); } gl_FragColor = color; }

上面的代码会导致如下现象:

Bleeding
No Bleeding
白色线条是Mipmap采样引起的

关闭Mipmap后现象消失, 因此可以定位到是Mipmap采样引起的,

非常幸运, pema在几个月前写了一篇关于Mipmap的文章 Mipmap selection in too much detail。 文章写得非常好, 强烈推荐阅读。

每次看到这么优秀的内容都会有点嫉妒, 要是我能写出来就好了 🌚。

原因

问题的根源在于Mipmap的采样方式。我们常用的硬件Mipmap采样:

vec4 color = texture(tex, uv);

软件模拟的话, 实际上是这样实现的:

float level(vec2 uv, vec2 res) { vec2 dx = dFdx(uv) * res; vec2 dy = dFdy(uv) * res; float d = max(dot(dx, dx), dot(dy, dy)); return max(0.0, 0.5 * log2(d)); } vec4 color = textureLod(tex, uv, level(uv, TEXTURE_SIZE));

可以看到这里用到了 dFdxdFdy 来计算采样的Mipmap层级。回去看上面的代码:

if (uv.x < uv.y) { st -= 0.5; // Cause of mipmap bleeding color = texture(tex1, st); } else { color = texture(tex2, st); }

由于我们在if中对st进行了修改, 导致进入不同分支时, dFdx(st)dFdy(st)的值变大, 进而导致计算出来的Mipmap层级也变大, 采样到了更低分辨率的Mipmap。 在极端情况时, 甚至会采样到最底层的的Mipmap。因为最底层的mipmap是1px x 1px的纯色, 所以发生跳变的像素位置附近出现了一根线。

我们可以构造一个mipmap让最后一层是一个Cyan的色块, 可以看懂线条被染上色, 证明了是采样到了最底层的mipmap:

解决方案

只要能避免采样坐标因为分支变化而产生跳变就可以解决这个问题。把坐标修改放到条件分支外面, 保证dFdxdFdy的值不变即可:

void main() { vec4 color = vec4(0.0); vec2 st1 = uv - 0.5; vec2 st2 = uv; if (uv.x < uv.y) { color = texture(tex1, st1); } else { color = texture(tex2, st2); } gl_FragColor = color; }

© 2020 - 2025 Ruiyao Luo

25/12/10 11:30

PROD

#68e6fa8