“神经光栅”这个词,最近一年在实时渲染圈子里出现的频率越来越高。不少朋友第一次听到,都以为是什么新的光栅化硬件,其实它是一条更软性的技术路线:把神经网络的推理能力,以合理的方式嵌入传统光栅化渲染管线的某个环节,让最终画面既保留光栅化的可控、稳定与实时性,又获得神经渲染才能做出来的高保真外观。这类项目做起来不难,难的是“无缝融合”这四个字——几何怎么对接、时序怎么稳定、光照怎么交互、性能怎么控住。这篇文章就围绕这个主题,把我自己实践过的底层逻辑、踩过的坑,以及一套可以直接照搬的落地方法整个梳理一遍。
这篇文章适合正在纠结 NeRF 或 3DGS 怎么接进自己引擎的图形程序员,也适合对实时渲染前沿感兴趣的技术美术和独立开发者。读完之后,你会清楚神经光栅的核心原理、方案选型、训练参数、引擎接入流程,以及常见的质量问题怎么排查。如果你只是好奇“神经渲染能不能替代传统渲染”,这篇文章也能帮你建立一个务实的判断框架。
1. 为什么非要把神经渲染和光栅化绑在一起
1.1 传统光栅化管线的天花板
先聊聊光栅化本身。它的本质,是把顶点着色的几何数据投影到屏幕并逐像素着色,整个过程快、稳定、可控,工程化程度极高。从 OpenGL 时代一路到 Vulkan、DX12,光栅化管线的“底子”已经非常成熟,GPU 厂商也围绕它做了大量硬件优化。
但它的天花板也非常明确:着色质量取决于开发者手工设计的模型,比如 PBR 里的 BRDF、全局光照里的各种近似方案。实时 GI 无非就是 SSGI、DDGI、屏幕空间反射这些技巧,都只是真实光照的“补丁”。遇到高光复杂的材质、半透明薄片、多层折射、次表面散射这类情况,传统管线的实时渲染结果往往与离线渲染判若两人。做数字人、汽车内饰可视化、建筑漫游的朋友应该都有体会,为了一个材质能实时跑出皮肤次表面散射,打磨 Shader 的时间可以按周计算。
更麻烦的是烘焙流程。烘焙光照贴图质量确实高,但场景只要一改灯光、一动家具,烘焙就得重跑。项目迭代到后期,这几乎是所有可视化团队最头疼的流程瓶颈。
1.2 神经渲染的偏科与短板
神经渲染这边,NeRF 和 3DGS 这两年的进展确实猛。NeRF 可以把一个场景训练成一个多层感知机,视角变化、反射、复杂几何都能重建得非常好,渲染质量在一段时间内是“全离线”路线里的标杆。3D Gaussian Splatting 又把速度拉到了实时,几十万高斯点用光栅化方式排序和投影,训练完就能交互式浏览,效果相当惊艳。
但这类方法也有明显短板。离线训练成本高,NeRF 一个场景动辄几十分钟到几小时,3DGS 也需要十几分钟到几十分钟。更麻烦的是场景表示不可控:你拿到的是一堆 MLP 权重或一堆高斯参数,不是干净的网格、材质、灯光,想编辑一个物体的颜色、旋转一个角色、换一盏灯,基本要重新训练。动态场景虽然也有扩展工作,但离完整动画管线还有距离。
也就是说,神经渲染擅长“外观重建”,但不擅长“可控生产”。生产环境里不光要好看,还要能改、能动、能跟引擎内的逻辑交互。
1.3 融合的出发点是数据流,不是野心
神经光栅的思路,不是用神经网络替代整个光栅化管线,而是让神经网络专注于“补传统着色模拟不了的高频细节”,让光栅化继续负责几何、深度、遮挡、排序这些确定性极强的事情。两者不是两层皮,而是共享同一坐标系、同一深度缓冲、同一相机参数。
这就像你请了一个非常擅长画材质细节的画师,但画布、透视、构图、轮廓线还是原来的摄影师出,画师只负责往画布上填充那些相机拍不出来的材质质感。边界一旦这样划分,融合就有了落点,不再是“全神经”或者“全传统”的二选一。
2. 底层逻辑:神经光栅到底融在哪里
2.1 一条管线,两个灵魂
我习惯把神经光栅画成一条串联的数据流:顶点着色器输出世界坐标和法线,光栅化阶段插值出逐像素数据,像素着色器里除了做传统的 PBR 计算,额外查询一份“神经特征”,把这个特征传给一个小型网络,网络输出最终的颜色项或者残差项。
这个特征从哪来?常用做法是预训练的哈希特征网格、低分辨率 3D 纹理,或者烘焙出的神经纹理贴图。像素着色器在世界坐标或 UV 坐标处采样特征,再送进网络。网络不需要很复杂,两三层 MLP 往往就够用,因为复杂的几何结构已经被光栅化阶段处理完了,网络只需要处理光线方向和材质细节的映射关系。
按照融合位置的不同,实际项目里有三种方案。几何端融合,把网络输出接在顶点位移或法线修改上,适合做网格形变、拓扑优化;像素端融合,这是最常用的一条路,适合材质外观建模,也就是我上面描述的主路径;帧级融合,把光栅化输出的整张图送进一个后处理网络,或者超分网络,通用性最强,但缺乏几何感知,细节容易糊。
2.2 可微光栅化:训练和推理为什么能共用一条路
神经光栅能成立,前提是训练阶段要把整个渲染过程写成可微计算图。输入是场景表示,比如特征网格的权重、高斯参数、网络权重;输出是一张图像。把渲染图像和真值图像算损失,梯度往回传,参数逐步更新,直到场景表示和网络权重都对齐了。
光栅化本身是离散操作,但离散并不完全不可微。对于网格光栅化,像素覆盖范围内,三角形重心坐标的插值关系是连续可微的;对于 3DGS,高斯点投影到屏幕上是一个连续函数,逐高斯点的 alpha 混合也是连续函数,求导非常自然。这也是为什么 3DGS 能训练得这么稳。
训练阶段的损失函数通常不只有像素重建误差。以最简单的形式为例,L = ||C_pred - C_gt|| + λ L_reg,其中 C_pred 是渲染图,C_gt 是真实拍摄图,L_reg 是几何或特征的正则项,λ 控制正则强度。正则项的加入很关键,它能约束特征网格的数值范围,避免网络退化成高频噪声生成器。
2.3 无缝融合的三个“无痕”标准
融合做得好不好,不是看单帧静态图,而是看动态交互时的整体观感。我给自己定了三个标准。
第一是几何无痕。神经模块输出的颜色,不能破坏深度和法线的一致性。常见的反面教材是特征网格空间分辨率不够,裁减边缘出现“棉花糖感”,或者远离视点的区域颜色漂移。解决办法是把特征网格的权重视为与几何绑定的属性,不要随意在世界空间中无约束地插值。
第二是时序无痕。逐帧独立推理最容易产生的问题是闪烁和抖动。神经网络的输出对输入特征的小扰动非常敏感,相邻两帧视角只动了零点几度,帧间颜色可能跳变明显。我建议训练时加入视频序列的时序一致性损失,或者推理端用时间滤波、TAA 配合,把网络输出限幅在低频变化范围内。
第三是光照无痕。如果神经模块输出的颜色是“完整颜色”,一旦场景里的定向光移动,输出不会跟着变,融合感立刻被打破。正确的做法是把灯光参数作为网络的输入条件,或者让神经输出只代表材质的固有属性(反照率、法线细节),光照计算仍然交给传统管线。这样灯动了,画面还是跟着正确变化。
3. 落地方法:从相机到像素着色器
3.1 技术路线选型:别急着跳进训练
先选路线,再谈参数。根据落地目标不同,我通常把项目分成三类,选型判断直接决定后续的工程量。
| 路线 | 场景表示 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| NeRF 蒸馏 | 哈希特征网格 + 浅 MLP | 质量高、可控性好、可编辑 | 训练周期长、硬件要求高 | 高质量离线资产实时化 |
| 3DGS + 外观网络 | 高斯点云 + 轻量网络 | 重建快、实时性好 | 场景难编辑、光照交互困难 | 快速重建、沉浸式浏览 |
| 自定义可微光栅 | Mesh + 神经纹理 | 与现有资产兼容、可动画 | 工程量大、调试成本高 | 数字人、可编辑产品展示 |
如果你手里已经有干净的美术模型,只是想要更真实的外观,第三类路线最合适,因为网格骨骼动画、材质分层都是现成的,只要把神经纹理接到像素着色器上。如果你是对真实场景做快速数字化展示,3DGS 路线性价比更高。至于 NeRF,我一般只用来做离线蒸馏的“老师网络”,把大模型的知识蒸馏到实时可跑的浅网络和低分辨率特征网格里。
3.2 数据采集和预处理:烂数据喂不出好模型
神经光栅对数据的依赖程度远超传统渲染。我见过很多项目翻车,不是网络结构不行,而是采集数据本身有硬伤。
采集时注意几个点。尽量用固定光圈、快门、ISO,避免光源闪烁造成训练目标不一致;每两个拍摄角度之间保持 70% 以上的重叠率,给后续特征匹配留足空间;避免画面中出现高光高亮的镜子、玻璃和动态物体,这些区域在特征匹配阶段会造成极大的误差;拍摄分辨率建议在 2K 到 4K 之间,太低了训练不出细节,太高了 COLMAP 的特征匹配会非常慢。
COLMAP 是预处理的核心工具,我常用的是特征提取、特征匹配、稀疏重建三步。命令行大致是这样:
colmap feature_extractor --database_path project.db --image_path images colmap exhaustive_matcher --database_path project.db colmap mapper --database_path project.db --image_path images --output_path sparse跑完 mapper 之后,检查一下重建出的点云数量和相机位姿分布。正常情况应该出现密集且连续的点云,相机轨迹平滑包围场景。如果某些区域点云稀疏,大概率是拍摄时覆盖不够或者纹理重复度过高。这一步千万别省钱,稀疏重建的质量直接决定神经光栅的几何边界,几何一塌糊涂,后面再强的网络也救不回来。
3.3 训练与蒸馏:参数背后的逻辑
训练阶段的参数配置,有一个容易忽略的底层逻辑:你不是在“训练一个模型”,而是在“对齐一份场景表示”。所以超参要根据场景复杂度和数据量调整,不能照抄论文里的数字。以下是我在多个项目里验证过的基准配置。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 优化器 | AdamW | 比 Adam 更稳,权重衰减更干净 |
| 学习率 | 初始 1e-3,余弦衰减 | 前 1000 步做 warmup,防止早期震荡 |
| 迭代次数 | 5000 ~ 30000 | 场景越小越简单,迭代可以越少 |
| 批大小 | 4096 条光线/样本 | 显存吃紧时降到 2048,效果差距不大 |
| 正则项权重 | 1e-6 ~ 1e-4 | 数值梯度爆炸时优先调这里 |
| 特征网格分辨率 | 16 ~ 512 多级哈希 | 分辨率越高细节越好,但显存暴涨 |
| 浅 MLP 结构 | 2 层,每层 128 神经元 | 实时推理的甜点区间,再深会爆延迟 |
训练完并不是直接就能上引擎。因为全分辨率特征网格加全精度的 MLP,在像素着色器里逐像素推理是撑不住实时帧率的。我通常还要做一次蒸馏:把大模型的输出作为监督信号,训练一个小网络或者低分辨率纹理,然后把小网络量化为 FP16 甚至 INT8。
蒸馏时有个细节:不要只压网络宽度,要配合特征网格一起降级。你可以把降级方案看成“信息预算”,网格分辨率负责高频空间信息,网络宽度负责材质映射精度,两者必须一起压缩,否则要么画面糊,要么特征稀疏导致闪烁。
3.4 引擎侧接入流程:从离线文件到实时画面
模型训练完,输出是一坨特征网格权重和一组神经网络权重。接入引擎时,要先把这些数据转成 GPU 友好的格式。特征网格我会导出成 3D 纹理或者 Buffer,三线性采样交给硬件;网络权重导出成推理引擎的模型文件,或者直接转成常量数组,在 CUDA 里手写推理。
渲染管线的改造并不复杂,核心是在像素着色器里加一段特征查询和网络推理。用 GLSL 表达思路大致是这样。
// 片段着色器伪代码 vec3 pos = worldPos; vec4 feats = texture(neuralGrid, pos); // 查询特征网格 vec3 neuralColor = neuralMLP(feats, lightDir); // 小型网络推理 vec3 baseColor = albedo * shadow * ambient; // 传统光栅化光照 vec3 finalColor = baseColor + neuralResidual; // 残差融合注意这里我用的是“残差融合”。神经模块不要直接输出最终颜色,而是输出一个残差项叠加在传统 PBR 结果之上。这样做的好处是,即便神经推理出现异常,基础渲染结果还在,不至于整块画面发黑发飘。这也是实现“无缝”最实用的工程技巧。
推理环节,工程上有三种做法。小网络直接写在像素着色器里,每次像素算一遍,适合极小的网络;计算着色器批量推理,先把特征打包成一张 UAV 纹理,再并行推理,适合 2 层 MLP 以上的网络;外部推理引擎,比如 TensorRT 或 ONNX Runtime,适合更复杂的网络,但会增加一帧拷贝延迟。
我测过的典型性能预算是这样的:1080p 分辨率下,特征网格查询加 2 层小 MLP 推理,大约 2 到 4 毫秒,配合传统 PBR 光照和后处理,总帧耗能控制在 8 到 10 毫秒左右,接近 100 到 120 FPS。
3.5 加速技巧:别把 GPU 算力花在没用的像素上
神经推理是逐像素的,但实际上并不是每个像素都需要全精度的神经输出。我在工程里用了三个加速技巧,效果立竿见影。
第一个是裁剪区域。神经特征只在画面中心区域有效,边缘低置信区域直接用传统 PBR 兜底。判断方法很简单,用相机方向和特征网格有效范围的射线求交,在屏幕空间生成一张低分辨率 mask,再上采样做边界羽化。
第二个是提前退出。对平坦、无细节的区域,网络输出和传统 PBR 差异非常小,可以用特征网格的“响应强度”作为置信度,低于阈值就直接走原 PBR,省下大部分像素的推理开销。
第三个是时域复用。把上一帧的网络输出缓存起来,当前帧只有相机或灯光变化超过阈值时,才在局部区域重新推理,其他区域直接复用旧结果。配合 TAA,视觉上几乎看不出差异,但推理开销能下降一半以上。
4. 性能与质量:怎么才叫真正的“无缝”
4.1 为帧预算算一笔账
落地之前,一定要把帧预算拆清楚。以 60 FPS 为基准,一帧只有约 16.6 毫秒。我会按下面的方式拆。
| 渲染阶段 | 预估耗时 | 说明 |
|---|---|---|
| 几何光栅化 | 2 ms | 模型复杂度决定 |
| 深度与法线生成 | 1 ms | GPU 带宽主要在顶点和深度缓冲 |
| 特征网格查询与网络推理 | 2 ~ 4 ms | 2 层 MLP、FP16 |
| 传统 PBR 与后处理 | 3 ~ 5 ms | AO、阴影、AA |
| 总计 | 8 ~ 12 ms | 有约 4 ms 余量给玩法或物理逻辑 |
如果你的目标是 VR 的 90 FPS,那神经模块预算就要压到 1 到 2 毫秒,网络得砍到 1 层、特征网格降到 INT8;如果你的目标只是离线预览,那不用太抠性能,可以把网络加到 4 层换更高画质。预算分配决定了整个技术方案的激进程度,这个表建议在项目启动第一天就定下来。
4.2 质量度量:PSNR 只是及格线
很多新入行的朋友只看 PSNR,但 PSNR 是逐像素误差的平均,它根本反映不了“闪烁”“跳变”“边缘破裂”这类时域问题。我自己的质量评估体系分四层。
| 指标 | 看什么 | 注意点 |
|---|---|---|
| PSNR | 全局亮度误差 | 容易被局部大误差带偏 |
| SSIM | 结构相似性 | 对模糊不敏感,容易高估画质 |
| LPIPS | 感知相似性 | 更接近人眼,推荐重点参考 |
| 时序抖动指标 | 帧间差分能量、闪烁频率 | 这是上线前最容易翻车的一层 |
时序测试我建议做成一个固定脚本:相机沿着一条预先设定好的轨迹来回扫动,场景里的定向光同时缓慢旋转,录制 30 到 60 秒视频。然后逐帧看一遍,重点观察材质接缝、高光边缘、半透明区域有没有跳变。很多项目静态图渲染非常出色,一上轨道镜就露馅,这类问题用 PSNR 是测不出来的。
5. 常见问题与排查实录
5.1 画面高频闪烁
这是神经光栅项目里最典型的问题。表现是镜头静止时帧和帧之间还会出现微弱的颜色跳动,高频区域尤其明显。我看过的大部分原因,一是特征网格分辨率过高,网络学习到了训练集里几乎不会重现的高频伪影;二是训练时没有做时序一致性约束;三是推理端没有做时间滤波。
处理顺序我建议是:先把特征网格分辨率降一级,重新训练一轮,看闪烁是否缓解;如果还闪,在推理端加一个轻量级时域滤波,比如alpha 混合上一帧结果;再配合 TAA,可以压到视觉不可感知。这一套组合拳能解决 80% 的闪烁问题。
5.2 训练不收敛或梯度爆炸
在水面折射、半透明材质、极暗场景里,训练经常出现 loss 突然拉高或者直接 NaN。最先检查的一定是学习率,其次检查损失里是否有极端值被带进来,比如某个光线采样到纯白高光,数值直接飞掉。处理办法是给像素误差加一个截断阈值,超出一定范围就 clamp 掉,同时对特征网格权重做谱归一化或 L2 约束。
如果是几何初始化很差导致的震荡,不妨先冻结网络,只训练特征网格,跑通几何对齐后,再解开网络训练。分层训练虽然不是最省时间的方式,但定位问题会快很多。
5.3 显存溢出和内存碎片
特征网格如果全分辨率常驻显存,很容易把一个 24G 卡吃满。我的经验是分成两部分:训练时用梯度检查点,把特征网格切块,一次只训练一部分;推理时只加载当前视角范围内的特征块,远离视锥的块直接卸载。这两个技巧可以把常驻显存砍掉一半以上。
还有一个容易忽略的坑,是推理用 TensorRT 做 FP16 时,有些层的 intermediate tensor 会在显存里临时分配,帧率不稳定。解决方案是先跑一次空转预热,把显存分配稳定下来,再用 CUDA Graph 固化推理流程。
5.4 神经模块与传统 PBR 资产观感不一致
最常见的原因就是颜色空间没对齐。神经模块在 sRGB 空间训练,而渲染管线在线性空间计算,直接乘在一起结果就是整体发灰发白。我强烈建议在训练时就把特征和输出定义在线性空间,输出后在管线末尾统一做色调映射。另一个原因是神经输出被当成“最终颜色”而不是“残差增量”,这会让 PBR 的高光、阴影完全失去作用,观感必然割裂。
调这类问题,我有个百试百灵的小工具:把神经网络的输出单独渲染成一张伪彩色图。如果是颜色趋于单调紫色,说明网络只学到了平均亮度;如果是五彩斑斓高频噪声,说明特征网格和网络在互相放大梯度。这种可视化定位,比盯着一堆数值高效得多。
我在实际项目中最大的体会是,神经光栅并不是“取代光栅化”,而是给光栅化这栋老房子接上新的水管和电路,结构还是原来的,功能却完全不一样。融合的成败,往往不取决于网络有多强,而在于边界选得是否干净。建议不要一开始就追求全场景、全时段的神经渲染,而是从一个材质、一盏灯的增量替换开始,跑通特征查询、网络推理、残差融合这条数据流,再逐步扩大覆盖范围。
最后再分享一个小技巧。调试神经光栅时,把神经输出独立渲染成彩色通道图,配合“静态帧 + 动态序列”两种模式交叉检查,能帮你快速区分问题是出在特征生成、网络推理,还是光栅化端的管线交接。这个习惯帮我省掉了大量无效排查时间,希望对你也有用。