实时渲染相关入门:给实验室科研轮转同学的一封信

内容纲要

新学期开始啦,实验室来了两位科研轮转的大二学生,它们都是前OI的国家集训队选手,其中一位对游戏引擎(UE)中的渲染感兴趣,另一位对实时全局光照明感兴趣,然后实验室里目前做实时全局光照明研究的博士生只有俺一人。于是昨晚导师给我们四个人拉了个小群,让我分享一下曾经的学习资料,而我的学习资料又多又杂且全然来自于伟大的互联网难以整理,我的知识也水平不足以支撑我侃侃而谈,身为社恐也找不到打开话匣的方式,这让我一整个白天在群里一言不发,又因为一言不发而惶惶不安,分外尴尬。

直到晚上,笔者终于下定决心好好分析一下该说些什么,来助力这两位能力很强且对实时渲染全局光照明感兴趣的学弟日后的学习。毕竟能有如此强人对实时渲染全局光照明感兴趣,以后或许还能一起探讨问题,笔者肯定是兴奋且高兴的。从五点四十开始写这些内容,结果一发不可收拾直到八点才写完所有想说的话。回头一看,这些内容或许也能位其它对实时渲染中全局光照明感兴趣的刚入门或准备入门的同志们带来帮助,于是决定写成一篇博客。

正文

大家好呀,我是Hineven,主要进行实时渲染中光照的研究,很高兴大家对实时渲染方面的内容感兴趣。我会尝试介绍一下实时渲染中光照相关的学习资料、现存的待解决的问题,可以学习、实现、探索的有趣内容。下述的所有有关实时渲染的内容我并不全然详尽了解,但我会尽可能提供相关的可以索引与查阅的名词和概况,如果大家对其中某个特殊方向比较感兴趣的话,就能从这个名词开始查阅资料并深入探索了。

实时渲染的光照部分的理论包括人眼感知色彩的原理、成像理论、光的传播模型、常见的材质模型、采样理论、蒙特卡洛、三维空间线性变换等内容的入门和基础在B站上有一系列GAMES公开课课程讲的很好,比如

