系列文章目录

简介:Computer Graphics From Scratch-《从零开始的计算机图形学》简介
第一章: Computer Graphics From Scratch - Chapter 1 介绍性概念
第二章:Computer Graphics From Scratch - Chapter 2 基本光线追踪
第三章:Computer Graphics From Scratch - Chapter 3 光照
第四章:Computer Graphics From Scratch - Chapter 4 阴影和反射


Chapter 5

  • 系列文章目录
  • Extending The Raytrace
  • 一、Arbitrary Camera Positioning - 任意相机定位
  • 二、Performance Optimizations - 性能优化
    • 2.1. Parallelization - 并行化
    • 2.2. Caching Immutable Values - 缓存不可变值
    • 2.3. Shadow Optimizations - 阴影优化
    • 2.4. Spatial Structures - 空间结构
    • 2.5. Subsampling - 二次抽样
  • 三、Supporting Other Primitives - 支持其他原语
  • 四、Constructive Solid Geometry - 构造实体几何
  • 五、Transparency - 透明度
    • 5.1. Refraction - 折射
  • 六、Supersampling - 超级采样
  • 七、Summary - 概括

Extending The Raytrace

Extending The Raytrace – 扩展光线跟踪

We’ll conclude the first part of the book with a quick discussion of several interesting topics that we haven’t yet covered: placing the camera anywhere in the scene, performance optimizations, primitives other than spheres, modeling objects using constructive solid geometry, supporting transparent surfaces, and supersampling. We won’t implement all of these changes, but I encourage you to give them a try! The preceding chapters, plus the descriptions offered below, give you solid foundations to explore and implement them by yourself.

我们将在本书的第一部分快速讨论几个我们尚未涵盖的有趣主题:将相机放置在场景中的任何位置性能优化球体以外的基元使用构造函数建模对象 实体几何支持透明表面和超级采样。 我们不会实施所有这些更改,但我鼓励您尝试一下! 前面的章节,加上下面提供的描述,为您自己探索和实施它们提供了坚实的基础。


一、Arbitrary Camera Positioning - 任意相机定位

在讨论光线追踪的一开始,我们做了三个重要的假设:相机固定在 (0,0,0)(0, 0, 0)(0,0,0),它指向 Z+⃗\vec{Z_+}Z+​​,它的“向上”方向是 Y+⃗\vec{Y_+}Y+​​ 。

在本节中,我们将解除这些限制,以便我们可以将相机放置在场景中的任何位置并指向任何方向

让我们从相机位置开始。 您可能已经注意到 OOO 在所有伪代码中只使用了一次:作为顶级方法中来自相机的光线的原点。如果我们想改变相机的位置,我们唯一需要做的就是为 OOO 使用不同的值,我们就完成了。

位置的变化会影响光线的方向吗? 一点也不。
光线的方向是【从相机到投影平面的矢量】。
当我们移动相机时,投影平面也随之移动,因此它们的相对位置不会改变。
我们写 CanvasToViewport 的方式和这个思路是一致的。

让我们将注意力转向相机方向。 假设您有一个旋转矩阵,它表示所需的相机方向
如果您只是旋转相机,相机的位置不会改变,但它看向的方向会改变; 它与整个相机进行相同的旋转。
因此,如果您有射线方向 D⃗\vec{D}D 和 旋转矩阵 RRR,则旋转后的 DDD 就是 R⋅D⃗{R}\cdot{\vec{D}}R⋅D 。

总之,唯一需要更改的函数是我们在 示例2-2 中写回的 main 函数。 示例 5-1 显示了更新后的函数。

for x in [-Cw/2, Cw/2]
{for y in [-Ch/2, Ch/2] {❶ D = camera.rotation * CanvasToViewport(x, y)❷ color = TraceRay(camera.position, D, 1, inf)canvas.PutPixel(x, y, color)}
}

示例5-1:主循环,更新为支持任意相机位置和方向


