☰
PRT GI实践:预计算辐射传输实现实时全局光照的工程细节
2026/10/14 8:52:03 网站建设 项目流程

1. 从"PRT GI实践"这个标题里能读出什么

第一次看到"PRT GI实践"这个标题,我脑子里蹦出来的第一个念头是:这大概率是一个图形渲染方向的实战记录。PRT和GI这两个缩写放在一起,指向性其实相当明确——PRT是Precomputed Radiance Transfer(预计算辐射传输),GI是Global Illumination(全局光照)。这两个词凑一块儿,基本就是在聊"怎么用预计算的手段把全局光照效果实时跑起来"这件事。

但标题里最值得玩味的其实是"实践"两个字。如果只是讲原理,标题多半会写成"PRT原理详解"或者"GI算法综述";既然叫"实践",说明作者大概率是在某个具体项目里真正动手跑过一遍,踩过坑、调过参、对比过效果,最后把经验沉淀下来。这种内容恰恰是最有价值的——因为PRT和GI的理论资料网上已经够多了,但真正把预计算跑通、把运行时性能压下来、把画面调好看的中文实战记录,说实话并不多。

这篇文章我想做的事情,就是围绕"PRT GI实践"这个核心,把预计算辐射传输在全局光照场景下的完整落地路径拆开来讲。从为什么要用PRT、预计算阶段到底在算什么、运行时怎么把数据用起来、实际项目中会遇到哪些坑,到最终效果怎么验证和调优,我会尽量用从业者之间交流的方式说清楚。不管你是刚接触实时渲染的新手,还是已经写过几版GI方案但效果不理想的老手,应该都能从里面找到能直接抄作业的东西。

需要提前说明的是,PRT本身是一个偏"重预计算、轻运行时"的思路,它最适合的场景是静态几何体加动态光源,或者静态几何体加静态光源但视角可自由移动。如果你的场景里物体本身在变形、在破碎、在大量动态生成,那PRT就不是最优解,得考虑别的路子。这个边界我会在正文里反复强调,因为很多实践翻车就是因为一开始选型就没想清楚。

2. PRT与GI的关系:为什么预计算能换来实时全局光照

2.1 全局光照到底难在哪

要理解PRT的价值,得先明白全局光照为什么在实时渲染里这么难搞。局部光照模型(比如经典的Blinn-Phong)只考虑光源直接打到表面的那一次交互,算起来很快,但画面会显得"平"——物体背光面死黑,物体之间没有颜色渗透,角落没有自然的暗部过渡。全局光照要补的就是这些:光线在场景里多次弹射,间接光照、颜色溢出、软阴影、环境遮蔽,全都属于GI的范畴。

问题在于,GI的计算量是爆炸性的。一个像素的最终颜色,理论上要积分它上半球所有方向的入射光,而每个方向的入射光又可能来自其他表面的反射,其他表面的反射又依赖更早的弹射……这是一个递归的积分方程,也就是渲染方程。实时去解这个方程,在目前的硬件条件下对复杂场景来说依然不现实。

所以业界走了两条路:一条是"近似+降质",比如屏幕空间反射、屏幕空间环境光遮蔽这类屏幕空间技术,快是快,但只能处理屏幕里看得到的信息,屏幕外的东西一概不知道,容易出现边缘缺失和闪烁;另一条就是"预计算",把场景里不变的部分提前算好存起来,运行时只做查表和简单组合。PRT走的就是第二条路。

2.2 PRT的核心思路:把光照传输拆成可预计算的部分

PRT的精髓在于一个数学上的分解。渲染方程里,出射辐射度可以写成入射辐射度和传输函数的积分。PRT做的事情是:把传输函数这一项——也就是"从这个点出发,各个方向的入射光最终会怎样贡献到出射方向"——提前算好,用一组基函数系数存起来。运行时,入射光照进来,把它也投影到这组基函数上,然后两组系数做点积,就得到了出射结果。

这里的关键是基函数的选择。最经典的是球谐函数(Spherical Harmonics,SH)。球谐函数是一组定义在球面上的正交基,阶数越高能表达的频率越高。实践中常用的是2阶到4阶:2阶只有9个系数,能表达很平滑的低频光照,适合漫反射为主的场景;4阶有25个系数,能表达更锐利的高频细节,但存储和计算成本也上去了。

