尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

轻量级注意力机制实现实时间接光照:原理、落地与踩坑实录

轻量级注意力机制实现实时间接光照:原理、落地与踩坑实录 如果你做图形渲染相关开发一定被间接光照折磨过。离线渲染里它是无数采样堆积出来的收敛时间实时渲染里它是各种 Hack 方案的斗兽场Lightmap 只能管静态场景Irradiance Probe 在遮挡复杂的地方漏光漏到怀疑人生SSAO/SDF 一类方案又只能给个“氛围感”离真正的多次弹射差得远。我在做项目时一直在找一个平衡点既要有路径追踪级别的准确性又要能在普通消费级显卡上实时跑起来。后来尝试的方案是把轻量级注意力机制直接用在间接光照的推断上。这个思路听起来像“拿 Transformer 做 GI”但实际上它跟那种动辄几十亿参数的视觉大模型完全是两码事。核心想法很简单把场景里稀疏分布的光照探针当成一组 token用注意力机制去计算每个着色点应该“采信”哪些探针的信息再结合可见性和表面特征做加权合成。它解决的核心问题是在遮挡复杂的场景里如何聪明地聚合附近探针的光照贡献而不是像传统方案那样用固定核函数加权重结果经常穿墙漏光。这篇文章我会从原理、架构设计、训练部署到踩坑实录完整讲一遍我落地这个方案的过程。适合对实时光照渲染有一定了解、想尝试用神经网络改进 GI 效果的技术美术和图形程序也适合离线渲染方向想了解注意力机制怎么落到具体问题上的读者。纯美术向的朋友可以直接跳到第 3、4 节看参数与效果调优。1. 从光照开销说起间接光照为什么需要“注意力”1.1 间接光照的经典实现和痛点间接光照的本质是光的多次弹射。想象一个房间阳光从窗户进来打到地板上再弹到天花板再弹到墙面阴影处这些二次以上的传播就是间接光照。物理上完美的做法是对每个着色点采样无数条随机光线追踪它们打到哪些物体、被反射了多少能量也就是路径追踪。这条路在离线渲染里已经完全成熟但在实时渲染里每像素每帧几十上百条光线根本跑不动。于是业界发明了各种加速策略。最古老的是 Lightmap把间接光照烘焙到纹理上静态场景效果好但只要物体一移动、灯光一变化就全废。Irradiance Volume / Light Probe 是另一种思路在场景里摆一堆球状探针记录每个球位置附近从各个方向来的平均辐射度运行时按位置插值。这个方案的问题是“平均”两个字探针之间如果有遮挡插值就会把不该互相看到的两块区域混在一起表现就是墙角漏光、暗部泛白。我在一个室内项目里遇到过特别典型的案例一堵 30 厘米厚的墙体墙两侧各放了一个探针墙体右侧是一盏强光。Irradiance Probe 插值之后墙左侧阴影区的亮度凭空高出 30%。这就是遮挡信息缺失的直接后果。后来想到能不能让“插值”更聪明一点让每个着色点自己决定“我应该相信哪些探针”。这不是标准的数学插值能解决的更像是一个匹配和筛选问题。这正是注意力机制擅长的领域。1.2 注意力机制在 GI 中的角色定位注意力机制最早是机器翻译里用来建模词与词之间关系的给定一个查询Query在所有的键Key里找到最相关的几个用这些相关性的权重去加权对应的值Value。放到 GI 场景里Query 就是当前要渲染的着色点的状态Key 是场景里每个探针的位置和朝向信息Value 是探针上记录的光照数据。关键区别在于传统插值是固定的、平滑的、按距离衰减的注意力插值是数据驱动的、动态的、按语义相关性筛选的。一个探针离你 1 米但中间隔了一堵墙传统方案照样给它一个不小的权重注意力机制学了大量遮挡关系之后会倾向于把这种高相关但不可见的探针权重压低。我第一次跑通这个思路的雏形时看到的效果比预期好很多。用一个只有 400 万参数的小网络把场景中 512 个探针的权重重算了一遍之前漏光的墙角和柱子阴影区域全部正常了。那一刻我意识到真正的价值不是“用了个新架构”而是找到了一个 结构化场景匹配的正确表达方式。1.3 轻量化的含义轻量级注意力有一个明确前提不能用标准 Transformer 那一套。标准 Transformer 把全场景所有探针当 token 做全局两两交互计算量是平方级。512 个探针看起来不大但每个 token 都要和所有 token 计算注意力512 的平方乘上 64 维特征甚至更高在 GPU 上跑起来就已经很吃力了更别说画面里每个像素都要跑一次这样的推理。轻量化的关键思路有三个局部窗口、低秩近似、浅层网络。局部窗口是最直观的——一个着色点在物理上只可能被周围几米范围内的探针影响远的探针贡献趋近于零强行算全局注意力没有必要。低秩近似是把 QK 点积换成低秩映射把计算复杂度从 O(N²) 降到 O(N)。浅层网络则是只用 2 到 3 层注意力不搞十几层堆叠。后面第 2 节我会详细拆这套架构的具体设计。这里想强调一句我的判断在 GI 这个问题上注意力机制的价值不在“多深的网络”而在“如何定义 token 与 token 之间的关系”。结构选对了小网络也能出大效果。2. 核心设计轻量级注意力机制的架构拆解2.1 数据表示与特征编码任何注意力模型第一步都是把输入变成 token。GI 场景里 token 就是探针。每个探针需要携带四类信息空间位置xyz、朝向法线法线方向、物理属性粗糙度、反照率、光照状态入射辐射度近似。这四类是让模型学会“光照传播规律”的最小必要信息。具体到特征编码我做过几轮实验。最直接的做法是把位置、法线原样作为连续值丢进网络效果也能跑但收敛很慢因为网络需要自己学会从坐标到空间关系的映射。后来参考了 NeRF 的做法对位置和方向做高频编码先用 sin/cos 函数把它们映射到多频段的特征空间再拼进网络输入。这个做法的好处是让网络在高频几何变化大的区域比如墙角、柱子边缘更容易拟合出锐利的光照过渡。光照状态我用的是一组二阶球谐系数9 个浮点数就能表达低频辐射分布足够平衡准确性和输入维度。如果你追求更高品质也可以对每个探针存一个 6x6 的入射辐射度采样图但网络输入会明显膨胀训练和推理都会变慢。我后来实际项目里是“球谐打底 局部细节残差”两个分支球谐分支负责低频残差分支负责补充小范围遮挡细节效果和纯高分辨率辐射图基本无异但计算量小了 5 倍以上。2.2 注意力计算流程整个推理流程分三个阶段。阶段一是 token 化渲染器把场景中的探针数据组织成一个 NxK 的张量N 是探针数量K 是特征维度。阶段二是注意力计算把当前着色点的特征作为 Query把所有探针的特征作为 Key计算一个相关性权重再把这权重作为 softmax 归一化后的系数去加权 Value。阶段三是回归出最终的间接光照值。注意力计算的标准公式是Attention(Q,K,V) softmax(QK^T / √d) · V。d 是特征维度除以它的平方根是为了让点积结果不会太大导致 softmax 梯度消失。在我这个场景里Q 是 1xK因为一次只处理一个着色点K 是 NxKV 是 NxDout。Dout 是输出维度通常就是间接光辐照度的球谐系数维度。这个过程中有一个我踩过坑的细节归一化。很多初版实现直接在 softmax 上做归一化但零遮挡的开放区域里所有探针权重均匀softmax 会把每个权重压得很小导致结果偏暗。我的做法是分两步先按距离计算一个物理先验权重再让注意力去学习一个修正系数两者相乘后再归一化。这样既保留了物理规律又让模型有足够的自由度去调整。2.3 轻量化策略对比与选型这个表格是我在多个方案实测后的对比结果不同项目硬件条件不同选型也会有差异方案计算复杂度内存占用遮挡处理能力推荐场景全局注意力O(N²)随探针数平方增长强探针极少的离线烘焙场景局部窗口注意力O(N·w)线性较强实时渲染室内为主线性注意力O(N)线性中等探针密度很高的大场景低秩注意力O(N·r)线性较强高性能预算有限的平台稀疏注意力O(N·k)线性强遮挡复杂的建筑可视化我最终在主力项目中选了“局部窗口 低秩近似”的组合原因很实际局部窗口把每帧的有效 token 数从几千降到了 64 个低秩近似让每个 token 的计算量进一步减半。这两个策略叠加之后整个网络单帧推理时间在 RTX 2060 上约为 1.8 毫秒在 PS5 级别的主机上也只需要 1.2 毫秒左右。注意选择局部窗口时窗口半径不能拍脑袋定。我在第一个项目里设了 2 米结果大场景里探针密度不够窗口内经常只有两三个探针注意力几乎没有选择空间。后来改成“自适应半径”先根据当前着色点周围的探针分布密度反推半径保证窗口内至少有 16 个探针。这个经验值非常重要。2.4 为什么不用 CNN 或者纯 MLP很多人会问光照插值本质上是一个空间问题为什么不直接上卷积原因是探针布局通常是不规则的——场景里遮挡密集的地方探针多空旷的地方探针少甚至动态物体周围的探针是实时更新的。CNN 需要在规则的栅格上操作处理这种不规则分布必须先插值到规则网格这一步等于又绕回了传统方案的老问题。纯 MLP 也有问题每一个输入维度的顺序是固定的模型没有机制去区分“这个探针离我更近”和“这个探针离我更远”。注意力机制的关键区别在于它的权重是动态计算的。同一个探针在 A 着色点看来可能权重很高在 B 着色点看来可能接近零这是静态的全连接层做不到的。3. 实操从训练到部署的全流程3.1 训练数据采集与管理训练数据是这类方案最难、但决定效果上限的部分。我的训练管线分三步用离线光线追踪渲染一批基准场景导出每个像素的间接光照真值同时导出该场景的探针布局和特征信息把两者配对成训练样本。真值数据用 Mitsuba 和 Cycles 都可以。我建议至少包含这几种场景类型室内房间遮挡多、光照层次多、半开放走廊一次弹射和多次弹射并存、室外带檐口有遮挡但有天空光泄漏。每种场景渲染 200 到 500 帧帧与帧之间用不同相机随机游走确保模型见过足够多样的视角。数据量方面我的一个经验值是 5 万帧左右基本够一个中小型项目使用。每帧输出分辨率不需要高256x256 足够但探针方向的分辨率要够我一般用 6 个朝向 x 6 个朝向即每探针 36 个方向做辐射度采样。这个设置可以在准确性和数据体积之间取得较好平衡。3.2 特征结构与损失函数设计模型的输入特征我整理成了一条固定顺序的特征向量位置编码 24 维位置 xyz 各用 8 维 sin/cos 编码、方向编码 24 维、粗糙度 2 维、反照率 3 维、球谐辐射度 9 维。总共 62 维。每维都做归一化到 [-1, 1]避免个别维度尺度过大主导训练。损失函数上第一版只用了 L2 损失虽然收敛快但容易“机灵过头”——在亮部区域预测得特别准暗部直接压到全黑。后来改成 L1 相对损失 余弦相似度两项的组合。相对损失是预测值和真值除以真值的商保证暗部也有足够的学习信号余弦相似度用于控制预测的球谐方向分布与真值一致而不是只关注数值大小。具体训练配置可以抄我这份Adam 优化器学习率 3e-4 起步每 20 个 epoch 衰减一次衰减系数 0.6。Batch size 32每个样本包含 256 个随机采样点。训练 150 epoch 大概需要 8 个小时单张 RTX 3090loss 可以降到 0.02 以内再往下就比较难了效果也足够用。3.3 工程部署上的算力优化路径训练好模型之后真正的挑战在推理阶段。我在工程落地时做了三层优化第一层是探针管理。场景里探针数量控制在 256 到 512 个之间每帧只更新可见范围内的探针数据远离摄像机的探针用上一帧的缓存。这个优化直接把 80% 的重复计算去掉了。第二层是结构剪枝。原始模型有 3 个注意力层、每层 4 个头共 180 万参数。剪枝后降为 1 层 2 头参数变成 60 万精度损失在 2% 以内。关键是 attention 层的隐藏维度从 128 降到 64输出量级没有变因为间接光照本身就是低频信号高维度主要贡献在遮挡边界而遮挡边界用位置编码就能表达。第三层是量化。我们最终的发布版用了 FP16 量化精度下降可以忽略。如果跑在移动端还可以做 INT8 量化但需要在训练时加入伪量化操作Quantization-Aware Training否则直接后训练量化会在亮度跳变的边缘区域出现明显噪点。提示部署时一定记得把训练时的随机性全部关掉。Dropout 归零、BatchNorm 换成固定均值方差。这些细节我踩过很深第一版部署时忘了关 Dropout推理结果在同一帧画面上闪得像霓虹灯排查了一整天才定位到这个坑。4. 常见问题与排查技巧实录4.1 漏光和暗部噪声遮挡信息的关键作用漏光是注意力机制 GI 最容易被骂的问题。漏光的根源在于模型把“两个空间相近但相互不可见”的探针赋予了过高权重。我遇到过一个极端案例一堵很薄的天花板楼上的强光透过地板漏到了楼下房间整个屋顶发白。排查过程让我发现了一个现象仅靠位置和法线特征模型很难学会“隔一堵墙就不该采信”。后来我在输入里加了三个额外特征当前着色点到探针的连线是否被遮挡一个标量、遮挡物的视觉厚度透射率估计、以及两者之间的垂直空间关系。加了这三个特征后漏光问题基本解决。暗部噪点则是另一个方向的坑。暗部区域光照值很小信号本身弱模型倾向于学一个“安全的小数值”结果就是死黑一片或者大面积噪点。我的解决方法是训练时对暗部区域加大采样权重让模型在暗部也有足够多的监督信号。另外推理阶段对预测值做了一个软下限防止数值降到 0 以下产生 NaN。4.2 闪烁与时间稳定性实时渲染里闪烁比漏光更让人头疼。注意力权重对位置非常敏感相机一抖动权重分布就可能剧烈变化导致同一面墙上出现明显的“游动亮斑”。处理这个问题我试过很多方法最终有效的是两个组合空间上做多帧超采样时间上做混合缓存。空间上把单帧推理生成的结果放在一个低分辨率缓冲里然后按高斯权重扩散到周围像素缓冲分辨率一般是屏幕分辨率的四分之一。时间上把上一帧的结果做重投影与当前帧结果按帧间隔比例混合。这样处理后闪烁降低到了可以接受的程度但注意不要混合过度室内快速运动的物体旁边会出现拖影。我的调参经验是混合系数取 0.2 到 0.4 之间运动越剧烈系数越低。4.3 训练和推理效果不一致的排查训练时 loss 下降得很好看到引擎里一跑就废这个问题我至少遇到三次。最典型的两个原因分别是数据分布不一致和随机操作残留。数据分布不一致是指训练时采样的场景探针密度和运行时实际场景不一样。我有个项目训练数据里探针间距平均 0.8 米运行时为了性能把间距拉到了 2 米结果模型在推理时对“探针距离远”这个分布外情况完全没有鲁棒性。解决办法是在训练时随机删掉 20% 的探针相当于做了数据增强让模型适应不同密度的分布。随机操作残留就是前面提过的 Dropout。排查方法很简单用一个固定输入跑两次推理如果输出不一样就一定还有没关干净的随机项。4.4 问题速查表现象可能原因解决方案墙角漏光严重遮挡特征缺失添加可见性标量与遮挡厚度输入暗部偏黑、无层次L2 损失主导换 L1相对损失暗部加大采样权重画面闪烁注意力权重高频抖动低分辨率缓存 时间混合物体运动拖影时间混合系数太大运动区域压低混合系数或加运动向量自适应大面积颜色偏灰探针密度太低增加探针数量或缩小局部窗口半径训练正常、部署发疯Dropout/BN 残留切换 eval 模式固定归一化参数高光区域溢出球谐阶数太低用二阶球谐 局部残差分支动态物体阴影不准探针未实时更新对动态物体生成独立探针集5. 我的几点体会和后续扩展方向这个方案从想法到落地前后折腾了将近四个月回头看最花时间的不是训练模型而是设计一个适合注意力机制处理的场景表示。探针布局、特征选择、数据管线这些看起来“不性感”的工程问题才是决定最终效果的关键。用一句话总结我的经验注意力机制在光照问题上的优势不是因为它叫“注意力”而是因为它提供了 一种让网络自行决定空间聚合权重的方式。实测下来有两点值得分享。一是模型规模真的不需要很大我们最终的轻量版只有 18 万参数推理延迟不到 1 毫秒但漏光和闪烁指标比原来的 Irradiance Probe 方案好了几个量级。二是在做你 自己的产品时不要把网络当成万能黑盒把物理知识注入到输入特征里效果提升比加深网络明显得多。后续我想做两个方向的扩展一是把时域信息直接建模进注意力权重里从根本上解决动态场景残留和闪烁而不是靠后处理混合去补二是把轻量级注意力机制接入 ReSTIR 或者实时光线追踪的降噪管线的中间层用它来决定哪些采样结果可信度更高。目前第二个方向已经有初步实验在噪点更密集的焦散场景下注意力选择的采样点质量明显优于固定权重的方案。
返回列表