深夜的城市,霓虹灯在湿漉漉的柏油路上拖出长长的倒影。一个像素在屏幕上“报案”:我看到的这块颜色从哪里来?传统的光栅化渲染就像一位快手画师,不管光的来路,直接按几何位置把颜色涂上画布——画得快,但画不出玻璃幕墙上那抹真实的霓虹倒影。而光线追踪,则像一队纪律严明的光侦探:从你的眼睛(相机)出发,沿着光的来路逐级追查,看这条光线究竟撞上了哪面墙、哪扇窗,最后又来自哪盏灯。听起来很完美,但侦探们很贵:一条光线要和场景里几百万个三角形逐一核对“你有没有挡我的路”,一帧画面要派出上亿条光线,普通 CPU 早就累趴了。于是,聪明的“警局”发明了三大件:加速结构(相当于城市分区地图册,快速锁定嫌疑区域)、专用硬件(相当于警用追踪仪器,把求交计算做成电路)、降噪算法(相当于证词筛选员,把采样不足的颗粒噪点还原成干净画面)。游戏引擎把这三大件组装成一条混合渲染管线:光栅化快反小组画大样,光追精英小组补反射与阴影细节,让《赛博朋克2077》这样的游戏既有物理正确的光影,又能跑满高帧率。本文就用这套“侦探调查”的故事,带你拆解实时渲染中光线追踪的完整工作流。

岗位关联

理解实时光线追踪,等于掌握下一代渲染引擎的“光语言”——无论是做游戏引擎、图形算法还是 AI 重建方向,这都是从“调参师”升级为“渲染架构师”的关键一步。

目录
  1. 可视化演示
  2. 光线追踪原理:侦探逆推光的来路
  3. BVH加速结构:城市分区地图册
  4. RTX硬件加速:警用追踪装备
  5. 混合渲染管线:联合办案模式
  6. 降噪算法:证词筛选与消息还原
  7. 游戏实践应用:赛博朋克2077全城查案
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示光线追踪原理(主光线+阴影光线+反射)、BVH加速结构光线遍历裁剪、混合管线降噪(噪声图像到清晰图像)三大核心技术。

本节配有交互动效(进入视口后自动加载,约 3KB)。

光线追踪原理 主光线从相机出发击中物体表面 绿色阴影光线检测遮挡 L=Le+kd*I*max(0,n*l) 反射光线递归追踪实现镜面反射效果

光线追踪原理:侦探逆推光的来路

深夜的警局接到了一个诡异的报案:屏幕上一个像素闪烁着诡异的红光,它想知道“我是从哪里来的”。传统光栅化画师会直接翻看几何台账,按投影位置涂上颜色——快,但根本不在乎光在到达眼睛前经历了什么。而光线追踪的办案思路截然相反:侦探不从光源出发,而是站在你的眼睛(相机)位置,沿着光的来路逆向排查。为什么要倒着走?因为如果顺着光源发射光子,绝大多数光子根本不会射进你的眼睛,等于在浪费警力;反向追踪只派发“有意义的线索”——每个像素发一条光线,只追查对最终画面有贡献的光路。

从数学上看,一条光线由两个要素决定:起点和方向。渲染器为每个像素生成一根“调查线”,与场景中的几何体逐一求交;一旦命中物体表面,就根据材质决定下一步——是反射、折射,还是直接连到光源判定阴影。如果在镜面上命中,就递归地继续追查反射方向,形成一条完整的光路链。路径追踪(Path Tracing)则是这条思路的终极形态:每个像素持续随机弹射光线,像侦探反复向不同方向走访目击者,用大量随机样本逼近真实的颜色。

侦探每走一步都要回答“朝哪走、走多远”——方向和距离定了,调查范围就定了;光线方程正是这个“办案指引”,而解出的 t 就是那条“嫌疑人与侦探之间的距离”。

