你简历里写了一个支持 PBR / IBL / MIS 的 CPU 路径追踪渲染器。请你介绍一下这个项目的整体架构:从场景加载、光线生成、材质采样、光照估计到最终成像,主要模块是怎么串起来的?
- 项目主要分成 Camera、Scene / Hittable、BVH、Material、PDF、Environment、Renderer 几个模块。
- 渲染时按 tile 并行遍历像素,每个像素发射多条 camera ray,累积 radiance 后输出 PPM。
- OBJ/MTL 解析生成 Triangle / Material。
- 三角形和几何体统一进 Hittable 接口。
- 构建 BVH,减少每条 ray 的相交测试成本。
- Camera 生成 primary ray。
- 命中物体后通过 Material 接口计算 BSDF、PDF、scatter 方向。
- 材质接口拆成 Scatter / Eval / PDF,是为了让 Lambert、Metal、Dielectric、PBR 材质共用同一套积分流程。
- 使用 Monte Carlo 估计渲染方程。
- 直接光部分结合 light sampling / environment sampling / BSDF sampling。
- 使用 MIS 的 power heuristic 平衡不同采样策略,减少只靠随机打中光源带来的高噪声问题。
- 支持 PBR、IBL、HDRI 环境光、normal map、OBJ/MTL。
- 用 BVH、多线程 tile 渲染和 thread-local RNG 优化性能。
- 基准场景下从 65.33s 降到 29.99s,约 2.18x 提速。
你这题改进的关键是:少讲“路径追踪一般怎么做”,多讲“我的代码里哪些模块承担什么职责”。
你简历里写到实现了 HDRI 环境光重要性采样 和 light / BSDF 的 MIS 采样框架。请你解释一下:为什么只做 BSDF 采样或者只做光源采样都不够?MIS 在你的渲染器里具体解决了什么问题?
- Monte Carlo 积分的方差和f(x) / pdf(x)的波动有关。
- 如果采样分布和被积函数不匹配,就会出现高方差和 firefly。
- BSDF 采样适合 glossy / specular lobe 明显的方向,但对小面积光源命中率低。
- Light sampling 能稳定采到光源方向,但对高光材质或复杂 BSDF 方向不一定匹配。
- MIS 不是二选一,而是把不同采样策略的贡献按权重合并。
- 我的项目里主要结合了 light sampling、environment sampling 和 BSDF sampling。
- 使用 power heuristic,根据当前方向在不同策略下的 PDF 计算权重,降低单一策略失配带来的噪点。
- HDRI 环境光可以看作包围场景的无限远光源。
- 如果只靠 BSDF 随机采样,可能很难稳定采到 HDR 中高亮区域。
- 因此对 HDRI 按亮度和球面面积做重要性采样,再和 BSDF 采样通过 MIS 合并,可以更稳定估计环境光贡献。
- 所以 MIS 在我的渲染器里主要用于降低直接光、HDRI 和高光材质组合下的方差,让相同 spp 下的图像更稳定。
你简历里写了 Scatter / Eval / PDF 分离式材质接口。请你解释一下为什么要这样拆?如果不拆,把所有材质逻辑都写在 ray_color() 或颜色递归函数里,会带来什么问题?
- Scatter:根据材质和入射方向采样下一条 ray,返回采样方向、衰减、是否为 delta/specular 路径等。
- Eval:给定wo和wi,计算该方向组合下的 BSDF/BRDF 值。
- PDF:计算该材质在某个采样方向上的概率密度,用于 Monte Carlo 权重和 MIS。
- 路径追踪主流程只关心“采样方向、评价 BSDF、计算 PDF、递归/迭代累积 radiance”。
- 不同材质只需要实现自己的采样和评价逻辑,主渲染函数不需要知道 Lambert、Metal、Dielectric、PBR 的细节。
- 这样ray_color()保持为统一积分框架,而不是堆满材质分支。
- MIS 需要知道同一个方向在 BSDF sampling 和 light sampling 下各自的 PDF。
- 如果没有独立的PDF和Eval,就很难把 light sampling 采到的方向再拿回材质上正确计算 BSDF 与 pdf。
- 所以接口拆分不是单纯为了代码好看,而是为了支持多采样策略组合。
- 后续增加 GGX、normal map、metallic-roughness、透明材质时,只改材质类,不破坏主渲染流程。
- 这更接近引擎里的模块化设计思路。
你这题可以补一句关键总结:Scatter 负责“怎么采”,Eval 负责“这个方向贡献是多少”,PDF 负责“这个方向被采到的概率是多少”。
你简历里写了一个 DirectX 12 实时渲染器。请你讲一下在 DX12 中,一帧从 CPU 端提交命令到最终显示到屏幕,大致会经过哪些步骤?重点说一下 Command List、Command Queue、Back Buffer、Present 和 Fence 的作用。
- 每帧先通过 Swap Chain 获取当前 back buffer index。
- Back Buffer 是这一帧的渲染目标之一,最终会被 Present 到屏幕。
- 等待对应 frame resource 的 Fence,确保 GPU 不再使用这一帧的 CommandAllocator。
- Reset CommandAllocator 和 CommandList,开始录制本帧命令。
- 将 Back Buffer 从PRESENT转到RENDER_TARGET。
- 设置 RTV / DSV,清空颜色缓冲和深度缓冲。
- 设置 Root Signature、PSO、Descriptor Heap、Viewport、Scissor、Vertex Buffer、Index Buffer 等。
- 调用 DrawCall,把绘制命令写入 Command List。
- 渲染结束后,将 Back Buffer 从RENDER_TARGET转回PRESENT。
- Close CommandList。
- 通过 Command Queue 执行:ExecuteCommandLists()。
- 调用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 使用的资源。
你在做 PathTracer 或 DX12 Renderer 的时候,应该遇到过渲染结果不对、画面发黑、噪点异常、性能很慢这类问题。请选一个你印象最深的 bug 或性能问题,按“问题现象 - 排查过程 - 根因 - 解决方案 - 结果”的结构讲一下
- 接入 HDRI / IBL 后,背景能显示 HDR 环境,但画面出现大量 firefly。
- 同样 spp 下,环境高亮区域附近噪点明显比面积光场景严重。
- 先确认不是 gamma、颜色空间或贴图读取错误,因为背景显示方向和颜色基本正确。
- 再对比面积光采样和环境光采样,发现原来的 light sampling 更适合有限面积光源,不适合直接套到无限远环境光。
- 重点检查环境光采样的两个部分:采样分布和 PDF。
- HDRI 的亮度分布很不均匀,高能量集中在少数 texel。
- 如果只靠 BSDF 采样,或者环境光 PDF 计算不正确,就会导致偶发的高贡献样本,形成 firefly。
- 环境贴图还涉及经纬图到球面方向的转换,PDF 需要考虑sin(theta),不能只按纹理坐标面积处理。
- 在Environment模块中为 HDRI 单独实现重要性采样。
- 按luminance * sin(theta)构建二维分布 / CDF。
- 增加环境光自己的 PDF 接口,并在直接光估计和 MIS 权重中使用正确的环境光 PDF。
- 用 AI 辅助检查公式推导和代码边界,但最终通过渲染结果和噪点变化验证。
- firefly 明显减少。
- 相同采样数下 HDRI 高亮区域的收敛更稳定。
- 也让后续 light sampling、environment sampling、BSDF sampling 能通过 MIS 放到统一框架里。
这题你可以保留“AI 辅助验证”,但主表达要从“我问了 AI”改成“我定位到 PDF 和采样分布不匹配,然后用工具辅助校验”。
你刚才提到了给 Environment 增加自己的 PDF 接口。那我继续追问:如果使用经纬度 HDR 贴图做环境光采样,为什么构建采样分布时通常要乘上 sin(theta)?如果不乘,会造成什么问题?
- HDRI 经纬图中每个 texel 在 2D 贴图上面积一样。
- 但映射到球面后,不同纬度对应的立体角不同。
- 球面面积微元是:domega = sin(theta) dtheta dphi。
- 赤道附近sin(theta)接近 1,同样大小 texel 对应更大的球面面积。
- 极点附近sin(theta)接近 0,同样大小 texel 对应更小的球面面积。
- 所以构建环境光重要性采样分布时,通常用luminance(texel) * sin(theta),让采样概率同时反映亮度和球面面积。
- 会把极点附近的小立体角 texel 采得过多。
- PDF 和真实积分测度不匹配,可能导致噪声变大、能量分布偏差,严重时环境光亮度估计不稳定。
- 在 MIS 中还会影响权重计算,因为环境采样 PDF 不准确。
因为经纬图到球面的映射不是等面积的,球面面积微元包含 sin(theta)。构建环境光采样分布时乘上它,是为了让 texel 的采样概率和它实际覆盖的立体角匹配,否则极区会被过度采样,PDF 也会和真实环境光积分不一致。
你简历里写 PathTracer 通过 BVH、多线程 tile 渲染和 thread-local RNG,把渲染时间降低了 2.18 倍。请你具体讲一下这个优化是怎么做的?为什么 tile 并行可以避免数据竞争?有没有哪些部分仍然可能成为瓶颈?
- 最初是单线程扫描线渲染,每个像素按顺序计算。
- 路径追踪每个像素要发射多个 sample,每个 sample 又可能递归多次 bounce,CPU 多核利用率不足。
- 我把图像划分成固定大小的 tile,例如每个 tile 包含一块连续像素区域。
- 多线程并行处理不同 tile。
- 每个 tile 只写自己负责的像素范围,最终 framebuffer 的索引由(x, y)唯一确定,所以不同线程不会写同一个像素,避免数据竞争。
- 相比“每个像素一个任务”,tile 粒度可以减少任务调度开销,也有更好的 cache locality。
- 路径追踪采样需要大量随机数。
- 如果多个线程共享 RNG,会有锁竞争或数据竞争。
- 所以我使用 thread-local RNG,让每个线程有自己的随机数状态,减少同步开销。
- 未加 BVH 时,每条 ray 都要遍历大量三角形。
- BVH 用层次包围盒先剔除不可能命中的区域。
- ray 先和 AABB 求交,只有命中包围盒才继续访问子节点或叶子三角形,从而显著减少三角形求交次数。
- 在 800x450 / 400 spp / 20 depth 的基准下,串行渲染约 65.33s,并行后约 29.99s,整体提速约 2.18x。
- 高 spp 仍然是主要成本。
- 复杂场景下 BVH traversal、材质 BSDF 计算、环境光采样和内存访问仍然占比较高。
- 不同 tile 的复杂度不同,也可能造成线程负载不均衡。
tile 并行的关键不是让线程随便抢像素,而是把 framebuffer 划分成互不重叠的写入区域;再配合 thread-local RNG 避免随机数状态竞争,BVH 则减少每条 ray 的求交成本。
你在 DX12 Renderer 里写了 split-sum IBL,并预计算了 irradiance map、prefiltered environment map 和 BRDF LUT。请你解释一下这三个贴图分别解决什么问题?运行时渲染一个 PBR 物体时,它们是怎么被用到的?
- 离线路径追踪可以对环境光做大量采样。
- 实时渲染不能每个像素每帧采很多条环境光方向。
- 所以把环境光积分拆成可以预计算的部分和运行时快速查表的部分。
- 解决 diffuse IBL。
- 它对环境贴图按每个法线方向做半球卷积。
- 运行时用法线N采样 irradiance map,再乘以 diffuse albedo,近似漫反射环境光。
- 解决 specular IBL 中环境反射采样成本高的问题。
- 按不同 roughness 对 HDR 环境贴图做 GGX 重要性采样预滤波,并存到不同 mip level。
- 运行时用反射方向R采样,并根据 roughness 选择 mip,roughness 越大结果越模糊。
- 解决 specular BRDF 积分中和视角、粗糙度相关的部分。
- 输入是NdotV和roughness。
- 输出通常是 scale / bias 两个值,用来和F0组合。
- 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。
JD 里提到熟练使用 CodeBuddy、Codex、Claude Code 等 AI 工具,并且你简历里也写了使用 AI 工具辅助开发。请你讲一个具体例子:你在项目里是怎么使用 AI 工具提升开发效率的?你如何判断 AI 给出的方案是正确的,而不是直接照抄?
- 在 PathTracer 中集成 PBR 流程时,我一开始掌握了 Cook-Torrance、GGX、Fresnel 等公式,但不确定怎么接入现有渲染器架构。
- 我先用 AI 帮我拆分实现路径,比如材质接口、BSDF eval、PDF、采样、贴图解析、测试场景。
- 然后让 AI 结合源码指出现有架构适合在哪些模块接入,而不是直接生成一整套代码。
- 我会先复习公式和参考开源实现,确认 AI 方案是否符合渲染方程和现有代码结构。
- 对关键部分,比如 GGX VNDF、normal map、IBL、MIS,我会重点检查 PDF、坐标空间和能量项是否一致。
- 每完成一个小模块,就用固定测试场景验证,例如不同 roughness、metallic、normal map、HDRI 光照。
- 再让 AI 做 code review,检查边界条件、变量含义和潜在 bug。
- 最后通过渲染结果和性能数据确认效果,比如多线程优化后的 2.18x 提速。
- AI 主要提升了方案拆解、资料对比和代码审查效率,但最终是否采用,还是由我基于公式、源码结构、测试图和 benchmark 判断。
我不会直接照抄 AI 代码,而是把 AI 当作方案拆解和 review 工具;关键图形逻辑会用公式推导、源码结构、固定测试场景和渲染结果来交叉验证。
原文:项目细节