Lode 的计算机图形学教程
光线投射 IV:方向精灵、门与更多特性
目录
返回目录
简介
光线投射 IV 是光线投射系列的最新篇章,写于 2017 年,距前三部分已过去 12 年。
目前,本部分尚未包含任何示例代码、图片或公式,仅对若干新增内容进行了高层次描述。读者可以据此将前几篇教程的代码扩展为这些新原理。希望以这种形式呈现仍能对大家有所帮助。
垂直摄像机移动
在光线投射引擎中,摄像机通常无法向上或向下看,只能向前看。我们只能水平移动和旋转,这对应于改变摄像机的偏航角(yaw)。
通过一些扩展,还可以实现更多功能:
改变俯仰角(pitch)
俯仰摄像机意味着向上或向下看。
此处描述的效果在透视上并不完全正确,因为在光线投射器中墙的边缘必须保持垂直,但只要角度不太大,效果看起来已经足够逼真。
在光线投射代码中,我们将 drawStart 计算为"-lineHeight / 2 + h / 2",将 drawEnd 计算为"lineHeight / 2 + h / 2"。
这使得墙的垂直中心与屏幕的垂直中心完全对齐。
要改变俯仰角,只需将屏幕上的所有内容垂直移动,即对 drawStart 和 drawHeight 都加上一个值"pitch",正值表示向上看,负值表示向下看。
地板、天花板、纹理和精灵也需要进行新的处理,具体内容可参见可下载的代码示例。
改变摄像机的垂直位置
改变摄像机的垂直位置与改变俯仰角非常相似,但需要将墙的距离考虑在内。
由于跳跃会改变垂直位置,我们将垂直位置称为"jump"。
对于当前的垂直条带,现在需要将 jump / perpWallDist 加到 drawStart 和 drawEnd 上。
若要同时改变俯仰角和跳跃,则加上 pitch + jump / perpWallDist。
地板、天花板、纹理和精灵同样需要进行新的处理,具体内容可参见可下载的代码示例。
俯仰角与垂直位置的代码
附带的文件 raycaster_pitch.cpp 包含了 raycaster_floor.cpp 和 raycaster_sprites.cpp 的全部内容,并在其中加入了俯仰角和跳跃功能。变量 pitch 和 posZ 是新增的,你可以在代码中搜索它们的所有用法,从而找到这些功能所需的全部改动。
raycaster_pitch.cpp
感谢 Michael Elliott 改进了改变垂直位置的公式。
改变滚转角(roll)
理论上滚转角也可以改变。此时不再是垂直条带,而是将条带旋转某个角度后绘制到屏幕上。光线投射本身完全相同,唯一的区别在于如何将条带绘制到屏幕上。对于 90 度倾斜,这很简单,此时我们使用水平扫描线而非垂直扫描线。但对于其他角度,会出现一个问题:当你在屏幕上绘制每条旋转后的条带时,必须确保所有屏幕像素都被填充,而实际上很可能无法做到。也许将线条加粗(例如在屏幕上将同一像素重复绘制两次,然后让相邻线条的重复部分相互覆盖)可以解决问题,但我自己并未尝试过。
方向精灵
前一篇教程介绍了精灵。那些精灵只有 1 张纹理,无论玩家从哪个角度看,始终看到的是精灵的同一面,就好像它一直朝向你一样。对于敌人来说,如果假设它们总是面向你,这样做是可以的;对于从各个方向看起来都一样的简单物体(如圆形灯具、柱子、桶),或者放在壁龛里只能从 1 个方向看到的物体,这样也没问题。
但如果你想从背后偷袭敌人,或者需要更复杂的物体(如椅子或树,从不同方向看起来不同),就需要更好的 3D 视觉效果。光线投射器无法以真正的 3D 方式渲染这类物体,但我们可以通过在不同角度渲染不同外观来提升效果。通常支持 8 个不同角度。此时物体需要 8 张纹理,对应 8 个可能的方向。如果你在 3D 软件中设计物体,可以从 8 个角度保存渲染图;或者用熟练的像素画技巧,手绘从 8 个角度看到的物体。
在渲染精灵时,必须选择绘制哪个角度的图像。这取决于玩家在 2D 地图中的位置与物体位置的关系。计算玩家 x、y 坐标与物体 x、y 坐标之间的差值 dx 和 dy,然后对 dx 和 dy 取 atan2 得到角度,再将其四舍五入到最近的 8 个支持角度之一,即为要选择的纹理索引。
薄墙
到目前为止,光线投射引擎一直专注于厚实的正方形墙体,这些墙体占据 2D 俯视网格的整个单元格。例如,以下是从上方看到的三面这样的墙:
+------+------+------+
| | | |
| | | |
| | | |
+------+------+------+
除正方形外,还可以制作其他形状,例如位于正方形之间中间位置的薄墙,如下图中间那面墙:
+------+ +------+
| | | |
| |------| |
| | | |
+------+ +------+
同样,上图是从上方渲染的,从第一人称视角看这面薄墙大概是这样的:
_______ _______
| |\____/| |
| || || |
| || || |
| ||____|| |
|______|/ \|______|
如果是一面独立的薄墙,旁边没有两面厚墙,从某个角度看可能是这样的(用一些远处的墙 === 来补全画面):
/|
/ |
/ |
| |
====| |=====
| |
\ |
\ |
\|
这与从第一人称视角看独立的厚方形墙有很大不同:
/|-._
/ | -._
/ | |
| | |
=| | |=
| | |
\ | _.|
\ | _.-
\|-
实现方法:光线投射是 2D 数学,摄像机发出的光线与 2D 形状相交,例如前几篇教程中的方形墙。对于方形墙,我们计算光线与正方形 4 条边的 2D 交点;而现在,改为只计算与正方形内部中间位置的一条线段的交点。从某种意义上说,这更简单,因为只有 1 条线段而不是 4 条。需要考虑两种类型:东西方向的和南北方向的。
对于这类格子,光线要么会击中内部的薄墙,要么不会并继续穿过。如果光线没有击中薄墙,则对该格子不做任何处理,继续使用 DDA 步进到下一个格子。
薄墙不必在中间,你可以制作其他变体,例如偏移到非中心的其他位置:
+------+ +------+
| |------| |
| | | |
| | | |
+------+ +------+
+------+------+------+
| | | |
| | | |
| | | |
+------+ +------+
门
门的实现方式与薄墙类似。不过,这次我们还希望渲染门的侧面(可选),并支持开关动画。与薄墙的单条线段(- 或 |)不同,现在我们有一个 H 形,H 的两条竖线代表门的两侧。
开关动画需要一个计时器。一种简单的方法是额外维护一张 2D 地图,为每个单元格存储一个浮点计时器,再用另一张地图存储整数状态码,可能的值为 0(关闭)、1(正在打开)、2(打开)、3(正在关闭)。当然,只有门的格子才会真正用到这些数据,这样做只是便于寻址。像《德军总部 3D》这样高度优化的游戏并非如此工作,据我所知,它有 64 个计时器,每扇门指向其中一个,因此整张地图最多只支持 64 扇门。
然后监听空格键等按键,检测玩家是否在开门。计算玩家前方的格子是否是门及其坐标。然后激活其计时器:若门处于关闭状态则将特殊整数码更新为 1(正在打开),若处于打开状态则更新为 3(正在关闭)。浮点计时器的值可以为 0(关闭)、1(完全打开),或介于两者之间(例如 0.5 表示门开了一半)。但玩家按下空格键时不要立即更新该值。
然后每帧遍历所有格子,对于每个是门且特殊整数码为 1(正在打开)或 3(正在关闭)的格子,更新浮点值:计算本帧与上一帧之间的时间差(秒)。根据时间差,对浮点值加上(若正在打开)或减去(若正在关闭)一个小量。若小于 0,则截断为 0 并将特殊码设为 0(关闭);若大于 1,则截断为 1 并将特殊码设为 2(打开)。
最后,绘制门时,用计时器值确定线段长度,即 1.0 - timer。例如,若计时器为 0.5,情形如下:
+------+ +------+
| | | |
| |--- | |
| | | |
+------+ +------+
与这条动态缩短的线段求交(用计时器值确定其长度),同时从纹理 X 坐标中减去计时器值,使纹理本身也随之滑动。
秘密推墙
《德军总部 3D》中的秘密推墙也会移动,与门类似。不同的是,这次我们移动的不是薄墙及其纹理向左或向右滑动,而是移动整个方块。这里可以复用相同的计时器。而且,就像在《德军总部 3D》中一样,我们被限制在当前网格方块内渲染内容,因此秘密墙无法真正移出方块,只能通过移动墙的可见面来模拟这一效果。
为了理解前几篇教程中的 2D 方形墙是如何真正绘制的,以下从俯视角度(而非第一人称)展示其实际情况:
预期形状:
+------+
| |
| |
| |
+------+
实际形状:
+
|
|
|
+------+
* 我们站在这里
如你所见,我们只能看到正方形朝向我们一侧的两条边,背面的边并不存在。我展示这个是因为它有助于理解《德军总部 3D》在绘制秘密墙时如何"作弊"。如果秘密墙是独立放置的,而不是正确地夹在其他墙之间,在《德军总部 3D》中大概是这样的(实际上可能更有问题,如我没记错的话):
t=0s
+
|
|
|
+------+
* 我们站在这里
t=0.5s
+
|
|
+-------
|
* 我们站在这里
t=1s
+
|
+-------
|
|
* 我们站在这里
t=1.5s
+
+-------
|
|
|
* 我们站在这里
t=2s
+-------
|
|
|
|
* 我们站在这里
这就是为什么秘密墙应该放在另外两面墙之间。下图展示了上述视觉效果在我们眼中的样子,背面的墙再次被移除,因为我们只能看到正面(以俯视角度展示,而非第一人称):
t=0s
| |
| |
| |
+------+------+------+
* 我们站在这里
t=0.5s
| |
| |
|------|
+------+ +------+
* 我们站在这里
t=1s
| |
|------|
| |
+------+ +------+
* 我们站在这里
t=1.5s
|------|
| |
| |
+------+ +------+
* 我们站在这里
t=2s
+------+
| |
| |
| |
+------+ +------+
* 我们站在这里
现在看起来不错了。我们将做一些类似于《德军总部 3D》的事情,只是为了避免看到错误的墙,我们只绘制 1 面薄墙而不是 2 面。因此,我们仍然需要确保将秘密墙放在其他墙之间;如果不这样做,当玩家激活秘密墙时,看起来就像我们的实心方块突然变成了一面移动的薄墙(如果你愿意,也可以修复这个问题:在当前格子渲染一个逐渐变薄的实心方块,同时在下一个格子渲染一个逐渐变厚的匹配实心方块,这需要多一些工作,以及更多地处理计时器和特殊码)。
实现方法:可以复用门所用的计时器缓冲区和特殊码缓冲区。在缓冲区中放入特殊值(如 4,0-3 已用于门)来表示秘密墙。当玩家在秘密墙前按下空格键时,它变为正在移动的秘密墙,根据玩家从哪一侧推动,可以向 4 个方向移动。因此,给这 4 种情况分配特殊码 5-8。当秘密墙移动时,与门类似,每帧更新计时器,并用计时器为薄线段提供正确的 2D 交点坐标。此时它不是向侧面移动,而是向后移动。根据特殊码是 5、6、7 还是 8,分别从北、东、西、南侧绘制 4 种可能的薄墙之一。
秘密墙必须移动跨越多个格子(例如 2 个),否则玩家无法通过。我们可以让秘密墙一直移动,直到撞上固定的墙为止。实现方法:一旦当前格子上秘密墙的计时器到达终点,清除该格子,移除其墙体,使其成为空白空间。如果下一个格子(根据我们的方向选取正确的那个)是空的,则将该格子变为秘密墙,将其特殊码设置为与之前相同的值(5-8),并启动其计时器。如此循环……
其他墙体形状
如果你真的喜欢 2D 求交数学,还可以制作其他墙体形状。以下所有形状均以俯视图展示,而在游戏中你是从侧面以第一人称视角看到它们的。所有图示都用两面普通方形网格墙来显示网格,特殊形状位于中间。
想法 1:半尺寸或更小的墙。
+------+------+------+
| | | |
| +------+ |
| | | |
+------+ +------+
+------+ +------+
| | +--+ | |
| | | | | |
| | +--+ | |
+------+ +------+
这些可以用与大墙类似的方式实现,只是求交发生在更小的边上。如果光线没有击中小的部分,则在 DDA 中继续到下一个格子。
想法 2:由竖条组成的栅栏。
+------+ +------+
| | | |
| |- - - | |
| | | |
+------+ +------+
这与薄墙类似,只是我们不是 1 条大线段,而是多条小线段。分别对每条小线段进行求交。如果一条都没有击中,光线就穿过栅栏继续到远处的墙。为此制作一张匹配的纹理,你就得到了一种不需要 Z 缓冲的整洁透视墙类型。一个有些类似的想法是细缝:
+------+ +------+
| | | |
| |-- --| |
| | | |
+------+ +------+
想法 3:45 度斜墙。
+------+ +------+
| | / | |
| | / | |
| |/ | |
+------+ +------+
+------+ +------+
| |\ | |
| | \ | |
| | \ | |
+------+ +------+
+------+ +------+
| |\ / | |
| | X | |
| |/ \ | |
+------+ +------+
这种墙允许非正交墙的存在。这里有 1 个额外的复杂性:到目前为止,我们只有南北方向和东西方向的墙,并用两种不同的亮度来区分它们。现在,对于 45 度墙的两个可能方向,我们需要再选择两种亮度变体。渲染该纹理时,应用一些中间值的着色。
想法 4:圆形柱子
+------+ .--. +------+
| |/ \| |
| | | |
| |\ /| |
+------+ `..' +------+
+------+ +------+
| | | |
| | () | |
| | | |
+------+ +------+
+------+------+------+
| | || |
| | /| |
| | _.' | |
+------+-' +------+
为此,需要进行直线与圆(或圆弧,如上面四分之一圆的示例)的 2D 求交,公式可以在维基百科等地方找到。然后,为了选择纹理坐标,用 atan2 计算交点与圆心之间的角度。对于着色,现在需要比 45 度斜墙更多的亮度级别,级别越多,圆形效果看起来越平滑。记住,要使颜色变暗,将其 R、G、B 系数乘以 0.0-1.0 范围内的某个浮点数,越接近 0 越暗。
透明墙与 Z 缓冲
与光线投射 III 中的精灵纹理类似,透明墙也可以拥有这类纹理,最适合用于薄墙,因为厚墙的内部不会被渲染,而透明纹理会将其暴露出来,看起来会很奇怪。
+------+ +------+
| | | |
| |------| |
| | | |
+------+ +------+
为此,在投射光线时,我们会击中这面透明墙。记住它与玩家的距离。然后像往常一样继续将光线投射到下一个格子。如果遇到更多薄墙,也一并记住。最后,像往常一样在击中普通非透明墙时停止。然后以相反的顺序绘制我们记住的条带:先绘制普通墙,再以相反顺序绘制之前的那些。这与光线投射 III 中用于精灵的画家算法非常相似,那里是从最远到最近排序的。
为了考虑可能位于透明墙之间的精灵,我们还需要使用光线投射 III 中的 Z 缓冲。但对于排序,需要进行合并:精灵是事先排好序的,但透明墙只对当前正在投射的这条特定条带才知道。不过我们已经以正确的顺序拥有它们了(如前所述,以相反顺序)。因此,可以在 O(N) 时间内将透明墙合并到精灵之间。如有需要,可以在网上搜索合并算法。
为了完全避免排序,精灵和薄墙的情况也可以改用 2D Z 缓冲,这更简单,只是多用一点内存。
具体做法:不再为每个垂直精灵在 1D 缓冲区中存储数据,而是在 2D 缓冲区中为屏幕的每个像素存储一个距离。每当绘制精灵的非透明像素、透明薄墙以及任何普通墙时,将绘制距离存入 Z 缓冲区。只有当距离不大于 Z 缓冲区中已存储的值时才进行绘制,这意味着该像素处已经绘制了更近的物体。这样,墙、透明墙和精灵可以以任意顺序绘制,最近的那些始终是最后写入像素的。
不要忘记在每帧开始时清空 Z 缓冲区(将所有值设为非常大或无穷大的值)。
然而,2D Z 缓冲与半透明效果并不容易兼容,所以如果你需要半透明,请使用上述排序方案。你仍然可以通过记住多个距离及其所属的精灵或墙,然后在最后按每像素或每条带的正确顺序渲染来实现类似效果,这稍微复杂一些,也更慢,你可以记住有限数量(例如 4 个,以便透过最多 4 个半透明物体看),或者不设上限(可能更慢)。
如果没有精灵,我们也可以在没有 Z 缓冲的情况下支持透明墙,因为在光线投射时我们以逆序穿过它们,然后在最后反转顺序逐一绘制。但是,这与有精灵的情况不兼容,因为那里面可能还有精灵。所以最通用的方法是对所有内容都使用 Z 缓冲。
动态纹理
动态纹理是时间相关的。如果一张纹理有多帧,则根据时间决定选择哪一帧。使用(毫)秒为单位的时间,而非已渲染的帧数,以确保无论帧率如何都保持一致。
另一种动画方式是只有 1 张纹理,但让它移动。例如,可以沿固定方向移动,或使用正弦函数在 X 和/或 Y 方向上波动。这对于地板上的水面或熔岩纹理效果很好。这里,我们用时间来选择纹理的相对坐标,而不是纹理本身。也就是说,当光线击中墙/地板/天花板并计算纹理坐标时,在此之后加上动态值,并使用取模运算使纹理平铺(对于取模,在 C/C++ 中不能直接使用 % 运算符或 fmod 函数,因为它们对负数的处理不符合我们的需求;使用整数时,与纹理尺寸 - 1 进行按位与运算(假设尺寸是 2 的幂);或者,为了更好的效果,使用浮点数让像素本身平滑移动,若小于 0 则加上纹理尺寸,若大于纹理尺寸则减去纹理尺寸),然后渲染。
雾效
雾效相对容易添加,效果也不错:将正在绘制的像素颜色与雾的颜色混合。条带距离越远,雾的颜色越占主导。使用加权平均来混合颜色。例如,要实现 75% 的雾,则颜色占 25%,红色通道使用 (1 - 0.75) * color_r + 0.75 * fog_r,绿色和蓝色同理。条带越远,雾的比例越大,颜色的比例越小。
黑色雾适合模拟能见度有限的夜晚,白色或灰色看起来像白天的雾,绿色可以呈现毒云效果,红色则有火焰感,等等。你也可以通过在 2D 地图中存储雾的浓度,并仅在玩家位于此类雾格时才渲染,来实现局部雾效。当然,这不是真正的体积雾,也就是说,当玩家站在非雾格上时,可以直接看穿雾的位置,但它已经能产生不错的局部效果,而且这个问题可以通过在局部房间之间设置走廊来遮挡雾效来掩盖。
敌人
要制作一个真正的光线投射游戏,需要敌人或 NPC。它们的渲染方式与精灵非常相似,但可以自由移动。因此,与精灵不同,它们最好不要存储为 2D 网格地图,而是作为各自独立的实体。它们还必须能够平滑移动,因此可以站在任何位置,而不仅仅是网格格子的正中心,所以它们需要浮点坐标。除了不必处于 0.5 位置之外,渲染它们与渲染精灵完全相同,使用相同的 Z 缓冲和排序原则(如果使用更简单的 2D Z 缓冲则无需排序),只是它们来自与普通精灵不同的列表——敌人列表。
如果你想对敌人使用方向精灵,则需要考虑两个角度来确定要渲染哪张纹理:玩家与敌人在 2D 地图上的夹角,以及敌人的朝向(注意玩家的朝向无关紧要)。
敌人还需要 AI 来控制其移动,需要多种状态(如等待、警觉、死亡……),以及生命值等机制。本教程关注图形学,因此不会深入探讨这些内容。不过,这正是引擎从图形引擎变为游戏的地方,是一个非常值得研究的主题。在下一节关于武器的内容中,我们还会稍微深入一些游戏玩法方面的内容。
敌人可能需要动态纹理,例如行走、射击等时的动画。请参阅动态纹理章节。根据敌人的状态,会选择不同的动画和纹理集。
武器
为了让游戏更像真正的游戏,可能需要武器。在光线投射引擎中,有几种可能的武器类型:
对于直接命中武器,可以复用光线投射代码来检测武器是否击中敌人。为此,需要对前几篇教程的代码进行一些重构:提取出计算从起点出发的光线与墙壁(如果支持透明墙则是多面墙)相交的代码部分,以及计算精灵是否在视野内的代码部分。射击直接命中武器时,对屏幕中心条带进行光线投射计算(因为你将枪瞄准中心),并获取被击中的最近物体(墙或精灵)。如果是精灵,且该精灵是敌人,则它被击中并接收命中事件(从而可以减少生命值,让 AI 做出响应等)。
对于投射物武器,射击时会生成一个投射物。与敌人类似,该投射物是一个精灵,像精灵一样绘制,根据某些规则移动,这次不是根据 AI,而是以固定速度沿固定方向简单移动。在某个时刻它击中某物或消失,因此必须更新对象列表(以及如果使用了排序的话)。如果它击中敌人(可以通过检测与敌人的距离是否小于某个浮点值来判断),则敌人接收命中事件。你还可以制作在墙上弹跳、改变方向、追踪敌人的制导导弹等投射物。
对于近战武器,可以简单到计算你与前方物体的距离,即对每个物体计算其与你的距离和角度,如果角度正确(在你前方)且足够近,则命中。对于较长的近战武器(如长矛),你可能需要像直接命中武器一样使用光线投射,否则它可能会穿墙攻击。这与直接命中武器基本相同,只是如果最近距离太远,则没有效果。
传送门与镜子
可以创建可以透过看的传送门:当光线击中传送门时,让其在不同位置(传送门的目的地)继续追踪,然后光线追踪从那里像往常一样继续。为了使其可用,当玩家走近传送门时,玩家也可以传送到目的地。按距离对精灵排序在这里不再适用,因此请确保使用 2D Z 缓冲,或者为每个传送门单独存储精灵排序顺序(可能比它的价值更复杂)。
镜子可以用非常相似的方式实现,这里光线根据镜子的朝向改变 X 或 Y 方向。
在两种情况下,在其前面放置一张透明纹理可以改善效果或减少一些视觉混乱,例如在传送门或镜子周围添加一些边框,或增加一些瑕疵感。
碰撞检测
本教程中的光线投射程序在移动时的碰撞检测非常简陋:它所做的只是检查你移动到的位置是否有墙。如果有两面对角相邻、仅在角点接触的墙,只要移动方向恰好对准,就可能穿墙而过。
一个可能的解决方案是使用光线投射器本身进行碰撞检测:不只检查移动目标点是否有墙,而是从玩家位置向下一个位置投射一条光线。如果光线到达了目标点,则玩家可以移动到那里。如果光线在到达目标点之前击中了墙,则那就是玩家移动可以到达的最终位置。
为了避免摄像机视角过于贴近墙壁,玩家的移动最好在碰撞点稍前停止,而不是在墙体本身处停止,因此光线可以延伸得稍微远一点,以测试与墙的距离。
光线追踪
光线追踪是光线投射从 1D 到 2D 的扩展在 2D 到 3D 上的延伸。也就是说,光线投射中,我们对每条 1D 垂直条带投射 1 条光线,并与物体进行 2D 求交运算;而在光线追踪中,我们对整个 2D 屏幕的每个像素投射 1 条光线,并与物体进行 3D 求交运算。
要实现一种与这里的光线投射引擎非常相似的光线追踪形式,可以使用由体素(voxel)组成的 3D 地图,每个体素是一个在 6 个面上都有纹理的正方体。这与有 4 个面纹理的光线投射方形墙非常相似。光线穿越 3D 体素的方式与我们穿越 2D 方格的方式非常相似。当体素被击中时,计算正方体侧面的 2D 坐标以获取纹理坐标,然后为该像素赋予与纹理像素匹配的颜色。
然而,光线追踪通常根本不使用这样的体素地图,而是有一个物体列表,例如球体、三角形等。然后需要对每个物体计算光线是否与其相交,再对最近的物体计算纹理坐标并将其作为我们的像素绘制出来。
对于复杂的多边形模型,可能有很多三角形,理论上需要与每一个都求交。为了加速,可以通过以下技术避免计算所有三角形:将空间划分为大体素,记录哪些物体在哪个体素中,只与这些物体求交;或者使用二叉空间分割(BSP)以及其他技术。此外,对于由多边形组成的物体,设置包围盒,只有当光线穿过包围盒时才计算单个多边形。
对于光线追踪中的半透明效果,在击中半透明物体后让光线继续前进,并记住遇到的每一个。然后以相反顺序渲染它们,类似于光线投射 III 中的半透明精灵。
对于镜子和折射,根据光学公式在光线击中这类表面时改变光线方向。
对于光源,在光线击中物体后,从该点向所有光源发射一条光线。如果光线没有被起点与光源之间的物体遮挡,则将该光源的颜色加到该点上,亮度取决于距离,并且可以使用 Gouraud 着色或 Phong 着色等着色方法代替简单的多边形平面着色,以获得更好的视觉效果。
还有更复杂的光线追踪技术需要支持,例如间接照明。我们也可以反向追踪,从光源而非摄像机出发,或者从两端同时出发并在中间相遇。可以查阅全局光照和渲染方程相关资料。
最后编辑:2020 年
版权所有 (c) 2004-2020 Lode Vandevenne,保留所有权利。