P(t)=O+t D,t>0\displaystyle \mathbf{P}(t) = \mathbf{O} + t\,\mathbf{D},\quad t > 0
光线方程说明:任意一条光线上的点,都可以用“起点 + 距离 × 方向”表达,t 是唯一的未知量,求交就是解出这个 t。
  1. P(t) 光线上的点表示光线传播到参数 t 处的位置,是这条光线与几何体求交时最核心的输出量。
    P(t)\mathbf{P}(t)
  2. O 起点光线发射的原点,通常取相机位置或上一个命中点,代表侦探当前所在的位置。
    O\mathbf{O}
  3. D 方向单位方向向量,决定光线朝哪个方向追踪,对应侦探下一步前往的方位。
    D\mathbf{D}
  4. t 距离参数等式限定 t 大于 0,只保留起点前方的交点,避免把身后的物体误算成“嫌疑人”。
    t>0t > 0

BVH加速结构:城市分区地图册

如果侦探每次办案都要挨家挨户敲门,把全城几百万户人家全部问一遍,那案子(一帧画面)还没破,天都亮了。聪明的警局准备了一本“城市分区地图册”:先把整座城市按街区划分,每个街区用一个大的包围盒框起来;再细分到小区、单元楼,每一层都用更小的盒子精确定位。办案时,侦探先判断嫌疑人可能在哪个大区,直接跳过不相干的区域,再逐层缩小范围,最终只核对最可疑的几个门牌。

这本“地图册”在图形学里就叫 BVH(Bounding Volume Hierarchy,层次包围盒结构)。它把场景里的三角形用轴对齐包围盒(AABB)逐层包裹,构建成一棵二叉空间树。求交时从根节点自上而下搜索:如果光线连某个节点的包围盒都碰不到,那这个节点下的整棵子树直接“剪枝”跳过,绝不多问一句。构建 BVH 常用的策略是“最宽轴分割 + 中位数分裂”——每次沿空间跨度最大的轴把物体对半分,让树尽量平衡,保证搜索路径最短。

有了地图册的侦探,处理千万人口的大案也游刃有余;没有它,每条光线都得敲遍全城每一扇门,画面早就卡成了幻灯片。

TBVH 遍历=O(log⁡n)≪T暴力求交=O(n)\displaystyle T_{\text{BVH 遍历}} = O(\log n) \ll T_{\text{暴力求交}} = O(n)
复杂度说明:暴力求交要检查 n 个物体,而 BVH 借助树形结构每次裁剪一半空间,把代价压到对数级——这正是“地图册”节省时间的数学本质。
  1. n 场景物体数量指场景中三角形或球体的总数,决定暴力搜索的起步代价。
    nn
  2. O(log n) BVH 遍历代价每层搜索砍掉一半候选区域,树高大约为 log n,所以总代价是对数级。
    O(log⁡n)O(\log n)
  3. O(n) 暴力求交代价逐一遍历全部物体,代价与物体数量成正比,场景越大越不可行。
    O(n)O(n)
  4. ≪ 远小于表示两个量级之间数量级的碾压差距,是 BVH 存在的全部意义。
    ≪\ll

RTX硬件加速:警用追踪装备

地图册再精细,侦探要是靠心算逐户比对,效率还是上不去。于是警局配备了专用追踪仪器——一颗颗小型“定位雷达”,把“光线会不会撞上包围盒/三角形”这种高频机械运算直接做成硬件电路,按下按钮毫秒级出结果。这就是 NVIDIA 从 Turing 架构开始在 GPU 里集成的 RT Core(光线追踪核心)。

RT Core 干的事非常纯粹:硬件加速 BVH 遍历与光线-三角形求交。软件模拟一次求交可能要执行几十条指令,而专用电路在几个时钟周期内就能返回“命中/未命中”以及命中距离。完整的 RTX 工作流分四步:先用加速单元构建 BVH,然后发射光线,RT Core 在硬件层面遍历 BVH 并返回命中信息,最后交给着色器程序处理命中点的材质与颜色。与此同时,GPU 里的 Tensor Core(张量核心)并行负责 DLSS 超分辨率等 AI 重建任务,一个管“光的物理”,一个管“画面的智能修复”,两个团队协同把算力缺口补上。

装备加持后,侦探每帧可以派出数百万条光线而不会超时;这正是光线追踪从“概念演示”变成“游戏默认选项”的历史转折点。

