面向电子游戏的 GPU 编程--基础光照

28 阅读10分钟

基础光照

介绍

这节课我们将主要学习局部光照,也就是直接照射物体的光源,而不是从其他光源反射的光线。而且,乍一看可能有点奇怪,我们暂时不会考虑光源和被照物体之间可能存在的障碍物。当然,也有一些方法可以添加阴影,但这需要大量的计算。我们会在课上学习一些,但要实现起来相当复杂。

image.png

全局光照则考虑了光线从其他物体反射的情况。比如,光线照射到某个物体上并反射,这样可以生成更加逼真的场景,但计算量也相当大。一个完整的全局光照版本会使用光线追踪之类的技术。虽然过去大约有 10...几年前,人们就利用这些显卡的 GPU 功能,也就是科学计算部分来进行光线追踪。但这些通常不是实时计算。英伟达和 ATI 的最新显卡确实内置了一些实时光线追踪功能,但仍然存在一定的局限性,而且很少有游戏使用这项功能,即使是使用的游戏,也只是将其用于某些特定效果。

image.png

基于物理的渲染

image.png

电影里基于物理的渲染技术的研究成果。

需要注意的是,使用基于物理的渲染并不一定意味着能够达到照片级的真实感。这里我们展示了一些角色,它们显然不是你在现实生活中会看到的。这里的主要理念是,即使角色本身可能高度风格化,构成角色表面的各种材质也会像真实材质一样对光线做出反应。这带来的主要好处是,无需修改物体材质属性的定义,就能大幅度改变场景的照明方式。同样,基于物理的渲染并不意味着游戏中的场景就一定好看。当然,你可以买很多摄像机和灯光设备,尝试自己拍摄电影,但除非你具备设置灯光和摄像机的经验,否则你的作品看起来仍然不会很专业。

image.png

这里有一个使用基于物理的光照和 Fox 引擎的绝佳例子,它被用于《合金装备 5》。我在这里停顿一下,给你一个机会仔细观察一下,看看哪些场景是小岛工作室会议室的真实照片,哪些是由他们的引擎渲染的。

image.png

这里有一些主要的线索。这里一处是海报的反射,它们被某种涂层遮挡,呈现出光泽的反射效果。你还可以看到光线在这里运作的一些微妙之处。如果我们看向这边,可以看到这些光源反射出的光线在这里有些模糊。

image.png

右边的场景,我可能有点过度解读了,但我认为右边椅子的靠背上也有一些更微妙的细节。

image.png

你也可以看到海报上光泽涂层的类似效果。

image.png

所以问题来了,看看这些场景,找出哪些是真实的,哪些是引擎生成的。

image.png

好吧,这是个陷阱题,它们都是引擎生成的。他们并没有真的把马牵进会议室。

Elements of PBR

当人们谈论基于物理的渲染时,它并非指某个特定的概念,而是许多不同因素的综合体现。

  • 线性空间光照的问题主要与纹理的创建和存储方式有关;
  • 能量守恒;
  • 互易性;

image.png

  • 金属或介电材料(非金属材料); 以前的计算机图形技术更多地基于启发式方法,而非物理原理,因此没有这种区分。
  • 所有物体都有镜面反射;
  • 所有物体都会所谓的 Fresnel; 你可以想象一下,如果你开车行驶在柏油路上,你不会觉得柏油路发射率很高,但如果下过雨,你以一定的掠射角(太阳位于地平线附近,强光会让你睁不开眼)行驶,那么路面就会变成一面镜子。
  • 为了充分利用基于物理的渲染,我们需要 render buffers 具有一定的分辨率和动态范围,以便处理逼真场景的高动态范围。 早期游戏中使用的光源图形技术通常受限于内存缓冲区中有限的动态范围,而内存缓冲区用于存储图形计算结果。早期游戏基本上是通过限制光照强度的范围来解决这个问题。然而,符合物理规律的逼真场景可以拥有非常广泛的光照强度。人眼通过对数尺度感知光照强度来处理这种情况,这与你感知音高和音量的方式相同。这就是为什么你能同时感知到喷气式发动机和掉落在地板上的针。然后,有一些技术可以将高动态范围的图像映射到显示器有限的动态范围。我们将在以后讨论。

Common light sources

image.png

如今,现代游戏引擎可以处理各种复杂的光源,甚至可以实时处理,但在本课程的大部分时间里,我们将专注于这些最简单的光源。想象一下,一个定向光源由许多光线组成,这些光线基本上平行地从非常遥远且非常明亮的物体(例如太阳)射出。更近距离的光源可能是一个点光源,其能量向各个方向辐射。能量会随着距离衰减,所以距离越远,强度就越低。

