URP 渲染实战 / 第 29 章 性能优化 / 案例 29.6
案例 29.6Mip 级别可视化入门
目标:把 GPU 实际选择的 Mip 级别画成颜色,检查贴图分辨率和平铺是否合理。
Assets/Cookbook/Ch29/MipLevelViz.shader(节选:mip)
float ComputeMipLevel(float2 uv, float2 texSize) { // 相邻像素之间 uv 变化了多少个纹素 float2 dx = ddx(uv * texSize); float2 dy = ddy(uv * texSize); float maxSq = max(dot(dx, dx), dot(dy, dy)); return 0.5 * log2(maxSq); // = log2(最大纹素跨度);<0 表示放大(一个纹素盖住多个像素) } half3 MipColor(float mip) { static const half3 c[7] = { half3(0.1, 0.35, 1), half3(0.15, 0.8, 0.3), half3(0.95, 0.9, 0.15), half3(1, 0.55, 0.1), half3(0.95, 0.15, 0.1), half3(0.8, 0.1, 0.7), half3(0.35, 0.1, 0.5) }; // c[0] 给“放大”区(mip<0),c[1] 起依次是 mip 0,1,2... float m = clamp(mip + 1, 0, 6); int i = (int)floor(m); return lerp(c[i], c[min(i + 1, 6)], frac(m)); }
float ComputeMipLevel(vec2 uv, vec2 texSize) { // 相邻像素之间 uv 变化了多少个纹素 vec2 dx = dFdx(uv * texSize); vec2 dy = dFdy(uv * texSize); float maxSq = max(dot(dx, dx), dot(dy, dy)); return 0.5 * log2(maxSq); // = log2(最大纹素跨度);<0 表示放大(一个纹素盖住多个像素) } vec3 MipColor(float mip) { const vec3 c[7] = vec3[7](vec3(0.1, 0.35, 1), vec3(0.15, 0.8, 0.3), vec3(0.95, 0.9, 0.15), vec3(1, 0.55, 0.1), vec3(0.95, 0.15, 0.1), vec3(0.8, 0.1, 0.7), vec3(0.35, 0.1, 0.5)); // c[0] 给“放大”区(mip<0),c[1] 起依次是 mip 0,1,2... float m = clamp(mip + 1.0, 0.0, 6.0); int i = int(floor(m)); return mix(c[i], c[min(i + 1, 6)], fract(m)); }
GPU 选 Mip 的依据就是屏幕空间导数:相邻像素之间 UV 跨过了多少个纹素。ddx(uv * texSize) 给出水平方向相邻像素的纹素差,取两个方向中较大的长度,log2 之后就是 Mip 级别(硬件的实际公式还涉及各向异性过滤,这里是各向同性的近似)。_BaseMap_TexelSize 是 Unity 自动为每张贴图属性设置的变量,(1/宽, 1/高, 宽, 高)。

Rendering Debugger 的 Material 页也提供了 Mipmap 相关的调试信息(URP 14 源码中有 DebugMipInfoMode 的 Level/Count/Ratio 模式),具体 UI 中可用的选项以实际版本为准。