第 1 题:简历深挖 / 项目动机

你简历里写了一个支持 PBR / IBL / MIS 的 CPU 路径追踪渲染器。请你介绍一下这个项目的整体架构:从场景加载、光线生成、材质采样、光照估计到最终成像,主要模块是怎么串起来的?

  1. 总体架构
  • 项目主要分成 Camera、Scene / Hittable、BVH、Material、PDF、Environment、Renderer 几个模块。
  • 渲染时按 tile 并行遍历像素,每个像素发射多条 camera ray,累积 radiance 后输出 PPM。
  1. 场景与加速结构
  • OBJ/MTL 解析生成 Triangle / Material。
  • 三角形和几何体统一进 Hittable 接口。
  • 构建 BVH,减少每条 ray 的相交测试成本。
  1. 光线与材质
  • Camera 生成 primary ray。
  • 命中物体后通过 Material 接口计算 BSDF、PDF、scatter 方向。
  • 材质接口拆成 Scatter / Eval / PDF,是为了让 Lambert、Metal、Dielectric、PBR 材质共用同一套积分流程。
  1. 光照估计
  • 使用 Monte Carlo 估计渲染方程。
  • 直接光部分结合 light sampling / environment sampling / BSDF sampling。
  • 使用 MIS 的 power heuristic 平衡不同采样策略,减少只靠随机打中光源带来的高噪声问题。
  1. 结果与优化
  • 支持 PBR、IBL、HDRI 环境光、normal map、OBJ/MTL。
  • 用 BVH、多线程 tile 渲染和 thread-local RNG 优化性能。
  • 基准场景下从 65.33s 降到 29.99s,约 2.18x 提速。

你这题改进的关键是:少讲“路径追踪一般怎么做”,多讲“我的代码里哪些模块承担什么职责”。

第 2 题:专业深挖 / MIS

你简历里写到实现了 HDRI 环境光重要性采样 和 light / BSDF 的 MIS 采样框架。请你解释一下:为什么只做 BSDF 采样或者只做光源采样都不够?MIS 在你的渲染器里具体解决了什么问题?

  1. 为什么单一采样不够
  • Monte Carlo 积分的方差和f(x) / pdf(x)的波动有关。
  • 如果采样分布和被积函数不匹配,就会出现高方差和 firefly。
  • BSDF 采样适合 glossy / specular lobe 明显的方向,但对小面积光源命中率低。
  • Light sampling 能稳定采到光源方向,但对高光材质或复杂 BSDF 方向不一定匹配。
  1. MIS 解决什么问题
  • MIS 不是二选一,而是把不同采样策略的贡献按权重合并。
  • 我的项目里主要结合了 light sampling、environment sampling 和 BSDF sampling。
  • 使用 power heuristic,根据当前方向在不同策略下的 PDF 计算权重,降低单一策略失配带来的噪点。
  1. IBL 场景下为什么更需要 MIS
  • HDRI 环境光可以看作包围场景的无限远光源。
  • 如果只靠 BSDF 随机采样,可能很难稳定采到 HDR 中高亮区域。
  • 因此对 HDRI 按亮度和球面面积做重要性采样,再和 BSDF 采样通过 MIS 合并,可以更稳定估计环境光贡献。
  1. 一句结果
  • 所以 MIS 在我的渲染器里主要用于降低直接光、HDRI 和高光材质组合下的方差,让相同 spp 下的图像更稳定。

第 3 题:专业深挖 / PBR 材质接口

你简历里写了 Scatter / Eval / PDF 分离式材质接口。请你解释一下为什么要这样拆?如果不拆,把所有材质逻辑都写在 ray_color() 或颜色递归函数里,会带来什么问题?

  1. 三个接口职责
  • Scatter:根据材质和入射方向采样下一条 ray,返回采样方向、衰减、是否为 delta/specular 路径等。
  • Eval:给定wo和wi,计算该方向组合下的 BSDF/BRDF 值。
  • PDF:计算该材质在某个采样方向上的概率密度,用于 Monte Carlo 权重和 MIS。
  1. 为什么要拆
  • 路径追踪主流程只关心“采样方向、评价 BSDF、计算 PDF、递归/迭代累积 radiance”。
  • 不同材质只需要实现自己的采样和评价逻辑,主渲染函数不需要知道 Lambert、Metal、Dielectric、PBR 的细节。
  • 这样ray_color()保持为统一积分框架,而不是堆满材质分支。
  1. 和 MIS 的关系
  • MIS 需要知道同一个方向在 BSDF sampling 和 light sampling 下各自的 PDF。
  • 如果没有独立的PDF和Eval,就很难把 light sampling 采到的方向再拿回材质上正确计算 BSDF 与 pdf。
  • 所以接口拆分不是单纯为了代码好看,而是为了支持多采样策略组合。
  1. 扩展性
  • 后续增加 GGX、normal map、metallic-roughness、透明材质时,只改材质类,不破坏主渲染流程。
  • 这更接近引擎里的模块化设计思路。