太阳也遵循同样的原理,但由于太阳距离我们非常遥远,这些向外辐射的光线看起来就像平行光线。就地球大小的物体而言,随着距离的增加,光波的强度会分散到更大的区域,因此强度会持续下降。但是地球或我们场景中的物体范围非常小,这种相对差异在计算衰减时影响不大。因此,我们假设场景中的这些物体强度基本相同。

image.png

聚光灯可以看作是点光源的精细版本,它强调特定的方向。

Vector notation

image.png

假设我们放置在太空中的物体有一个表面,我们会考虑从该表面发出的法线,这对于我们进行的照明计算至关重要。我们还要关注指向光源的方向矢量。一些计算我们将在稍后的课程中讨论,今天不涉及。绕法线以相同角度发射的向量,会有一个向量从表面上的该点(或试图照亮的点)指向摄像机所在的位置。我们稍后讨论的计算会用到一个叫做半向量的东西,它平分了光向量和视向量(或观察向量)。我们可以通过对这些向量求平均值,然后再进行归一化来计算这个平分向量。需要说明的是,所有这些计算都假设这些向量已被归一化为单位长度,也就是说,它们的长度都是 1。这里所有向量的长度都是 1,这条法线的长度也是 1,光向量,所有向量的长度都已归一化为 1。我强调这一点是因为在图形编程中一个极其常见的错误,就是忘记归一化,而这在很多情况下都会导致问题。我想强调的一点是,这些法向量不是我们在背面剔除中讨论的法向量,那些法向量是针对特定三角形动态计算的,这些三角形源自于你的艺术家使用的软件(例如 3D Max、Maya 或 Blender)计算出的法线。这些软件会为每个顶点生成一个法线,用于光照计算。这些由软件生成的法线试图包含一些关于底层表面的信息,3D 模型会尝试对其进行粗略细分,从而生成三角形。

diffuse lighting

image.png

光照包含漫反射和镜面反射分量。稍后讨论镜面反射,漫反射表面是指相对于照射它的光的波长而言粗糙的表面。这是从光线的角度来看的,但肉眼可能看不出来。一个有趣的实验是,用激光笔对准你的桌子,并以一定的角度倾斜,你会看到奇怪的斑点图案。这是因为激光笔发出的相干光会发射光线。以一种类似于普通非相干光(例如灯泡、蜡烛或太阳发出的光)呈现的方式,展现这些粗糙的细节。由于表面非常粗糙,当光线照射进来时,由于表面的随机性,能量会均匀分散。因此,对于漫反射光源只要表面在摄像机的可见范围内,摄像机的位置就无关紧要。那么这是如何实现的呢?我们将涉及到一些物理知识。假设光线、法线和表面在同一条直线上(这里我们稍微分开它们,只是为了避免混淆光矢量和法线矢量),你应该把它们想象成彼此重叠。如果它们像这样排列,我再次强调,假设它们的长度都是单位长度。

image.png

虽然这里的图形显示可能不太清晰,但它们的长度都应该是单位长度。现在,如果光线相对于法线以一定角度入射,摄像机的位置也无关紧要,那么发射到我们这里的光线就会减少。我们以 90 度角入射,基本上没有发射。因此,发射的类型取决于光线,遵循余弦定理,即角度的余弦值。余弦衰减的好处在于,你实际上不需要计算角度的余弦三角函数,你可以使用一些三角函数来得到余弦值,它只是法向量和光向量的点积。这里,你取每个坐标 XYZ,将法向量 X 与光向量的 X 相乘,法向量的 Y 与光向量的 Y 相乘,法向量的 Z 与光向量的 Z 相乘,并将这些乘积相加。

image.png

image.png

ClightC_{light} 这里指的是光线的 RGB 颜色,MdifM_{dif} 是漫反射的 RGB 颜色值,而这里带圆圈的奇怪乘号表示颜色乘积,也就是红乘红绿乘绿蓝乘蓝,这三个颜色值相乘得到另一个颜色值。

specular lighting

image.png

镜面反射更复杂,它取决于摄像机的视角。如果我们把这个效果添加到漫反射中,这里我还添加了一些环境光。这算是早期版本遗留下来的东西,早期版本使用了更原始的临时光照模型。环境光基本上就是添加到所有物体上的少量光线,在旧游戏引擎中,它试图表现场景中整体的反射效果。如今,我们有了更先进的技术来处理这个问题,我们将在后面的课程中介绍。早期版本的镜面光照使用了一种 Blinn-Phong 模型,在某些方面不符合物理规律,现在我们可以使用一些受其启发而来的基于物理的镜面反射模型,我们将在以后的课程中讨论。

Bidirectional Reflectance Functions

image.png

我之前展示的公式看起来非常像这样:如果我们没有这部分。如果我们只把漫反射颜色放在这里,那么它就等于我们之前的结果。所以我现在展示的是一个稍微复杂一些、更通用的视图。

image.png

这里有 Lamberson 项,它计算漫反射光,但现在我们加入了一个叫做双向发射函数的东西。它是光线入射角度的函数,说明一下,入射角度的约定是,光矢量实际上指向光源,观察矢量也一样,它指向眼睛。