Speedup=T软件求交TRT Core 求交≈10∼30×\displaystyle \text{Speedup} = \frac{T_{\text{软件求交}}}{T_{\text{RT Core 求交}}} \approx 10 \sim 30\times
加速比说明:同一批光线求交运算,RT Core 比纯软件快约 10 到 30 倍,这个量级把“离线渲染”变成了“实时渲染”。
  1. Speedup 加速比衡量硬件替代软件后性能提升的倍数,是评估 RT Core 价值的核心指标。
    Speedup\text{Speedup}
  2. T\_软件求交光线与场景求交全部由 GPU 通用计算单元执行所消耗的时间,通常是几十条指令的累积。
    T软件求交T_{\text{软件求交}}
  3. T\_RT Core 求交同一任务交给专用电路完成的时间,一个周期内即可判断包围盒是否被穿过。
    TRT Core 求交T_{\text{RT Core 求交}}
  4. 10~30 倍实测经验量级,说明专用硬件把最贵的机械运算变成了“按一下按钮”的成本。
    10∼30×10 \sim 30\times

混合渲染管线:联合办案模式

让全城侦探都去逐帧重查所有案件,经费肯定爆炸。警局最终决定“联合办案”:光栅化快反小组动作快、覆盖面广,负责整体轮廓和直接光照;光追精英小队只在关键环节出手,专攻反射、阴影、全局光照这些“疑难杂症”。两边各取所长,最后把两份报告合并成一张定案照——这就是混合渲染管线(Hybrid Rendering)。

具体到一帧画面,光栅化首先渲染出 G-buffer(几何、法线、深度、材质等“现场基础信息”),这一通道极其廉价。然后光追只对屏幕空间处理不了的效果做定点追踪:例如玻璃幕墙的镜面反射、面积光源的软阴影。为了控制成本,光追特效还会做分辨率分级——先用半分辨率计算,再通过上采样补齐细节。反射率低的粗糙表面直接复用便宜的屏幕空间反射,反射率高的镜面区域才走全分辨率光追。这种“按需分配”让 RTX 的开销被压缩到总帧时间的 20%~30%。

快反小组出大样、精英小队补细节,两路人马在同一帧内合并成一张可信的“现场照片”——这是工程上的绝妙折中,也是玩家流畅体验的保证。

T帧=T光栅化+T光追特效+T降噪后处理\displaystyle T_{\text{帧}} = T_{\text{光栅化}} + T_{\text{光追特效}} + T_{\text{降噪后处理}}
帧时间公式说明:一帧的总预算被拆成三路开销,引擎优化就是让这三路的总和跑进 16.6 毫秒(60 FPS 的帧预算)。
  1. T\_帧 总帧时间渲染一帧画面允许消耗的全部时间,60 FPS 下预算为 16.6 毫秒。
    T帧T_{\text{帧}}
  2. T\_光栅化 基础通道时间光栅化输出 G-buffer 与直接光照的时间,通常是三路里最便宜的一路。
    T光栅化T_{\text{光栅化}}
  3. T\_光追特效 精英小队时间只计算反射、阴影、全局光照等关键特效,时间占比受分辨率分级策略控制。
    T光追特效T_{\text{光追特效}}
  4. T\_降噪后处理 收尾时间把带噪点的光追结果清洗干净的时间,优先级高但成本必须压得很低。
    T降噪后处理T_{\text{降噪后处理}}

降噪算法:证词筛选与消息还原

侦探人手有限,每条光线只能采集一个颜色样本,就像几十个目击者各说各话:这个人说“灯是红的”,那个人说“灯是蓝的”,画面因此布满颗粒噪点。降噪专员的工作,就是把不同时间、不同邻域的证词汇聚起来对比,剔除明显不可靠的谎话,最终还原出一张干净的“事件全貌”。没有这位专员,光追画面就像全城目击者一起抢话,谁也听不清。

降噪(Denoising)是实时光追的必备搭档。因为实时渲染中每像素通常只发射 1 条光线,蒙特卡洛估计的方差极大,必须靠算法补救。降噪分成两个维度:时域上,用时间抗锯齿的思想把历史帧结果与当前帧按指数加权混合——画面静止时噪点指数级收敛,运动时借助运动向量做重投影,防止残影;空域上,对相邻像素做边缘感知的滤波,利用 G-buffer 提供的法线、深度信息约束,避免把不同物体的颜色糊成一团。