我们将相机的旋转矩阵❶(它描述了它在空间中的方向)应用于我们将要追踪的光线的方向。
然后我们用相机位置作为射线的起点❷。

图 5-1 显示了我们的场景从不同的位置和不同的相机方向渲染时的样子。

图 5-1:我们熟悉的场景,用不同的相机位置和方向渲染

您可以在以下位置找到该算法的实时实现https://gabrielgambetta.com/computer-graphics-from-scratch/demos/raytracer-06.html


二、Performance Optimizations - 性能优化

Performance Optimizations — 性能优化

前面的章节着重于以最清晰的方式来解释和实现光线追踪器的不同功能。 因此,它功能齐全,但速度不是特别快。 以下是可以自行探索的一些想法,以使光线追踪器更快。 只是为了好玩,测量每一个的前后时间。 你会对结果感到惊讶!

2.1. Parallelization - 并行化

使光线追踪器更快的最明显方法是一次追踪多条光线。 由于离开相机的每条光线都独立于其他光线,并且场景数据是只读的,因此您可以在每个 CPU 内核中跟踪一条光线,而不会造成太多损失或同步复杂性。 事实上,光线追踪器属于被称为可并行化的一类算法,正是因为它们的本质使得它们非常容易并行化。

但是,为每条射线生成一个线程可能不是一个好主意。 管理潜在数百万个线程的开销可能会抵消您获得的加速。 更明智的想法是创建一组“任务”,每个任务负责对画布的一部分(矩形区域,下至单个像素)进行光线追踪,并将它们分配给在物理内核上运行的工作线程 它们变得可用。

2.2. Caching Immutable Values - 缓存不可变值

缓存是一种避免一遍又一遍地重复相同计算的方法。 每当有一个昂贵的计算并且您希望重复使用此计算的结果时,存储(缓存)此结果并在下次需要时重新使用它可能是一个好主意,特别是如果此值不经常更改。

考虑在 IntersectRaySphere 中计算的值,光线追踪器通常会花费大部分时间:

a = dot(D, D)
b = 2 * dot(OC, D)
c = dot(OC, OC) - r * r

不同的值在不同的时间段内是不可变的。

加载场景并知道球体的大小后,就可以计算 r∗rr * rr∗r。 除非球体的大小发生变化,否则该值不会改变。

至少,某些值对于整个帧是不可变的。 一个这样的值是 dot(OC,OC)dot(OC, OC)dot(OC,OC) ,如果相机或球体移动,它只需要在帧之间改变。 (请注意,阴影和反射跟踪的光线不是从相机开始的,因此需要注意确保在这种情况下不使用缓存值。)

对于整条射线,某些值不会改变。 例如,您可以计算 ClosestIntersection 中的 dot(D,D)dot(D, D)dot(D,D) 并将其传递给 IntersectRaySphere

还有许多其他计算可以重用。 动用你的想象力! 然而,并非每个缓存的值都会使事情总体上更快,因为有时记账开销可能高于节省的时间。 始终使用基准来评估优化是否真的有帮助。


2.3. Shadow Optimizations - 阴影优化

当表面的一个点因为有另一个物体挡住而处于阴影中时,它旁边的点很可能也处于同一物体的阴影中(这称为阴影相干性)。 您可以在图 5-2 中看到一个示例。

图 5-2:靠近的点很可能在同一个物体的阴影中。


在搜索点和灯光之间的对象时,要确定该点是否在阴影中,我们通常会检查与其他所有对象的交点。 但是,如果我们知道紧邻它的点位于特定对象的阴影中,我们可以先检查与该对象的交点。 如果我们找到一个,我们就完成了,我们不需要检查所有其他对象! 如果我们没有找到与该对象的交集,我们只是恢复检查每个对象。

同样,在寻找射线与物体的交点来确定一个点是否在阴影中时,您并不需要最近的交点; 知道至少有一个十字路口就足够了,因为这足以阻止光线到达该点! 因此,您可以编写一个专门的 ClosestIntersection 版本,该版本在找到任何交点后立即返回。 你也不需要计算和返回最接近的t; 相反,您可以只返回一个布尔值。