image.png

现在对于漫反射光照,有一个非常简单的双向反射分布函数,它只是一个常数,基本上就是材质颜色。这使得我们之后可以通过更复杂的模型来加入镜面反射。BRDF 函数中,我们可以添加一些镜面反射项,轻松地将我们之前讨论过的漫反射整合进去。

image.png

你可能对 PI 值感到好奇,这涉及到计算机图形学一些更复杂的数学技术细节。BRDF 函数的分母中有一个 PI,它起到归一化器的作用。如果你将这些 PI 值结合起来,它们最终会相互抵消。所以当你查看着色器代码时,你会发现有时需要注意,因为有时你会看到公式中出现 π\pi,有时则不会,有时你会看到像这样的 BRDF,或者分母中出现 π\pi。如果是更复杂的非漫反射模型,也许就不应该出现 π\pi。情况可能会变得复杂,因为人们经常会说,既然 π\pi 在两个地方都出现了,为什么还要放进行呢?为什么不节省计算量呢?所以,一个合格的 BRDF,也就是一个符合物理规律的 BRDF,才是我们所追求的。我们并不是要完美地匹配描述场景的所有物理定律,而是要找到一些在某些方面可能近似,但至少符合一些基本物理属性的模型。这里的互易性意味着,我们应该能够改变光源和眼球的位置,并得到相同的结果。这被称为 Helmholtz reciprocity。

image.png

显然,对于我们之前看到的漫反射滑动 BRDF,它只是一个常数。所以它满足了这一点,但非常重要的是,我之前提到的布林-冯模型不具备互易性。

image.png

能量守恒

image.png

BRDF 模型要想在物理上合理,还需要满足另一个重要条件:反射的光不能多于入射光。这里我们讨论的是发射,也可以有发光表面,比如霓虹灯,或者想想你的电炉,如果电流通过加热元件,它不仅会变热,还会发光,它会产生自己的光。从技术上讲,对于我们的漫反射模型,你不能随意的选择 MdifM_{dif} ,它必须小于 1/π1/\pi

image.png

艺术家在为红色、绿色和蓝色材质设置属性时,请确保每个数值都小于 1。这样就能自然地实现:如果用强度为 1 的光照射物体,并且光线垂直照射(即以 0 度角正面照射),则反射光强度为 1。这会让用户感到满意。但需要记住的是,如果您要编写复杂的全局光照解决方案,例如设置大型矩阵方程来处理所有边界滑动效果,那么您可能需要重新考虑这个问题。否则,您可能会得到一个不稳定的迭代解决方案,因为这些表面实际上会输出比输入更多的能量。

Light source properties

对于方向光,由于它位于无限远处,我们假设在场景范围内无需考虑距离衰减。但对于点光源和聚光灯,您可能需要考虑能量场的衰减情况。你知道,如果你观察单位面积辐射出的能量,能量守恒定律意味着,每个单位面积的能量会越来越少,就像这里写的那样。所以你需要考虑光线会随着距离衰减这一点,人们有时会使用基于物理的模型,有时会使用像这样的模型,其中包含一些可以设置的常数,以获得不同的艺术效果。这个 D2D^2 在物理上是合理的。这里的 D 不是 d 的线性项,但你可以选择它来获得某种艺术效果。

Atten=1/(a0+a1d+a2d2)Atten = 1 / (a_0 + a_1d + a_2d^2)

Spotlight effect

image.png

有很多不同的方法来构建聚光灯,没有哪种技术一定比其他技术更好。我将向你展示一种我在英伟达之前发布的 CG 教程中看到的方法,你现在可以在网上免费阅读。总之,思路是这样的:我们有一个指向被照亮点的向量(无论它在表面上的哪个位置),还有一个向量表示聚光灯的指向方向。同样,假设这两个向量都已归一化为单位长度。我们将根据这里称为 alpha 的角度来确定衰减。同样,我们不需要使用余弦函数,可以直接使用点积。需要注意的是,我们之前看到的照明矢量是从物体指向光源的,为了与光源发出的方向矢量相匹配,这里的照明矢量将是从光源指向表面的。无论如何,这里的点积计算的是这个角度。我们将再次使用最大值 Teen,因为显然,如果角度为 90 度,它不会被消除。这里 f 是一个后续因子,请记住,我们取 f 次方的量介于 0 和 1 之间。因此,如果我们取幂,它会改变余弦形状,并以不同的方式压缩它,所以它可能看起来像这样,或者看起来像这样。因此,随着 f 的增加,我们基本上是在收紧聚光灯效果。我之前提到的 Blinn-Phong 模型使用了类似的计算方法来计算镜面反射光照,并且对镜面高光的宽度也有类似的控制。但我说的这个镜面高光和聚光灯效果不一样。