一帧一帧地“综合证词”,警局最终交出的报告既清晰又真实;降噪让少采样也能出高质量画面,是实时光追性价比最高的武器。

It=α St+(1−α) It−1\displaystyle I_{t} = \alpha\,S_{t} + (1-\alpha)\,I_{t-1}
时域累积公式说明:当前帧的采样结果 S_t 与历史累积结果 I_{t-1} 按系数 α 混合,α 越小,历史占比越高,去噪越强,但运动时越容易出现拖影。
  1. I\_t 当前帧累积结果经过降噪后输出的干净像素值,是“综合证词”后的最终报告。
    ItI_{t}
  2. S\_t 当前帧原始采样这一帧光线追查到的原始颜色,通常带有大量噪声,是“新到的目击证词”。
    StS_{t}
  3. α 混合系数控制新旧证词的信任比例,静止场景调小,运动场景动态调大,以平衡噪点与拖影。
    α\alpha
  4. (1-α)I\_t-1 历史衰减项上一帧累积结果按比例衰减后继续参与混合,让信息在帧间滚动累积,方差逐步收敛。
    (1−α) It−1(1-\alpha)\,I_{t-1}

游戏实践应用:赛博朋克2077全城查案

把整套侦探体系搬进夜之城(Night City):霓虹闪烁的街道上,玻璃幕墙倒映着广告牌,雨水浸湿的地面拖出红蓝光影——这是《赛博朋克2077》为玩家呈现的实时光追盛景。2022 年起,CD Projekt RED 为游戏加入了“光线追踪:超速模式”(路径追踪),把过去只存在于离线渲染的渲染方程实时地搬进游戏。它不是单点突破,而是整套体系的全城总动员:BVH 负责快速定位,RT Core 负责海量求交,降噪管线负责清洗画面。

路径追踪是光线追踪的终极形态:对每个像素持续随机弹射光线,用蒙特卡洛方法近似求解渲染方程,每次弹射都累积一份辐射度贡献。这一模式对硬件极其贪婪——4K 分辨率下一帧需要数亿条光线,原生渲染只有个位数帧率。此时 AI 登场:DLSS 3 帧生成技术利用光流与张量核心,在渲染帧之间智能插值出额外帧,硬生生把帧率拉升到 60 FPS 以上。游戏还提供“光追反射 / 光追阴影 / 光追全局光照”分档开关,正是前文混合渲染管线的工程落地:玩家可以根据显卡能力,只让精英小队处理最关键的特效。

从侦探单人追凶到全城联合查案,光线追踪用数学、硬件与工程三件套,把光的真相实时呈现在玩家眼前——这就是实时渲染技术的魅力所在。

Lo(x,ωo)=Le(x,ωo)+∫Ωfr(x,ωi,ωo) Li(x,ωi) (ωi⋅n) dωi\displaystyle L_o(\mathbf{x},\omega_o) = L_e(\mathbf{x},\omega_o) + \int_{\Omega} f_r(\mathbf{x},\omega_i,\omega_o)\,L_i(\mathbf{x},\omega_i)\,(\omega_i\cdot \mathbf{n})\,d\omega_i
渲染方程说明:某个表面点朝某个方向发出的光 = 它自己发出的光 + 它反射的所有入射光之和;路径追踪就是用随机采样来估计这个积分。
  1. L\_o 出射辐射度表面点 x 朝观察方向 ω_o 射出多少光,也就是我们看到这个像素的最终颜色。
    Lo(x,ωo)L_o(\mathbf{x},\omega_o)
  2. L\_e 自发光项物体自身发射的光,例如霓虹灯管、屏幕、车灯,是光路链条的源头。
    Le(x,ωo)L_e(\mathbf{x},\omega_o)
  3. 半球积分把整个上半球 Ω 所有方向的入射光累加起来,代表“四面八方进来的光都要算进去”。
    ∫Ω⋯ dωi\int_{\Omega} \cdots \,d\omega_i
  4. f\_r 双向反射分布函数描述表面材质如何反射光线——磨砂、金属、玻璃各有各的反射“脾气”。
    fr(x,ωi,ωo)f_r(\mathbf{x},\omega_i,\omega_o)
  5. L\_i(ω\_i·n) 入射光与余弦项入射光强度乘以入射角余弦,斜着照进来的光贡献更小,符合生活常识。
    Li(x,ωi) (ωi⋅n)L_i(\mathbf{x},\omega_i)\,(\omega_i\cdot \mathbf{n})