用生活化的类比来说,这就像把一段复杂的音乐用几个主要音符的强度来表示。低频光照就像低音鼓点,用很少的系数就能描述个大概;高频细节就像镲片,需要更多系数才能还原。PRT的取舍就在于:你要多精细,就得付多少存储和计算代价。

2.3 为什么"实践"比"原理"更值得写

理论层面,PRT的论文从本世纪初就有了,公式推导在各类图形学教材里也能找到。但真正动手做的时候,你会发现一堆论文里不会细说的问题:预计算时采样点怎么分布才不会有明显接缝?球谐系数存成什么格式、精度够不够?运行时动态光源怎么和预计算数据结合?间接光弹射次数取几次性价比最高?这些问题没有标准答案,全靠实践里一点点试出来。

我见过不少团队,原理讲得头头是道,真上手做出来的效果却发灰、发脏、有块状伪影,最后归咎于"PRT不行"。其实不是PRT不行,是实践细节没处理好。这篇文章的重点就放在这些细节上。

3. 预计算阶段:数据是怎么一步步算出来的

3.1 场景准备与几何体处理

预计算的第一步不是写代码,而是把场景整理干净。PRT对几何体有几个硬性要求,不满足的话后面全是坑。

第一,参与预计算的几何体必须是静态的。这里的"静态"指的是在运行时不会发生顶点级别的形变。如果一棵树在风里摇摆,那它的顶点位置每帧都在变,预计算的传输系数就失效了。常见的处理办法是把这类物体从PRT场景里剔除,用其他方式(比如简单的环境光近似)单独处理。

第二,几何体最好做一次合并和清理。零碎的小物件、重复的面片、内部看不见的结构,都会增加预计算的时间。我一般会先把场景里所有静态网格合并成少数几个大网格,同时删掉法线朝向错误的面和退化三角形。这一步看起来不起眼,但能省下大量预计算时间。

第三,UV和法线要规范。PRT的传输系数通常是存在每个顶点上或者存在纹理里。如果存顶点上,顶点密度要足够,太稀疏会导致光照过渡不平滑;如果存纹理里,UV不能有重叠,否则不同位置会互相污染。我个人的经验是,中小型场景直接存顶点最省事,大型场景或者需要高精度的场合再考虑纹理方案。

3.2 采样点分布与传输系数计算

传输系数的计算,本质上是模拟"从每个采样点出发,向各个方向发射光线,看这些光线最终打到什么、弹射几次后回来多少能量"。这个过程可以用光线追踪来做,也可以用辐射度方法来做。

采样点的分布直接决定了最终效果的均匀程度。如果采样点只放在顶点上,而模型某些区域顶点很稀疏,那这些区域的光照就会显得很"糊",和周围不连贯。我的做法是:先按顶点算一版,然后在顶点密度不足的区域手动加密,或者干脆改用纹理空间采样,按纹素来算。

传输系数的计算量非常大。假设一个采样点要发射几千条光线,每条光线弹射三次,场景有十万个采样点,那总光线数就是几十亿条。这个量级在单机上跑可能要几个小时甚至更久。实践中我会做几件事来加速:一是降低光线数,用重要性采样把光线集中在能量贡献大的方向;二是减少弹射次数,通常两到三次弹射就能覆盖绝大部分间接光贡献,再多收益递减明显;三是并行化,把采样点分块丢到多核或者多机上跑。

这里有个容易忽略的点:传输系数算完之后,通常要做一次归一化或者缩放。因为不同采样点接收到的总能量可能差异很大,不处理的话运行时容易出现某些区域过亮、某些区域过暗。我一般会统计所有采样点的系数均值,然后统一缩放到一个合理范围,保证整体亮度一致。

3.3 球谐系数的存储与压缩

算出来的球谐系数怎么存,是个直接影响运行时内存和带宽的问题。以4阶球谐为例,每个采样点有25个系数,每个系数如果是32位浮点,那就是100字节。十万个采样点就是10MB,看起来还能接受;但如果场景更大、阶数更高,内存就吃紧了。

