GPU Gems 2 · Chapter 28. Mipmap-Level Measurement

用 alpha 标定纹理、遮挡查询与参考计数测量实际可见的 mip 层,再以显著性阈值和分摊调度驱动纹理内存管理。

学习目标

  • 能解释 Mipmap-Level Measurement 为什么让真实 GPU 采样路径回答“哪些 mip 层可见”,而不是由 CPU 猜测
  • 能实现 calibration alpha、reference/query ratio、显著性阈值与分摊调度,并核对状态覆盖和内存结果
  • 能回答:距离、表面朝向、阈值和测量间隔改变时,应保留哪一层、会节省多少内存,又可能出现什么画质或延迟风险

先测量用户真正看见的细节

一张大图为远近不同的物体准备了多档清晰度。最高档往往占据最多空间,却可能只在极少视角中出现;永远把它留在显存里,会挤掉其他角色、地形和特效的预算。

问题在于,最终选择不仅由距离决定,还会受到表面朝向、屏幕设置和硬件内部策略影响。与其在主处理器上重建一套不完整的猜测,不如让图形硬件按真实状态再画一次,再把“有多少片元需要某档细节”读成管理决策。先预测:在实验里同时增加距离和掠射角,首个可见层会往 L0 还是 L3 移动?

Mipmap-Level Measurement 实验

先预测:距离和掠射角同时增大时,首个可见层会向更细还是更粗移动?把阈值提高又会释放哪些顶层?

mip measurement lab · measurement · threshold 15%measurement coverage viewnear / face-onfar / grazingquery / referenceL00L10L29,750L313,000first visibleL2saved / top 41280 / 1360 KBmeasure every 4 frames · about 67 ms at 60 fpsresponsive schedule

查询比例以 reference fragments 为分母;距离、朝向和真实采样状态共同决定可见层。

本章的 Mipmap-Level Measurement 不是自己实现一套 mip 选择公式,而是把 GPU 已经隐式做出的选择转成可查询的片元数量,再决定哪些高分辨率层值得常驻。

1. 从人工 false color 到 alpha 标定

美术检查常把每个 mip 层涂成强烈不同的颜色:看到哪种颜色,就知道硬件正在采哪一层。这个方法直观,却需要人在画面里寻找少量像素。自动化的关键是把“颜色识别”改写为 GPU 已能高效完成的通过/拒绝测试。

标定纹理尺寸应与被测纹理一致,采样状态也必须一致。最简单的最高层测试在 L0 写 alpha 1,其余层写 alpha 0;替换目标纹理后重画对象,只让 alpha 大于零的片元通过。RGB 不参与判定,可用于 false-color 调试,或填入有代表性的图案,避免驱动把异常纯色纹理走入不同优化路径。

先问“最高细节层是否真的进入最终画面”512² RGBA mip chainL0L1L2L3每层内存L0 · 512²1024 KBL1 · 256²256 KBL2 · 128²64 KBL3 · 64²16 KB顶层不需要时,先释放 L0/L1 比微调尾部小层更有价值

以未压缩 512×512 RGBA8 为例,L0、L1、L2、L3 分别约占 1024、256、64、16 KB。每降一级边长减半,面积与内存变成四分之一,因此确认 L0/L1 不可见,收益远大于继续分析 mip chain 尾部。

2. 把层选择变成 query count

遮挡查询原本用于判断物体是否被挡住;它统计通过深度测试的片元,也会遵守该 draw call 的其他管线状态。原章利用这一点:重放物体、替换标定纹理、关闭颜色写入,再让 alpha 阶段只保留正在测试的 mip 区间。这样 query count 就成为该层对当前画面的可见证据。

让 GPU 把“选了哪层”编码为可计数的通过片元1. 重放 draw call同一 mesh / depth2. 替换 calibrationalpha 编码 mip3. remap + alpha test只让目标层通过4. occlusion query统计通过 fragments另跑一次无 alpha test 的 reference query,分母才反映当前可见像素color write 可关闭;深度、视图与真实采样状态必须保留

原始 Direct3D 路径会覆盖 pixel shader、alpha-test states 与 texture,并使用 D3DRS_COLORWRITEENABLE 禁止标定颜色写进画面。现代 API 可在 fragment shader 中执行等价阈值判断;但 WebGL2 的 ANY_SAMPLES_PASSED 只提供布尔结果,不直接返回原章所需的精确计数,移植时要选择支持 samples-passed 计数的宿主 API,或改用分级布尔策略。