AI 应用场景

游戏引擎渲染工程师:混合管线调优

阶段:实时渲染管线优化与迭代

解决的问题:传统光栅化画不出物理正确的反射与阴影,而全分辨率光追又扛不住帧率预算。工程师需要在两者之间设计混合管线,把算力用在刀刃上。

具体案例:在 UE5 项目中开启硬件光追,为镜面反射分配半分辨率光追 + TAAU 上采样,为粗糙表面复用屏幕空间反射;同时根据物体运动程度动态调节降噪混合系数 α,避免雨天场景出现拖影。最终反射质量接近离线渲染,帧时间增量控制在 4 毫秒以内。

AI 图像算法工程师:DLSS 与降噪网络

阶段:AI 重建与画质增强

解决的问题:路径追踪模式下每像素光线数太少导致噪点爆炸,单纯堆光线数会让 RTX 硬件不堪重负。AI 算法负责用超分与帧生成技术,从低分辨率、带噪的输入中重建出高清干净画面。

具体案例:训练超分辨率网络,输入半分辨率的光追降噪图,输出 4K 高细节画面;再用光流网络预测运动,在真实渲染帧之间智能生成中间帧,把《赛博朋克2077》路径追踪模式的帧率从 20 FPS 拉到 60 FPS。降噪网络则结合 G-buffer 做边缘感知重建,让湿地面倒影既锐利又不闪烁。

自动驾驶仿真渲染:传感器级物理光路

阶段:合成数据生成与传感器仿真

解决的问题:自动驾驶训练需要海量带标注的“行车影像”,但真实路采成本极高且场景不可控。光线追踪能生成物理正确的光照、反射与阴影,让合成图像更接近真实传感器输出。

具体案例:在 NVIDIA DRIVE Sim 中,用 RTX 硬件光追为虚拟城市生成模拟车灯、路灯与雨天反光路面的相机图像;BVH 加速保证多传感器视角并行渲染的实时性,合成数据直接用于训练感知模型,显著提升夜间与逆光场景的识别准确率。

Python 演示