实践中常用的压缩手段有几种。一是降精度,把32位浮点换成16位半精度,视觉上几乎看不出差别,内存直接减半。二是量化,把系数映射到8位整数范围,配合一个全局缩放因子,内存再减半,但要注意量化误差在暗部可能比较明显。三是只存有效系数,很多高阶系数在平滑场景里接近零,可以裁掉。

我个人的建议是:先按半精度存,跑起来看效果,如果内存还是紧张再考虑量化。不要一上来就追求极致压缩,因为调试阶段精度损失会让问题定位变得困难。

4. 运行时阶段:预计算数据怎么和实时光照结合

4.1 动态光源的球谐投影

预计算数据准备好之后,运行时的核心工作就是把当前光照投影到球谐空间,然后和预计算的传输系数做点积。对于动态光源,比如一个移动的点光源或者方向光,它的球谐投影其实很简单:方向光只需要根据方向和颜色算出几个低阶系数;点光源稍微复杂一点,要考虑距离衰减,但也就是多几步计算。

这里有个实践中的关键点:光源的球谐投影阶数必须和传输系数的阶数匹配。如果传输系数是4阶,光源只投影到2阶,那高阶部分就浪费了;反过来如果光源投到4阶而传输只有2阶,多出来的系数也没地方用。我一般统一用3阶或4阶,兼顾效果和性能。

另一个点是多光源的处理。场景里如果有多个动态光源,每个都单独投影再叠加,计算量会线性增长。实践中如果光源数量多,我会先做一次光源裁剪,只保留对当前视角影响大的几个,其余的用环境光近似代替。

4.2 间接光的多次弹射近似

PRT预计算的时候通常已经包含了多次弹射的信息,但运行时如果场景里有动态物体,这些动态物体对间接光的贡献是预计算里没有的。这时候就需要一个近似方案。

常见的做法是:把动态物体当作一个额外的"光源",用它的平均颜色和位置去近似它对周围静态物体的间接光照贡献。这个近似很粗糙,但在大多数场景里视觉上可以接受。如果动态物体很大或者很亮,近似误差就会明显,这时候可能需要更复杂的方案,比如实时更新局部区域的传输系数,但那个成本就上去了。

我的经验是,动态物体对间接光的影响在室内场景里比较明显,在室外大场景里相对次要。如果你的项目是室内为主,这块要多花点心思;如果是室外,简单近似往往就够了。

4.3 视角相关效果的取舍

PRT有一个经典的限制:基础的PRT只能处理漫反射,也就是视角无关的出射。如果要处理高光、镜面反射这些视角相关的效果,需要用到PRT的扩展版本,比如把传输函数按视角也展开,存储量会大幅增加。

实践中怎么取舍?我的判断标准是看项目需求。如果项目是写实风格、材质以粗糙表面为主,那漫反射PRT完全够用,高光用传统的局部光照模型补一下就行。如果项目需要大量光滑金属、镜面地板,那要么上扩展PRT,要么用屏幕空间反射之类的技术来补,纯靠基础PRT是出不来效果的。

这里要提醒一句:不要为了追求"全GI"而硬上扩展PRT。存储和计算成本的增长往往超出预期,而视觉收益在多数场景里并不成正比。先把漫反射GI做扎实,再考虑要不要加视角相关效果,这个顺序比较稳妥。

5. 实践中踩过的坑与排查思路

5.1 画面发灰、对比度不足

这是PRT实践里最常见的问题。画面整体像蒙了一层灰,暗部不够暗,亮部不够亮。原因通常有几个:一是传输系数没有做归一化,能量分布不均匀;二是球谐阶数太低,高频信息丢失导致光照过于平滑;三是间接光弹射次数太多,能量被过度平均化。

排查的时候我会先看传输系数的统计分布,确认均值和中位数是否合理。如果均值偏离预期,那就是归一化的问题。如果分布正常但画面还是灰,就试着提高球谐阶数,看高频细节能不能回来。如果提高阶数也没用,那可能是弹射次数的问题,试着减少弹射次数看对比度是否改善。

5.2 接缝和块状伪影

接缝通常出现在采样点分布不均匀的区域,或者纹理存储时UV边界处理不当的地方。块状伪影则多半是压缩精度不够导致的,尤其是在暗部,量化误差会被放大。