2.4. Spatial Structures - 空间结构

计算光线与场景中每个球体的交点有点浪费。 有许多数据结构可以让您一次丢弃整组对象,而无需单独计算交集。

假设您有几个彼此靠近的球体。 您可以计算包含所有这些球体的最小球体的中心和半径。 如果一条射线不与这个边界球相交,你可以确保它不与它包含的任何球相交,代价是单次相交测试。 当然,如果是这样,您仍然需要检查它是否与它包含的任何球体相交。

您可以更进一步,拥有多个级别的边界球体(即球体组),形成一个层次结构,只有当一个实际球体很有可能成为时,才需要一直遍历到底部 被射线相交。

虽然这一系列技术的具体细节超出了本书的范围,但您可以在名称边界卷层次结构下找到更多信息。


2.5. Subsampling - 二次抽样

这是使您的光线追踪器速度提高 NNN 倍的简单方法:计算的像素数减少 NNN 倍!

对于画布中的每个像素,我们通过视口跟踪一条光线以采样来自该方向的光的颜色。 如果我们的光线少于像素,我们将对场景进行二次采样。 但是我们怎样才能做到这一点并且仍然正确地渲染场景呢?

假设您跟踪像素 (10,100)(10, 100)(10,100) 和 (12,100)(12, 100)(12,100) 的光线,并且它们碰巧击中了同一个对象。 您可以合理地假设像素 (11,100)(11, 100)(11,100) 的光线也将击中同一个对象,因此您可以跳过与场景中所有对象相交的初始搜索并直接跳到计算该点的颜色。

如果您在水平和垂直方向上每隔一个像素跳过一次,您可以减少多达 7575%75 的主要光线场景相交计算——这是 444 倍的加速!

当然,您很可能会错过一个非常薄的物体; 这是一种“不纯”的优化,从某种意义上说,与之前讨论的不同,它产生的图像与未经优化的图像非常相似,但不能保证完全相同。 在某种程度上,这是通过偷工减料来“作弊”。 诀窍是在保持令人满意的结果的同时知道可以削减哪些角落; 在计算机图形学的许多领域,重要的是结果的主观质量。


三、Supporting Other Primitives - 支持其他原语

Supporting Other Primitives — 支持其他原语

在前面的章节中,我们使用球体作为基元,因为它们在数学上很容易操作。 也就是说,找到射线和球体相交的方程相对简单。 但是,一旦您拥有一个可以渲染球体的基本光线追踪器,添加对渲染其他基元的支持就不需要太多额外的工作了。

请注意,TraceRay 只需要能够为射线和任何给定对象计算两件事:它们之间最近交点的 ttt 值和该交点处的法线。 光线追踪器中的其他所有内容都与对象无关!

三角形是一个很好的支持原语。 三角形是最简单的多边形,因此您可以用三角形构建任何其他多边形。 它们在数学上很容易操作,因此它们是表示更复杂表面近似值的好方法。

要为光线追踪器添加三角形支持,您只需更改 TraceRay。 首先,计算射线(由其原点和方向给出)与包含三角形的平面(由其法线和与原点的距离给出)之间的交点。

由于平面无限大,光线几乎总是与任何给定平面相交(除非它们完全平行)。 所以第二步就是判断射线平面的交点是否真的在三角形里面。 有很多方法可以做到这一点,包括使用重心坐标或使用叉积来检查点是否相对于三角形的三个边中的每一个都在“内部”。

一旦确定该点在三角形内部,相交处的法线就是平面的法线。 让 TraceRay 返回适当的值,无需进一步更改!


四、Constructive Solid Geometry - 构造实体几何

Constructive Solid Geometry — 构造实体几何

假设我们想要渲染比球体或曲面物体更复杂的物体,这些物体很难使用一组三角形准确建模。 两个很好的例子是镜片(就像放大镜中的镜片)和死星(那不是月亮…)

