着色原理:光线
在我们考虑制作第一个着色图像之前,我们首先需要介绍光的概念。 我们在前面的课程中提到的场景由相机、物体和灯光组成。 灯光是特殊的“实体”,其唯一功能是指示场景中光线从何处发出。 如前所述,我们看到物体的唯一原因是光源发出的光从物体表面反射回来。 如果不在场景中创建灯光,则场景应呈现为黑色。
1、光源
在现实世界中,每个光源都有一个物理实体。 光源是具有发光特性的物体。 但光源只不过是一个标准物体:它有形状和尺寸。 尽管它具有其他物体所没有的属性:它会发光。 因为光源本身就是物体,所以它们也可以被眼睛直接看到。 但一般来说,切勿用肉眼直接观看光源,因为它可能有害。 赤身裸体地看太阳,是的,会灼伤你的眼睛。 不过,我们可以安全地考虑其他光源,例如篝火的火焰或火炬的灯泡。 如果你正在阅读这些内容,你甚至正在看一个:手机或计算机的屏幕。
问题在于,在 CG 中,以物理上精确的方式模拟光(即将光表示为具有其形状和大小的对象)的计算成本很高。 这种光称为区域光(area lights)或更一般的几何区域光。 因此,很长一段时间以来,CG中的灯光仅被表示为理想化的物体,即没有物理尺寸的实体。 与区域光相反,这种光有时也被称为增量光(delta lights),这来自术语增量函数,它是数学中的一个特殊函数,创建它是为了定义一个在自然界中不存在但确实存在于抽象世界中的函数。虽然抽象,但对于解决各种有趣和实际的问题非常有用。 我们基本上可以区分两种类型的 Delta 光:定向光或远距离光(distant lights)以及球形光(spherical lights)或点光源(point lights)。 前者通常是后者的子集,但我们稍后会对此进行解释。
你需要知道:出于实际原因,在计算机图形学的早期使用 Delta 光,因为长时间模拟区域光成本太高。 随着 2000 年代末计算机变得越来越快,切换到区域光也慢慢成为可能。 尤其是在今天,应尽可能避免使用 Delta 灯。 为什么? 因为不将光表示为区域光会导致很多问题。 例如,我们知道物体会反射其他物体,当然,光源因为本身就是物体,也应该被光滑的表面或镜子反射。 虽然光滑表面反射物体的大小取决于被反射物体的大小和到光滑表面的距离,这两个概念,物体的大小和被反射物体到反射表面的距离可以合并为立体角(solid angle)的概念,我们在本课中不会讨论太多。有关此主题的更多信息,请查看高级部分的课程。
当灯光没有尺寸时,根本不可能决定它们在光滑表面上的反射应该有多大! 这多年来在渲染领域造成了很多问题。 人们必须通过调整反射表面的粗糙度以及通过调整光本身的“粗糙度”参数来人为地控制该粗糙度来控制这些反射的大小。 这样做可以帮助你在每个灯光的基础上欺骗灯光的大小,但这肯定永远不会导致产生物理上准确的图像。 希望这种做法现在几乎完全消失了,主要是因为正如刚才提到的,模拟区域光现在已经可以负担得起(尽管模拟区域光的计算成本仍然很高,并且需要仔细优化)。
2、远光
远光(distant lights)是指距离我们很远的光,它们发出的光只能以彼此平行的光线形式到达我们。 对于这样的光源,我们所关心的只是这些光线的方向。 远光的一个例子是我们的太阳。 太阳是一个球体,因此你可以正确地认为它是球形光。 在太阳系的尺度上,甚至在更大的尺度上,是的,太阳就像一个球形的光。 然而,如下图所示,与太阳相比,地球很小,更重要的是,距离恒星很远,到达地球表面的阳光被包含在一个超小的方向锥内。
对于熟悉立体角概念的读者来说,这个圆锥体的立体角大约只有 0.0000687 球面度。 在CG中,当我们渲染场景时,场景仅覆盖地球表面的一小部分区域。 因此,我们可以放心地假设照亮该场景的太阳光线都是彼此平行的。 可能会有很小的变化,但它是如此微不足道,以至于我们可以完全忽略它(无论如何,我们可能永远不会有足够的数值精度来在计算机中表示这种变化)。 换句话说:我们不在乎。 照射到地球表面的太阳光线彼此平行。
因此,在这种特殊情况下,我们关心的是光线的方向,因此称为定向光(directional lights)。 或者换句话说,光源相对于渲染场景的位置是无关紧要的,因为光线彼此平行的唯一原因是光源距离场景非常远,因此光线照亮了场景,包含在一个微小的方向锥体中。 因此,术语“远光”。 本质上,我们在 CG 中模拟太阳或任何其他远处的光线所需要的只是一个方向,而不是其他。
不过,请注意,太阳离我们很远,但它在天空中仍然有一定的大小。 事实上,尽管看起来令人惊讶,太阳在天空中比月亮更大。
在本课程中,我们将首先渲染漫反射对象并学习如何使用远处的灯光投射阴影。 然后我们将学习如何模拟球形光。
3、球形光源
球形光源是自然界中最常见的光源类型。 在某种程度上,即使不是球形的光也可以以某种方式近似为球形光源的集合。 对于远距离光源的差异,球形灯的位置很重要。 事实上,对于球形光源来说,这是唯一重要的事情。 我们需要知道它们在太空中的位置。 如果我们将 P 称为要着色的物体表面上的点,那么找到球形光源在 P 方向上发出的光线方向很简单:它只是 P 减去球形光源位置(让我们 称之为 Lp),如图 2 所示。
在接下来的章节中,我们将证明 P 和光源之间的距离也很重要(对于球形光而不是远距离光,照亮物体的光量取决于光和物体之间的距离)。 我们将需要这个距离,然后我们可以用它来规范化光方向矢量。 这将在本课后面进行解释。
4、光强度(和颜色)
除了光位置(如果是球形或点光源)或光方向(如果是远距离或定向光)之外,我们还需要什么来定义光源? 嗯,光源发出的光在 CG 中我们可以简单地表示为一种颜色。 通常最好将光颜色定义为颜色和强度(实数)的组合。 光的颜色可以保持在[0,1]范围内,光强度可以取0到无穷大之间的任何值(或者在计算机代码中,例如我们可以用浮点数表示的最大值)。 最终发出的光量由光色乘以光强度得出:
5、光源实现
在代码中,我们将通过创建一个特殊的 Light 类来区分灯光和几何体。 我们将向该基类添加以下成员变量:
- lightToWorld:灯光也可以通过 4x4 矩阵进行转换。 我们将使用这个矩阵来计算球形光的位置和定向光的方向。
- color:灯光的 RGB 颜色,值在 [0,1] 范围内。
- intensity:光强度。
我们看一下代码:
class Light
{
public:
Light(const Matrix44f &l2w) : lightToWorld(l2w) {}
virtual ~Light() {}
Matrix44f lightToWorld;
Vec3f color;
float intensity;
};
默认情况下,我们假设光点是在世界的原点创建的。 我们将使用光到世界矩阵将光转换到其在世界空间中的位置。 请注意,灯光不受比例影响。 点光源也不受旋转的影响(但远距离光源会受到影响)。 远处的灯光不受平移的影响。 现在让我们看看点光源的一种可能的实现:
class PointLight
{
public:
PointLight(const Matrix44f &l2w, const Vec3f &c = 1, const float &i = 1) : Light(l2w)
{
this->color = c;
this->intensity = i;
l2w.multVecMatrix(Vec3f(0), pos);
}
Vec3f pos;
};
与点光源类似,我们假设默认情况下远处光源的方向指向负 z 轴(如果你可以访问 Maya,则创建一个远处光源并检查其默认方向)。 换句话说,默认的光线方向是(0,0,-1):
同样,为了改变或控制光线方向,我们将改变光到世界的变换矩阵:
class DistantLight
{
public:
DistantLight(const Matrix44f &l2w, const Vec3f &c = 1, const float &i = 1) : Light(l2w)
{
this->color = c;
this->intensity = i;
l2w.multDirMatrix(Vec3f(0, 0, -1), dir);
dir.normalize();
}
Vec3f dir;
};
现在我们知道了如何在系统中创建灯光,让我们学习如何模拟漫反射对象的外观。
原文链接:Lights
BimAnt翻译整理,转载请标明出处