float encoded = texture(calibrationMip, uv).a;
float remapped = clamp(4.0 * (encoded - levelOffset), 0.0, 1.0);
if (remapped <= alphaReference) discard;
 
// color attachment writes disabled; depth test remains identical
outColor = vec4(0.0);

单独重放一次无 alpha test 的 reference query,得到当前物体所有可见片元数。它自动包含遮挡、裁剪和视口变化;若省略分母,同样的 650 个片元在近景小物体与满屏地形上会被错误解释为同等重要。

3. 一张纹理测多个 mip 区间

不必为每个被测层创建一张标定纹理。原章把 L0 到 L3 的 alpha 依次设为 1、0.75、0.5、0.25;trilinear filtering 会在相邻层之间产生连续梯度。第 kk 次重放仅改变 offset,把目标区间缩放回 alpha test 的完整动态范围。

本节符号如下:

  • aa:标定纹理采样得到的 alpha
  • ckc_k:测试第 kk 层时使用的偏移
  • aka'_k:重映射后送入 alpha test 的值
  • QkQ_k:第 kk 层查询计数
  • QrefQ_{ref}:参考查询计数
  • rkr_k:第 kk 层相对可见比例
ak=clamp ⁣(4(ack),0,1),ck{0.75,0.50,0.25,0}.a'_k=\operatorname{clamp}\!\left(4(a-c_k),0,1\right), \qquad c_k\in\{0.75,0.50,0.25,0\}.

这个式子在说:四倍缩放把每个四分之一宽的 alpha 区间扩展到零至一,使 alpha test 对四个层级都使用同样的精度和参考值。

一张 calibration texture,四次 remap 分离四个 mip 区间alpha mip ladderL0 · a=1.00L1 · a=0.75L2 · a=0.50L3 · a=0.25per-level pixel passa′ = clamp(4(a − cₖ))测 L0cₖ = 0.75测 L1cₖ = 0.50测 L2cₖ = 0.25测 L3cₖ = 0.00RGB 仅供调试;算法判定来自 alpha 与 query count

查询必须异步读取,不能在提交后立刻等待,否则 CPU/GPU pipeline 会被同步点卡住。实践中把 query handle 与对象、frame index、被测层一起放进 pending 队列,后续帧确认结果可用后再解释和更新资源状态。

for (const level of measuredLevels) {
  beginSamplesPassedQuery(object.id, level);
  replayWithCalibration(object, levelOffsets[level]);
  endSamplesPassedQuery();
}
 
beginReferenceQuery(object.id);
replayWithoutAlphaReject(object);
endReferenceQuery();

4. reference ratio 与显著性阈值

先把绝对计数归一化:

rk=QkQref,k=min{krkτ}.r_k=\frac{Q_k}{Q_{ref}},\qquad k^*=\min\{k\mid r_k\ge\tau\}.

这个式子在说:从最细层开始寻找第一个达到阈值 τ\tau 的层 kk^*;比它更细的层只影响过少片元,可以从当前纹理预算中去掉。必须保护 Qref=0Q_{ref}=0 的完全不可见对象,并把它交给不可见资源策略,而不是除零。

query count 先除 reference,再与 significance threshold 比较reference = 13,000 fragmentsthreshold 15%L00%L15%L281%L3100%第一个超过 15% 的层是 L2:L0 与 L1 可从本次预算中移除

原章给出的典型计数为 reference 13000、L0 0、L1 650(5%)、L2 10530(81%)、L3 13000(100%)。采用 15% 阈值时,L2 是首个显著可见层,因此 L0/L1 可移除。阈值越高,内存越省,但局部高细节表面会更早变软;它是明确的画质—空间旋钮,不是越大越好。

5. 在引擎里分摊五次附加绘制

测四个层加一个 reference,需要五次附加 draw call。若每帧对所有对象执行,draw 数会膨胀到不可接受;但纹理层通常随相对位置缓慢变化,没有必要每个对象每帧重测。原章在 Leviathan 引擎中每帧从循环队列取一个对象,因此固定只增加五次 draw calls。

不是每帧测全部对象,而是把五次附加 draw 分摊到时间轴one measured object per frameframe 1terrain A4 levels + refframe 2vehicle4 levels + refframe 3avatar4 levels + refframe 4terrain B4 levels + refframe 5building4 levels + refframe 6terrain A4 levels + refvelocity / camera change raises prioritystable objects remain in the ordinary queue原章四层测量的固定开销:每帧 5 个附加 draw calls