我们可以很容易地用简单的语言描述这些对象。 放大镜看起来像两片粘在一起的球体; 死星看起来像一个球体,从中取出了一个较小的球体。

我们可以将其更正式地表达为对其他对象应用集合操作(例如并、交或差)的结果。 继续上面的例子,一个透镜可以描述为两个球体的交点,而死星是一个大球体,我们从中减去一个较小的球体(见图 5-3)。

图 5-3:正在运行的构造立体几何。 A∩BA ∩ BA∩B 给了我们一个镜头。 C−DC - DC−D 给了我们死星。


您可能会认为计算实体对象的布尔运算是一个非常棘手的几何问题。 你是完全正确的! 幸运的是,事实证明,构造立体几何让我们可以渲染对象之间集合操作的结果,而无需显式计算这些结果!

我们如何在光线追踪器中做到这一点? 对于每个对象,您可以计算光线进入和离开对象的点; 例如,在球体的情况下,光线在 min(t1,t2)min(t1, t2)min(t1,t2) 处进入并在 max(t1,t2)max(t1, t2)max(t1,t2) 处退出。 假设您要计算两个球体的交集; 当光线在两个球体内部时,光线在相交内,当它在任一球体外部时,它在外部。 在减法的情况下,当光线在第一个对象内部但不在第二个对象内部时,它就在内部。 对于两个对象的并集,当光线在其中一个对象内部时,光线就在内部。

更一般地,如果要计算射线与对象 A⊙BA⊙BA⊙B 之间的交点(其中 ⊙⊙⊙ 是任何集合操作),您首先分别计算光线与 AAA 和 BBB 之间的交集,从而为您提供每个对象“内部”的 ttt 范围,即 RAR_ARA​ 和 RBR_BRB​。 然后计算 RA⊙RBR_A⊙R_BRA​⊙RB​,这是 A⊙BA⊙BA⊙B 的“内部”范围。 一旦你有了这个,射线和 A⊙BA⊙BA⊙B 之间最近的交点就是 ttt 的最小值,它既在物体的“内部”范围内,又在 tmint_{min}tmin​ 和 tmaxt_{max}tmax​ 之间。 图 5-4 显示了两个球体的并集、交集和减法的内部范围。


图 5-4:两个球体的并集、交集和减法.


交点处的法线要么是产生交点的对象的法线,要么是相反的法线,这取决于您是在查看原始对象的“外部”还是“内部”。

当然,A 和 B 不必是原语; 它们可以是集合操作本身的结果! 如果你干净利落地实现这一点,你甚至不需要知道 A 和 B 是什么,只要你能从中得到交点和法线。 这样,您可以取三个球体并计算,例如 (A∪B)∩C(A ∪ B) ∩ C(A∪B)∩C。


五、Transparency - 透明度

Transparency ---- 透明度

到目前为止,我们已经将每个对象都渲染为完全不透明的,但事实并非如此。 我们可以渲染部分透明的物体,比如鱼缸。

实现这一点与实现反射非常相似。 当光线照射到部分透明的表面时,您像以前一样计算局部颜色和反射颜色,但您还计算了额外的颜色——通过对象的光线的颜色,通过对 TraceRay 的另一次调用获得。 然后根据对象的透明程度,将此颜色与局部颜色和反射颜色混合,这与我们在计算对象反射时所做的方式非常相似。


5.1. Refraction - 折射

在现实生活中,当一束光线穿过透明物体时,它会改变方向(这就是为什么当你将一根稻草浸入一杯水中时,它看起来“破碎”了)。 更准确地说,当光线穿过一种材料(例如空气)并进入另一种材料(例如水)时,它会改变方向。

方向变化的方式取决于每种材料的属性,称为折射率,根据以下方程,称为斯涅尔定律
sin(α1)sin(α2)=n2n!\dfrac{sin(α_1)}{sin(α_2)} = \dfrac{n_2}{n_!} sin(α2​)sin(α1​)​=n!​n2​​