python104 行
# -*- coding: utf-8 -*-# 简易实时光追演示: BVH 加速 + 多采样降噪import math, random class Vec3:    def __init__(self, x=0, y=0, z=0): self.x, self.y, self.z = x, y, z    def __add__(self, o): return Vec3(self.x+o.x, self.y+o.y, self.z+o.z)    def __sub__(self, o): return Vec3(self.x-o.x, self.y-o.y, self.z-o.z)    def __mul__(self, s): return Vec3(self.x*s, self.y*s, self.z*s)    def dot(self, o): return self.x*o.x + self.y*o.y + self.z*o.z    def unit(self):        n = math.sqrt(self.dot(self))        return self * (1.0/n) if n > 0 else Vec3() class Sphere:    def __init__(self, cx, cy, cz, r, cr, cg, cb):        self.c = Vec3(cx, cy, cz); self.r = r; self.color = Vec3(cr, cg, cb)    def hit(self, o, d):        oc = o - self.c; b = oc.dot(d); c = oc.dot(oc) - self.r*self.r        disc = b*b - c        if disc < 0: return -1        t = -b - math.sqrt(disc)        return t if t > 0 else -1 class BVHNode:    def __init__(self, items):        self.minb = Vec3(1e9, 1e9, 1e9); self.maxb = Vec3(-1e9, -1e9, -1e9)        for s in items:            self.minb = Vec3(min(self.minb.x, s.c.x-s.r), min(self.minb.y, s.c.y-s.r), min(self.minb.z, s.c.z-s.r))            self.maxb = Vec3(max(self.maxb.x, s.c.x+s.r), max(self.maxb.y, s.c.y+s.r), max(self.maxb.z, s.c.z+s.r))        if len(items) == 1:            self.left = self.right = None; self.spheres = items; return        self.spheres = []        axis, span = 0, self.maxb.x-self.minb.x        if self.maxb.y-self.minb.y > span: axis, span = 1, self.maxb.y-self.minb.y        if self.maxb.z-self.minb.z > span: axis = 2        items.sort(key=lambda s: s.c.x if axis == 0 else (s.c.y if axis == 1 else s.c.z))        mid = len(items)//2        self.left = BVHNode(items[:mid]); self.right = BVHNode(items[mid:])    def hit(self, o, d, inv, skip=None):        tmin, tmax = -1e9, 1e9        for a in range(3):            oa = o.x if a == 0 else (o.y if a == 1 else o.z)            ia = inv.x if a == 0 else (inv.y if a == 1 else inv.z)            mn = self.minb.x if a == 0 else (self.minb.y if a == 1 else self.minb.z)            mx = self.maxb.x if a == 0 else (self.maxb.y if a == 1 else self.maxb.z)            t1, t2 = (mn-oa)*ia, (mx-oa)*ia            if t1 > t2: t1, t2 = t2, t1            if t1 > tmin: tmin = t1            if t2 < tmax: tmax = t2        if tmin > tmax or tmax < 0: return -1, None        if self.spheres:            for s in self.spheres:                if s is skip: continue                t = s.hit(o, d)                if t > 0: return t, s            return -1, None        t1, s1 = self.left.hit(o, d, inv, skip)        t2, s2 = self.right.hit(o, d, inv, skip)        if t1 > 0 and (t2 < 0 or t1 < t2): return t1, s1        if t2 > 0: return t2, s2        return -1, None W, H, SAMPLES = 100, 75, 6SCENE = [Sphere(-0.9, 0, -2.5, 0.7, 1.0, 0.4, 0.3),         Sphere(0.9, -0.2, -2.2, 0.6, 0.3, 0.6, 1.0),         Sphere(0, -30, 0, 30, 0.4, 0.4, 0.45)] def shade(o, d, root, light):    inv = Vec3(1.0/d.x, 1.0/d.y, 1.0/d.z)    t, s = root.hit(o, d, inv)    if t < 0: return Vec3(0.2, 0.3, 0.4)    p = o + d*t    n = (p - s.c).unit()    ld = (light - p).unit()    inv_ld = Vec3(1.0/ld.x, 1.0/ld.y, 1.0/ld.z)    st, _ = root.hit(p + ld*0.01, ld, inv_ld, s)    shadow = 0.15 if st > 0 else 1.0    return s.color * (0.2 + 0.8*max(0.0, n.dot(ld))*shadow) def render():    root = BVHNode(SCENE)    img = [Vec3() for _ in range(W*H)]    for s in range(SAMPLES):        for y in range(H):            for x in range(W):                u = (x + random.random())/W                v = 1.0 - (y + random.random())/H                d = Vec3(u*2-1, v*2-1, -1).unit()                img[y*W+x] = img[y*W+x] + shade(Vec3(0, 0, 2), d, root, Vec3(1, 3, 1))    return root, img def save_ppm(img, fname):    with open(fname, 'w') as f:        f.write('P3\n%d %d\n255\n' % (W, H))        for p in img:            q = p * (1.0/SAMPLES)            f.write('%d %d %d\n' % (min(255, int(q.x*255)), min(255, int(q.y*255)), min(255, int(q.z*255)))) if __name__ == '__main__':    random.seed(42)    root, img = render()    save_ppm(img, 'rt_shadow.ppm')    print('渲染完成: %dx%d, 每像素 %d 次采样(BVH 加速+降噪)' % (W, H, SAMPLES))
目的:用不到一百行实现一个微型光追渲染器,演示 BVH 加速求交、阴影光线判定与多采样降噪(时域累积的近似),最终输出 PPM 格式图片。
  1. disc = b*b - c求解光线与球体交点的一元二次方程判别式,判别式小于零说明光线没撞上球。
  2. items.sort(key=lambda s: s.c.x if axis == 0 else (s.c.y if axis == 1 else s.c.z))按最宽轴中心坐标排序并二分,构建平衡的 BVH 树。
  3. if tmin > tmax or tmax < 0: return -1, None三个轴向上的区间都成立才说明光线穿过包围盒,否则整棵子树被剪枝。
  4. if s is skip: continue阴影光线检测时跳过命中点自身,避免“自己挡自己的光”产生伪阴影。
  5. img[y*W+x] = img[y*W+x] + shade(Vec3(0, 0, 2), d, root, Vec3(1, 3, 1))每个像素多次采样并累加,相当于时域累积的离线版,最终平均得到降噪后的干净颜色。