解决接缝的办法是加密采样点,或者在接缝区域做插值平滑。解决块状伪影的办法是提高存储精度,或者对暗部系数单独做保护,不让它们被量化到零。我一般会在压缩之后做一次视觉检查,专门看暗部区域有没有出现明显的色块。

5.3 动态物体与静态场景的光照不匹配

动态物体放进PRT场景后,经常出现"贴上去"的感觉——它的亮度和周围环境对不上。这是因为动态物体没有参与预计算,它的间接光照是近似出来的,和静态部分的精确计算有差距。

缓解这个问题的办法有几个:一是让动态物体的材质参数和静态部分保持一致,减少视觉差异;二是给动态物体加一个基于周围采样点的环境光探针,让它的间接光至少有个合理的来源;三是在后处理阶段做一次局部色调匹配,把动态物体区域的亮度和周围拉齐。这几个手段组合使用,基本能把违和感压到可接受范围。

5.4 预计算时间过长

预计算动辄几小时,迭代一次等半天,这是很影响开发效率的。我的优化顺序是:先降光线数和弹射次数,看效果损失能不能接受;再优化采样点分布,去掉冗余采样;最后才是上并行和分布式。很多时候前两步做完,预计算时间就能从几小时压到几十分钟,效果损失微乎其微。

还有一个技巧是分层预计算:先算一版低精度的用于快速预览,确认整体方向没问题之后,再算高精度版本用于最终出包。这样迭代阶段不用每次都等完整预计算。

6. 效果验证与调优的实操方法

6.1 建立可对比的基准场景

调优最怕没有参照。我一般会准备一个基准场景,里面包含典型的GI考验元素:封闭角落、彩色墙面、大面积地板、几个不同粗糙度的球体。每次改动参数后,在固定视角下截图对比,看间接光、颜色溢出、软阴影的变化。

基准场景的好处是可控。真实项目场景太复杂,变量太多,改一个参数很难判断到底是哪个因素在起作用。基准场景把变量隔离出来,调优效率会高很多。

6.2 分通道检查光照贡献

PRT的最终画面是直接光、间接光、环境光等多个通道叠加的结果。调优的时候我会把这些通道分开渲染,逐个检查。如果间接光通道本身就有问题,那叠加之后肯定不对;如果间接光正常但合成后不对,那就是叠加方式或者色调映射的问题。

分通道检查还能帮你定位性能瓶颈。如果间接光通道开销过大,可以考虑降低它的更新频率,或者只在视角变化明显时重算。

6.3 参数调优的优先级

面对一堆可调参数,我的调优优先级是这样的:先调球谐阶数,它决定效果的上限;再调弹射次数,它影响间接光的丰富程度;然后调采样密度,它影响平滑度;最后调压缩精度,它影响内存和带宽。这个顺序的逻辑是,前面的参数决定了效果的基本盘,后面的参数更多是在基本盘上做微调。反过来调的话,容易在错误的方向上浪费大量时间。

7. 一些个人体会和后续可扩展的方向

PRT GI实践做下来,我最大的体会是:这项技术的难点不在原理,而在工程细节的堆叠。每一个环节单独看都不复杂,但把它们串起来、让整体效果和性能都达标,需要大量的试错和调整。我见过太多项目在原理验证阶段很顺利,一到实际场景就各种问题,最后不了了之。

另一个体会是,PRT不是万能的,它有明确的适用边界。静态几何体、动态光源、漫反射为主,这是它最舒服的场景。超出这个范围,要么效果打折,要么成本飙升。选型阶段想清楚这一点,比后期拼命优化要省事得多。

如果后续要继续深入,我觉得有几个方向值得尝试。一是把PRT和屏幕空间技术结合,用PRT补屏幕外的间接光,用屏幕空间补屏幕内的高频反射,两者互补。二是探索更高效的基函数,球谐函数不是唯一选择,小波基在某些场景下能用更少的系数表达更锐利的光照。三是把预计算流程自动化,和美术资产管线打通,让美术改完场景能一键重新预计算,减少程序介入的成本。

这些方向我自己也还在摸索,有新的进展再找机会分享。如果你也在做类似的事情,欢迎交流踩坑经验,很多时候别人的一个坑就能帮你省下好几天。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询