这里,α1α_1α1​ 和 α2α_2α2​ 是光线与穿过表面前后的法线之间的夹角,n1n_1n1​ 和 n2n_2n2​ 是物体内外材料的折射率。

例如,nairn_{air}nair​ 约为 1.0,nwatern_{water}nwater​ 约为 1.33。 因此,对于以 60° 角进入水中的光线,我们有:
sin(60)sin(α2)=1.331.0\dfrac{sin(60)}{sin(α_2)} = \dfrac{1.33}{1.0} sin(α2​)sin(60)​=1.01.33​
sin(α2)=sin(60)1.33sin(α_2) = \dfrac{sin(60)}{1.33} sin(α2​)=1.33sin(60)​

α2=arcsin(sin(60)1.33)=40.628°α_2 = arcsin( \dfrac{sin(60)}{1.33} ) = 40.628° α2​=arcsin(1.33sin(60)​)=40.628°

本示例如图 5-5 所示:

图 5-5:一束光线在离开空气并进入水中时被折射(改变方向)。


在实现层面,每条光线都必须携带一条额外的信息:它当前正在穿过的材料的折射率。 当光线与一个部分透明的物体相交时,您会根据当前材料和新材料的折射率计算从该点开始的光线的新方向,然后像以前一样继续。

停下来考虑一下:如果你实现了建设性的立体几何和透明度,你可以模拟一个放大镜(两个球体的交叉部分),它的行为就像一个物理上正确的放大镜!


六、Supersampling - 超级采样

Supersampling — 超级采样

超级采样或多或少与子采样相反。 在这种情况下,您正在寻找准确性而不是性能。 假设对应于两个相邻像素的射线撞击不同的物体。 您将用相应的颜色绘制每个像素。

但请记住让我们开始的类比:每条光线都应该确定我们正在查看的“网格”的每个正方形的“代表”颜色。 通过每个像素使用一条光线,我们任意决定穿过正方形中间的光线的颜色代表整个正方形,但这可能不是真的。

解决这个问题的方法是每个像素追踪更多的光线——4、9、16,如许多你想要的,然后平均它们以获得像素的颜色。

当然,这会使您的光线追踪器慢 4、9 或 16 倍,原因与二次采样使其速度快 N 倍的原因完全相同。 幸运的是,有一个中间立场。 您可以假设对象属性在其表面上平滑地变化,因此每个像素拍摄四条光线在非常细微的不同位置撞击同一个对象可能不会对场景有太大改善。 因此,您可以从每个像素一条光线开始并比较相邻的光线:如果它们击中不同的对象或颜色差异超过某个阈值,则对两者应用像素细分。


七、Summary - 概括

在本章中,我们简要介绍了几个您可以自行探索的想法。 这些以新的有趣方式修改了我们一直在开发的基本光线追踪器——使其更高效,能够表示更复杂的物体,或者以更好地接近我们的物理世界的方式对光线进行建模。

这本书的第一部分应该证明光线追踪器是漂亮的软件,它可以使用简单、直观的算法和简单的数学来产生令人惊叹的美丽图像。

可悲的是,这种纯度是有代价的:性能。 尽管有很多方法可以优化和并行化光线追踪器,正如本章所讨论的,但对于实时性能而言,它们的计算成本仍然太高; 虽然硬件每年都在变快,但某些应用程序要求图片的速度要快 100 倍,而且质量不会下降。 在所有这些应用程序中,游戏是最苛刻的:我们希望每秒至少绘制 60 次完美的图像。 光线追踪器只是不削减它。

那么,自 90 年代初以来,电子游戏是如何做到的呢?
答案在于我们将在本书的第二部分探讨的完全不同的算法家族。