常见误区

误区我以为开启了光线追踪,游戏就所有画面都靠光线计算,光栅化彻底退休了。毕竟“追光”听起来比“涂颜色”高级得多。

正解实际游戏中几乎没有纯光追的实时渲染。主流方案是混合渲染管线:光栅化负责 G-buffer 和直接光照,光追只补充反射、阴影等关键特效。即使《赛博朋克2077》的路径追踪模式,底层也要靠降噪、超分和帧生成来兜底。就像警局办案,快反小组和精英小队缺一不可。

为何常见:厂商宣传时总强调“光线追踪”四个字,很少解释内部混合分工,容易让人以为光追是全能的替代者。

误区我觉得 BVH 加速结构在加载场景时构建一次,之后就能一直用,场景里物体随便动都不用管。

正解动态物体会让包围盒失效。角色跑动、车门打开时,对应节点的包围盒必须更新——小规模用 refit(快速重算包围盒),大规模用 rebuild(局部重建子树)。BVH 更新是实时光追的经典性能热点,引擎往往把动态物体单独建树,与静态场景树分离管理,就像地图册要随时修订“临时路况”。

为何常见:初学图形学时 BVH 常被当作静态离线结构讲解,忽略了实时渲染中“场景会动”这个前提。

误区我觉得降噪算法只是补救手段,想画质好就得多发射光线,毕竟样本越多蒙特卡洛估计越准,硬件不够就买更贵的显卡。

正解实时渲染的帧预算决定了光线数不可能堆太高。工程实践中“1 条光线 + 强力降噪 + 时域累积”的画面质量,往往优于“16 条光线但不降噪”。因为降噪利用帧间与空间相关性,用极低成本换取了大量有效样本,性价比远超堆光线数。这就像综合多位证人证词,远比让一个侦探反复跑现场更高效。

为何常见:离线渲染里采样数几乎免费,直接迁移到实时场景就容易产生“采样越多越好”的思维惯性。

误区我认为既然花了光追的算力,就应该在原生 4K 全分辨率下计算反射和阴影,半分辨率是偷工减料。

正解人眼对高频细节(纹理)与低频信息(反射、阴影)的敏感度不同。引擎通常用半分辨率甚至四分之一分辨率计算光追特效,再通过时域上采样补齐,视觉差异极小但性能节省巨大。这正是混合渲染管线“分辨率分级策略”的核心思想:把算力花在刀刃上。

为何常见:玩家习惯用“分辨率越高越好”衡量画质,忽略了感知质量与算力效率的工程平衡。

面试题

Q1. 光线追踪中“反向追踪”指的是沿什么方向发射光线?

单选题 · easy

  • A.从光源射向眼睛的方向
  • B.从眼睛射向场景的方向← 正确答案
  • C.从屏幕中心向四周扩散
  • D.从场景物体射向光源的方向

答案:B。反向追踪从相机(眼睛)位置出发,向场景发射光线并逆推光的来路。若从光源出发,绝大多数光子不会进入眼睛,造成巨大浪费,这就像侦探逆着人流找目击者,而不是顺流盲目搜索。

Q2. 光线方程 P(t) = O + t·D 中,参数 t 表示什么?

单选题 · mid

  • A.光线的颜色值
  • B.光线的发射时间
  • C.沿方向 D 的传播距离← 正确答案
  • D.光线的强度数值

答案:C。t 是沿方向 D 走过的距离参数,决定光线上的采样点位置。求交就是解出使 P(t) 落在物体表面的 t 值,相当于追问侦探“走了多远才撞上嫌疑人”。

Q3. BVH 加速结构把场景求交复杂度从 O(n) 降到了多少?

单选题 · easy

  • A.常数级复杂度 O(1)
  • B.对数级复杂度 O(log n)← 正确答案
  • C.线性对数级 O(n log n)
  • D.平方级复杂度 O(n²)

