6个月手搓Lumen:从零实现自研渲染器全局光照

📅 2026/8/27 8:43:35
6个月手搓Lumen:从零实现自研渲染器全局光照
2022年之后还在从零写渲染器的人基本都有一个绕不开的目标把 Unreal Engine 5 的 Lumen 全局光照在自研渲染器里复现出来。这次东汉书院这套教程的标题很直接6个月从零手搓 Lumen 第一帧画面而且不是只教一个 API是 OpenGL、Direct3D、Vulkan、Metal 四条线同时展开。先说结论这套教程的定位不是“带你调 Unreal 面板”而是从窗口创建、管线搭建、光栅化、SDF 建模、光线追踪到最终全局光照输出把 Lumen 的关键路径完整走一遍。也就是说你要亲手写出一个带实时全局光照的自研渲染器最后在屏幕上看到属于你自己那一帧 Lumen 画面。如果你正在纠结“先学哪个 API”“Lumen 到底是怎么做的”“Vulkan 调试和 D3D12 调试差多少”这篇文章可以收藏。下面我会把这套教程的学习目标、API 选型、环境准备、6 个月路线拆解、第一帧验证方法、常见排错和最佳实践完整过一遍。1. 核心能力速览先给一张速览表快速判断这套教程适不适合你。能力项说明项目目标6 个月内从零实现 Lumen 风格全局光照的第一帧画面覆盖 APIOpenGL、Direct3D 12、Vulkan、Metal实践形式手写渲染器不依赖 Unreal Engine 运行时核心知识点光栅化管线、SDF、Ray Marching、软件光追、全局光照学习周期约 6 个月建议每天 2 到 3 小时前置基础C 基础、线性代数基础最好了解任意一种图形 API运行平台Windows、Linux/WSL、macOS 均可按所选 API 区分验证方式分阶段跑通窗口、三角形、网格、SDF 光照、GI 输出输出物一个自研实时全局光照渲染器 Demo需要注意一点标题说的是“第一帧画面”不是“完整复刻 UE5 Lumen”。也就是说你会把 Lumen 的核心思想在主渲染管线里跑通而不是去改 Unreal 源码。这个目标更现实也更适合拿来检验图形学基本功。2. Lumen 是什么为什么值得手搓Lumen 是 Unreal Engine 5 的全局光照与反射系统。它和传统烘焙光照贴图最核心的差别是Lumen 是动态的场景中物体的位置、灯光颜色、材质属性发生变化后光照结果会实时更新不需要重新烘焙。从实现原理看Lumen 并不是单一技术而是多个技术的耦合有向距离场SDFSigned Distance Field用来描述场景的几何近似供光线追踪查询。软件光线追踪在 GPU 上通过 shader 对 SDF 做光线步进而不是依赖硬件 RT Core。屏幕空间追踪先在屏幕像素已经计算好的深度和法线信息上追踪光线能复用就复用。Radiance Cache 辐射缓存用一种低分辨率缓存保存光照缓存再通过插值扩散到全场景。Mesh Distance Field / Global Distance Field场景网格的距离场表示。所以如果你只是会用 Unreal点开 Lumen 开关你看到的是最终效果完全看不到里面的雷达分层。只有自己写一遍你才会明白“为什么 Lumen 能动态”“为什么有些材质有漏光”“为什么 SDF 需要从低精度逐步更新”。这就是“手搓 Lumen 第一帧”最有价值的地方。它逼你把图形学里最硬核的部分全部串起来从光栅化坐标变换到 SDF 建模再到 shader 中递归/循环追踪最后合成到屏幕颜色上。这个过程学完你再看 Unreal 的 Lumen 文档很多之前不懂的选项会突然通。3. 四套图形 API 怎么选教程同时覆盖 OpenGL、Direct3D、Vulkan、Metal但你不需要四套全刷一遍。更合理的方式是根据你的平台和职业目标选一条主线其余 API 做对照阅读。API跨平台调试工具上手难度适合场景OpenGLWindows/Linux/macOSRenderDoc、KHR_debug较低快速验证图形学概念学习经典管线Direct3D 12Windows/XboxPIX、Nsight Graphics高游戏行业、Windows 平台开发VulkanWindows/Linux/AndroidRenderDoc、Vulkan Validation Layers、Nsight高跨平台底层渲染、现代 GPU 特性MetalmacOS/iOSXcode Metal Debugger中Apple 平台应用、Metal 开发如果只是为了先把 Lumen 原理跑通OpenGL 是最适合的起点。它虽然老但着色器流程清晰环境配置成本极低。很多图形学教材的基础例子还是 OpenGL因为你能把注意力放在“光照计算本身”而不是从零搭一个 Vulkan 实例。如果目标是做商业引擎或者游戏客户端开发建议以 Vulkan 或 Direct3D 12 为主。Vulkan 的好处是 Linux 和 Windows 都能跑而且驱动行为更透明Direct3D 12 的优势是 Windows 游戏生态完整PIX 调试器对 GPU 性能分析特别强。如果 6 个月时间有限我的建议是按月拆解时不要换 API。前 3 个月用 OpenGL中间 1 个月换成 Vulkan 或 D3D12后 2 个月在自选底层 API 上实现 Lumen 核心效果。先跑通再换平台深挖。4. 6 个月学习路线拆解“6 个月从零手搓 Lumen 第一帧”不是靠热情堆出来的必须有一个可验证的阶段目标。这里给出一套可行的按月拆分方案你可以根据实际进度调整。4.1 第 1 个月环境与基础数学这个月不写渲染相关代码先把环境和基础知识打牢。掌握现代 C 基础RAII、智能指针、std::vector、std::string、模板基本用法。复习线性代数向量、矩阵、点积、叉积、矩阵乘法、四元数基础概念。建立本机图形开发环境。Windows 上装好 Visual Studio 2022 和 CMake。Linux/WSL 上装好 g/clang、CMake、Mesa 开发库。macOS 上装好 Xcode Command Line Tools。建议动手写一个小型 vec3/mat4 库不支持任何图形 API只在 CPU 上做变换计算。这一步能让你后面 debug 矩阵问题时不发慌。4.2 第 2 个月窗口、上下文与第一个三角形这个月进入实际渲染。用 GLFW 或 SDL 创建窗口创建 OpenGL 上下文。如果选 Vulkan使用 GLFW 加 VkInstance、VkSurface选设备并创建逻辑设备。如果选 D3D12使用 Windows 窗口加 ID3D12Device、ID3D12CommandQueue。如果选 Metal创建 MTLDevice 和 CAMetalLayer。验证标准屏幕上出现一个纯色三角形且能响应窗口 resize。这里会遇到第一个坑不是所有人都有独立显卡。如果你的环境只有核显或者 WSL 下 OpenGL 没有启用硬件加速三角形也能画出来但性能可能非常差。这在后面的常见问题里展开。4.3 第 3 个月基础光栅化与材质在画完三角形后开始正式搭建渲染基础MVP 矩阵变换把模型坐标转到屏幕坐标。深度缓冲解决遮挡关系。纹理采样与过滤UV 坐标正确显示。简单的 Blinn-Phong 光照模型。相机控制鼠标控制视角、WASD 移动。验证标准加载一个 mesh比如 OBJ 格式的斯坦福兔子能以 FPS 视角自由查看表面有正确的漫反射和高光。4.4 第 4 个月SDF 与光线步进Lumen 的全局光照大量依赖 SDF所以这个月要彻底掌握 SDF 表示和光线步进。理解 SDF 的数学定义每个点到最近表面的带符号距离。实现基本 SDF 体球体、盒子、圆柱、圆环。实现 SDF 的布尔运算并集、交集、差集。实现光线步进从相机出发发射射线沿射线逐步前进直到距离小于阈值。为 SDF 场景添加法线和简单光照。验证标准屏幕上出现一个带基础光照的 SDF 场景球体和盒子混合鼠标拖动视角时边缘稳定不闪。4.5 第 5 个月从 SDF 到软件光追Lumen 的核心是 SDF 软件追踪这个月的目标是把 SDF 光线步进升级为可用的全局光照雏形。实现主光线命中后在命中点沿法线方向发射次级光线。用次级光线对 SDF 场景做遮挡测试模拟环境光遮蔽 AO。实现简化的多次弹射一束光线命中物体后随机反弹方向继续步进。引入光源让 SDF 场景产生软阴影。把 SDF 追踪结果和光栅化网格结果结合。验证标准一个简单的场景中物体之间有接触阴影角落有环境光遮蔽移动光源能看到动态阴影变化。4.6 第 6 个月合成 Lumen 第一帧最后一个月把前面所有模块拼成最终输出。生成场景的全局距离场。在低分辨率下对每个像素或 cluster 发射光线获得光照缓存。通过插值把低分辨率缓存扩散到全分辨率屏幕。合并直接光照、间接光照、反射可用性。加入色调映射和 Gamma 校正。在窗口内稳定实时渲染。验证标准同样的 Mesh 和灯光在自研渲染器中能看到类似 Lumen 风格的动态全局光照物体之间互相反射颜色、暗部有间接光、光照变化实时更新。到这里你看到的画面就是“你手搓的 Lumen 第一帧”。5. 环境准备与前置条件学习这套教程最关键的是稳定、可复现的开发环境。下面按平台给出通用准备清单。5.1 硬件要求CPU8 代 i5 及以上即可SDF 构建阶段比较吃 CPU 编译时间和预处理后面渲染主要靠 GPU。内存16GB 及以上编译大型 shader 和加载场景资源时更稳定。GPU支持 OpenGL 4.6 / Vulkan 1.2 / D3D12 / Metal 的显卡即可。若只跑 OpenGL核显也能完成前 3 个月的学习但进入 SDF 和高分辨率渲染后独立显卡体验更好。显存4GB 及以上可以应付学习场景更复杂的 GI 场景需要 6GB 至 8GB具体以实际项目资源和分辨率为准。5.2 Windows 环境推荐使用 Visual Studio 2022安装时勾选“使用 C 的桌面开发”工作负载和 CMake 工具。安装完成后用 CMake 配置项目生成 Visual Studio solution。如果选用 Vulkan还需要额外做两件事安装官方 Vulkan SDK包含编译器、验证层和工具。在项目链接配置里加入 vulkan.lib 和 vulkan-1.dll 依赖。5.3 Linux 与 WSL 环境Linux 下使用 apt 安装基础依赖sudo apt update sudo apt install build-essential cmake libglfw3-dev libglm-dev libvulkan-dev vulkan-tools mesa-utils对于 WSL 用户最常见的问题是GPU 被识别但 OpenGL 渲染仍然在 CPU 软件模拟上运行。这通常不是驱动安装错误而是 WSLg 的 OpenGL 转译层没有启用硬件加速。优先检查glxinfo -B如果输出中的 “OpenGL renderer” 是 llvmpipe说明是软件渲染。安装并启用 Mesa 的 Direct3D 12 转译驱动通常可以解决sudo apt install libgl1-mesa-dri libglx-mesa0 mesa-utils然后导出环境变量export MESA_D3D12_DEFAULT_ADAPTER_NAMENVIDIA如果你的 GPU 是集成显卡需要把适配器名称改成对应型号。WSL 下 Vulkan 通常比 OpenGL 更容易拿到硬件渲染也可以优先走 Vulkan 路线。5.4 macOS 环境macOS 上推荐使用 Metal。你需要安装 Xcode或者 Command Line Tools。使用 CMake 的 Xcode 生成器或者 Makefiles 生成器。链接 MetalKit 和 Metal 框架。Metal 的 Vertex/Shader 使用 MSL也就是 Metal Shading Language语法和 C 类似但更接近 CUDA编译在 Xcode 内完成。5.5 调试工具准备无论选哪套 API至少要装一个 GPU 调试器RenderDoc支持 OpenGL、Vulkan、D3D11、D3D12免费抓帧分析非常方便。Nsight GraphicsNVIDIA 官方工具对 GL 和 Vulkan 的性能分析更强但只适合 NVIDIA 显卡。PIX微软官方专门针对 D3D12 和 Windows。Xcode Metal DebuggermacOS 上调试 Metal 的唯一选择。建议第一周先把 RenderDoc 跑通后面所有绘制问题都能“抓一帧”来看而不是靠猜。6. 第一帧画面的功能测试与验证学习过程中每一阶段都要有明确的验证标准。下面按最小可运行路径给出测试步骤和预期效果。6.1 窗口创建测试目标创建窗口并清除背景色。操作方式// GLFW 窗口创建通用示例 glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); GLFWwindow* window glfwCreateWindow(1280, 720, Lumen Tutorial, nullptr, nullptr); glfwMakeContextCurrent(window); glClearColor(0.1f, 0.1f, 0.2f, 1.0f); while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); glfwSwapBuffers(window); glfwPollEvents(); }预期结果窗口显示深蓝色背景。判断成功标准窗口能正常打开、关闭resize 不崩溃。6.2 三角形绘制测试目标在背景上绘制一个彩色三角形。这一步会验证顶点缓冲、顶点着色器、像素着色器、VAO/Pipeline 是否正常工作。预期结果屏幕中央出现渐变三色三角形。判断成功标准三角形边缘光滑窗口改变大小时三角形比例不变形。6.3 矩阵上传测试glUniformMatrix4fv很多人在这一步卡住。OpenGL 中上传矩阵的通用写法是glUseProgram(program); GLint mvpLoc glGetUniformLocation(program, mvp); glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, glm::value_ptr(mvpMatrix));参数含义第一个参数mvpLocuniform 位置。第二个参数1上传 1 个矩阵。第三个参数GL_FALSE矩阵按列主序上传。第四个参数矩阵数据指针。如果你用 glm默认是列主序所以GL_FALSE是对的。如果矩阵看起来是错位或者歪斜的优先检查第三个参数是否传反了。在 Vulkan 中使用 DescriptorSet上传矩阵的代码风格完全不一样需要注意 mat4 与 std140 / std430 的布局对齐。D3D12 使用 DescriptorHeap 和 root signatureMetal 使用 Argument Buffer。这四套 API 的上传方式不同但数学逻辑完全一致。6.4 SDF 球体测试目标在 shader 中实现 SDF 并做光线步进。float sdSphere(vec3 p, float r) { return length(p) - r; } vec4 raymarch(vec3 ro, vec3 rd) { float t 0.0; for (int i 0; i 64; i) { vec3 p ro rd * t; float d sdSphere(p, 1.0); if (d 0.001) { return vec4(p, 1.0); } t d; if (t 20.0) break; } return vec4(0.0); }预期结果屏幕上出现一个带明暗差别的球体球面光滑没有明显锯齿。判断成功标准球体在旋转视角时形状稳定远看没有扩散或闪烁。6.5 第一帧 Lumen 验证这一步是整个学习路径的最终验收。你可以设置一个简单场景一个地面、两个不同材质的盒子、一个方向光。开启自研的 SDF 次级追踪后应该看到盒子和地面之间出现软阴影。两个盒子相邻处有颜色溢散也就是间接光照带来的 color bleeding。灯光移动时阴影和间接光实时更新。判断成功标准目标帧率保持在 30 FPS 以上画面无大面积闪烁移动相机时不出现明显漏光或黑块。7. API 接口与数据流对比写 Lumen 这类全局光照渲染器时你很快会发现计算量最大的不是最终像素而是 shader 之间传递的数据结构。在不同 API 里这部分差异非常大。7.1 OpenGL uniform 与 SSBOOpenGL 适合快速测试uniform 传单个矩阵很方便但传大数组效率低。Lumen 的 Radiance Cache 如果要在 OpenGL 里实现常用 SSBOShader Storage Buffer Object传缓存数据而不建议用大量 uniform。glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, radianceCacheSSBO); glBufferData(GL_SHADER_STORAGE_BUFFER, size, cacheData, GL_DYNAMIC_DRAW);shader 里使用 buffer 定义访问。7.2 Vulkan 与 D3D12 的资源管理Vulkan 和 D3D12 的资源绑定都比较复杂但都很适合做大规模 GI 计算。Vulkan使用 DescriptorSet 和 push constant需要在创建管线前规划好 layout。D3D12使用 root signature 和 descriptor heap还需要手动处理资源屏障。Metal引入 Argument Buffer 简化资源访问类似 Vulkan 的 descriptor但编译和调试体验更好。第一版 Lumen 场景建议先不做复杂资源调度。把场景中物体的 SDF 数据打包成一个全局 buffer全屏计算时只绑定这一个 buffer降低绑定复杂度。功能跑通后再优化为多 buffer 管理。7.3 API 调用示例通用模板调用任何图形 API总流程都是相似的创建设备和队列。创建交换链。创建渲染目标。创建描述符/管线状态。录制命令。提交命令。展示。// 伪代码Vulkan 单帧绘制流程 beginCommandBuffer(); transitionImageLayout(drawImage, COLOR_ATTACHMENT); beginRenderPass(drawImage); bindPipeline(giPipeline); bindDescriptorSets(giDescriptorSets); draw(3, 1, 0, 0); endRenderPass(); transitionImageLayout(drawImage, PRESENT_SRC); endCommandBuffer(); vkQueueSubmit(graphicsQueue, submitInfo, FENCE); vkQueuePresentKHR(presentQueue, presentInfo);这段伪代码展示了 Vulkan 每帧的标准骨架。D3D12 的名称是ExecuteCommandListsMetal 用MTLRenderCommandEncoder思路一致。8. 资源占用与性能观察写 Lumen 风格渲染器时最容易失控的是 SDF 光线步进的循环次数和显存占用。shader 里一次求交计算看似很轻如果全屏每像素跑 64 次步进再叠加次级光线GPU 压力会指数上升。你要重点观察几个指标指标观察方式常见问题帧率RenderDoc / Nsight 的 FPS 统计步进次数过高导致帧率暴跌GPU 占用率微星小飞机 / NVIDIA 性能面板占用率长期接近 100%说明 shader 过重显存占用NVIDIA-SMI / 任务管理器高分辨率 SDF 场景容易爆显存编译时长第一次运行耗时shader 编译 cache 未生效降低消耗的通用手段降低步进最大次数比如从 64 降到 32观察画质差异。先在小目标分辨率下测试比如 640x360功能稳定后再放大。低分辨率计算 GI 光照缓存再用双线性插值放大到全分辨率。启用 shader 编译缓存避免每次启动都重新编译。显存不足时的表现通常是窗口闪退或者画面突然变成纯色。这时候先检查 SDF 纹理和 radiance cache 的尺寸不要盲目开 4K。9. 常见问题与排查方法这里整理学习过程中最容易遇到的 8 类问题。问题现象可能原因排查方式解决方案窗口黑屏无绘制输出管线状态错误、未绑定渲染目标RenderDoc 抓帧看 draw call检查管线创建顺序和根签名匹配WSL Ubuntu GPU 被识别但 OpenGL 仍是 CPU 软件模拟Mesa 未安装 D3D12 转译层或适配器选择错误glxinfo -B查看 renderer安装 mesa 驱动并配置 MESA_D3D12 适配器OpenGL 线段粗细设置无效核心模式下线宽不一定支持大于 1查看 GL_LINE_WIDTH_RANGE改为用矩形条带模拟粗线glUniformMatrix4fv 后模型变形或消失参数传反或矩阵数据不足打印上传前后的矩阵值检查行列主序和指针偏移Vulkan 编译失败报验证层错误DescriptorSet 或 Buffer 生命周期不对开启 Vulkan validation layers按报错逐步修复资源回收顺序D3D12 显存突然飙升资源 barrier 未正确转换或未释放旧资源使用 PIX 查看 GPU 资源补全资源屏障并做内存池化Metal 深度测试异常MTLDepthStencilState 未设置或 depth attachment 格式不匹配查看 Metal API validation 输出确认渲染目标格式与 depth state 一致Chrome 开启 Vulkan 硬件加速后白屏驱动不完整与 Chrome 兼容性问题检查 chrome://gpu 的 Vulkan 状态更新 GPU 驱动或临时关闭硬件加速9.1 WSL 下 OpenGL 软件模拟问题网络热词中反复出现“WSL Ubuntu GPU 被识别了但 OpenGL 渲染仍然在使用 CPU 软件模拟”这是常见坑。Windows 侧虽然安装了 NVIDIA 驱动但 WSL 内部 Mesa 没有正确选取 d3d12 转译设备导致glxinfo -B显示 llvmpipe。处理顺序确认vulkaninfo能看到 GPU。安装 mesa-vulkan-drivers 与 gl1-mesa-dri。导出MESA_D3D12_DEFAULT_ADAPTER_NAME你的GPU名。重新运行glxinfo -B确认 renderer 不再是 llvmpipe。如果 OpenGL 持续无法硬件加速直接改用 Vulkan 写渲染器。Vulkan 在 WSL 下通常更容易识别物理 GPU。这也是我建议先把 OpenGL 做入门然后转到 Vulkan 或 D3D12 的原因之一。9.2 Vulkan 与 OpenGL 的选择矛盾如果你看到“Vulkan 渲染器下载”“memtest vulkan”这类词大概率是在装某款应用或做显存测试。不要被这类热词带偏。学习图形学不是“下载一个 Vulkan”就行而是要自己创建 VkInstance 并管理显存资源。做一个简单的显存测试可以通过 Vulkan 读取堆属性vkGetPhysicalDeviceMemoryProperties(physicalDevice, memoryProperties);遍历memoryProperties.memoryHeaps查看VK_MEMORY_HEAP_DEVICE_LOCAL_BIT对应的大小就能知道设备可用显存。10. 最佳实践与学习建议10.1 从最小可运行代码开始不要一开始就追求把 Lumen 的所有模块放进一个工程。建议保持一套“最小可运行配置”一个窗口、一个三角形、一个 MVP 矩阵、一个深度缓冲。每次加入新特性前都在这套基线上打分支。推荐工程目录LumenTutorial/ ├── assets/ # 模型、纹理、SDF 数据 ├── shaders/ # GLSL / HLSL / MSL / SPIR-V ├── src/ │ ├── core/ # 窗口、设备、管线封装 │ ├── math/ # 数学库 │ ├── render/ # 渲染器和绘制逻辑 │ └── gi/ # SDF、Radiance Cache、反射 ├── tests/ # 最小验证用例 └── build/ # CMake 构建输出10.2 保留一版可回滚配置每一阶段完成时把源码、shader、依赖版本记录到一个 changelog 里。当你调整 Shader 导致画面异常时能用 git diff 快速定位是哪一步改坏的。10.3 善用帧调试器不要用“打印日志”的方式调试 Lumen。全局光照的中间结果都是 GPU buffer直接抓帧查看颜色缓冲、深度缓冲、SDF 中间缓冲是否合理效率高得多。调试顺序建议先看红色通道是否异常。再看法线方向是否正确。最后看 SDF 射线的命中距离是否为合理范围。10.4 性能和稳定性建议同一帧里不要每像素都跑 64 次 SDF 步进优先用低分辨率 GI 缓冲。场景物体静态时距离场不要每帧重建只更新变化的区域。批量任务或者长时间渲染时要给 shader 编译设置超时机制避免卡死。接口服务若是在局域网中运行确认未暴露到公网防止被滥用。10.5 合规与版权提醒手搓 Lumen 的过程中可以使用 UE5 公开文档、引擎自带测试资产来学习。但要注意不要直接复制 Epic 的非公开源码和资产用于商业发布。不要使用来路不明的模型、贴图和 SDF 数据尽量使用 CC0 或自有资产。若后续把 Demo 发布到社区或商用建议明确标注哪些部分使用了第三方资源。11. 总结与下一步这套教程最值得尝试的点是把“学好图形学”落到一个清晰目标上6 个月后你的自研渲染器要能输出一帧动态全局光照画面。它逼着你把 OpenGL/Direct3D/Vulkan/Metal 中任意一条技术栈吃透同时让你真正理解 Lumen 的原理而不是只会调 Unreal 面板。最先应该验证的功能是第 1 个月的窗口和三角形基础。这个环节能快速暴露环境问题和 API 选型的错误。最容易踩的坑是 WSL 下 OpenGL 软件模拟以及矩阵 uniform 上传的布局问题建议提前看第 9 节的排查表。后续可以继续扩展的方向很多从单帧渲染推进到连续动画从单光源推进到多光源 GI从静态 SDF 更新到动态场景支持从无后处理推进到完整 PBR 管线甚至可以再接一层硬件 RT 来加速追踪和软件 SDF 追踪做性能对比。建议收藏备用按照 6 个月路线先跑通第 1 个月再决定是否继续深入。第一帧 Lumen 画面值得你亲手写出来。