每个 buffered draw call 要能重放并局部覆盖状态:pixel shader、标定 texture、alpha 判定与 color-write mask;几何、depth、采样状态和通常的 view transform 保持一致。如果希望测量视锥外对象,可以临时重设 view,但这回答的是另一个问题,结果不可与当前画面直接混用。

简单循环队列会产生延迟:对象快速接近相机时,新高分辨率层可能在若干帧后才被重新请求,出现 resolution pop。改进方法是让速度大、投影变化快或刚进入视野的对象提高优先级,并设置升清晰度快、降清晰度慢的 hysteresis,避免阈值附近反复换层。

6. 朝向、阈值与实际内存收益

相同距离不能推出相同层级。平坦地形以掠射角进入视野时,一个屏幕像素覆盖很长的纹理区域,常落到较粗层;正对相机的崖壁即使等距,也可能暴露更细层。这正是 CPU 距离近似最容易漏掉、而真实 sampler 已自然纳入的变量。

距离相同不代表 mip 需求相同,surface orientation 同样改变 footprintview A · cliff faces cameraview B · grazing terraincliff can expose L0/L1flat region settles on L2/L3让真实 sampler 与 render state 作答,比 CPU 猜测专有硬件规则更可靠

原章在 Leviathan terrain 的样本中报告:不测量时使用 4.333 MB;0%、15%、30% 阈值分别降到 0.864、0.538、0.464 MB,对应 80%、88%、89% 节省。数字属于当时的特定场景与引擎,不应当作现代项目保证;可迁移的是测量方法,以及“高阈值继续省内存但逐步降低画质”的趋势。

扩展方向有三类。第一,调整标定纹理与 viewport 的相对尺寸,可把 magnification 也编码进测量范围。第二,给每层 alpha 加入同源、缩放偏置的确定性纹理,使小幅距离变化也有片元穿过阈值,可观察层级上升、下降或静止的趋势。第三,支持导数指令的 shader 可用 ddx/ddy 直接估计 mip level,省去标定纹理带宽,但结果回读仍需 query 或 reduction 机制。

三步验收:编码、解释、调度

三个阶段使用同一 mesh、相机路径和 sampler preset。先证明 alpha 编码隔离了目标层,再证明 ratio 与阈值结论正确,最后才把结果接入纹理常驻策略。

分步1 / 3

第一步:核对 calibration alpha 与状态覆盖

逐层检查 1、0.75、0.5、0.25 的 alpha,记录 offset 与 alpha-test 输出;重放时仅覆盖 shader、texture、alpha 判定和 color-write,保留真实 depth、geometry 与 sampler。

一张 calibration texture,四次 remap 分离四个 mip 区间alpha mip ladderL0 · a=1.00L1 · a=0.75L2 · a=0.50L3 · a=0.25per-level pixel passa′ = clamp(4(a − cₖ))测 L0cₖ = 0.75测 L1cₖ = 0.50测 L2cₖ = 0.25测 L3cₖ = 0.00RGB 仅供调试;算法判定来自 alpha 与 query count

本章小结

  • calibration texture 让真实 sampler 把层选择编码进 alpha。
  • occlusion query 与 reference query 把可见证据变成相对比例。
  • significance threshold 决定首个值得常驻的细节层。
  • round-robin 将四层加参考值的五次重放分摊到多帧。
  • 距离、朝向、过滤状态与调度延迟必须共同进入资源决策。

练习

问题 1|解释查询结果。 某对象的 reference 为 20000,L0 到 L3 查询计数依次为 400、2400、15000、20000。阈值为 15% 时首个显著层是哪一层?L0/L1 能否同时移除?

问题 2|设计调度策略。 场景有 600 个稳定地形块和 20 个快速角色,四层测量每对象需要五次附加 draw。怎样控制成本并减少角色靠近镜头时的 resolution pop?

问题 3|修改实验并提交证据。 在 MeasurementLab 中把 distance、grazing angle、threshold 和 cadence 分别推到高值。你应保存哪些状态,如何判断“省内存但不可交付”?

名词解释

本章出现的专业名词,用大白话再讲一遍。

calibration texture
occlusion query
alpha test
reference query
significance threshold
round-robin measurement

资料与写作方式声明

本章以GPU Gems 2 · Chapter 28. Mipmap-Level Measurement权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…