Computer Graphics From Scratch - Chapter 5相关推荐

  1. Computer Graphics From Scratch - Chapter 3

    系列文章目录 简介: Computer Graphics From Scratch-<从零开始的计算机图形学>简介 第一章: Computer Graphics From Scratch ...

  2. Computer Graphics From Scratch - Chapter 6

    系列文章目录 简介:Computer Graphics From Scratch-<从零开始的计算机图形学>简介 第一章: Computer Graphics From Scratch - ...

  3. Computer Graphics From Scratch - Introduction

    <Computer Graphics From Scratch><从零开始的计算机图形学> Introduction 简介 一.这本书是给谁看的? 二.本书涵盖的内容 三.为什 ...

  4. HDU4716 A Computer Graphics Problem

    问题链接:HDU4716 A Computer Graphics Problem.入门练习题,用C语言编写. 题意简述:根据输入的数(代表剩余电量,值范围是0到100),打印一个反映剩余电量的字符图案 ...

  5. Mathematics for Computer Graphics

    Mathematics for Computer Graphics 最近严重感觉到数学知识的不足! http://bbs.gameres.com/showthread.asp?threadid=105 ...

  6. Vector Math for 3D Computer Graphics

    2019独角兽企业重金招聘Python工程师标准>>> http://programmedlessons.org/VectorLessons/vectorIndex.html#09 ...

  7. 计算机图形(Computer Graphics)经典书籍推荐(1)

    这些书都是非常非常经典!!!!! 1- An Introduction to Ray Tracing. 1989 2- Physically Based Rendering_From Theory T ...

  8. 计算机图形学多边形填充代码_计算机图形学 Computer Graphics (第一周笔记及课件翻译)...

    本文使用 Zhihu On VSCode 创作并发布 注:本文部分内容源自于UDE课程 Computer Graphics(Prof. Dr. Jens Krüger),仅供本人自己学习与作为课程笔记 ...

  9. GAMES101笔记_Lec01_计算机图形学概述 Overview of Computer Graphics

    作为一名想要了解图形学的学生,已经在无数地方看到有人推荐闫令琪老师的GAMES101课程,但由于自己是美术专业,在笼统看过这门课程之后认为这门课有一定学习难度,所以为了打下比较扎实的基础和方便自己日后 ...

最新文章

  1. Flink从入门到精通100篇(十)-双亲委派模型与 Flink 的类加载策略
  2. php奇数乘法表,PHP九九乘法表
  3. CSS中!important的使用
  4. nvme固态硬盘开机慢_为何我使用了固态硬盘开机速度还是需要20-30秒
  5. JSon转化为DaTable
  6. Arcgis Javascript那些事儿(八)--图层获取与图层顺序
  7. printf函数重定向
  8. clipboard.js使用方法
  9. SAPI V1.4发布,轻巧的API输出测试组件
  10. php进度台帐管理系统,捷雅途 - 工程量0号台账管理系统快速操作说明
  11. 全能扫描王的实现(python版本)- 目标检测图像矫正
  12. esp32(ROS2foxy)之飞龙在天turtlesim最快能多快???
  13. IE7 - 千呼万唤始出来
  14. Altium Designer 19简易教程(原理图的绘制)
  15. word文档除号怎么打出来之除号插入的方法教程
  16. python 方差分析_使用Python的重复测量方差分析
  17. 浅谈网络劫持的原理及影响
  18. rabbitmq-server: ERROR: epmd error for host xxx: timeout (timed out)
  19. Pycaffe 使用集锦
  20. 密集恐惧症候群测试图

热门文章

  1. unity 开发射击打靶vr_自制Unity3D VR射击游戏
  2. matlab 用visio编辑_在线运行Matlab, Visio, Office, SPSS...,科研者的福利
  3. vuetify使用详细入门步骤和日历的教程,方便下次使用,记录下来。
  4. html制作曲线,HTML5 canvas基本绘图之绘制曲线
  5. vxworks 网络不可达_通信运维日常问题处理,华为eSight网管告警SNMP不可达
  6. sql优化建议与技巧
  7. lwIP TCP/IP 协议栈笔记之十八: Socket接口编程
  8. html ul li做左侧菜单,css中使用ul li ul li ul li ul li 实现四层级联菜单
  9. 关机状态下启动微型计算机叫什么,微机的冷启动方式是什么?
  10. cmd命令cd无法切换盘符