GPU Gems 1 · Chapter 15. Managing Visibility for Per-Pixel Lighting

用 camera、light 与 shadow visibility sets 剪枝 per-pixel lighting 的 batch 和 fill-rate 成本,并用 scissor 限制真正受光的屏幕区域。

学习目标

  • 能用 batch、pass、CPU 提交率解释 per-pixel lighting 为什么会把渲染成本乘上灯光数量
  • 能构造 camera visible set、light set、illumination set 与 shadow set,并说明它们为什么不能互相替代
  • 能为视锥内外的光源选择 F∩L 或 light-frustum convex hull 的 shadow candidates
  • 能把可见对象投影到屏幕,生成 tight scissor rectangle,并通过集合与 fill-rate 指标验证剪枝收益

per-pixel lighting 让每个光源都能贡献更细腻的材质和阴影,但它也会把同一批几何重复送入硬件。GPU Gems 1 第 15 章的出发点很直接:GPU 内部处理一个 batch 越来越快,CPU 把 batch 提交给 GPU 的速率却没有同样增长;最快的 polygon,仍然是根本不提交的 polygon。

为什么 per-pixel lighting 需要 visibility

是本章的计量单位;Direct3D 的一次 DrawPrimitive 可以看作一个 batch。场景的初始 ambient pass 要建立深度并处理全局 ambient/emissive,之后每盏灯通常还要先做 shadow pass,再做 lighting pass。

如果一个房间和三个模型被材质、骨骼拆成 14 个 batch,三个光源的朴素成本就是:

batches=ambient+lights×(shadow+lighting)=14+3×14×2=98batches = ambient + lights × (shadow + lighting) = 14 + 3 × 14 × 2 = 98

这不是 shader 指令数的问题,而是 CPU 提交与重复 pass 的乘法。即使一个 GPU 可以很快处理多边形,应用线程仍可能先被 batch submission 限制。

passes multiply the batch problemambient14 batches3 × shadow42 batches3 × lighting42 batches14 + 3 × 14 × 2 = 98 batchesambient + lights × (shadow + lighting)
这里的 14 来自示例场景的 8 个房间材质 batch 与 6 个模型 batch;每盏灯再把对象重复送入 shadow 和 lighting pass。

是所有裁剪和遮挡测试的起点。但 per-pixel lighting 还需要从每盏灯的视点构造另一套集合;如果只使用 V,无法知道灯是否能看见对象,也无法知道屏幕外 caster 是否能投影进画面。

1. 用集合逻辑拆开三种工作

对每盏灯单独计算。点光源是球形视野,不能简单投影到一个平面;可以把光源包在 cube 中,对六个 face 做 visibility query,但要去重同一对象被多个 face 看见的情况。

满足 I = V ∩ L。它只保留同时出现在屏幕和光源视野中的对象;若 I 为空,可以直接跳过这盏灯。它特别适合剪掉位于视锥边缘、被遮挡或光源实际上碰不到的灯。

与 I 不同:对象即使不在 V 中,也可能在 S 中。shadow pass 的判断是“从光源挤出的阴影体积是否与可见区域相交”,而不是“对象自身是否出现在镜头里”。

visible sets → fewer batchesVLI = V ∩ Llighting passS · shadow castersskip if I = ∅camera seeslight sees
不要把 I 误用于 shadow pass:屏幕外或相机不可见的 caster 仍可能把阴影投进画面,因此 S 必须单独计算。

一个稳妥的 per-light 顺序是:先得到 V,再为当前灯得到 L;用 V∩L 生成 I;再根据光源位置和投影体积生成 S。最后以 S 生成 shadowing term,以 I 重新渲染 lighting contribution。

V = visible_from_camera()
L = visible_from_light(light)
I = intersection(V, L)
S = shadow_casters_into_view(light, V, L)
render_ambient(V)
render_shadow(S, light)
render_lighting(I, light)

2. 生成 shadow set:光源在不在视锥内

当光源在 view frustum 内时,阴影总是从光源位置向外投射。此时,视锥内但被遮挡的对象也不能从 F 中删除;令 F 表示视锥内所有对象,shadow set 可以使用 S = F ∩ L。如果错误地使用 V,可能丢掉被另一个对象挡住、却仍然能把阴影投向可见表面的 caster。

当光源在视锥外,屏幕外的 caster 可能沿光线投影进视锥。此时构造由光源点和 view frustum 形成的 :保留光源位于 inside half-space 的原始平面,并为从光源看见的 silhouette edge 创建经过光源的平面。落入 hull 且属于 L 的对象才是 shadow candidates。

shadow set:light + frustum 的 convex hullview frustumlight outsidecastersilhouette edge → planeS = objects whose extruded shadow intersects this volume
当 light 在 frustum 外,不能只取相机可见对象;由 light 与 frustum 形成的 convex hull 才能包住投影进画面的外部 caster。

凸包算法可以用每个 frustum plane 对 light 的 inside/outside 判断开始:inside plane 直接加入结果;outside plane 则为它涉及的 edge 增加计数。某条 edge 的计数为 1 时,它是相对于 light 的 silhouette edge,需要用 edge 两端点和 light 生成一个新平面,并根据 winding 修正法向。

edgeCount = zeroes(frustum.edges)
for plane in frustum.planes:
  if lightInside(plane): add(plane)
  else: for edge in plane.edges: edgeCount[edge] += 1
for edge in frustum.edges:
  if edgeCount[edge] == 1:
    addPlane(through=edge.points + light, winding=edge.winding)

