Light field displays (LFDs) require rendering an interlaced image that encodes many view-dependent observations. This multi-view requirement introduces substantial computational overhead, making real-time rendering difficult to achieve. While 3D Gaussian Splatting (3DGS) is efficient for single-view rendering on 2D displays, directly extending it to LFDs is computationally expensive. Moreover, prior accelerations either suffer from GPU inefficiency under spatially incoherent subpixel layouts or rely on computationally heavy multi-plane intermediates. In this paper, we propose CoherentRaster, a 3DGS-based light field rendering framework that performs subpixel-level rasterization. Our method employs Cross-view Coherent Attribute Reuse to eliminate redundant computation across neighboring viewpoints and applies View-coherent Remapping to restore warp-level memory efficiency degraded by the interlaced subpixel layout. Together, CoherentRaster provides an efficient pipeline for real-time, high-quality light field synthesis on consumer-grade hardware. The code is at https://github.com/sgj0402/coherent-raster.
- 光场屏为什么一下子要出几十张画面,以及为什么 3DGS 那一套单视点的快法在这里会失灵。
- 屏上的几何账:一个子像素归哪只眼睛,怎么用一个角度、一个线数和一个错位量算出来。
- GPU 的两条硬约束:线程束要齐步走、显存要连着读,这是后面两堵墙的物理来源。
- 3DGS 的四步管线一步一步走一遍,然后看它在多视点下坏在哪两步。
- 朴素子像素级光栅化:省掉了无用的像素,却把线程打散了。
- 四件属性分两类:哪一件必须逐视点算、哪三件可以每簇算一次,以及键里为什么从「视点 ID」换成「簇 ID」。
- 视角相干重映射:把子像素按视点排好再交给线程;以及最终合成为什么写成一条式里带两个不同下标。
- 一个 64 位整数怎么装下 tile/cluster/depth 三件事,外加两块伪码逐行读完。
- 实验怎么搭:两块真机屏、两套数据集、四条环绕轨迹,以及三个基线为什么是「复现核心思想」而不是「复跑代码」。
- 结果:4K 真实场景进了实时,对全帧 3DGS 提速 7.6 倍,画质几乎不掉。
- 消融:两个部件各管一段、谁也替不了谁,以及簇大小取 8 的由来。
- 换一张缓存只有 6MB 的卡结论是否还成立,以及时间到底花在哪一段。
- 代价与边界:镜面会露出破绽,什么时候不必担心,以及三条不可逾越的前提。
1一块屏要同时喂几十只眼睛,而 3DGS 的快不是为它准备的
1.1 你站在屏前左右挪一步,画面要跟着换
光场显示器(LFD)和普通显示器最直观的区别,是你不用戴任何东西、左右挪一步,看到的画面会跟着变。它给出的不是一张图,而是一段连续的视差:站在左边看到物体的左侧面,站到右边看到右侧面,中间过渡平滑得像真的一样。做到这件事的物理手段是把几十张不同视点的画面,按一张预先算好的图案交错地塞进同一块面板,再用一层微透镜把不同子像素的光折向不同方向;你的两只眼睛落在不同方向上,于是各自只接收到属于自己的那批子像素[§1, p1]。
代价藏在「交错」这两个字里。屏幕的物理分辨率是 2K 或 4K,但每一个物理像素里的三个 RGB 子像素,可能分属三个不同的视点。于是一帧画面要同时把几十到上百个视点都算出来,原文给的两个规格是 4K(3840×2160)下 71 个视点、2K(1440×2560)下 63 个视点,都压在 53° 的水平视锥里[§5.1, p6]。把它说成「渲染一张大图」并不准确:真正要做的是「渲染一堆彼此不同又必须严丝合缝的图」。
作者在开篇把这件事的分量点得很清楚:这几年不戴眼镜的光场屏已经能买到(原文点名 Leia、Looking Glass、Sony 三家),内容侧的供给就成了瓶颈:屏在那里,喂给它的高质量三维内容从哪来[§1, p1]。而辐射场这一族表示(NeRF、3DGS 及其后继)本来是理想的候选:它们能从照片里重建出可自由查询的三维场景,做内容的门槛因此大幅下降[§1, p1]。
问题出在 3DGS 那句人尽皆知的卖点上。「常规 2D 屏上实时、甚至几百帧」这个成绩,是用 tile 光栅化在单视点的前提下换来的[§1, p1]。换到光场屏,前提没了:一帧要出 71 个视点,高斯属性本来该随视点变,而 tile 光栅化的每一步(投影、生成排序键、排序、混合)都是按「一张图」组织的。原文对这件事的措辞很直接:光场屏要求的是「同时合成许多视点相关的图像」,而不是一张图,这个根本差别带来了常规 3DGS 管线没有设计去处理的瓶颈[§1, p1]。
③复算:这两组数字的倍数关系是可以当场算出来的。4K 那一组:24.4 ÷ 2.7 = 9.0 倍;2K 那一组:33.8 ÷ 4.0 = 8.5 倍。也就是说在图上标的这两个场景里,本篇比全帧 3DGS 快了八到九倍。这个倍数与摘要里那个「4K 71 视角真实场景最高 23 FPS」是两套不同的读数:图 1-1 标的是 Mip-NeRF 360 的 Garden 与 Bicycle 两个具体场景,摘要说的是真实场景集合上的上限,两者不矛盾,但也不能互相替代,读到 23 与 24.4 时要知道它们量的不是同一件事。另外注意 2K 的绝对帧率反而不比 4K 高多少(33.8 对 24.4):这不是笔误,而是 2K 那一档用的是竖屏规格(1440×2560,63 视点),它的子像素总数与交错图案与 4K 横屏并不是简单的「分辨率减半」关系[§5.1, p6]。
④边界:这张图只说明「在同一块屏上本篇更快且画质相当」,它不说明任何关于「更正确」的事。图注自己也留了一句余地:实拍照片因为拍摄过程的关系,可能出现轻微的色彩偏移或错位[Fig. 1 图注, p1],也就是说,两张实拍的差异里有一部分是拍摄引入的,不能全部记在方法账上。另一个不在图上的前提是「屏的规格已知且固定」:本篇的所有几何推算都建立在「角度、线数、错位量这三个显示参数已知」之上(第 2 章那三条式),屏一换、参数一变,映射表就要重算。这个前提在第 13 章会被再次提起,因为它是本篇不可迁移的那一部分。
1.2 两堵墙,以及前人各撞了哪一堵
把「为什么慢」拆开,会看到两件性质完全不同的事,而它们恰好是前人两条路线各自漏掉的那一半。
第一堵墙是冗余算力。用最老实的方式做这件事,就是为 71 个视点各跑一遍完整的 3DGS 管线,再把 71 张图交错拼起来。原文把这条路称为传统的光场重建流程,并指出它的代价是计算量与内存占用随视点数线性增长,而交错图本身的高分辨率又把这个负担再放大一层[§1, p1]。关键不在于「线性」这个词,而在于这 71 遍里的绝大多数工作是可以省的,相邻的视点看着几乎同一片表面,它们的协方差、深度、颜色这些属性本来就是接近的,重复计算一遍只是把同样的数算了两遍。
第二堵墙是访存散掉,而且它比第一堵更隐蔽。DirectL 这一线的工作先动的是第一堵墙:它们注意到交错图的每个像素只用到一小部分子像素,于是提出子像素级渲染,不为每个视点渲染整张图,只去算那些真正会出现在最终画面里的子像素[§1, p1;§2.3, p3]。Ji 等人把这个想法接到了 3DGS 上[§1, p1]。算力确实省了,可它引出一个前人不曾面对的新麻烦:现代 GPU 靠「一个线程束里的 32 个线程读连续的显存地址」来打满带宽,而光场屏的交错图案恰恰让相邻子像素属于不同视点;相邻线程于是去读互不相干的高斯属性,线程束级的效率随之显著下降[§1, p1]。
另一条路走的是完全相反的方向。Kim 等人用多平面图像的思路,把场景表示成一组与深度对齐的平面,让相邻视点共享中间渲染结果,从而削掉视点之间的重复计算[§1, p1;§2.3, p3]。这条路把第一堵墙拆得很干净,却带来了一个新的规模瓶颈:要有高保真,平面数往往要上百,而开销随空间分辨率迅速增长;原文直说这条路在消费级硬件上「让实时渲染不切实际」[§1, p1]。换句话说,它是用「换一种更重的中间表示」来换取「少算几遍」,这个交换在分辨率一高就不成立了。
两句话叠起来就是本篇的位置:第一堵墙要靠「属性复用」拆,第二堵墙要靠「重排线程」拆,而这两件事必须同时做、还必须做得足够薄(不能再引入一层随分辨率膨胀的中间表示)。原文把这一点写进了自己的定性里:本方法在精神上与 MPI 那条路相近(都在利用跨视点的相干性),但避免了多平面方法那种沉重的中间表示,于是能在消费级 GPU 上跑实时[§1, p2]。账面结果是:4K 71 视角的真实场景 23 FPS,对全帧 3DGS 流水线 7.6 倍提速,画质只有可忽略的下降;而在缓存受限的 RTX 3090 上,4K 仍能到 6.0 FPS(全帧 3DGS 1.1,Subpixel-3DGS 5.7)[摘要;§5.2, p7;表 4, p13]。
这里要提前说清一件容易被误读的事:本篇不是在做「更快的 3DGS」。它没有改高斯的表示、没有改训练、也没有改渲染方程;它改的是「同一批高斯在多个视点之间怎么组织计算与访存」这件事。这个定位决定了它的适用范围与它的天花板:适用的前提是「视点足够密、簇内足够相干」,因此静态场景可以,镜头之间跳变很大的动态场景不行(第 13 章的三条前提之一);天花板则由「簇内共用一个属性会带来多大的误差」决定,最极端的反例就是高镜面表面(第 13 章图 13-1 给的就是这个)。
2先把屏读懂:哪个子像素归哪只眼睛
拿起一块光场屏,在屏幕上随便挑一个红子像素,问一句「它归哪只眼睛」:这个问题的答案是一张固定的对应表,与屏幕上正在演什么毫无关系。它看起来很琐碎,但它确实是全篇唯一一个不以场景内容为转移的换算:不管你在屏上渲染的是一台鼓还是一个人,这个对应关系都是同一张表。认出这一点很重要,因为第 7 章那张让程序快起来的关键查找表,正是靠「这张对应表与场景无关」这个性质才敢提前算好的[§3.1, p3;§4.3, p6]。原文用的是柱透镜式光场显示器作为贯穿全篇的例子,理由是它「是目前交错式多视点显示器里最被广泛采用的商业实现」[§3.1, p3]。
2.1 柱透镜屏的构造,以及它为什么能「分光」
柱透镜屏的堆叠只有两层:底下是一块普通的 LCD 面板,面板上方贴着一层竖向排列的柱状透镜阵列[§3.1, p3]。每一根柱透镜横跨若干个(而不是一个)子像素,它把这些子像素发出的光折射向不同的方向;于是站在某个具体位置的观察者,只会接收到那批「本来就指定给他」的子像素[§3.1, p3]。
这样一个子像素该定给谁,就变成了一个纯几何问题。原文说,构造这张交错图需要一个「视点索引矩阵」,为每一个子像素指定它应该代表哪个视点,而这些编号就是图 2-1(a) 里那些数字[§3.1, p3]。编号的来历是指定了三个显示参数之后算出来的:光栅倾角 α、光栅线数 L_x(以子像素为单位)、以及透镜与面板之间的错位量 K_offset[§3.1, p3]。
V;(b) 柱透镜光场显示器,透镜阵列把 LCD 面板发出的光按子像素位置折射向特定观察角度,于是每个子像素只对某一个方向可见[Fig. 2 图注, p3]。 ②读法:两幅图要连着看,因为它们讲的是一条链的两端。(a) 端是信号侧:屏幕上那些密密麻麻的颜色块,其实是按一张编号表交错排布的,同一行相邻的两个子像素可能属于相隔很远的两个视点;图上那些数字就是编号本身。(b) 端是光学侧:透镜把这幅交错图翻译成空间中的方向分布,同一束光只往一个方向去,反过来站在那个方向的观察者就只看到属于他的那批子像素。判别这张图看懂了没有,有一个很实用的办法:如果 (a) 里相邻子像素的编号是「乱」的,那就说明为什么 GPU 会在这种屏上遇到访存问题(第 3 章讲的是同一件事的物理面);如果 (b) 里每根透镜下面覆盖了若干个子像素,那就说明为什么「以像素为单位」去想这件事会算错(第 2.2 节那三条式里的因子 3 就是这个原因)。
来源:CoherentRaster: Efficient 3D Gaussian Splatting for Light Field Displays, Fig. 2, p3 · SIGGRAPH Conference Papers '26(ACM, 14 pages, DOI 10.1145/3799902.3811217) · CC BY 4.0(© 2026 Copyright held by the owner/author(s)) · 原样引用,未修改
③推导:这张图里的「编号表」是一个可以被一次性算出来的量,这是本篇能快的第一块地基。推理链是这样的:编号由屏的几何决定(倾角、线数、错位量),与屏幕上要显示什么无关;于是只要屏定了,编号表就定了,可以在渲染任何一帧之前先算好、缓存起来。原文在 §4.3 正是这么用的,那张把「线程序号」映射到「子像素坐标」的查找表 Ψ,因为透镜几何是固定的,所以只预计算一次[§4.3, p6]。这一步的价值在于把「一帧内的随机读」换成了「一次性的顺序写」:付一次预计算的代价,之后每一帧都直接查表。第 7 章会把这条链的收益量出来。
④边界:这张图的机制说明有一个明确的范围。第一,它只讲柱透镜式(竖向排列的柱状透镜),原文在正文里明确说这是「最被广泛采用的商业实现」并把它当作全篇的贯例[§3.1, p3],换成别的分光方式(比如视差屏障或指向背光),(1)–(3) 那三条式的具体形式会变,尽管「编号表可预计算」这个结论仍然成立。第二,图上出现的那些编号是示意值,不是某一台真实设备的实际编号表;实际编号由厂商给的三个参数算出,原文因此在 §5.1 专门交代了它用的两块实机屏与各自的视点数[§5.1, p6]。第三,这张图里每个子像素只有一个编号,也就是一个子像素只服务一只眼睛;这是交错式光场屏的基本约定,也是本篇后面所有「子像素级」措辞的语义基础。
2.2 三条式:从子像素坐标算到视点编号
原文的推导从一个具体问题起步:给一块宽 W、高 H 的 LCD 面板,怎么把一个子像素 (x, y, u) 翻译成它的视点编号?这里的三个坐标分别是列号 x ∈ {0, …, W−1}、行号 y ∈ {0, …, H−1}、以及RGB 通道 u ∈ {0, 1, 2}[§3.1, p3]。
第一步是算它在一个光栅单元内的水平偏移:
这条式子的四项各有各的来历,逐项拆开才算读懂。3x + u 是第一项与第二项合起来的效果:x 是像素列号,一个像素有三个子像素(红绿蓝),所以「第 x 个像素的第 u 个通道」对应整条扫描线上第 3x + u 个子像素;那个因子 3 就是 RGB 子像素布局的直接结果,原文在式 (2) 之后专门用一句话交代了这件事[§3.1, p3]。3y·tan(α) 处理的是倾斜:光栅本身是斜的,相对竖直方向倾斜了 α,所以每往下一行,同一个编号的位置就要多错开 3·tan(α) 个子像素,乘 3 仍然是因为一行里走的是子像素而不是像素。K_offset 是装配误差:透镜阵列与面板贴在一起时不可能完全对齐,这个错位量被折进公式里减掉[§3.1, p3]。
第二步是把「沿整条扫描线的累计偏移」折叠回一个单元之内:
取模这一步容易被当成技术细节滑过去,但它是「周期性」的正式表达。L_x 是一个光栅单元包含多少个子像素;因为同一根柱透镜覆盖的是一段固定长度的子像素序列,同一只眼睛看到的子像素在整条屏上是周期性重复的,所以真正决定「归谁」的只是单元之内的那个相位,而不是累积了多长的距离。取模之后,x_offset 落在 [0, L_x) 里。这也解释了为什么式 (1) 要老老实实把 K_offset 减掉而不是忽略:错位量会整体平移相位,在一个周期性的判据里,平移量的大小直接改变结果。
第三步才轮到视点编号:
(这条式里的两个定界符是照录还原的,判形依据见下方的照录说明。)
这条式讲的是「把相位切成 N 等份」。x_offset / L_x 是单元内的归一化相位(落在 0 到 1 之间),乘上视点总数 N 就把它映射到 0 到 N 之间的一个实数,取下整得到的就是视点编号 j。原文紧接着用一句话封口:N 是视点的总数,把所有 (x, y, u) 上的 j 收集起来,就得到视点索引矩阵 V ∈ Z^{W×H×3},它由显示参数唯一确定[§3.1, p3]。
txexs 的私有码位 U+0016/U+0017,直接读会变成不可见字符,本页按实测还原为 ⌊/⌋(下取整)。判形依据有三条:① 底脚朝向互为镜像,左者的底脚向右伸、右者的底脚向左伸,这正是 ⌊/⌋ 的定义特征,而括号没有底脚;② 同一字体里的括号被实测为弯曲,式 (10) 用的是同字体的 U+0012/U+0013,其墨迹质心自上而下持续漂移、墨宽由 2px 增至 10px,证明「直杆加底脚」不是这个字体的括号画法;③ 语义上必须是下取整,原文说 V 属于整数集 Z^{W×H×3},而 x_offset ∈ [0, L_x) 使 N·x_offset/L_x ∈ [0, N),只有下取整能把它映射成 0 到 N−1 这恰好 N 个整数。取证记录见 选簇与台账.md §〇。③推导:三条式串起来,得到的结论比公式本身更重要。结论一:映射与场景无关。(x, y, u) → j 只用到屏的几何参数(α、L_x、K_offset、N),一个场景相关的量都没有。所以这张编号表可以在任何一帧之前算好,第 7 章那张 Ψ 表就是这个结论的直接兑现[§4.3, p6]。结论二:交错的「乱」是必然的。j 由相位 x_offset 决定,而相位里带着行相关项(3y·tan(α))与模运算,所以相邻的子像素完全可以落到相隔很远的编号上,这就是第 3 章要讲的那堵访存墙的几何来源。结论三:参数一变全表作废。这一条看着像废话,但它是本篇适用范围的边界:屏换型号、装配松动、把屏横过来用,都要重算。原文在 §5.1 给两块实机屏分别指定了视点数(4K 71 个、2K 63 个)[§5.1, p6],本质上就是在交代「参数表长什么样」。
④边界:这三条式在三种情况下会失效或需要修正。第一,子像素几何不只是 RGB 三色。式子里的因子 3 硬编码了「一个像素三个子像素」,如果面板是四子像素(例如加白子)或别的排布,从像素号换算到子像素号的那一项就要改写,u 的取值范围也随之变化。第二,mod 的边界要小心。当 d_offset 恰好是 L_x 的整数倍时,x_offset 取 0 还是取 L_x 会差一个编号,实际实现里必须与厂商给的相位基准对齐,否则整条屏会平移一格。第三,这三条式只解决了「谁归谁」,没有解决「怎么算得快」,它们甚至恰好造成了第二堵墙(相邻子像素不同视点)。接下来的第 3 章要先补上 GPU 那两条硬约束,才能说清为什么「归谁」这件事会变成一个性能问题。
3GPU 的两个硬约束:线程束要齐步走,显存要连着读
GPU 的快,是建立在一个容易被忽略的约定上的:同一组 32 个线程,最好在读同一段连续的显存。这个约定写在原文 §3.2 里,讲的是现代 GPU 的单指令多线程(SIMT)执行模型与内存合并(memory coalescing)[§3.2, p3]。这一章没有一条公式,也没有一张图,但它是全篇第二堵墙的全部依据。读这一章有一个诀窍:把它当成「一条规则加一条后果」。规则是线程必须齐步走,后果是访存必须连续;而光场屏的交错布局恰好同时违反这两条,于是第 5 章讲朴素做法时,那两处「内伤」就有了物理出处。
3.1 线程束:32 个线程绑在一起齐步走
现代 GPU 用的是 SIMT 架构来取得大规模并行[§3.2, p3]。在这个执行模型里,单个线程被编成一组一组,每一组叫一个线程束(warp),在 NVIDIA 的架构上通常是 32 个线程;线程束内部的线程以「锁步(lock-step)」的方式执行同一条指令[§3.2, p3]。
「锁步」这两个字是理解后面一切的关键,它有两面。正面是效率来源:一条指令下发给 32 个线程,控制单元的开销被摊薄了 32 倍,这就是 GPU 能便宜地堆出上万个并行单元的原因。反面是刚性:这 32 个线程必须能在一起做同一件事,如果它们各自要走的路径不同,硬件只能把分支串起来执行(一部分线程先做、另一部分等着),并行度就此掉下去。原文对这件事的总结是:这种架构允许很高的计算吞吐,但对访存模式提出了严格要求,否则效率保不住[§3.2, p3]。
这一条直接预告了本篇的第一个病灶。光场屏的交错图案让相邻子像素属于不同视点,而相机、投影矩阵、以及高斯的视点相关属性都是按视点组织的。于是同一个线程束里的 32 个线程,看起来在做「同一件事」(都在算自己那个子像素的颜色),实际上各自要读的是属于不同视点的一批数据。齐步走在形式上还成立(指令是一样的),但「一起读到同一段东西」这个隐含前提已经被破坏,第 3.2 节讲的正是这件事为什么会变成带宽问题。
3.2 访存:控制器把请求并成最少笔事务
线程束发出访存请求之后,显存控制器会把这些请求当作一批来处理:它按对齐的段(aligned segment)去取数据,并且会做一件事叫内存合并,也就是把同一个线程束里 32 个线程的请求,合并成数量最少的内存事务[§3.2, p3]。
这个优化的生效条件非常具体,只有两条:一个线程束里所有线程访问的地址必须是连续的,而且是按对齐的段组织的,原文把这个状态称为合并访存(coalesced access)[§3.2, p3]。两条都满足时,32 个线程要的数据可能只落在一两笔事务里,带宽利用率接近满格;任意一条被破坏,硬件就只能拆成多笔事务分别去取。
反过来那一面原文也点明了:当访存模式是散的时候,就会出现内存访问发散(memory divergence),控制器不得不发出多笔彼此独立的取数事务;这种碎片化显著抬高延迟、浪费显存带宽,最终导致硬件利用率达不到应有水平[§3.2, p3]。注意这里的措辞是「浪费」而不是「错」,散访存不会算出错误的结果,它只是把一次能搬完的数据拆成了好几次。这一点很重要,因为它决定了本篇的两种解法是互补而不是替代的:属性复用减少的是工作量(第 6 章),重映射改善的是访存模式(第 7 章),一个不能替另一个(第 11 章的消融会把这一点量出来)。
把这两条纪律换成一句可操作的话:在 GPU 上,「32 个线程各自要做的事有多像」这件事,比「它们各自要做的事有多少」更决定性能。这听起来有点反直觉,常规的性能推理总是先数运算量,把访存当成次要因素;但在显存带宽的增速远慢于算力的今天,一次浪费掉的事务比一堆空转的算术更贵。原文之所以要把 §3.2 单列成一个小节来讲这两件事[§3.2, p3],正是因为它全篇的两个部件做的都是同一件事:让 32 个线程要做的事更像一点,复用让同一批数据被更多线程共用(数据更集中),重映射让同一批线程去读同一段数据(地址更集中)。把它们归结成一句话:本篇不是在减少运算,而是在减少「不一样的运算」。
③推导:把这一章的两条约束,套到光场屏的具体情形上,可以直接算出访存会散成什么样。设屏上一个光栅单元覆盖 L_x 个子像素、视点数为 N。如果屏是「每个子像素按光栅顺序依次对应视点」的理想排布,那么一个线程束里的 32 个相邻子像素会落到大约 32·N/L_x 个不同的视点上;而在真实的光栅倾角下,子像素的行相关偏移让这个分布更加不规则(第 2 章式 (1) 里那个 3y·tan(α) 项)。于是问题的量级是:一笔本来可以合并的请求,可能被拆成接近线程数个数的多笔。这解释了两件事:为什么本篇的定标是「提速数倍到十几倍」而不是「几个百分点」(散的代价很大);也解释了为什么光场屏这种布局下,前人「单视点管用」的优化技巧会成批失效。
④边界:这两条约束有两个常见的误用方式,值得提前拆掉。第一,别把它读成「只要访存连续就一定快」。合并访存是带宽侧的必要条件,不是性能的充分条件,第 12 章的图 12-3 会给出一个直接的例子:在 Mip-NeRF 360 的 4K 配置上,本篇把混合阶段的耗时从 67.43 ms 压到 32.41 ms(几乎砍半),可整个流水线的耗时结构里其他阶段的占比反而更显眼了。第二,别把「线程束 32」当成硬编码的常数。原文写的是「通常是 32」,这个数字随厂商与架构变化,也不必然是并行的最小单位(同一线程束内的线程可能被进一步切分成更小的执行单元)。本篇的所有机制都不依赖 32 这个具体数字,它依赖的只是「线程束内的线程绑在一起、且它们的访存会被合并」这两条定性事实,这也是为什么这一章可以不给公式。
最后回到本篇的位置:第 2 章算清了「谁归谁」,这一章讲清了「齐步走 + 连着读」这两条硬件纪律;两者一撞,就撞出了第 5 章那个看起来「已经很聪明」却仍然不够快的朴素做法。
43DGS 的四步管线,以及它在多视角下的两处内伤
一组各向异性的三维高斯,四个参数,四步渲染:3DGS 的全套机器就这么多。原文在 §3.3 用一个小节把它的表示与渲染流程复述干净,又在 §4 开头把「光场屏版本」的对应关系铺开[§3.3, p4;§4, p4]。要看清本篇改了什么,得先把原来那四步走一遍:这一章做三件事,把四步逐项讲清、把「单视点为什么顺」这件事点破、再把「换成 71 个视点之后坏在哪两步」摆出来,最后一步就是本篇两个部件的靶子。
4.1 场景是什么:一堆带方向的椭球
3DGS 把一个场景建模成一组各向异性的三维高斯:G = {G_i},其中每一个高斯 G_i 由一个三维均值 𝝁_i、一个协方差矩阵 𝚺_i、一个不透明度 o_i、以及一组球谐(SH)系数 h_i 定义[§3.3, p4]。这四件的分工值得点一下:均值决定它在哪里,协方差决定它长什么形状(各向异性就体现在这里),不透明度决定它有多实,球谐决定它从不同方向看过去是什么颜色,最后这一件正是「视点相关」的源头。
渲染走的是基于 tile 的光栅化:像平面被切成固定大小的矩形块(原文举的例子是 16×16 像素),块与块之间并行处理;渲染一个视点分成四步,投影、生成键、排序、不透明度混合[§3.3, p4]。四步各自的职责是:每个高斯被投影到像平面、并算出它覆盖了哪些 tile;每个「高斯–tile 对」拿到一个键;这些对按深度排序,得到每个 tile 的一份从前到后的飞溅序列,原文把它叫作高斯列表;最后按深度顺序做不透明度混合,得到最终的像素值[§3.3, p4]。
键里装的是「tile 编号 + 视空间深度」这两件事[§3.3, p4]。注意这里已经埋下了本篇改动的那一颗种子:键决定排序,排序决定访存顺序,而访存的连续性恰好是第 3 章那条纪律。原始 3DGS 里,每个像素的混合在一个独立线程里完成,线程到像素的映射天然满足内存合并,所以 GPU 跑得很顺[§3.3, p4]。
4.2 单视点为什么顺:一次投影只服务一张图
把上面这套流程放进「一帧一个视点」的场景,它在结构上是自洽的。投影出来的属性只有一份(因为这个视点只有一个),键里的视点信息是多余的,排序后每个 tile 的那份高斯列表被连续地写进显存,混合阶段每个线程顺着列表往前走,每一步的访存都是连续的。原文对这一段的描述是:这套映射固有地满足内存合并,从而保证高效的 GPU 执行[§3.3, p4]。
问题从「一帧要出 71 个视点」开始。原文对这条转折的表述是:这套流程对单视点渲染是高效的,但把它扩展到多视点渲染会显著抬高计算成本,因为「高斯–tile 对」的数量随视点数成比例增长[§3.3, p4]。这句话里藏着一个不那么显眼的断言:增加的这部分成本是成比例的,也就是没有免费的部分。为什么没有?因为投影、键、排序、混合这四步在原始设计里全都是「按视点」组织的,视点从 1 个变成 71 个,这四步的工作量基本上各涨 71 倍(具体倍数取决于实现的批量策略),而其中很大一部分算出来的是几乎相同的数。
③推导:这张图给出的两阶段划分,可以用「改的是哪个量」来验证。前半段的复用改的是每帧要算多少次投影:把四件属性里三件的计算频率从「每个视点一次」降到「每个簇一次」,于是这部分工作量从正比于 N 变成正比于 K(K 是簇数且 K < N)[§4.2, p4]。后半段的重映射改的是每帧要发多少笔访存事务:它不改变任何计算结果,只改变线程与数据的对应顺序,从而把散的地址重新排成连续的[§4.3, p6]。两个量分属「算力」与「带宽」两侧,所以它们的收益应当是可以叠加的,这正是第 11 章要验证的事:图 11-1 的四行里,「只留复用」与「只留重映射」各自都赢不了合起来,合起来才拿到最好的那个数[表 3, p8]。
④边界:这张图有三处容易被读快的地方。第一,别把「复用」理解成「近似」。(b) 里被共用的那三件属性确实与原值有差(它们在簇内并不是严格不变的),这一点的可见后果在第 13 章图 13-1 里给出,高镜面表面上会露破绽。原文的态度是:靠商用屏的高视角密度、加上球谐天然衰减镜面反射的高频成分,这个差别在通常情况下不构成明显的感知退化,但在极端条件下可观测[§C.3, p13]。第二,别把「重映射」当成缓存优化。(c) 做的是重排线程与子像素的对应关系,它在语义上是恒等的(输出的每个子像素还是那个子像素的颜色),所以它不会改变画质,消融表里「只去掉重映射」那一行掉的是帧率,不是指标[表 3, p8]。第三,图里的三段划分对应的是「代码里的三个阶段」而不是「三次显存往返」。原文在自己的实现里把这套流程落成两块伪码(第 8 章逐行讲),投影与生成键共用同一轮并行遍历,所以三段的边界不等于 kernel 的边界[§B.2, p12]。
5直接按子像素光栅化:省掉了无用的像素,却打散了线程
既然交错图里每个像素只用到一小部分子像素,为什么还要为每个视点渲染整张图? 这个问题一问出来,下一个做法几乎自己就跳出来了。前三章已经把该铺的都铺好了:屏的几何账(第 2 章)、GPU 的两条纪律(第 3 章)、3DGS 原本那套按视点组织起来的流程(第 4 章)。把这三件事合起来,会自然而然地想到一个「聪明的做法」,而原文做的是把这个做法写清楚、然后指出它为什么还不够。 这个做法就是子像素级光栅化。
5.1 把光栅化的粒度收到子像素
既然交错图里每个像素只用到一小部分子像素,为什么还要为每个视点渲染整张图? 顺着这个想法,本篇把 3DGS 那套 tile 光栅化直接降到子像素这个粒度:光场屏在子像素层级上交错多个视点,每一个子像素 (x, y, u) 被分配给一个视点索引 V[x, y, u];于是光栅化要算的对象从「一个视点」扩大成「同时多个视点」,从「每个像素」细到「每个子像素」[§4.1, p4]。
这一步的收益是直接的:不该出现的子像素根本不算,省掉的是全帧多视点渲染里那些「算完就被丢掉」的计算。但原文紧接着把它的代价写了出来,原文说,这套扩展要按四步铺开(投影、生成键、排序、混合),而这套看起来很直白的子像素级管线,暴露出两处严重限制吞吐的效率问题:冗余的逐视点求值,以及交错子像素布局下的非合并访存[§4.1, p4]。
照录一下原文对这两处内伤的定性:第一处,投影这一阶段里,N 个视点各要一份屏幕空间属性,同一批高斯被反复投影;第二处,混合阶段里,线程到子像素的映射不再连续,因为相邻子像素属于不同视点,第 3 章那条「齐步走 + 连着读」的纪律在这里失效[§4.1, p4;§3.2, p3]。注意这两处的性质完全不同:前者是多算了(算力侧),后者是读散了(带宽侧),正因为不同,所以需要两个部件分别对付,这就是第 6 章与第 7 章的分工。
把「冗余」这件事算成具体的数:以本篇的 4K 配置为例,71 个视点意味着同一批高斯要被投影 71 次、按视点各生成一组键;而在这条测试轨迹上,相邻视点看到的几乎是同一片表面。换句话说,这份代价里真正必要的部分,只是「每个视点确实看到了略有不同的东西」,而不是「每个视点都要从头重算一遍」。 原文在 §4.2 的切法比这更细,它没有整体地复用,而是先问一句:这四件属性里,哪几件在邻近视点之间是真的变了[§4.2, p4]。「先分类、再复用」这个顺序是整篇方法的关键:如果反过来(先复用、再看误差),就会在均值那里直接出几何错误,因为均值决定 tile 覆盖(第 6 章会展开)。
5.2 式 (4):投影要产出哪四件,以及它凭什么贵
在讲清内伤之前,先把投影这一步的产出摆出来。对每一个视点 v_j,每一个高斯 G_i 会被投影到像平面上,一并得到四件东西:
(原文把这一件与它的三件同伴 𝚺2D_{i,j}、d_{i,j}、c_{i,j} 写成一个四元组,共用同一个投影算子 Π;上式是它的第一件。)
这四件的名字与含义是:二维均值 𝝁2D_{i,j}、二维协方差 𝚺2D_{i,j}、深度 d_{i,j}、以及视点相关颜色 c_{i,j};式中的 Π(·) 是这个投影算子;原文随后补一句:这为每个高斯产出 N 组屏幕空间属性,每个视点一组[§4.1, p4]。
「每个视点一组」这五个字,就是第一处内伤的全部量化依据。把它摊开算一算就明白贵在哪:设场景里有 M 个高斯、帧里有 N 个视点,这一步要产出 M × N 组四元组。以本篇的实测场景为例,4K 配置是 71 个视点,Mip-NeRF 360 上 4K 的「高斯–tile 对」数量是 551.5 百万[表 6, p14],虽然这个数不是 M × N 本身(它含 tile 覆盖的重复计数),但它给出量级感:这套东西是百亿级到十亿级的中间量,而它们必须被写出来、再被读回去做排序。原文在 §4.2 对这一步的收益给了明确的量化方向:把键与属性从「逐视点」改成「逐簇」,「高斯–tile 对」的数量随之大幅下降[§4.2, p5]。
③推导:这条式还有一个不显眼但很重要的信息,四件属性里哪几件其实可以省。先用一个思想实验把界限找出来:投影的本质是把三维椭球投到像平面上。这一步真正随视点移动的是「投到哪里」,也就是二维均值这个点;而「投成什么形状、离你多远、朝你的一面是什么颜色」这些东西,在邻近视点之间变化要平缓得多。为什么平缓?因为它们主要由局部表面朝向与着色决定:视点挪一小步,椭球的朝向基本没变、透视尺度基本没变、球谐查表的方向基本没变,所以这三件的变化是渐进的[§4.2, p4]。唯独均值不行,原文在这一段的措辞是:二维均值随视点变化明显,因为它就是三维椭球中心的投影点,哪怕视点稍微挪一点,投影中心也会在像平面上移动,而这直接影响 tile 归属[§4.2, p4]。「影响 tile 归属」这一句是关键:形状、深度、颜色的误差是数值上的小偏差,而均值的误差会让一个高斯被塞进错误的 tile,或者漏掉一个本该覆盖的 tile,那是几何错误。于是四件属性被这条线切成了两类,第 6 章要做的正是把这个切分写成公式。
④边界:子像素级光栅化这条路有三处边界,前两处原文自己写了,第三处需要读者自己补上。第一处是「省掉的只是像素,不是视点」:不渲染整张图,不等于不用为每个视点算属性;式 (4) 里那个 N 依然在,所以这条路只解决了「算错地方」(算了不会出现的像素),没有解决「算重了」(同一个高斯在相邻视点被重复投影)[§4.1, p4]。第二处是它让访存问题变严重而不是变轻:全帧渲染时,「线程到像素」的映射是连续的(原始 3DGS 的固有性质),而按子像素采样之后这个连续性被打破了,原文在 §2.3 对这一点的定性是:子像素级渲染破坏了光栅化管线里的空间局部性,导致相邻线程去访问互不相干的视点相关高斯属性,从而降低内存合并与线程束级效率[§2.3, p3]。第三处是「LFD 专属」这个限定:整套思路建立在「交错图只用到一部分子像素」这个前提上,换成普通 2D 屏就没有这个前提,因此这一路的加速对常规渲染毫无意义,它是一套为光场屏量身定制的管线。
6四个属性分两类:变化快的逐视点算,变化慢的每簇算一次
投影要为每个视点产出四件属性,而其中只有一件在邻近视点之间真的变了。第 5 章结尾留下的这个思想实验,把它正式化就是本篇的第一个部件:跨视角相干属性复用。原文对它的定性是消除逐视点的冗余计算,靠的是复用那些在空间相邻视点上变化平滑的投影高斯属性[§4.2, p4]。这一章要做三件事:把「哪一件不可复用」写成公式、给它配上一个「视点簇」的结构、再一路推到排序键上。
6.1 把视点分组:一个簇一个代表
要「复用」,先得有「可以一起用」的一组东西。原文的做法是把整个视点集合均匀地切成 K 个互不相交的簇:V = {v_i} 分成 {V_0, …, V_{K−1}},其中 K < N,并且为每个簇 V_k 选一个几何中心视点 v′_k 作为它的代表[§4.2, p4]。「几何中心」这个措辞是全文里最容易被读快的地方,它不是「随便挑一个」,而是按相机在环绕轨迹上的位置取中位:补充材料 B.1 把这件事写得很具体,相机沿观察轨迹顺序编号、按大小 |V_k| 切成连续簇,簇内代表取「中位索引」那台相机,也就是局部下标 ⌊|V_k|/2⌋ 那一个[§B.1, p12]。
为什么必须是中位而不是端点?因为复用的误差取决于「簇内视点离代表有多远」。取端点,簇另一头的视点就离代表差了整簇的跨度;取中位,最远的视点最多差半簇,误差的上界直接减半。这也解释了原文为什么要把「簇大小」当成一个可调的超参数去扫(第 11 章的图 11-2 与图 11-1:|V_k| = 2/4/8/16/18 各有读数),以及为什么最后默认取 8[§5.1, p6;表 3, p8]。
还有一处工程细节只在补充材料里交代,但值得记下来:当视点数 N 不能被簇大小整除时,原文的处理是「把最后一台相机复制一份补齐」,而不是丢掉余数[§B.1, p12]。这个选择有代价:被复制的那台相机会在统计上被算两次;但它避免了「最后一簇尺寸不一致」带来的分支,在一个要求所有线程齐步走的 kernel 里,均匀比精确更值钱。从这里也能看出本篇的工程取向:它宁可接受一点点冗余,也不愿意让数据布局出现不规则的尾巴。
6.2 式 (5) 与式 (6):一对式的切分
切分的正式形式是两条式,它们必须成对读。第一条留下那个不可复用的:
第二条拿走其余三件:
把两条式并排看,差别只在两处下标:式 (5) 里是 j(视点下标),式 (6) 里是 k(簇下标);对应的自变量也从 v_j 换成了 v′_k。这个「一个字母的差别」就是整个部件的全部内容:均值逐视点算(M × N 次),三件同伴逐簇算(M × K 次)。
式 (5) 为什么单独成条,原文给了很扎实的理由:二维均值随视点变化明显,因为它就是三维椭球中心的投影点;视点哪怕只挪一点点,这个中心也会在像平面上移动,而这直接决定它落在哪些 tile 里[§4.2, p4]。原文为此专门写了一句话解释动机:为了避免「tile 覆盖不正确」这类几何瑕疵,所以均值为每个视点独立计算[§4.2, p4]。「tile 覆盖不正确」这五个字是要害,均值一旦复用错了,错的不是颜色深浅,而是这个高斯根本没被算进去、或者算进了不该算的 tile。
式 (6) 的三件为什么可以共用簇代表,原文的解释依次落在「表面朝向与着色」上:它们主要取决于局部表面朝向与着色,在视点小位移下变化是渐进的;原文的措辞是「变化要平滑得多」(vary much more smoothly)[§4.2, p4]。把这三件分开看会更清楚:协方差是椭球在像平面上的投影形状,由椭球朝向与视点方向共同决定,视点小位移下透视变化很小;深度是中心到相机的距离,在环绕轨迹上近乎线性地变;颜色是球谐在这个方向上的取值,本身就是一个光滑函数。三件都满足「可微且变化率有界」,这就是可复用的数学依据。
式 (7) 把切分的好处一路传到了排序键上:
键里最大的变化是「视点 ID 换成了簇 ID」。原文对这一段的表述是:因为键的数量相比逐视点生成键大幅减少,整体排序工作量随之显著下降,带来可观的计算节省[§4.2, p6]。把这句话量化:逐视点时键的数量随「高斯 × 视点 × tile」增长,逐簇时变成「高斯 × 簇 × tile」,降幅就是 N/K。以本篇的两个非默认配置为参照,N=63 且 K=16 时降了约 3.9 倍(2K 竖屏配置),N=71 且 K=18 时降了约 3.9 倍(4K 横屏配置)[表 1 图注, p7],注意这两组参数不是随便挑的,16×4 = 64 刚好盖住 63、18×4 = 72 刚好盖住 71,也就是为了让簇均匀整除而专门选的簇大小[表 1 图注, p7]。
6.3 复用的代价:可能多塞高斯,但贡献趋零
复用不是免费的,原文在 Discussion 段把账算得很清楚,这一段值得逐句读。代价是什么:把一批高斯按簇共用,会偶尔把一个高斯分配到某个 tile 里,而它在该簇内某些视点上其实并不可见[§4.2, p6]。为什么会出现:因为「一个高斯覆盖哪些 tile」是用簇代表视点算出来的,代表看得见的 tile,簇里别的视点未必看得见。换句话说,这个代价是「多算了」,不是「算错了」,多出来的高斯会被算进去,但它们的贡献本该趋近于零。
为什么贡献趋零,原文给了两层理由,一层在几何、一层在最终混合。几何那一层是:这些多出来的高斯,在真正被处理时,会按它自己的均值与协方差、以及当前子像素的坐标精确求值[§4.2, p6],也就是说式 (10) 里那个二次型会把它压下去(离得远的子像素,指数项衰减很快)。混合那一层是:多余的高斯不透明度可忽略,于是被自然而然地处理掉,高保真的渲染质量因此保住了[§4.2, p6]。原文最后给出结论:减少排序开销带来的性能收益,显著超过这份代价[§4.2, p6]。这个论断不是修辞,它在第 11 章被兑现:消融表里「去掉复用」那一行的帧率掉得很明显,说明不复用带来的算力损失确实大于复用带来的多余高斯开销[表 3, p8]。
③复算:复用的收益可以用「配对数量」这个指标直接读出来,第 12 章的两张表给了逐行的数。先看 Mip-NeRF 360 的 4K 那一档[表 6, p14]:「复用与重映射都去掉」时,配对数(#Pairs)是 551.5M、峰值显存 18.12GB;只留复用(去掉重映射)时,配对数降到 129.7M、显存降到 5.01GB。两项都降了 4.25 倍(551.5 ÷ 129.7 = 4.25;18.12 ÷ 5.01 = 3.62)。再看 Synthetic Blender 的 4K 那一档[表 5, p14]:配对数从 134.3M 降到 27.5M,也是 4.88 倍。这两个倍数与 N/K 的量级完全对得上(4K 是 71 个视点,默认 |V_k| = 8 时 N/K ≈ 8.9;实测 4.25–4.88 倍,差额来自 tile 覆盖的重复计数与簇边界的对齐)。换句话说,「复用」这一招省下来的远不止百分之几:整个中间量级被砍掉了四分之三以上。
④边界:复用这一招有三条边界,一条原文写了,两条需要从它的机制里推出来。第一条(原文写了):高镜面表面会露破绽。原文把它放进 Limitations 与补充材料 C.3:因为复用的前提是「簇内局部一致」,所以具有高频镜面效果的场景可能因此出现视觉瑕疵[§6, p8;§C.3, p13]。具体是什么机制、在什么条件下不必担心,留给第 13 章连同图 13-1 一起讲。第二条:K 不能取太小。复用比例越高、簇越大,代表视点离簇内成员就越远,式 (6) 那三件的偏差就越大;图 10-1 里 |V_k| = 16 已经把 4K 那一档的 PSNR 压到 37.50 dB(|V_k| = 2 时是 53.35 dB),说明这条边界是真实存在的[表 1, p7]。第三条:簇必须是「几何上连续」的。原文切簇的方式是沿观察轨迹顺序编号后再切连续段[§B.1, p12],这个前提不能省,如果把轨迹上相隔很远的两个视点凑进一个簇(比如按亮度或按内容聚类),「相邻视点看着相近」这个前提就不成立了,式 (6) 的复用会直接给出错的结果。这三条合起来,就是「跨视角相干」这四个字的适用条件。
7让同一个线程束里的子像素属于相近视点
第 6 章把「算力」那一侧的问题解决掉了,可是第 3 章那堵访存墙还立着。这一章要做的,是把线程与子像素的对应关系重新排一遍,让同一个线程束里的线程对着同一只或邻近的眼睛取数。原文对这个部件的定性是:它通过重排 GPU 线程到子像素的分配方式,来解决子像素级光栅化中的非合并访存问题[§4.3, p6]。关键点是:它不改变任何计算结果,它只改顺序。
7.1 为什么不按光栅顺序发线程
先看清原来那个默认做法错在哪。原文的表述是:传统的 tile 光栅化管线假定「空间上相邻的子像素共享相似的数据需求」[§4.3, p6]。这个假定在普通 2D 屏上是对的:屏幕上一小块区域的像素,看的是场景里很小的一块,它们要的高斯、要的深度、要的颜色都高度重合,所以按行主序发线程,一个线程束恰好落在一小片相干的数据上。
但在光场屏上,这个假定当场失效。原文讲得很直接:透镜的几何把视点交错地铺在面板上(就是第 2 章图 2-1 那件事),结果是即便线程被映射到同一个 tile 里,它们的视点索引也可能发散,必须去访问按「tile 与簇」组织的不同高斯列表[§4.3, p6]。注意这里牵出了第 6 章的安排:高斯列表是按 tile、再按簇组织起来的(式 (7) 那个键排出来的),所以「视点不同」并不直接等于「数据不同」,真正决定数据在哪一段的是簇 ID。这给了本篇一个可乘之机:只要让相邻线程的视点索引相近,它们的簇 ID 就基本相同,落在同一段连续内存里。
做法因此很朴素:在把子像素分配给线程之前,先按视点把它们分组,从而确保线程束内的线程访问同一个或邻近的高斯列表,显著降低带宽开销[§4.3, p6]。
7.2 式 (8):视点单调性
「按视点分组」这件事需要用一条不等式说清楚。对每个 tile,原文先把子像素坐标 x 按行主序线性化,再按它们的视点索引 V[x] 排序,把重排后的索引存进一张查找表 Ψ;因为透镜几何固定,这张表只预计算一次;渲染时每个线程通过这张表去取自己该处理的子像素,从而在线程束内强制出视点单调性[§4.3, p6]。形式上就是这一条:
这条式要逐项读:r 是线程序号,Ψ(r) 是这个线程该处理的子像素坐标,V[·] 取的是该子像素的视点索引。整条式说的是:线程序号增大时,它处理的子像素的视点索引不会往回走。 原文用一句话点出它的后果:这种排序提高了「相邻线程处理同一或相邻视点的子像素」的可能性,也就意味着它们的簇 ID 相同或相邻;而由于高斯列表先按 tile、再按簇组织,线程于是自然地访问到同一个或邻近的列表,合并访存由此恢复[§4.3, p6]。
这张 Ψ 表为什么能提前算,值得单独说一句。它算的是「谁处理哪个子像素」,这件事与场景完全无关,它只依赖第 2 章那三条式给出的 V 矩阵,而 V 由显示参数唯一确定。所以预计算成立,代价只付一次。 原文对这件事的措辞是:因为透镜几何是固定的,所以预先计算一次即可[§4.3, p6]。注意这与第 6 章的复用是两种不同性质的「省」:复用省的是每帧的重复计算,预计算省的是每帧的重复排序。
7.3 式 (9) 与式 (10):合成,以及一条式里两个下标
重排完之后,混合这一步要做的事就回到教科书了。原文给了最终的合成式:
这是标准的从前到后 alpha 合成。逐项认一下:N 是列表里有序的高斯集合;c^{(u)}_{i,k} 是第 i 个高斯在通道 u 上的颜色(注意下标是 k,它是复用来的那一份);α_i 是该高斯的有效不透明度;那个连乘是「前面已有多少透光率」,∏(1 − α_p) 从 p = 1 连乘到 i − 1,含义是前 i − 1 个高斯都没把光挡住的那一部分比例。终止条件原文也交代了:当累计不透明度饱和、或者列表走完时终止;得到的强度 C(x̂) 被直接写到光场图 I_LF 的对应子像素位置上,不需要额外的后处理[§4.3, p6]。
而 α_i 的算法就是全篇最该盯住的一行:
这条式里藏着一个「两个下标」的结构,它就是「跨视角属性复用」的全部含义。看清楚:二次型里出现的两次 𝝁2D 都带 j(视点下标,来自式 (5),逐视点算的那一件),而协方差 𝚺2D 带 k(簇下标,来自式 (6),复用来的那一件)。一条式里两个下标不同,说明「同一个高斯的中心位置」和「它长成什么形状」来自两个不同粒度的计算,前者每只眼睛各算一遍,后者每一簇算一遍。这也是全篇最容易被读漏的一处:如果只记住「属性复用」这四个字,很容易以为被复用的是整块属性;实际上被复用的只有那三件,均值一直是逐视点的,而这条式正好把两者摆在同一行里。
逐项再认一遍:o_i 是学出来的不透明度(与视点无关,属于高斯自身的参数);指数里那个二次型是以二维高斯密度衰减,子像素离投影中心越远,贡献越小;−½ 是标准正态密度里的常数;因为协方差用的是簇代表那一份,它的逆矩阵也是簇级别的,也就是说同一个高斯在整簇视点里共用同一个「胖瘦与朝向」。
③复算:这一章的内容可以直接对着第 12 章的图 12-2/图 12-3 验算「重映射到底省在哪一段」。取 Mip-NeRF 360 的 4K 那一档做例子[表 6, p14]:两个部件都去掉时,四阶段耗时是 Proj. 1.92 / KeyGen 9.04 / Sort 0.26 / Blend 67.43 毫秒;只留复用(去掉重映射)时是 1.92 / 9.05 / 0.26 / 48.29;只留重映射(去掉复用)时是 0.34 / 4.87 / 0.15 / 61.03;两个都留时是 0.35 / 4.98 / 0.15 / 32.41。四项对比读出来的结论有三条:第一,重映射专攻 Blend,从 67.43 降到 48.29(只留重映射时是降到 61.03,效果小得多),两者合起来才把它压到 32.41;第二,复用专攻 Proj 与 KeyGen,从 1.92 / 9.04 降到 0.34 / 4.87,降幅分别是 5.6 倍与 1.9 倍;第三,两项的收益确实可以叠加,混合阶段的 48.29 + 61.03 → 32.41 这种叠加不是线性相加能解释的,说明「数据放对了地方」之后,访存本身的效率也跟着变了。
④边界:重映射这一招的边界比复用硬得多,因为它踩在一个物理事实上。第一,它不改画质,但也不改算法复杂度,它是一场置换,输出逐子像素与重排前完全一致,所以图 11-1 里「去掉重映射」那一行指标一模一样、只有帧率掉[表 3, p8]。第二,它换来的是散写:把子像素按视点重新排队之后,最终写下颜色的时候地址反而是散的。原文在 §C.1 专门回应了这件事:虽然视角相干重映射在存储最终子像素颜色时引入了散写,但它只在每个子像素完成 alpha 混合之后发生一次;反观被复用的那些高斯属性,是在混合循环里被反复读取的,于是「合并这些频繁的读」的收益,超过了那一次散写的开销[§C.1, p12]。第三,Ψ 表是「屏专用」的:它由透镜几何决定,所以它与第 2 章那三条式绑在同一个前提上,屏一换、参数一变,表就要重算。这三条合起来,就是「视角相干」这四个字的代价表。
8一个 64 位整数装下三件事,外加两块伪码
一个 64 位整数,能同时装下三件事。前两章讲的是「怎么想」,这一章讲原文「怎么落地」:原文把实现细节放进了补充材料 B,B.1 讲视点分簇的具体做法(第 6 章已经引用过),B.2 用两块伪码把整条光栅化管线逐行列了出来[§B.1, p12;§B.2, p12–p13]。这一章先把那个 64 位键拆开,再把两块伪码逐行读完。本篇没有图元落在这一章,所以它全靠这两样东西撑着,必须读扎实。
8.1 式 (11):把三层排序压进一次整数排序
第 6 章的式 (7) 说了键里装什么(tile、簇、深度),式 (11) 说这三样怎么装进一个数:
逐项拆开:≪ 是左移,| 是按位或。三段的分配是:顶层是 tile 编号 t,它被左移 32 + Bit_K 位,也就是占住最高的那一段;中间是簇编号 k,左移 32 位,占住中间 32 位;底层是深度 d_{i,k},占住最低的 32 位;Bit_K 是为簇编号预留的位宽(因此 t 的起始位置随簇数的位宽自动让位)。验证一下位数:32 + Bit_K 位的 tile + Bit_K 位的簇 + 32 位的深度 = 总共 64 位,正好是一个 64 位整数。
这个排布的妙处要结合「排序」这件事才看得出来。原文随后给了一段解释:把 tile ID 放在最高位、簇 ID 与深度的整数表示依次跟在后面,于是高斯会「先按空间位置、再按簇、最后按深度」被排序[§B.2, p12]。也就是说:一次普通的整数升序排序,同时完成了三层分组。 为什么要这么做?因为后面混合阶段要按「tile → 簇」去取一段连续的高斯列表(算法 2 里那个 IdentifyGaussianList),而这个「连续」正是靠位布局保证的,键相同的段自然相邻,排序后它们就落在一起。这一步是「排序」与「访存连续性」之间的接口:原文在 §4.2 说过,排序把高斯重组成连续列表,每个列表对应一个唯一的「tile–簇」对,而位结构使列表内的高斯天然按深度有序[§4.2, p5]。
③推导:这套位布局隐含两个约束,原文没有展开,但工程上必须满足。第一个是位宽预算:32 + Bit_K 位的 tile 编号意味着 tile 数不能超过 2^{32 + Bit_K},而 32 位的深度意味着深度必须被量化成 32 位整数,这一点在算法 1 的 GenerateKeys(𝝁2D, 𝚺2D, d, T, r) 调用里能看出端倪(深度是作为参数传进去的整数,不是浮点)[§B.2, p12]。第二个是深度的整数化会不会损失排序正确性:把浮点深度截成 32 位整数,只要截断方向一致(比如统一向下取整),相对次序基本保持;而 32 位的分辨率对于「同一 tile 内的高斯前后关系」来说远远够用。原文没有讨论这两点,是因为它们在前人的 3DGS 实现里早已是通行做法,本篇的创新不在位宽本身,而在把「簇」这一层插进了原有的「tile + 深度」两段里。
③复算:把本篇两个分辨率的实际位宽算出来,可以看到余量有多大。2K 配置是 1440×2560,tile 用的是 16×16,所以横向 90 块、纵向 160 块,共 14400 个 tile,它的二进制长度是 14 位;4K 配置是 3840×2160,同样切 16×16,横向 240、纵向 135,共 32400 个 tile,需要 15 位。而 t 在键里占的是 32 + Bit_K 位,只要 Bit_K 取 1,t 就已经有 33 位可用,对一万五到三万多个 tile 来说余量是十几个数量级。再看簇那一侧:本篇的 K 最大也不超过「视点数 ÷ 簇大小」的量级(4K 是 71 个视点,|V_k| = 8 时不到 10 个簇),Bit_K 只需 4 位就够用。换句话说,这套 64 位布局里真正紧张的那一段是深度的 32 位,而 32 位整数对「同一 tile 内的高斯前后排序」来说远远够用。这也解释了为什么原文可以把它当成一句顺带交代的实现细节:位宽在这个规模上根本不是瓶颈,真正的设计压力在「把簇插进去」这件事上。
8.2 算法 1:主流程的四阶段
原文的算法 1 把整条管线列成一块伪码,标题下面标了 (main)[§B.2, p12]。逐段读完:
ALGORITHM 1: CoherentRaster rasterization pipeline
(main)
Input: M: # Gaussians; N: # views; K: # clusters
𝝁, 𝚺, o, SH: 3D means, covariances, opacities, SH coefficients
V = {v_j} (j = 0…N−1): views; V′ = {v′_k} (k = 0…K−1): cluster centers
T: tile IDs; V: viewpoint index matrix
Output: I_LF: interlaced light-field image
Function Rasterization(M, 𝝁, 𝚺, o, SH, V, V′, T):
// Stage 1: Projection with Cross-view Coherent Attribute Reuse
𝝁2D ← ∅
𝚺2D, d, c ← ∅
for r ← 0 to M × N − 1 do // in parallel over M × N threads
i, j ← GetIndex(r)
𝝁2D_{i,j} ← Π_mean(v_j; 𝝁_i)
end
for r ← 0 to M × K − 1 do // in parallel over M × K threads; reused across views
i, k ← GetIndex(r)
𝚺2D_{i,k}, d_{i,k}, c_{i,k} ← Π_cov, depth, SH(v′_k; 𝚺_i, SH_i)
end
// Stage 2: Key Generation with Cross-view Coherent Attribute Reuse
P ← ∅
for r ← 0 to M × K − 1 do // in parallel over M × K threads
K_{i,k} ← GenerateKeys(𝝁2D, 𝚺2D, d, T, r)
P ← SaveToBuffer(K_{i,k})
end
// Stage 3: Sort by (tile t, cluster k, depth d)
L ← Sort(P)
// Stage 4: Alpha Blending with View-coherent Remapping
I_LF ← 0
for r ← 0 to W × H × 3 − 1 do // in parallel over pixel threads
I_LF ← Alpha-Blend(I_LF, V, L, T, 𝝁2D, 𝚺2D, c, o, r)
end
return I_LF
这块伪码里最该盯的是那两轮遍历的循环上界。第一轮的界是 M × N − 1(M 个高斯 × N 个视点),它只做一件事:算均值的投影 𝝁2D_{i,j} ← Π_mean(v_j; 𝝁_i)。第二轮的界是 M × K − 1(M 个高斯 × K 个簇),它一口气算掉其余三件:𝚺2D_{i,k}, d_{i,k}, c_{i,k} ← Π_cov, depth, SH(v′_k; 𝚺_i, SH_i)。两轮的注释也把意图写明了:第一轮的注释是「in parallel over M × N threads」,第二轮的注释是「in parallel over M × K threads; reused across views」[§B.2, p12]。这是式 (5) 与式 (6) 在代码里的样子:N 那一轮留下不可复用的均值,K 那一轮拿走可复用的三件,并且第二轮算出来的东西会被所有视点共享。
还有两处细节值得单独标出来。第一,第二轮里投影的输入是 𝚺_i, SH_i 而不是完整的 G_i,它只需要协方差与球谐系数,不碰均值(均值已经在第一轮算完了)[§B.2, p12]。这说明复用的边界在实现上是精确切开的:不是「复用一整个高斯的投影结果」,而是「按需取属性」。第二,Stage 3 只有一行 L ← Sort(P),而它的注释把排序口径写全了:「by (tile t, cluster k, depth d)」[§B.2, p12],这正是式 (11) 那个位布局要保证的次序。
8.3 算法 2:两个子程序,以及它的退路
算法 2 的标题下面标的是 (subroutines),它把两个函数展开[§B.2, p13]。两块伪码合起来顺序读完:
ALGORITHM 2: CoherentRaster rasterization pipeline
(subroutines)
Function GenerateKeys(𝝁2D, 𝚺2D, d, T, r):
i, k ← GetIndex(r)
T_{i,k} ← ∅ // Merge tiles over all views in cluster V_k
for l ← 0 to |V_k| − 1 do
j ← GetViewpointIndex(k, l)
region ← ComputeRegionAccuTile(𝝁2D_{i,j}, 𝚺2D_{i,k})
foreach tile t ∈ T overlapped by region do
T_{i,k} ← T_{i,k} ∪ {t}
end
end
K_{i,k} ← ∅
foreach t ∈ T_{i,k} do
key ← (t ≪ (32 + Bit_K)) | (k ≪ 32) | d_{i,k}
K_{i,k} ← Append(K_{i,k}, ⟨key, i⟩)
end
return K_{i,k}
Function Alpha-Blend(I_LF, V, L, T, 𝝁2D, 𝚺2D, c, o, r):
x̂ ← Ψ_remap(r) // View-coherent Remapping
t ← IdentifyTileID(x̂, T)
j ← V[x̂] // Per-subpixel viewpoint index
k ← GetClusterID(j)
[S_{t,k}, E_{t,k}) ← IdentifyGaussianList(L, t, k)
p ← 0
for e ← S_{t,k} to E_{t,k} − 1 do
i ← L[e]
// Per-view 𝝁2D_{i,j}; reused 𝚺2D_{i,k}, c_{i,k}
p ← BlendColor(p, x̂, 𝝁2D_{i,j}, 𝚺2D_{i,k}, c_{i,k}, o_i)
end
I_LF[x̂] ← p
return I_LF
头一个函数(GenerateKeys)藏着第 6 章那条「复用代价」的实现真相。看它的内层两件事:先遍历簇内所有视点 l(上界 |V_k| − 1),对每个视点算出它覆盖的区域 region(用的均值是 𝝁2D_{i,j},逐视点那一份,协方差是 𝚺2D_{i,k},逐簇那一份,两个下标又一次并排出现);然后把该区域覆盖到的 tile 并进 T_{i,k}[§B.2, p13]。注意这个 for 循环里做的是「并集」而不是「交集」,这就是原文 Discussion 里那句「可能把一个高斯分配到它并非对簇内所有视点都可见的 tile 里」的代码级来源[§4.2, p6]。换句话说:复用的代价不是抽象的描述,它就是这一层并集操作,而作者选择承担它,因为收益(键数下降)远大于代价(少量无效高斯)。
后一个函数(Alpha-Blend)是第 7 章那三件事的完整落地,按顺序它有五个动作。第一,x̂ ← Ψ_remap(r),线程不直接拿到自己的子像素,而是先查那张按视点排好序的表,这一步就是式 (8) 的兑现。第二与第三,t ← IdentifyTileID(x̂, T) 与 j ← V[x̂],再 k ← GetClusterID(j),从子像素反推出它属于哪个 tile、哪个视点、进而哪个簇。第四,[S_{t,k}, E_{t,k}) ← IdentifyGaussianList(L, t, k),取回一段半开区间(这就是 Stage 3 那次整数排序保证的连续段)。第五,for e ← S_{t,k} to E_{t,k} − 1 逐高斯累加 BlendColor,而那一行的注释是全篇最短也最要紧的一句:「Per-view 𝝁2D_{i,j}; reused 𝚺2D_{i,k}, c_{i,k}」[§B.2, p13]。
把最后那句话与式 (10) 对着看,就闭环了。算法里那一行注释说的正是式 (10) 里那两个下标:均值 𝝁2D_{i,j} 带 j(逐视点)、协方差 𝚺2D_{i,k} 与颜色 c_{i,k} 带 k(逐簇复用)。到这里,本篇的两处「复用什么、不复用什么」在公式层、算法层、注释层三处说的是同一件事,这种一致性本身就是可以核对的证据。
④边界:这一章的两样东西各有各的适用边界。式 (11) 的边界是位宽:它假定「tile 编号、簇编号、量化后的深度」三样能塞进 64 位,场景一大、tile 一多,Bit_K 就要跟着调;而且深度被量化成整数这件事在高动态范围的场景里要小心截断方式(原文未展开,属于实现细节)。两块伪码的边界则是「它写的是主流程,不是全部」:算法 1 里那条 ComputeRegionAccuTile、IdentifyGaussianList、BlendColor 都没有展开实现,而它们各自都可能藏着自己的近似(尤其第一个,它直接决定了那段并集的松紧)。原文把展开留在代码里而不是论文里,这是一个诚实但需要读者自己补的缺口。
9实验怎么搭:两块实机屏、四条轨迹,和只做「复现」的基线
两块实机屏、两套数据集、四条环绕轨迹:这组数字就是在这套条件下量出来的。本篇的实验设置里有两处安排值得单独拎出来讲:一处是显示规格与数据集的错配怎么处理,另一处是三个基线为什么不是「跑别人的代码」。原文在 §5.1 用三个粗体小标题把这件事写完:Implementation Details、Datasets and Evaluation Protocol、Baselines[§5.1, p6–p7]。
9.1 实现:挂在 gsplat 上,自写与借用各一半
本篇的实现建立在 gsplat 这个开源高斯飞溅库之上,并为渲染管线定制了 CUDA kernel[§5.1, p6]。另外,原文还把自己用到的第三个部件明确标了出来:AccuTile 这个算法被默认集成进本篇与所有基线,用途是保证高斯与 tile 的相交判定准确[§5.1, p6]。这一句看似细节,其实是一个公平性声明,它说的是:「tile 覆盖算得准不准」这件事不在本轮的比较范围内,因为所有方法都用了同一套判定。
训练侧的配置也交代清楚了:3DGS 优化沿用 gsplat 的标准训练配置,而真实场景额外套用 3DGS-MCMC 的正则化方案[§5.1, p6]。渲染时的默认簇大小取 |V_k| = 8;所有实验在一张 RTX 5090(32GB)上完成[§5.1, p6]。这里有一个细节值得记住:|V_k| = 8 是默认值而不是最优值,第 11 章会说明为什么最后落在这里,以及它在两个数据集上分别意味着什么。
9.2 数据与协议:八个合成场景、七个真实场景、四条环绕轨迹
数据集用的是两套标准件:Synthetic Blender 的八个合成场景与 Mip-NeRF 360 的七个真实场景[§5.1, p6]。
原因为什么还要自己造轨迹,值得说清楚:光场屏的交错图需要「密集且相邻」的视点,而这两套数据集的原始拍摄轨迹是为别的用途准备的,视点不够密、间距也不均匀。于是原文的做法是:每个场景合成四条测试轨迹,做法是绕着场景中心环绕,并且相机始终朝向内侧,以此覆盖尽量多样的观察角度[§5.1, p6]。「朝向内侧 + 环绕」这两个条件合起来,正好对应第 6 章切簇那个前提(相邻视点看着相近),也就是说,本篇的数据协议本身就是为了让「跨视角相干」这个假设成立而设计的,这一点在评估结论时要一直记着。
显示规格有两套,而且是两种不同的屏:
| 横屏(landscape) | 竖屏(portrait) | |
|---|---|---|
| 分辨率 | 4K(3840 × 2160) | 2K(1440 × 2560) |
| 视点数 | 71 | 63 |
| 水平视锥 | 53° | 53° |
| 实机 | Looking Glass 16" Light Field Display | Looking Glass Go |
[§5.1, p6]
因为要迁就显示规格,原文动了相机的内参:对 Synthetic Blender 是把视场角放大,把原本在画面之外的周边区域纳进来;对 Mip-NeRF 360 则是把视场角收窄、让它适配目标分辨率,代价是裁掉了原有的图像覆盖范围[§5.1, p6]。这个调整有两层后果:好的那一层是「真实场景在 4K 光场屏上」这个设置本身很硬气(真的用实机评);要记着的那一层是两个数据集被改造的方向正好相反(一个扩、一个裁),所以跨数据集的绝对数值不可直接对比,只能各自内部比。
评估口径是三件事:每视点图像上量 PSNR、SSIM、LPIPS[§5.1, p7]。但这里有一个必须点破的口径问题:因为四条测试轨迹的真值图像并不存在(轨迹是新合成的,没有实拍可对),原文拿原版 3DGS 渲染出的图当「伪真值」来评估保真度[§5.1, p7]。速度那一侧的口径是:沿每条测试轨迹,报告生成最终交错图的平均帧率[§5.1, p7]。这两条口径各自有一个后果:伪真值这一条意味着,本篇量的其实是「离原版 3DGS 有多远」,而不是「离真实照片有多远」,图 10-1 里 3DGS 那一行直接写着 pseudo ground-truth,就是这个意思;平均帧率这一条意味着,报告的是整条轨迹的平均,不是某一帧的峰值。
9.3 基线:为什么是「复现核心思想」而不是「复跑代码」
原文把比较对象分成三类,总共四种配置[§5.1, p7]。第一类是全帧渲染:原版 3DGS 与它的 AccuTile 变体,做法是把多视点图像全部按全分辨率渲染完,再交错。第二类是子像素那一系:也就是本篇方法的前身,采用 §4.1 那套子像素采样方案,但去掉跨视角属性复用与视角相干重映射,原文给它取名 Subpixel-3DGS。第三类是 MPI 那一系:从一台参考相机出发构造 64 张 MPI 平面覆盖所有视点,按 4 倍降采样计算以降低显存开销;渲染交错图时同样按子像素采样,从平面里选出需要的子像素做混合[§5.1, p7]。
那两家里最值得留意的一句是这一段:因为现有的光场渲染方法(点名 Ji 等人与 Kim 等人)并不提供代码,所以本篇复现的是它们各自的核心思想,而不是做逐行的重新实现[§5.1, p7]。这句话的诚实度值得肯定,但读数字时必须把它的后果带上:「Subpixel-3DGS」与「MPI」这两个名字代表的是「思路」,不是「某篇论文的官方实现」,因此图 10-2 与图 11-1 里它们落后多少,不能直接写成「某方法慢多少倍」,只能说「按这套思路实现在这个配置下是这么多」。原文自己在讨论 MPI 的 PSNR 时也保持了这个分寸:它指出 MPI 这条路 2K/4K 上分别是 20.84/21.28 dB(Synthetic Blender)与 19.48/19.11 dB(Mip-NeRF 360),并把落后的原因归给「深度平面数量有限带来的离散化瑕疵」,而不是归给实现质量[§5.2, p7]。这个归因是可检验的:平面数一加,瑕疵会减少但延迟会涨,所以「这条路在这个数量级下不划算」这个论断是成立的。
③推导:把这一章的设置串起来,可以预判出图 10-1 与图 10-2 会呈现的格局。第一,全帧那两行必然最慢,因为它们的 71 倍重复计算一点没省(图 10-2 里 3DGS 是 5.8/4.1/3.9/2.1 FPS)[表 2, p7]。第二,Subpixel-3DGS 会明显快于全帧、但明显慢于本篇,因为它省了像素却没省视点、也没修访存(图 10-2 里是 28/19/11/5.7)[表 2, p7]。第三,MPI 会在速度上垫底、在画质上被离散化拖住(图 10-2 里是 0.8/0.4/0.8/0.4 FPS)[表 2, p7]。第四,本篇会在所有配置上同时赢速度与画质(图 10-2 里是 88/56/30/16)[表 2, p7]。这四条预判一旦在图 10-2 里全部成立,就说明本篇的两处设计确实各自命中了一个瓶颈,这正是第 10 章要做的核对。
④边界:这一套设置有三处需要读者自己拿捏的地方。第一,伪真值口径带来的天花板效应:既然真值是原版 3DGS 渲染出来的,那么任何方法在 PSNR 上都只能「逼近」它、不可能超过它,图 10-1 里 3DGS 那几行写着 pseudo ground-truth,而本篇的 PSNR 在 |V_k| = 2 时达到 62.13 与 53.35 dB,这个「高」只能读成「接近原版 3DGS」,不能读成「画质接近真值」[表 1, p7]。第二,两个数据集的改造方向相反(一个扩视场、一个裁视场),所以它们的绝对数字不能并列横比,只能在同一数据集内部纵向看趋势。第三,四条轨迹是环绕且朝内的,这个轨迹形态天然有利于跨视角相干,换成视点不成环、或者视点之间跳变的轨迹(比如沿走廊直线前进、或者切镜头),第 6 章那条「簇内局部一致」的前提就要打折扣,而这正是第 13 章要交代的边界之一。
10结果:4K 真实场景进了实时,对全帧 3DGS 提速 7.6 倍
4K 的真实场景进了实时,对全帧 3DGS 提速 7.6 倍:这两个数是怎么算出来的,这一章逐格核一遍。原文在 §5.2 分两段给出主结果,Evaluation 段讲本篇自己跑成什么样,Comparison 段讲与前人三条路线的对照[§5.2, p7]。本章要逐格读两张表与一张实拍图,并且把每一个倍数当场算出来。
10.1 表 1:六个配置 × 四个指标,加一列簇大小扫描
先看第一张表。它是本篇的自评,同一套指标(FPS 与 PSNR/SSIM/LPIPS)在六个配置上各读一遍,其中五行是本篇在不同簇大小下的结果。这张表的读法有一个诀窍:不要横向比大小,而是竖向看趋势,因为它的每一行代表的是一种「效率与保真的兑换比」,行与行之间是可比的。
|V_k| = 16、4K 的 71 视角配置取 |V_k| = 18,为的是让每个显示规格下的视点都能被均衡地分簇[Table 1 图注, p7]。 ②读法:这张表要看的是「一列一列往下走」的趋势,而不是横向比大小。它的六行里,第一行是作为伪真值的原版 3DGS(质量那几列直接写 pseudo ground-truth),后五行是 |V_k| 取 2/4/8/16/18 的本篇方法。往下走一行,簇就变大、复用就更多、速度就更快,而 PSNR 就往下掉,这四列里 FPS 与 PSNR 的方向永远是相反的,这就是这张表的全部信息:它在给出一条「效率与保真的兑换曲线」。另有一处排版上的读法:|V_k| = 16 那一行只有 2K 两列有数、4K 两列是破折号;|V_k| = 18 那一行正好相反。这不是缺数据:这两档本来就各自只服务一个显示规格**(见 ①图注里那句「为的是均衡分簇」)。
来源:CoherentRaster: Efficient 3D Gaussian Splatting for Light Field Displays, Table 1, p7 · SIGGRAPH Conference Papers '26(ACM, 14 pages, DOI 10.1145/3799902.3811217) · CC BY 4.0(© 2026 Copyright held by the owner/author(s)) · 原样引用,未修改
③复算:把这张表逐格读出来,能看清「放弃多少保真换多少速度」这个单价。先看 Synthetic Blender 的 2K 那一列[表 1, p7]:|V_k| = 2 是 54 FPS / 62.13 dB / 0.9997 / 0.0003;= 4 是 75 / 56.75 / 0.9994 / 0.0005;= 8 是 88 / 51.94 / 0.9989 / 0.0009;= 16 是 84 / 46.86 / 0.9976 / 0.0024。从 2 走到 8(本篇的默认档),速度涨了 88 ÷ 54 = 1.63 倍,PSNR 掉了 62.13 − 51.94 = 10.19 dB。再看 Mip-NeRF 360 的 2K 那一列:2 → 4 → 8 → 16 的 FPS 是 20 → 28 → 30 → 24,PSNR 是 53.08 → 47.44 → 42.74 → 37.50。这一列有一个很值得注意的现象:速度在簇继续变大时反而回落(30 降到 24),而 PSNR 一路下滑。也就是说「簇越大越快」并不是单调成立的,簇过大时,式 (6) 那三件的复用收益被多余高斯带来的额外混合开销吃回去了,这正是第 6 章 Discussion 里那句「收益超过代价」的成立区间上界[§4.2, p6]。最后看 4K 那一档的对照:本篇 |V_k| = 8 在 Synthetic Blender 上是 56 FPS、在 Mip-NeRF 360 上是 16 FPS[表 1, p7],后者与图 10-2 里同一配置的读数一致(图 10-2 的 4K Mip-NeRF 360 列写的正是 16)[表 2, p7],两表在同一格上对得上。
10.2 表 2:四种配置上的帧率对照
再看与基线的对照。这张表只列帧率,行是三条前人路线加上本篇。它的价值在于把两条前人路线的失败形态分开量:全帧那一组的数字、子像素那一组的数字、多平面那一组的数字,三组各自差了一个档次,这个「档次」本身就是诊断结果。
③复算:逐格算倍数,本篇对全帧 3DGS 的那个「7.6×」正是从这里出来的。先看 Mip-NeRF 360 的 4K 那一列[表 2, p7]:Ours = 16,Subpixel-3DGS = 5.7,3DGS(batch=36) = 4.0,3DGS = 2.1,MPI = 0.4。对本篇与全帧 3DGS 算:16 ÷ 2.1 = 7.62,摘要里那个「7.6 倍」就是这一格。对本篇与子像素那一组算:16 ÷ 5.7 = 2.81 倍,这 2.8 倍就是本篇两个部件的净贡献(因为 Subpixel-3DGS 用的就是本篇的采样方案、只少了那两个部件)[§5.1, p7]。再看四个配置的并列:Synthetic Blender 上本篇是 88(2K)/ 56(4K),对全帧 3DGS 的 5.8 / 4.1 分别是 15.2 倍 / 13.7 倍;Mip-NeRF 360 上是 30(2K)/ 16(4K),对 3.9 / 2.1 分别是 7.7 倍 / 7.6 倍。这组数字说明一件很重要的事:受益最大的是合成场景那一档(十几倍),真实场景那一档是七八倍,原因在图 12-3 里能看出来:真实场景的高斯–tile 配对数比合成场景大一个量级(4K 下 129.7M 对 27.5M)[表 6, p14;表 5, p14],配对数越大,混合阶段本身就越重,能被复用省掉的那一部分占比反而下降。原文对此的总结是:在 Synthetic Blender 上两种分辨率的 LFD 都做到了实时;而在 Mip-NeRF 360 上,2K 做到实时、4K 达到可行的帧率;原文随后补了一句分量很重的判断:本方法是首个让真实世界三维场景在超过 2K 的分辨率上实时的[§5.2, p7]。
10.3 图 4:把画面送上屏再拍回来
数字之外还需要一张图。前面两张表说明「快了多少」,这张图要说明「看起来行不行」,而且它不是渲染截图,是从物理显示屏上拍回来的照片。这一点决定了它的读法:它用来抓结构性失败,不用来比像素。
③推导:原文在正文里给这张图配了一段相当具体的定性描述,其中两处可以直接对照。第一处是 MPI 的失效机制:离散化瑕疵来自深度平面数量有限,而周边视角的几何不一致来自「从单一参考视角做形变」[§5.2, p8]。这两条与第 9 章那个基线设置的细节严丝合缝,原文实现 MPI 时用的是64 张平面、4 倍降采样、单台参考相机覆盖所有视点[§5.1, p7],三个限制各自对应一种坏相:平面少 → 深度离散;降采样 → 分辨率损失;单参考 → 大角度形变失真。换句话说,这条基线的失败是可以从它的配置里推出来的,不需要靠图来说服。第二处是本篇的定性主张:本篇达到更好的视觉质量、生成与全帧基线相称的锐利画面,即便在复杂区域也是如此;而且是在显著超过全帧与 MPI 两条基线的速度下做到这一点的[§5.2, p8]。注意这句话的结构是「质量 × 速度」的联合主张,不是单看哪一头,这也正是这张图存在的意义:它要同时承载「看起来不差」与「下面那张表说明它快」两件事。
④边界:这一章的三件证据合起来有三处边界。第一,「首个超过 2K 实时」这个说法有精确的范围:原文的表述是在真实世界三维场景上、超过 2K 分辨率下的实时[§5.2, p7],它没有声称在所有场景、所有屏上都实时,图 10-2 自己就摆着 4K 真实场景只有 16 FPS 这个数,16 FPS 算不算实时其实取决于用途(原文用了「可行的帧率」这个措辞来区分 2K 与 4K)。第二,伪真值口径限制了能声称的质量上限:既然真值来自原版 3DGS,那么任何「比基线更接近真值」都是循环论证,所以本篇的质量主张只能落在「不掉、且感知上难以区分」这个层次上(图 10-1 里 |V_k| = 2 时 PSNR 高达 62.13 dB,本质上是「几乎等于原版 3DGS」)。第三,实拍图不能当定量的证据:图注自己承认有色彩偏移与错位,所以这张图只能用来排除「明显的结构性失败」,不能用来主张「更清晰」,谁更清晰这件事,要靠图 10-1 里那三列指标去说。
11消融:两个部件各管一段,谁也替不了谁
把两个部件一个个拿掉,看帧率掉多少:这件事必须放在同一张表里做。第 10 章证明了「合起来快」,这一章要证明「为什么必须两个一起上」。原文在 §5.2 的 Ablation Study 段把两个部件的贡献拆开量了一遍[§5.2, p8],配上图 11-1 与图 11-2。这一章的价值在于:它把第 4 章那个「两个量分属算力侧与带宽侧、所以收益可以叠加」的推断,变成可以逐格核对的数字。
11.1 表 3:四行消融,看两个部件各自管哪一段
先看消融表。它只有四行,但这四行是本篇全部论证的支点:前三行分别去掉一个或两个部件,第四行是两个都在的完整方法。读它的关键是「差在哪两行之间」,而不是每行的绝对值。
Ours w/o Reuse w/o Remap 是两个部件都去掉的裸管线(也就是第 5 章那个朴素做法,与表 2 里的 Subpixel-3DGS 是同一个东西);第二行只加回复用;第三行只加回重映射;第四行 Ours 是两个都加。读的时候盯三件事:① 只加一个能涨多少;② 两个相加的涨法是不是简单的加法;③ 第三条(只加重映射)与第二条(只加复用)哪一条涨得多,第三条答案会告诉你「在这个瓶颈上,算力与带宽哪一侧更卡」。
来源:CoherentRaster: Efficient 3D Gaussian Splatting for Light Field Displays, Table 3, p8 · SIGGRAPH Conference Papers '26(ACM, 14 pages, DOI 10.1145/3799902.3811217) · CC BY 4.0(© 2026 Copyright held by the owner/author(s)) · 原样引用,未修改
③复算:把四行逐格读出来,可以看到「谁也替不了谁」这句话是量出来的。取 Mip-NeRF 360 的 4K 那一列(这一列分野最大)[表 3, p8]:裸管线 5.7,只加复用 6.4,只加重映射 11,两个都加 16。三件事由此可以确认:第一,两个部件各自的增益不相等,只加复用只涨 0.7(5.7 → 6.4,涨 12%),只加重映射涨了 5.3(5.7 → 11,涨 93%),说明在这套配置上访存那一侧才是主要瓶颈。第二,两者的效果不能互相替代,如果只留一个就够,那「只加重映射」那一行应该已经接近 16,但它停在 11。第三,合起来的增益明显超过两段之和的朴素估计,5.7 加上两段的绝对增量(0.7 + 5.3 = 6.0)得到 11.7,而实际是 16;多出来的这部分,正是「属性放对地方之后,重映射才真正起作用」这件事的量化表现(第 7 章 ③复算里那段混合阶段 67.43 → 48.29 → 32.41 的读数讲的是同一件事)。再看另外两个配置:Synthetic Blender 的 4K 列是 19 → 22 → 41 → 56,2K 列是 28 → 34 → 67 → 88,同样呈现「重映射涨得多、两者合起来又超出之和」的形态[表 3, p8]。原文对此的定性是:没有跨视角属性复用时,需要大量的跨视角计算,复杂场景的多视角渲染因此无法高效进行;没有视角相干重映射时,低效的内存合并会阻碍真实数据上的实时渲染[§5.2, p8]。
11.2 图 5 与「簇大小为什么定在 8」
有了表,还要有图。图 10-1 已经用数字给出了簇大小这条兑换曲线,这张图补的是它在人眼上的样子。两张证据摆在一起,才能回答「默认值为什么取 8」这个问题,图 10-1 能说明 8 是帧率的峰值,但峰值附近是否有裕度,只有看图才知道。
|V_k| 越大、PSNR 越低、FPS 越高),图 5 给出的是同一个变量在画面上的样子。读的时候重点看「从哪一档开始能看出差别」:如果 K = 2 与 K = 4 之间肉眼看不出差别、而 K = 16 开始露出失真,那么「默认取 8」这个选择就落在一个有裕度的位置上。四个场景横着排,是为了排除「只在某一个场景上成立」这种可能,它同时覆盖了两个数据集与四种不同性质的内容(Counter 是密集杂乱的室内、Bonsai 是前景细节丰富的盆栽、Drums 与 Ship 是合成件)。
来源:CoherentRaster: Efficient 3D Gaussian Splatting for Light Field Displays, Fig. 5, p11 · SIGGRAPH Conference Papers '26(ACM, 14 pages, DOI 10.1145/3799902.3811217) · CC BY 4.0(© 2026 Copyright held by the owner/author(s)) · 原样引用,未修改
③推导:「默认取 8」这个决定是可以从图 10-1 的那条曲线反推出来的。原文在正文里给出了推断的依据:每个簇里的视点数决定了效率与质量之间的取舍,数量增加,渲染速度提升,但高保真无法完全保住;随后给出结论:|V_k| = 8 在两套数据集上都取得了最好的整体表现[§5.2, p7]。把这条推断对着图 10-1 的格子核一遍:在 Synthetic Blender 的 2K 列上,|V_k| = 8 确实是 FPS 的峰值(88,高于 = 16 的 84);在 Mip-NeRF 360 的 2K 列上,= 8 同样是峰值(30,高于 = 16 的 24)[表 1, p7]。也就是说「最好」的定义是「考虑到质量的效率最优点」,而不是「帧率最高」或「质量最好」,它是一个双目标上的折中,而两列数据的峰都落在 8 附近,才有了这个默认值。从这里也能反推出本篇的两个部件为什么被设计成这样:如果只追求帧率,把 K 打到 16 甚至更大会更快;但那样式 (6) 的三件属性偏差变大,第 13 章要讲的那些失效就会出现,所以「默认 8」实际上是在边际收益开始变差之前收手。
④边界:消融这一章有三处需要自己拿捏的地方。第一,图 11-1 的第一行与图 10-2 的 Subpixel-3DGS 是同一个东西(都是「有子像素采样、没有那两个部件」),所以两张表在这一点上可以互相印证:图 11-1 第一行的 4K 真实场景读数是 5.7[表 3, p8],与图 10-2 里 Subpixel-3DGS 的 5.7 完全一致[表 2, p7],两表对得上,说明消融的基线是干净的。第二,「两个部件都去掉」不等于「什么都不做」:那一行仍然是子像素级渲染(否则就退回全帧 3DGS 了),所以图 11-1 量的是「本篇的两个部件」的贡献,不是「整篇方法」的贡献,整篇方法相对全帧 3DGS 的贡献要去看图 10-2 的 7.6 倍。第三,图 11-2 的「没有明显失真」这句主张,与第 13 章图 13-1 的「可观测瑕疵」并不矛盾:图 11-2 扫的是簇大小这个变量,用的场景都是常规内容;图 13-1 换的是材质(高镜面),指出的是这套方法在材质维度上的边界。两图合起来才是完整的结论:在常规内容上,把 K 扫到 16 都不难看;在高频镜面上,复用的代价会显出来,而这一点与 K 取多大无关。
11.3 为什么峰值在两个数据集上并不相同
图 10-1 的两列峰值都落在 |V_k| = 8,但两条曲线的形状并不一样。Synthetic Blender 的 2K 列是 54 → 75 → 88 → 84(在 8 处到顶、到 16 回落 4 FPS),而 Mip-NeRF 360 的 2K 列是 20 → 28 → 30 → 24(同样在 8 处到顶、到 16 回落 6 FPS)[表 1, p7]。两处回落的量不一样,原因可以从第 12 章的成本结构里读出来:真实场景的混合阶段本身更重(4K 下 67.43 ms 对合成场景的 21.27 ms)[表 6, p14;表 5, p14],所以「多余高斯带来的额外混合开销」在真实场景上被放得更大,簇一变大,被并进列表的无效高斯就变多,而每一颗都要走一遍算法 2 里那个 BlendColor 循环[§B.2, p13]。这也解释了默认值为什么取在 8 而不是 16:8 是两条曲线的共同顶点,而 16 是两者共同开始回落的地方。
把这条观察放回第 6 章那道「收益与代价」的账上,正好补上更早埋下的一句。第 6 章说过,复用的代价是「偶尔把一个高斯塞进它并不起作用的 tile 里」,而收益盖过代价[§4.2, p6]。这一节量的就是这个「盖过」的边界在哪,它不是无条件的:当簇大到某个程度,代价那一侧的增速超过收益那一侧(收益随 N/K 走、代价随簇跨度走),曲线就回头了。而这个回头点在不同数据上不一样,恰好说明它是一个与内容相关的量,而不是一个固定的工程常数,这也是原文只敢说「8 在两套数据集上都取得了最好的整体表现」,而不说「8 是最优簇大小」的原因[§5.2, p7]。换句话说,|V_k| = 8 是一个经验默认值,它的适用范围就是原文测过的这两套数据与两块屏。
12换一张缓存只有 6MB 的卡,以及时间到底花在哪一段
第 10 与第 11 章用的是同一张 RTX 5090,这一章换了一张卡。原文在补充材料 C 用两节做了两件事:C.1 把实验搬到一张 L2 缓存小得多的 RTX 3090 上,看结论是否还成立;C.2 把整条流水线的耗时拆成四个阶段,看时间到底花在哪[§C.1, p12;§C.2, p13]。这两件事合起来回答了本篇最实际的两个问题:「换硬件还行不行」,以及「快是靠什么快」。
12.1 表 4:缓存从 96MB 掉到 6MB
换卡这件事要用一张表来说。第 10 与第 11 章的所有读数都来自一张 RTX 5090,而这张表把整套对照搬到了一张 L2 缓存小十六倍的卡上。它的用途是排除一种可能:如果本篇的优势只是某一张卡的缓存红利,那么换卡之后结论就会塌;反过来,如果结论还在、而且相对次序还在,就说明这套设计针对的瓶颈是真实的。
③复算:先看两件事:换卡之后谁还站着,以及各配置的相对次序有没有变。逐格读 4K 真实场景那一列[表 4, p13]:3DGS = 1.1,3DGS(batch=18) = 1.7,裸管线 = 2.9,只留复用 = 3.2,只留重映射 = 4.0,本篇 = 6.0。三件事由此确认:第一,本篇仍然领先,6.0 是这一列的最高值,对原版 3DGS 是 5.5 倍(6.0 ÷ 1.1),对全帧的批处理变体是 3.5 倍(6.0 ÷ 1.7)。第二,消融的相对次序没变,仍是「只留重映射 > 只留复用」(4.0 对 3.2),与图 11-1 在 5090 上的结论一致,说明「访存那一侧是主要瓶颈」这个判断不是某张卡的特性。第三,绝对帧率整体下降但,同一套方法在两张卡上都成立,只是 4K 真实场景在 3090 上从 16 FPS 掉到 6.0 FPS(图 10-2 对图 12-1)[表 2, p7;表 4, p13]。再看一个细节:「只留重映射」这一档在 3090 上的 4.0 已经超过了同卡上全帧 3DGS 的 1.1 与批处理的 1.7,也就是说光靠重映射这一招、在一张缓存只有 6MB 的卡上,就已经能赢过全帧,这从侧面印证了「访存是主要瓶颈」这条判断。
④推导:这张表还有一处值得单独推的隐含信息,为什么 3DGS 的批处理变体在这里只能开到 18。原文在 C.1 交代了原因:由于这张 GPU 只有 24GB 显存,3DGS 基线在开启视点批处理时最多只能开到 batch = 18[§C.1, p12]。注意这与 5090 上那张表的对比:图 10-2 里 3DGS 的批处理变体开到的是 batch = 36(显存更宽裕)[表 2, p7]。也就是说这张表的「全帧那一档」实际上是被显存压住的:把批处理从 36 降到 18,吞吐掉下去一部分,所以图 12-1 里全帧那两行的低数字里,有一部分是显存预算造成的、而不全是缓存造成的。读这张表时必须把这一层带上:它量的是「在 24GB 显存的卡上,各方能挤出的最好成绩」,而不是「同样的配置换一张卡」。
12.2 表 5 与表 6:四个阶段的耗时、显存与配对数
接下来是最能说明「快靠什么」的两张表。它们把整条流水线拆成四个阶段,逐阶段列出耗时、显存与配对数,并且每一张都按四档消融配置列了四行。这两张表是第 6 与第 7 章那两条设计主张的直接证据,前面只能论证「复用应当省算力、重映射应当省带宽」,这里可以看到它们各自究竟省在哪一格。
③复算:先看最贵的哪一列,再看两个部件各自动了哪些列。取 4K 那一半(这一半四行齐全)[表 5, p14]:裸管线是 Proj 0.83 / KeyGen 1.47 / Sort 0.14 / Blend 21.27,配对数 134.3M;只留复用是 0.83 / 1.48 / 0.14 / 10.67,配对数仍是 134.3M;只留重映射是 0.20 / 0.91 / 0.13 / 17.09,配对数 27.5M;两个都留是 0.20 / 0.91 / 0.13 / 10.41,配对数 27.5M。四条结论由此读出来:第一,混合阶段是绝对的大头,裸管线里它占 21.27 ÷ (0.83+1.47+0.14+21.27) = 90%。第二,复用动的是「配对数量」,不是「配对之外的时间」,从 134.3M 降到 27.5M(4.88 倍),但它的四段耗时几乎没变(0.83/1.47 变成 0.20/0.91 是另一个部件的功劳,见下文)。第三,两个部件的作用面完全不同,复用把 #Pairs 从 134.3M 砍到 27.5M、显存从 6.25GB 砍到 3.54GB;重映射把 Proj 从 0.83 压到 0.20(4.2 倍)、KeyGen 从 1.47 压到 0.91、Blend 从 21.27 压到 10.67(2.0 倍)。第四,两者合起来时 Blend 才进一步降到 10.41,而 Proj/KeyGen 停在 0.20/0.91。这里要提醒一句:Proj 与 KeyGen 的下降看起来是「重映射」带来的,这与第 6 章的预期相反(复用应该动这两段)。细看数据可以解释:只留复用那一行的 Proj/KeyGen 仍是 0.83/1.48,与裸管线几乎一样,说明这两段的耗时并不由「算多少次」决定,而由「访存散不散」决定;复用减少的是「有多少个配对要处理」(#Pairs),而重映射改变的是「处理它们时访存顺不顺」。两条证据合起来,本篇的因果链才完整:复用让中间量的规模降下来,重映射让处理这些中间量时的访存连续起来,缺任何一条,另一条的收益都兑现不了。
#Pairs、重映射还是不是动 Blend。如果三条都成立,说明这套因果解释在两个量级上都站得住。
来源:CoherentRaster: Efficient 3D Gaussian Splatting for Light Field Displays, Table 6, p14 · SIGGRAPH Conference Papers '26(ACM, 14 pages, DOI 10.1145/3799902.3811217) · CC BY 4.0(© 2026 Copyright held by the owner/author(s)) · 原样引用,未修改
③复算:逐格核一遍,三条都成立,而且量级差被量得很清楚。4K 那一半[表 6, p14]:裸管线 1.92 / 9.04 / 0.26 / 67.43,配对数 551.5M、显存 18.12GB;只留复用 1.92 / 9.05 / 0.26 / 48.29,配对数与显存完全不变;只留重映射 0.34 / 4.87 / 0.15 / 61.03,配对数 129.7M、显存 5.01GB;两个都留 0.35 / 4.98 / 0.15 / 32.41,配对数与显存同上一行。三条结论:第一,混合仍是大头,裸管线里占 67.43 ÷ 78.65 = 86%。第二,复用仍只动规模,#Pairs 从 551.5M 砍到 129.7M(4.25 倍)、显存从 18.12GB 砍到 5.01GB(3.62 倍),而它那一行的四段耗时与裸管线几乎一致。第三,重映射仍动访存,Blend 从 67.43 压到 48.29(只留重映射)/ 32.41(两个都留)。把两个数据集并起来看,还多出一条结论:真实场景的配对数比合成场景大一个量级(4K 下 551.5M 对 134.3M,4.1 倍),这正好解释了第 10 章那个现象,为什么本篇在合成场景上能快十几倍、在真实场景上只有七八倍:基准量越大,混合阶段本身占的比重越高,能被省掉的那部分占比反而下降[表 5, p14;表 6, p14;表 2, p7]。
④边界:这一章有两条必须登记的口径边界。第一条:四个阶段的耗时加起来对不上帧率。以 Synthetic Blender 的 4K 为例,本篇四段之和是 0.20 + 0.91 + 0.13 + 10.41 = 11.65 ms,而图 10-1 给这一档的帧率是 56 FPS(对应 17.9 ms),两者相差 6.2 ms、约 35%。Mip-NeRF 360 的 4K 同样对不上:0.35 + 4.98 + 0.15 + 32.41 = 37.89 ms(26.4 FPS),而图 10-1 写的是 16 FPS(62.5 ms),相差 24.6 ms、约 39%。原文没有解释这个差额,流水线提交、交错写回,以及其他未被列入的四段之外的工作都可能占一部分;本页只登记读数、不替原文补原因。读这两张表时要记住它们量的是「阶段耗时」,而不是「帧时」,两者不可互相换算。第二条:换卡那一节改变了实验的变量个数。图 12-1 里既换了 GPU,又被迫把 3DGS 批处理变体的 batch 从 36 降到 18(24GB 显存所限)[§C.1, p12],所以它不是一张严格的「单变量对照表」;它的结论只能读成「在这张 24GB/6MB 缓存的卡上,各方的最好成绩分别是多少」,不能读成「缓存缩小 16 倍会让每个方法掉多少」。这两条边界合起来其实是好事:它们说明本篇作者在补充材料里给的是原始读数,而不是经过修饰的对照。
13代价与边界:镜面会露出破绽,什么时候不必担心
一张高镜面的桌面上,复用会露出破绽。这一章要把整套方法「什么时候不成立」讲清。原文把这个话题分在三处:补充材料 C.3 专门讨论属性复用对渲染质量的影响[§C.3, p13],正文 §6 的 Limitations 段点出两条边界[§6, p8],C.1 里还有一段对「散写对反复读」的辩护[§C.1, p12]。本章把它们收在一处,逐一交代机制与条件。
13.1 图 6:镜面表面的瑕疵从哪来
最后一张图要讲的是代价。前十二章都在说「快且画质不掉」,这一章要指出它在什么条件下会掉。这张图给的是最不利的那一档:高镜面材质。先把它摆在正确的位置上,它量的是这套方法的上界,不是典型表现。
k 下标联系起来,就说明因果链通了,出问题的是被 k 复用的那三件,不是逐视点算的均值。
来源:CoherentRaster: Efficient 3D Gaussian Splatting for Light Field Displays, Fig. 6, p13 · SIGGRAPH Conference Papers '26(ACM, 14 pages, DOI 10.1145/3799902.3811217) · CC BY 4.0(© 2026 Copyright held by the owner/author(s)) · 原样引用,未修改
③推导:原文在 C.3 把「为什么通常不必担心」拆成了三条理由,而且给了一条很硬的量化依据。第一条理由是视角密度:商用光场屏以很高的角密度采样视点,这使每个簇内的角度偏差保持在很小的范围内[§C.3, p13]。也就是说,簇的物理跨度本来就小,簇内视点之间的角度差小,反射方向的差也小,这正是式 (6) 复用的前提在物理上成立的原因。第二条理由是球谐本身的衰减:3DGS 用的球谐天然会衰减镜面反射的高频成分[§C.3, p13]。这一条很有意思:它说的是表示本身就丢掉了一部分最敏感的信息,所以复用带来的偏差落在一个已经被削平的信号上,表现自然不会太剧烈。第三条理由是实测结论:虽然镜面不一致在极端条件下可观测(就是图 13-1),但本篇的定性评估与消融表明,复用属性并没有带来显著的感知退化[§C.3, p13]。把这三条放回第 6 章那条推理上,可以看到一个完整的闭环:假设(簇内局部一致)→ 物理依据(角密度高、簇跨度小)→ 表示依据(球谐衰减高频)→ 实测(只有极端镜面可观测),四段各自都能对上。
13.2 三条前提:静态、连续、屏固定
Limitations 段点出的两条,加上前面各章反复出现的一条,合起来是本篇的三条前提。
第一条前提:目前只支持静态场景。原文把它写成一条明确的局限,当前的框架只针对静态场景,未来工作会专注于把管线扩展到动态内容[§6, p8]。为什么动态会打破它:本篇整套机制建立在预计算与复用两件事上,Ψ 表之所以能提前算,是因为屏的几何与场景都固定(第 7 章);v′_k 这个簇代表之所以能代表整簇,是因为同一簇的视点看的是同一时刻的同一片几何(第 6 章)。一旦场景随时间变化,后者立刻不成立:不同时刻的簇代表看到的表面可能完全不同,复用就变成了把一个时刻的形状套到另一个时刻上。原文没有展开这层机制,但从它的切簇方式可以直接推出来(簇是沿轨迹切的、而轨迹是随时间走的)[§B.1, p12]。
第二条前提:依赖簇内的局部一致性。原文的措辞是:尽管取得了实时性能,CoherentRaster 仍然依赖视点簇内的局部一致性[§6, p8]。这一条与第一条其实是同一根绳的两端:前者说的是「时间上不一致怎么办」(不能做),后者说的是「空间上不一致怎么办」(会出现瑕疵)。高镜面是空间不一致的极端情形(13.1 节),而任何大跨度的视点跳变都会放大它,这也解释了第 9 章那四条「环绕且朝内」的轨迹为什么是恰当的测试协议:它天然保证了簇内一致。
第三条前提:整个管线绑在「屏的几何参数已知且固定」上。这一条原文没有单列,但它散落在全篇:第 2 章的三条式由 α、L_x、K_offset、N 四个参数决定;第 7 章那张 Ψ 表因此只能预计算一次;第 8 章那个 64 位键里 Bit_K 的位宽也要跟着簇数调[§3.1, p3;§4.3, p6;§B.2, p12]。把它单列出来的理由是:它是本篇最容易被忽视、也最难迁移的一处,换一块屏,所有预计算都要作废重来;而如果屏的参数在运行中变化(比如可调倾角的实验设备),这个框架就得重新设计。
③推导:把三条前提排成一张表,本篇的适用范围就清楚了。能用的情形需要同时满足:场景静态、视点连续且朝向一致、屏规格固定。三类情形会踩线:动态内容踩第一条(根本不能用);视点跳变或大角度环绕踩第二条(会出现瑕疵,严重程度随材质变化);换屏或参数漂移踩第三条(谈不上质量下降,直接就得重算)。原文在结尾对未来的表述是:未来工作会集中于把管线扩展到动态内容[§6, p8]。从这三条前提看,扩展动态内容要动的不是渲染层,而是「簇代表」与「预计算表」这两个基础假设,前者要变成逐时刻的,后者要变成部分可复用的。这是一个相当重的改动,原文把它留作未来工作,是很诚实的处理。
④边界:这一章自己也有两处要交代的边界。第一,C.3 那三条理由里,前两条都是「降低可见度」,不是「消除误差」。视角密度高、球谐衰减高频,这两条做的是把误差压到看不见,而不是把误差消掉;所以只要条件变了(屏的角密度低、或者内容的高频更强),图 13-1 那种瑕疵就会从「极端条件下可观测」变成「常见」。第二,本节给出的三条前提是从原文散落各处的内容里归并出来的,其中第三条第 ③ 项(屏固定)原文并未单列,本页把它单列的理由已经写明(它与全篇的预计算绑在一起),但读的时候要知道这是本页的归并,不是原文的原话。
13.3 什么样的内容最值得用它
把三条前提反过来读,就能得到这套方法的「甜区」。它最值得用的场合有三个特征:第一,场景静态(不需要逐帧重建几何);第二,观察方式是连续的环绕或平移(保证簇内一致);第三,内容以漫反射为主、高频镜面占比低(避免图 13-1 那种失效)。这三条一凑,本篇最典型的用例其实就是它自己选的那套协议,在光场屏上展示一个已重建好的静态场景,观众绕着它走。这恰好也是光场屏最主流的商用形态(商品展示、数字标牌、教育与医疗的可视化),所以这套方法与它的目标场景是匹配的。
反过来说,有三类内容不值得用它。第一类是动态内容:直接踩第一条前提,当前框架根本不支持(原文把动态列为未来工作)[§6, p8]。第二类是高镜面密集的场景:汽车漆面、珠宝、金属构件这一类,图 13-1 那种瑕疵会从「极端条件下可观测」变成常态[§C.3, p13]。第三类是视点不成环的轨迹:比如沿走廊直线推进、或者自由漫游时频繁跳变,簇内一致性没有保证,而第 9 章那四条测试轨迹恰恰是环绕且朝内的[§5.1, p6],换句话说,原文并没有在「视点不成环」的轨迹上做过验证,这一点是数据协议留给后续工作的空白。
把这三类与前面那三条前提合起来,本篇的适用范围就是一张小表:横轴是内容类型(静态漫反射 / 静态高镜面 / 动态),纵轴是轨迹形态(环绕朝内 / 直线平移 / 跳变漫游)。能直接用的格子只有左上角那一格;右上与中间几格需要先把簇切分改掉(比如按轨迹的弧长而不是按序号切);最右下那一格则要动「簇代表」这个基础假设。把这张表画出来的价值在于:它让「这篇论文能不能用在我的场景上」这个问题有了一个可以逐格回答的形式,而不是停在「效果不错但要看情况」。
这一篇的交接到此为止:11 条编号式、12 件原文图元、2 块伪码轨、6 条边界与反例都已逐条落到章节里。回看整篇的结构,有一条线值得带走:光场屏这种交错布局同时违反了算力与访存两条纪律,而本篇的两处设计恰好各接一条,属性复用把中间量的规模砍掉四分之三以上(#Pairs 从 551.5M 降到 129.7M),视角相干重映射把处理这些中间量时的访存接回连续(混合阶段从 67.43 ms 降到 32.41 ms)。两者合起来,4K 真实场景从 2.1 FPS 变成 16 FPS(7.6 倍),4K 合成场景从 4.1 变成 56(13.7 倍)[表 2, p7;表 5, p14;表 6, p14]。而它的代价也很具体:高镜面表面上会露出破绽(图 13-1),动态场景目前不支持,换一块屏就要重算全部预计算表。
出处与许可。本篇发表在 SIGGRAPH Conference Papers '26(ACM),14 pages,DOI 10.1145/3799902.3811217,会议于 2026-07-19–23 在 Los Angeles, CA, USA 举行[出处信息]。本页所据是 arXiv 的 v1 版(2605.04509v1,2026-05-06),14 页(正文 11 页 + 补充材料 3 页)[所据版本]。作者六人分属两个机构:Gyujin Sim、Seungjoo Shin、Sunghyun Cho 来自 POSTECH,Hosung Jeon、Gwangsoon Lee、Hyon-Gon Choo 来自 ETRI;致谢里点名感谢 Jiyun Won 在实验结果可视化上的协助,并列出了四项资助来源(ETRI 的 25YC1600 项目、韩国 NRF 的 RS-2026-25492695、IITP 的 RS-2026-25517417 与 RS-2019-II191906)[作者与致谢:§Acknowledgments, p8]。代码地址写在摘要末尾:https://github.com/sgj0402/coherent-raster[摘要]。许可判定登记在 选簇与台账.md §二:所据 PDF 首页左栏底部明印 This work is licensed under a Creative Commons Attribution 4.0 International License.,同处另有 © 2026 Copyright held by the owner/author(s). 与 ACM ISBN 979-8-4007-2554-8/2026/07,而 Please use nonacm option or ACM Engage class to enable CC licenses 这句 0 命中(说明 CC 行已启用,不是篇目 56 那类未启用的情形);arXiv 摘要页的许可图标同向指向 creativecommons.org/licenses/by/4.0/。两处证据同向且同版 ⇒ 取 CC BY 4.0,落点三件(含托管原文 PDF)、卡片 chip 不带 warn、图件逐图四字段署名。这是系列里「arXiv 与 PDF 都写 CC BY 4.0」的首例,也是判定最干净的一次许可。
三处必须自己核过的取证事实。第一,式 (3) 的两个定界符。文字层给出的是伸长字体 txexs 的私有码位 U+0016/U+0017(直接读是不可见字符),实测判为 ⌊/⌋:左者的底脚向右伸、右者的底脚向左伸(互为镜像,正是下取整的定义特征),而同字体的 U+0012/U+0013 被实测为明显弯曲(墨迹质心自上而下从 48.5px 漂到 30.0px、墨宽由 2px 增至 10px),证明「直杆加底脚」不是这个字体的括号画法;语义上 V ∈ Z^{W×H×3} 也要求取下整。第二,U+F000 的 306 个私有区字形全部落在图内。它们集中在 p5 的 y∈[78, 376] 区间,也就是 Fig. 3 的图体范围;正文与图注 0 命中,且该件走整页区域栅格化,视觉不受影响 ⇒ 不列为还原项。第三,图注与表注的方向相反。本篇是 ACM 体例:图注在图体下方、表注在表体上方(与篇目 71 的补充材料体例正好相反),两条轨道的扫描方向因此必须分开定;表注底到表首横线的实测距离是 13.0–33.0pt[阶段 2 实测:stage2-gate.txt/stage2-geom.txt]。
最后交代一下本篇在系列中的读法。它是一篇「机理 + 系统」型的论文:12 件图元里 11 件挤在后四章,而第 3 到第 9 章一件图元都没有,那七章全靠 11 条编号式与 2 块伪码撑着。读它的顺序建议是:先读第 5 章(式 (4) 那四件属性里哪一件不能复用,全篇的靶子在这里),再读第 6 与第 7 章(两条式一对、一条式里两个下标,把「复用什么」讲透),最后回到第 10 到第 12 章去核对它换来了多少、以及代价在哪儿。
本页完