你这题可以补一句关键总结:Scatter 负责“怎么采”,Eval 负责“这个方向贡献是多少”,PDF 负责“这个方向被采到的概率是多少”。

第 4 题:专业问题 / DirectX 12 管线

你简历里写了一个 DirectX 12 实时渲染器。请你讲一下在 DX12 中,一帧从 CPU 端提交命令到最终显示到屏幕,大致会经过哪些步骤?重点说一下 Command List、Command Queue、Back Buffer、Present 和 Fence 的作用。

  1. 获取当前 Back Buffer
  • 每帧先通过 Swap Chain 获取当前 back buffer index。
  • Back Buffer 是这一帧的渲染目标之一,最终会被 Present 到屏幕。
  1. 重置命令资源
  • 等待对应 frame resource 的 Fence,确保 GPU 不再使用这一帧的 CommandAllocator。
  • Reset CommandAllocator 和 CommandList,开始录制本帧命令。
  1. 资源状态转换
  • 将 Back Buffer 从PRESENT转到RENDER_TARGET。
  • 设置 RTV / DSV,清空颜色缓冲和深度缓冲。
  1. 录制绘制命令
  • 设置 Root Signature、PSO、Descriptor Heap、Viewport、Scissor、Vertex Buffer、Index Buffer 等。
  • 调用 DrawCall,把绘制命令写入 Command List。
  1. 切回显示状态并提交
  • 渲染结束后,将 Back Buffer 从RENDER_TARGET转回PRESENT。
  • Close CommandList。
  • 通过 Command Queue 执行:ExecuteCommandLists()。
  1. 显示与同步
  • 调用SwapChain->Present(),把当前 Back Buffer 交给显示系统。
  • 用CommandQueue->Signal(fence, value)标记 GPU 进度。
  • 下一次复用同一帧资源前,CPU 检查 Fence 是否完成。

一句话版本:

CPU 每帧重置并录制 Command List,把 Back Buffer 从 Present 状态切到 Render Target,完成清屏、绑定管线和 DrawCall 后再切回 Present,通过 Command Queue 提交给 GPU 执行,最后 Swap Chain Present 显示;Fence 用来确认 GPU 执行进度,避免 CPU 提前复用仍在被 GPU 使用的资源。

第 5 题:行为面试 / 问题解决

你在做 PathTracer 或 DX12 Renderer 的时候,应该遇到过渲染结果不对、画面发黑、噪点异常、性能很慢这类问题。请选一个你印象最深的 bug 或性能问题,按“问题现象 - 排查过程 - 根因 - 解决方案 - 结果”的结构讲一下

可以这样答:

  1. 问题现象
  • 接入 HDRI / IBL 后,背景能显示 HDR 环境,但画面出现大量 firefly。
  • 同样 spp 下,环境高亮区域附近噪点明显比面积光场景严重。
  1. 排查过程
  • 先确认不是 gamma、颜色空间或贴图读取错误,因为背景显示方向和颜色基本正确。
  • 再对比面积光采样和环境光采样,发现原来的 light sampling 更适合有限面积光源,不适合直接套到无限远环境光。
  • 重点检查环境光采样的两个部分:采样分布和 PDF。
  1. 根因
  • HDRI 的亮度分布很不均匀,高能量集中在少数 texel。
  • 如果只靠 BSDF 采样,或者环境光 PDF 计算不正确,就会导致偶发的高贡献样本,形成 firefly。
  • 环境贴图还涉及经纬图到球面方向的转换,PDF 需要考虑sin(theta),不能只按纹理坐标面积处理。
  1. 解决方案
  • 在Environment模块中为 HDRI 单独实现重要性采样。
  • 按luminance * sin(theta)构建二维分布 / CDF。
  • 增加环境光自己的 PDF 接口,并在直接光估计和 MIS 权重中使用正确的环境光 PDF。
  • 用 AI 辅助检查公式推导和代码边界,但最终通过渲染结果和噪点变化验证。
  1. 结果
  • firefly 明显减少。
  • 相同采样数下 HDRI 高亮区域的收敛更稳定。
  • 也让后续 light sampling、environment sampling、BSDF sampling 能通过 MIS 放到统一框架里。