GAMES101(https://www.bilibili.com/video/BV1X7411F744 )、202(https://www.bilibili.com/video/BV1YK4y1T7yY )。如果想要更系统地学习理论基础,可以从pbrt中的1-10章学习(https://pbr-book.org/ ,第三版和第四版均可),若想要再进一步则可以继续阅读pbr-book的reference页中的相关论文,若要深入,我认为Veach的博士学位论文也是一个较好的阅读材料(https://graphics.stanford.edu/papers/veach_thesis/ )。

理论一般是比较寂寞而枯燥的,实时渲染的实践则显然非常有趣,同时也很重要。没有实践,实时渲染的乐趣就损失了一大截(至少我如此认为)。

实时渲染实践这对工程能力也有一定要求。引入别人写好的数学库、创建带有窗体的应用需要在工程中链接额外的库(比如glm、GLFW),因此需要入门C++链接步骤,了解一种构建系统比如CMake、SCons、Xmake或比较简单的makefile等。C++链接相关内容大家应该已经有所了解,而构建系统这部分我时通过开源工程和网络搜索学习的。pbr-book所描述的离线渲染器(https://github.com/mmp/pbrt-v3 )对我有过很大帮助,当然,也有一些图形学教学网站在教授时会顺带地提供构建系统和链接的简约教程(比如https://learnopengl.com/ ),或者直接从这些构建系统的官方网站进行学习。

编写能在GPU上运行的代码需要学习并了解一种图形API,和一种着色语言。前者是大家约定的、GPU厂商通过驱动实现的调用GPU计算能力的接口,后者则可以被各种编译器编译成能在GPU上运行的字节码。

学习图形API才能开始编写真正的实时渲染算法,而且学习图形API有助于了解GPU的与CPU大不相同的执行模式(如果诸位用过CUDA那想必已经对这方面比较了解了)。现有的、流行的桌面级图形API主要包含OpenGL、Vulkan、Direct3D,这三者各有特点,都可以学习。

OpenGL是一个相对较老的规范且正逐渐停止更新,性能也较低,但跨平台且上手难度简单,学习资源非常丰富,适用于快速入门和编写原型。网上有很多学习资料,比如(https://learnopengl.com/ )。OpenGL有其官方的维基(https://www.khronos.org/opengl/wiki )。

Vulkan是一个复杂的、新的、旨在替代OpenGL的新规范,支持足够多的新特性、自由度高、跨平台,但相对的,上手难度会很高,编程模型比OpenGL更复杂导致代码长且晦涩,驱动Bug也可能多一点。Vulkan的学习网站包含(https://vulkan-tutorial.com/ )(https://vkguide.dev/ ),前者适合用于入门,后者内容更加丰富且我个人认为讲得更好。我在遇到疑惑时会查阅Vulkan的官方文档(https://docs.vulkan.org/spec/latest/index.html ),它虽然冗长但更为严谨,内容也最为详尽。

Direct3D是由微软维护的、只由Windows平台支持的规范。目前大部分3A游戏的绘制都使用此规范作为图形API。D3D的最新版本是12,它基本拥有桌面端最好的性能和硬件厂商的优化,它支持足够多的新特性,但代码复杂度和上手难度也基本仅略低于Vulkan或与Vulkan对等。它的官方文档由微软维护,比较丰富,但可读性不如Vulkan好。

PS:如果你想阅读Unreal的源代码,Direct3D是我推荐学习的图形API,因为Unreal对D3D的支持是最好的,其RHI层抽象我个人认为也最接近D3D。

着色语言是可以被编译为GPU能执行的字节码的语言,用于描述GPU上执行的逻辑,它们被用于编写实时渲染中的核心逻辑,也是必须学习至少一种。实时渲染的着色语言中GLSL与HLSL最为常用。二者都可以学习,但笔者推荐HLSL,后者生态相对更好,不开启各种奇奇怪怪的细碎扩展也能拥有丰富的语法和表达能力,写起来会更自由。注意这些着色语言需要寻找专门的编译器编译。

除此之外,着色语言还有MIT和NVIDIA的Slang、Taichi提供的类似python的语言和清华LuisaCompute提供的类似C++的语言等等,这些语言用户较少,生态相对较差但功能可能更加强大,笔者其实也没有尝试过,如果有兴趣应该也可以使用。

调试在GPU上运行的代码相对困难。一种方式是通过使用抓帧工具捕获渲染中每一帧的图形API调用、着色器输入输出等。其中,RenderDoc用于OpenGL,Nvidia Nsight Graphics可用于NVIDIA系列显卡,Pix可用于Windows上的Direct3D11/12,这些工具除了用于调试,还能用于进行性能分析,但坏处是,它们可能导致没有错误信息的崩溃。第二种方式是古老的输出调试法,一些着色语言和API会支持在Shader中调用类似printf的函数往输出流中输出字符串。要启用这个方法,在GLSL中包含扩展GL_EXT_debug_print,或在HLSL中使用SM4以上并直接调用printf函数,并同时在你使用的渲染API内开启相应功能。第三种方法则是我(我相信一般图程都这么做)常用的调试方法,将数值信息可视化为颜色直接输出到屏幕上。

关于硬件光线追踪,虽然它是先进全局光照明管线中非常重要的基础构件,但众所周知只有部分硬件支持硬件光线追踪,你至少需要NVIDIA 20系或更新型号的显卡(或AMD、Intel等其它支持的显卡)才能在着色语言中调用相关功能。如果没有这些显卡,也有许多传统的、不依赖于硬件光线追踪的渲染管线(或者全局光照明算法)可以研究与学习,静态的包含光照贴图烘培、Radiosity、光探针烘培等,动态的包含屏幕空间算法(SSRT/SSGI等等各种各样)、LPV、SVOGI/VXGI、或者Lumen复杂的DF软光追等等。

关于游戏引擎方面,游戏引擎是一个庞大的话题,渲染虽然很重要,但实际只是其中迷你的一小部分。而全局光照明管线则是渲染这一小部分中的一小部分(虽然它也非常重要)。游戏引擎中的渲染我的了解就很有限了,目前源代码可阅的游戏引擎有Godot(开源)、Unreal、Cocos2d(开源)等(Unity不知道能不能看源码),我只读过UE的渲染部分的光照部分的代码。除了游戏引擎,还有很多很多开源的迷你游戏或渲染引擎/框架,它们被用于学习或搭建与实验渲染算法的快速原型,在其上进行实验或开发Demo很简单,但一般不能用于开发真正的产品。比如AMD的Capsaicin、NVIDIA的Falcor等(数量很多,总有一款合你口味的,这些就需要诸位自行探索了),我们实验室的学长也有一些自用的框架,大部分都是用CUDA写的光追渲染器(https://github.com/woAIxuexiSR/SRT )(https://github.com/cuteday/KiRaRay )。大家也可以自己试着写一个,对我而言,这么做对于算法的理解与编码能力的提升有很大好处。

在学习全局光照明管线前,我建议先尽可能多地了解一些简单的实时渲染器的实现思路和其中的常用技术,比如GPU-Driven理念、延迟渲染、抗锯齿算法、各种AO、BentNormal这种小Trick、光源索引注入等,我认为这些东西是一个好的全局光照明管线的地基,它们原理简单,也并不难实现,但总能显著地提升渲染性能与效果,相对较为复杂地全局光照明性价比更高。

我可以以我的认知介绍一下实时全局光照明方面相关的话题。我认为实时全局光照明是比较特殊的、工业界与学术界齐头并进(或者工业界胜过学术界)的一个领域。不考虑体积渲染(比如雾气和布丁)和特殊材质(比如头发和毛巾),实时全局光照明中直接光照明的计算方法按照光源种类不同又多种多样且不完全互相垂直(直接算、阴影相机、算解析解近似(LTC)、各种蒙特卡洛等)。间接光照明的流派也有很多,我所了解的流派或方法可以被分类为Radiosity(PRT)、光探针、屏幕空间方法、基于体素、基于硬软光追的各种蒙特卡洛方法等。降噪和后处理则是另一话题。工业级的、完整的实时全局光照明管线一般会将问题拆分成多个部分后,使用多种不同算法因地制宜,混合着色,并包含多种后处理。这些技术中的一部分有比较新或者比较有代表性的论文(SVGF(非神经网络的)降噪、DDGI光探针、Interactive Indirect Illumination Using Voxel-Based Cone Tracing体素、ReSTIR GI蒙特卡洛等)也有一些学术界对神经网络在全局光照明中除降噪外的应用的探索,但据我所知相关方法还没有进入工业界,或者性能并不可靠。总而言之,这是一个有趣但内容繁杂的话题,大家可以自由了解其中的任意部分,并尝试组建自己的全局光照明管线。

关于UE源码的阅读与学习,其实我个人认为这是一个比较耗时的任务,不建议一开始就进行。因为UE比较庞大,也有些复杂,它的注释也写的不是很好,如果大家在有一些名词知识的情况下进行阅读会效率更高,我一开始阅读的时候就完全昏了头。如果你想阅读UE源码,应该对先进桌面图形API比较了解(Direct3D12或Vulkan,推荐前者),了解一定现代C++规范(也不用很了解),熟悉HLSL。

学习UE有一些不错的互联网资料,其中包括UE在SIGGRAPH Course上的技术报告(在Youtube上可搜索),一位博客园大佬写了一系列博客(https://www.cnblogs.com/timlly/p/13512787.html ),尽管是UE4,但这仍对我非常有用。UE5和UE4在许多地方都是相似的,而他的博客虽然我有时第一次看不懂,但至少可以告诉我代码应当从何读起。

阅读UE源代码最好要有一个不错的IDE,这会为浏览和分析UE的代码提供莫大的便利。我推荐使用Rider for Unreal,它的语法提示速度和功能对我而言都比Visual Studio更加强大(当然这也有可能是因为我Visual Studio用得不熟练),值得注意的是,你需要预先准备至少32GB的内存才能畅快地在IDE中阅读代码。

在开始阅读渲染代码之前,UE的编译工具(UBT、UHT)、多线程和任务系统、RenderGraph、RHI抽象需要先行学习。其后,请重点阅读Renderer模块。

如果大家在学习过程中踩了坑或者遇到玄学情况,我们可以在我们实验室UE学习的微信群里一起交流,或许我或者其它学长踩过相关的坑,就不用在同样的坑上浪费时间了。很高兴大家能对实时渲染这么感兴趣,大家一起学习,共同勉励。

此条目发表在学习, 生活分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注