这个计算不是替代所有 visibility 系统的通用实现,而是把已有 frustum、portal 或 occlusion 结果接到 per-pixel lighting 的集合语义上。集合生成的 CPU 成本必须和省下的 batch、shadow fill 一起评估。

3. 用集合缩小 fill rate

CPU batch 不是唯一瓶颈。stencil volume 或大型光照几何会让 GPU 重复填充屏幕;即使对象数下降,如果每盏灯仍覆盖整个 viewport,fill rate 也可能先饱和。

应由实际受影响对象生成:把 I 中每个 primitive 的 bounding sphere 或 AABB 投影到屏幕,求总 bounding box,再与光源半径投影出的 box 相交。对象粗包围体通常足够,因为为了多挤出一点像素而计算精确几何,收益很小。

visibility → screen-space scissornaive light-radius boxlarge radius, small affected areatight scissor / z-scissorproject I objects → union boundsless screen area → less fill-rate work
光的半径不是屏幕影响区域;把实际受影响对象投影后求并集,再和光的投影范围取交集,才能得到紧的 scissor。

对 point light,sphere 视野可能需要六个方向的 visibility query;对大半径光源,仍应检查最终 I 的投影范围。若硬件支持 z-scissor,可以进一步限制深度范围,形成空间中的 scissor frustum。其目标都是让“不可能被这盏灯影响”的片元尽早被测试拒绝。

4. 集合数据结构与更新策略

集合可以有两种实现:集合持有对象引用,或对象持有自己属于哪些集合的 flags。前者需要排序列表后做线性 merge,适合本来就有排序集合的系统;后者可以先遍历对象设置 V/L 标志,再扫描轻量对象列表得到 I,代码更直接,但操作结束必须 reset flags。

对每盏灯不必同时把所有 L 保存在内存中:按灯依次计算并累加 lighting buffer 即可。对 portal visibility 之类已经同时服务相机与灯的系统,可以共享空间划分;对 point light 的球形视野,则需要额外的无平面投影方案或六面 cube 查询。

工程上建议把每帧的证据分成四组:V 的对象数与 occlusion 命中率、L 的对象数与查询成本、I/S 的 batch 数、scissor 前后的屏幕像素面积。这样才知道收益来自 CPU 剪枝、GPU fill-rate,还是只是改变了对象的提交顺序。

5. 动手实验:让 I 和 S 各自承担正确的工作

先预测:把 camera visible V 调小,I 会怎样变化?此时如果 light 在视锥外,S 是否一定同步变小?关闭 tight scissor 后,batch 数可能不变,但屏幕影响面积会发生什么?

Visibility Set Lab

先猜:为什么 I 小了,S 不能直接跟着变小?

可交互
camera × light × shadow visibilityI=9 · S=9view frustumlight influencetight scissor · screen area 0.27estimated batches: ambient 14 + S 9 + I 9 = 32

观察:I 只决定 lighting pass;S 还要覆盖屏幕外 caster。减少 I 可降低 batch,scissor 则进一步降低 fill rate。

实验里的绿色对象属于 I,紫红色斜线对象属于 S;黄色表示光源可见但不一定在屏幕上。切换 light 是否在 frustum 内,会把 shadow-set 逻辑从 F ∩ L 切到 hull 近似;scissor 只改变屏幕影响面积,不改变 I/S 的集合计数。

小结:先求集合,再决定渲染

  • per-pixel lighting 把 ambient、shadow 和 lighting pass 乘到每个光源上,batch submission 与 fill rate 都会成为瓶颈。
  • V 表示相机可见对象,L 表示当前光源可见对象,I=V∩L 只服务 lighting pass。
  • S 必须独立计算:光源在视锥内使用 F∩L,光源在视锥外需要 light 与 frustum 的 convex hull 来捕获屏幕外 caster。
  • 对 I 中的对象做屏幕投影并求 tight scissor,可以减少真正被光源 pass 填充的区域;它不是用光源半径生成的粗 box。
  • 集合生成成本、batch 数、scissor 面积和 fill-rate 需要一起测量,不能只看平均 FPS。

练习

问题 1|集合推导 当前相机可见集合 V 有 12 个对象,光源集合 L 有 9 个对象,其中交集 I 有 5 个。光源在视锥外,light-frustum hull 与 L 相交得到 7 个 shadow candidates。当前灯的 shadow 和 lighting 各执行一次,ambient 已完成 14 个 batch。额外需要多少 batch?

问题 2|改 Demo 代码 把实验中的 tight scissor 关闭,保持 V、L、I、S 不变。请分别记录 batch 数、屏幕影响面积和 GPU fill-rate,说明为什么前两者可能不变。

问题 3|独立实现题 一个点光源在视锥外,房间里有 portal visibility 系统但它只支持平面投影。你会如何构造 L 与 S,并如何避免同一对象因 cube 的多个 face 被重复加入?

名词解释

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

batch
一次不被状态切换打断、送入图形硬件的多边形集合。
visible set
从相机视点可见的对象集合 V。
lights set
从当前光源视点可见、可能参与该灯渲染的对象集合 L。
illumination set
V 与 L 的交集 I,只用于当前光源的 lighting pass。
shadow set
会把阴影投进可见区域的 caster 集合 S。
convex hull
包住光源与视锥投影区域、用于筛选外部 shadow caster 的凸体积。
scissor rectangle
限制光源 pass 屏幕写入区域的紧包围矩形。

资料与写作方式声明

本章以GPU Gems 1 · Chapter 15. Managing Visibility for Per-Pixel Lighting公开完整正文核定章节范围、事实坐标与时代语境,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

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

讨论

评论区加载中…