这题你可以保留“AI 辅助验证”,但主表达要从“我问了 AI”改成“我定位到 PDF 和采样分布不匹配,然后用工具辅助校验”。

第 6 题:专业问题 / HDRI 环境光采样

你刚才提到了给 Environment 增加自己的 PDF 接口。那我继续追问:如果使用经纬度 HDR 贴图做环境光采样,为什么构建采样分布时通常要乘上 sin(theta)?如果不乘,会造成什么问题?

  1. 经纬图不是等面积映射
  • HDRI 经纬图中每个 texel 在 2D 贴图上面积一样。
  • 但映射到球面后,不同纬度对应的立体角不同。
  • 球面面积微元是:domega = sin(theta) dtheta dphi。
  1. 为什么乘sin(theta)
  • 赤道附近sin(theta)接近 1,同样大小 texel 对应更大的球面面积。
  • 极点附近sin(theta)接近 0,同样大小 texel 对应更小的球面面积。
  • 所以构建环境光重要性采样分布时,通常用luminance(texel) * sin(theta),让采样概率同时反映亮度和球面面积。
  1. 不乘的后果
  • 会把极点附近的小立体角 texel 采得过多。
  • PDF 和真实积分测度不匹配,可能导致噪声变大、能量分布偏差,严重时环境光亮度估计不稳定。
  • 在 MIS 中还会影响权重计算,因为环境采样 PDF 不准确。

一句话版:

因为经纬图到球面的映射不是等面积的,球面面积微元包含 sin(theta)。构建环境光采样分布时乘上它,是为了让 texel 的采样概率和它实际覆盖的立体角匹配,否则极区会被过度采样,PDF 也会和真实环境光积分不一致。

第 7 题:简历深挖 / 性能优化

你简历里写 PathTracer 通过 BVH、多线程 tile 渲染和 thread-local RNG,把渲染时间降低了 2.18 倍。请你具体讲一下这个优化是怎么做的?为什么 tile 并行可以避免数据竞争?有没有哪些部分仍然可能成为瓶颈?

  1. 原始问题
  • 最初是单线程扫描线渲染,每个像素按顺序计算。
  • 路径追踪每个像素要发射多个 sample,每个 sample 又可能递归多次 bounce,CPU 多核利用率不足。
  1. tile 并行
  • 我把图像划分成固定大小的 tile,例如每个 tile 包含一块连续像素区域。
  • 多线程并行处理不同 tile。
  • 每个 tile 只写自己负责的像素范围,最终 framebuffer 的索引由(x, y)唯一确定,所以不同线程不会写同一个像素,避免数据竞争。
  • 相比“每个像素一个任务”,tile 粒度可以减少任务调度开销,也有更好的 cache locality。
  1. thread-local RNG
  • 路径追踪采样需要大量随机数。
  • 如果多个线程共享 RNG,会有锁竞争或数据竞争。
  • 所以我使用 thread-local RNG,让每个线程有自己的随机数状态,减少同步开销。
  1. BVH 优化
  • 未加 BVH 时,每条 ray 都要遍历大量三角形。
  • BVH 用层次包围盒先剔除不可能命中的区域。
  • ray 先和 AABB 求交,只有命中包围盒才继续访问子节点或叶子三角形,从而显著减少三角形求交次数。
  1. 量化结果
  • 在 800x450 / 400 spp / 20 depth 的基准下,串行渲染约 65.33s,并行后约 29.99s,整体提速约 2.18x。
  1. 剩余瓶颈
  • 高 spp 仍然是主要成本。
  • 复杂场景下 BVH traversal、材质 BSDF 计算、环境光采样和内存访问仍然占比较高。
  • 不同 tile 的复杂度不同,也可能造成线程负载不均衡。