答案:B。BVH 每次遍历都把候选区域砍掉一半,搜索路径长度等于树高,约 log n。这就像用分区地图册逐层锁定街区,而不是挨家挨户敲门,效率天差地别。

Q4. 关于混合渲染管线的说法,正确的有哪些?

多选题 · hard

  • A.光栅化负责基础 G-buffer 与直接光照← 正确答案
  • B.光追负责所有像素的全局照明
  • C.光追特效可按分辨率分级计算← 正确答案
  • D.光栅化输出最终全分辨率高光反射

答案:AC。混合管线中光栅化输出 G-buffer 和底图,光追只补反射、阴影等特效,且常做半分辨率分级计算。B 错在全场景全像素光追太贵,D 错在高光反射恰恰是光追的活,光栅化算不准。

Q5. RT Core 硬件加速的核心运算是哪一类?

单选题 · mid

  • A.纹理采样与材质过滤
  • B.BVH 遍历与光线求交← 正确答案
  • C.神经网络矩阵推理
  • D.顶点坐标变换与光栅化

答案:B。RT Core 专门把 BVH 遍历和光线-三角形求交做成电路,几周期内返回命中结果。神经网络推理归 Tensor Core,纹理与顶点变换归传统渲染管线,各有分工。

Q6. 时域降噪的指数移动平均公式 I_t = α·S_t + (1-α)·I_{t-1} 中,影响最终结果的因素包括哪些?

多选题 · hard

  • A.当前帧的光线采样数
  • B.混合系数 α 的取值← 正确答案
  • C.历史帧累积结果 I_{t-1}← 正确答案
  • D.摄像机的曝光补偿参数

答案:BC。公式直接由 S_t、α、I_{t-1} 三个量决定。采样数只间接影响 S_t 的质量,曝光补偿属于后处理参数,不进入该公式。α 越小历史占比越高,去噪越强但运动拖影越明显。

Q7. 路径追踪与普通光线追踪的核心区别是什么?

单选题 · easy

  • A.路径追踪不使用递归调用
  • B.路径追踪随机弹射累积近似求解← 正确答案
  • C.路径追踪只处理镜面反射
  • D.路径追踪完全不需要光源

答案:B。路径追踪对每个像素持续随机采样出射方向,用蒙特卡洛方法逼近渲染方程积分。它不是单次追踪,而是反复弹射累积辐射度,像侦探不断变换线索方向走访目击者。

Q8. 《赛博朋克2077》光追超速模式下,把路径追踪帧率拉回可玩水平的关键技术是?

单选题 · mid

  • A.使用 MSAA 多重采样抗锯齿
  • B.使用 DLSS 3 帧生成技术← 正确答案
  • C.降低渲染分辨率到 720p
  • D.关闭所有动态光影特效

答案:B。DLSS 3 利用光流与张量核心在真实渲染帧之间生成中间帧,配合超分辨率重建,把 4K 路径追踪从个位帧率拉到 60 FPS 以上。MSAA 是几何抗锯齿,与帧率恢复无关。

Q9. 从命中点向光源发射阴影光线后,可能出现的情况有哪些?

多选题 · hard

  • A.未命中任何物体则该点被照亮← 正确答案
  • B.命中物体且距离小于光源距离则产生阴影← 正确答案
  • C.命中物体但法线背离光源仍是阴影
  • D.命中自身表面必然产生阴影

答案:AB。阴影光线只看几何遮挡,未命中则亮,命中且中间隔着物体则暗。法线朝向不影响遮挡判定;自相交已通过起点偏移跳过,不会必然产生阴影。这就像问“光线到光源的路上有没有人挡路”,与挡路者面朝哪无关。

Q10. 降噪做空域滤波时,为什么必须结合 G-buffer 的几何信息?

单选题 · mid

  • A.加快 BVH 加速结构的构建速度
  • B.避免混合不同物体与深度的像素← 正确答案
  • C.减少每条光线追查的次数
  • D.提升贴图分辨率与画面细节

答案:B。空域滤波若只看颜色,会把前景与背景、不同法向的表面混在一起,产生“糊边”。G-buffer 提供深度、法线、材质 ID,让滤波器只在几何连续的区域取平均,像警察只相信同一条街的目击者证词。