一句话总结可以这样说:

tile 并行的关键不是让线程随便抢像素,而是把 framebuffer 划分成互不重叠的写入区域;再配合 thread-local RNG 避免随机数状态竞争,BVH 则减少每条 ray 的求交成本。

第 8 题:专业问题 / DirectX 12 IBL

你在 DX12 Renderer 里写了 split-sum IBL,并预计算了 irradiance map、prefiltered environment map 和 BRDF LUT。请你解释一下这三个贴图分别解决什么问题?运行时渲染一个 PBR 物体时,它们是怎么被用到的?

  1. 为什么需要 split-sum IBL
  • 离线路径追踪可以对环境光做大量采样。
  • 实时渲染不能每个像素每帧采很多条环境光方向。
  • 所以把环境光积分拆成可以预计算的部分和运行时快速查表的部分。
  1. irradiance map
  • 解决 diffuse IBL。
  • 它对环境贴图按每个法线方向做半球卷积。
  • 运行时用法线N采样 irradiance map,再乘以 diffuse albedo,近似漫反射环境光。
  1. prefiltered environment map
  • 解决 specular IBL 中环境反射采样成本高的问题。
  • 按不同 roughness 对 HDR 环境贴图做 GGX 重要性采样预滤波,并存到不同 mip level。
  • 运行时用反射方向R采样,并根据 roughness 选择 mip,roughness 越大结果越模糊。
  1. BRDF LUT
  • 解决 specular BRDF 积分中和视角、粗糙度相关的部分。
  • 输入是NdotV和roughness。
  • 输出通常是 scale / bias 两个值,用来和F0组合。
  1. 运行时组合
  • diffuse:irradiance(N) * albedo
  • specular:prefilteredEnv(R, roughness) * (F0 * brdf.x + brdf.y)
  • 最后再加上直接光、阴影、tone mapping 等。

一句话总结:

irradiance map 负责漫反射环境光,prefiltered environment map 负责不同粗糙度下的环境反射,BRDF LUT 负责视角和粗糙度相关的 BRDF 积分项;三者合起来让 PBR 材质在实时渲染中用少量纹理采样近似 IBL 积分。

这题你的方向是对的,补上 N、R、roughness mip、NdotV、F0 这些关键词,会更像真正写过 shader。

第 9 题:行为面试 / AI 工具使用

JD 里提到熟练使用 CodeBuddy、Codex、Claude Code 等 AI 工具,并且你简历里也写了使用 AI 工具辅助开发。请你讲一个具体例子:你在项目里是怎么使用 AI 工具提升开发效率的?你如何判断 AI 给出的方案是正确的,而不是直接照抄?

  1. 场景
  • 在 PathTracer 中集成 PBR 流程时,我一开始掌握了 Cook-Torrance、GGX、Fresnel 等公式,但不确定怎么接入现有渲染器架构。
  1. AI 的作用
  • 我先用 AI 帮我拆分实现路径,比如材质接口、BSDF eval、PDF、采样、贴图解析、测试场景。
  • 然后让 AI 结合源码指出现有架构适合在哪些模块接入,而不是直接生成一整套代码。
  1. 自己的判断
  • 我会先复习公式和参考开源实现,确认 AI 方案是否符合渲染方程和现有代码结构。
  • 对关键部分,比如 GGX VNDF、normal map、IBL、MIS,我会重点检查 PDF、坐标空间和能量项是否一致。
  1. 验证方式
  • 每完成一个小模块,就用固定测试场景验证,例如不同 roughness、metallic、normal map、HDRI 光照。
  • 再让 AI 做 code review,检查边界条件、变量含义和潜在 bug。
  • 最后通过渲染结果和性能数据确认效果,比如多线程优化后的 2.18x 提速。
  1. 总结
  • AI 主要提升了方案拆解、资料对比和代码审查效率,但最终是否采用,还是由我基于公式、源码结构、测试图和 benchmark 判断。

一句话版本:

我不会直接照抄 AI 代码,而是把 AI 当作方案拆解和 review 工具;关键图形逻辑会用公式推导、源码结构、固定测试场景和渲染结果来交叉验证。

原文:项目细节