【译】GAMES101-现代计算机图形学入门:Lec13~14.5_Whitted风格光线追踪

127 阅读14分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情


大家好,我是曹骏。因为本人对计算机图形学、WebGPU等非常感兴趣,因此会陆续发布闫令琪博士的《GAMES101:现代计算机图形学入门》课程译文,希望能帮助到更多的同学理解课程,爱上图形学这一浪漫的学科。

先给出课程的链接:

www.bilibili.com/video/BV1X7…
www.bilibili.com/video/BV1X7…
www.bilibili.com/video/BV1X7…

1 阴影贴图 Before we move on——Shadow Mapping

1.1 光栅化着色中如何绘制阴影 How to draw shadows using rasterization

  • 光栅化着色在着色过程中只会局部地考虑该该着色点、光源、摄像机,对于全局的效果如其他物体、甚至该物体其他部分对该点的影响不会考虑

  • 在光栅化着色中绘制阴影的一种方法是Shadow Mapping

  • Shadow Mapping本质是一种图像空间的做法,在生成阴影这一步不需要知道场景的几何信息,该方法会出现走样现象,经典的shadow mapping只能处理点光源的情况 An Image-space Algorithm, no knowledge of scene’s geometry during shadow computation, must deal with aliasing artifacts

  • Shadow Mapping的核心思想是:如果有点不在阴影里,那么这个点可以被摄像机和光源都看到 Key idea: the points NOT in shadow must be seen both by the light and by the camera

1.2 阴影贴图的具体做法 Shadow Mapping

  • 第一步:从光源看向场景 Render from Light
    • 得到光源视角的深度信息 Depth image from light source

  • 第二步:从摄像机看向场景 Render from Eye
    • 把摄像机视角看到的点投影回光源视角的投影平面上,比较深度值,如果深度一致,则说明该点可以被光源照射到;如果深度不一致,说明该点无法被光源照射到 Project visible points in eye view back to light source, (Reprojected) depths match for light and eye. VISIBL. depths from light and eye are not the same. BLOCKED

1.3 可视化阴影贴图的过程 Visualizing Shadow Mapping

  • 从光源方向看向场景 The scene from the light’s point-of-view

  • 得到光源方向下的深度信息 The depth buffer from the light’s point-of-view

  • 与shadow map比较深度信息得到阴影区域 omparing Dist(light, shading point) with shadow map

  • 得到有阴影的场景 Scene with shadows

1.4 阴影贴图的问题 Problems with shadow maps

  • 只能处理硬阴影(点光源) Hard shadows (point lights only)

  • 质量取决于阴影贴图分辨率(基于图像空间的技术的问题)Quality depends on shadow map resolution (general problem with image-based techniques)

  • 比较深度值是否相等涉及浮点数精度问题,会带来各种问题 Involves equality comparison of floating point depth values means issues of scale, bias, tolerance

  • 即使有很多问题,阴影贴图的方法仍是早期3D动画和现在所有3D游戏中的基本阴影技术 Basic shadowing technique for early animations (Toy Story, etc.) and in EVERY 3D video game

1.5 软阴影 Hard shadows vs. soft shadows

  • 有一定大小的光源照射物体会产生本影(Umbra)和半影(Penumbra),会形成软阴影
  • 如果形成了软阴影,一定是因为光源有一定大小,点光源不会形成软阴影

2 为什么要引入光线追踪 Why Ray Tracing?

  • 光栅化不好解决全局的效果:软阴影、光泽反射、间接光照 Rasterization couldn’t handle global effects well: Soft shadows, Glossy reflection, Indirect illumination

  • 光栅化速度很快,但是质量相对低 Rasterization is fast, but quality is relatively low
  • 光线追踪很准确,但是很慢 Ray tracing is accurate, but is very slow
    • 光栅化容易做到实时,光线追踪经常用于离线应用 Rasterization: real-time, ray tracing: offline

    • 光线追踪一帧画面就需要花费一万个cpu小时 ~10K CPU core hours to render one frame in production

3 基本光线追踪算法 Basic Ray-Tracing Algorithm

3.1 光线的定义 Light Rays

  • 光线是沿着直线传播(不考虑波动性) Light travels in straight lines (though this is wrong)

  • 光线与光线不会发生碰撞(严格上也是错误的) Light rays do not “collide” with each other if they cross (though this is still wrong)

  • 是从光源出发最终到达人眼的(在应用时,由于光路的可逆性,会采用人眼到光源的方法) Light rays travel from the light sources to the eye (but the physics is invariant under path reversal - reciprocity)

当你凝视着深渊时,深渊也在凝视着你——尼采
“And if you gaze long into an abyss, the abyss also gazes
into you.” — Friedrich Wilhelm Nietzsche (translated)

3.2 光线投射 Ray Casting

3.2.1 Appel 1968 - Ray casting

  1. 假设向场景中看去,眼前放了一个成像平面,通过成像平面上的每个像素投射一条射线到场景,找到射线和场景内的物体的交点 Generate an image by casting one ray per pixel
  2. 通过从交点向灯光连线来判断该位置是否处于阴影 Check for shadows by sending a ray to the light

3.2.2 实际例子

  1. 只考虑眼睛是一个位置位于一点的针孔相机(Pinhole Camera Model),不考虑实际相机处理、镜头等(这部分会在路径追踪中说)
  2. 从眼睛(eye point)开始,穿过成像平面(image plane)上的像素向场景中投射光线(eye ray),记录与场景内物体的最近的交点(closest scene intersection point),这一步其实已经完美解决了深度测试的问题

  1. 从交点向光源连线(shadow ray)判断该点是否能被光源照亮,如果该连线中间没有物体阻挡,则说明该点可以被光源照亮。有了法线、入射方向、出射方向等数据就可以利用着色模型(如Blinn Phong模型)计算该点的着色,写入该像素

3.3 递归(Whitted风格)光线追踪 Recursive (Whitted-Style) Ray Tracing

  • 上面使用光线投射的例子只考虑了光线弹射一次,但实际上光线可以弹射很多次,Whitted风格光线追踪就是在做这个事情

  1. 类似光线投射的例子,假设该场景内的球是玻璃球,从眼睛穿过一个像素向场景内投射光线,打到物体上某一点时会发生折射(Refraction)与反射(Reflection)

  1. 在Whitted风格光线追踪中,光线进行了多次弹射,在每一个弹射点都去计算着色的值,最后将所有的值都加到该像素中(考虑能量守恒,比如该例子中碰到球体后反射的光线占60%,折射的光线占40%)

4 光线和物体表面相交 Ray-Surface Intersection

4.1 射线方程 Ray Equation

  • 一条光线可以由起点(Origin)和方向(Direction)确定 Ray is defined by its origin and a direction vector

  • 光线上的任意一点都可以用一个以t为自变量的函数来表示 Ray equation:

4.2 光线与隐式表面求交 Ray Intersection With Implicit Surface

  • 与隐式表面相交,则交点p既满足射线方程,也满足表示隐式表面的函数

  • 光线与球体相交 Ray Intersection With Sphere

  • 光线与一般隐式表面相交 Ray Intersection With Implicit Surface

4.3 光线与显式表面求交 Ray Intersection With Triangle Mesh

  • 对于显式表面的渲染,光线如何与三角形求交很重要 Rendering: visibility, shadows, lighting ...

  • 通过这个方法也可以判断一个点是否在物体内(点如果在封闭形状内,向外打一条光线,得到的交点数量一定是奇数) Geometry: inside/outside test

4.3.1 最简单的做法:遍历所有三角形 Simple idea: just intersect ray with each triangle

  1. 求光线与三角形所在平面的交点 Ray-plane intersection

  2. 判断该点是否在三角形内(三次叉乘判断符号是否一致) Test if hit point is inside triangle

4.3.2 平面方程 Plane equation

  • 一个平面可以由该平面的一个法向量与平面上的一个点定义 Plane is defined by normal vector and a point on plane

  • 平面上的任意一点可以用以下方程表示 Plane Equation (if p satisfies it, then p is on the plane):

4.3.3 光线与平面求交 Ray Intersection With Plane

  • 光线与平面相交,则交点即满足射线方程也满足平面方程

4.3.4 MT算法 Möller Trumbore Algorithm

  • 一种更快的方法,直接求出光线与三角形所在平面交点的重心坐标,通过重心坐标判断是否在三角形内 A faster approach, giving barycentric coordinate directly

  • 该方程组有3个式子,3个变量,对于有N个式子N个变量的线性方程组可以使用克莱姆法则(Cramer's Rule)求解

  • 对于求出的重心坐标表示的三角形所在平面的一点,都为非负的则该点在三角形内

5 轴对齐包围盒 Axis-Aligned Bounding Box

5.1 体积盒 Bounding Volumes

  • 一种加速光线与表面求交的方法 将复杂的物体包裹在简单的形状内 Quick way to avoid intersections: bound complex object with a simple volum

  • 计算时先计算光线是否与包围盒相交,如果相交再计算与物体的相交 Test BVol first, then test object if it hits

5.2 轴对齐包围盒 Axis-Aligned Bounding Box

  • 包围盒是三组对面形成的交集 box is the intersection of 3 pairs of slabs

  • 我们经常用的一种包围盒是轴对齐包围盒,该包围盒任何一个轴都是沿着坐标轴的 Specifically: We often use an Axis-Aligned Bounding Box (AABB)

5.3 光线与轴对齐包围盒求交 Ray Intersection with Axis-Aligned Box

  • 对于二维情况下(两组对面),我们分别看光线何时进入离开x平面和y平面,记录进入时间和离开时间,则光线在这两组对面形成的长方形内部的位置即为前两段分别求出的交集

  • 关键点 Key ideas:
    • 光线只有在进入所有成对的平板时,才进入盒子 The ray enters the box only when it enters all pairs of slabs
    • 只要光线离开任何一对平板,光线就会离开盒子 The ray exits the box as long as it exits any pair of slabs
  • 因此对于三维包围盒,,当时,光线在包围盒内传播了一段时间
  • 考虑到光线是一条射线,而不是一条直线
    • 如果,则包围盒在光线之后——没有交点
    • 如果,则光线的原点在包围盒内——有交点
    • 总结:当且仅当 && 时,光线与轴对齐包围盒有交点
  • 为什么要使用轴对齐包围盒 Why Axis-Aligned?
    • 在平面沿轴放置的情况下,求t的时候可以只用某一轴的信息,而不用整个坐标,计算量更小

6 使用轴对齐包围盒加速光线追踪(不同加速结构) Using AABBs to accelerate ray tracing

6.1 均匀空间划分 Uniform Spatial Partitions (Grids)

  • 场景的预处理 Preprocess

  • 在光线追踪的过程中,只需要判断光线交到的是不是含有物体表面的格子,如果不是的话跳过,是的话和其中的物体求交(假设光线与物体求交很慢,与盒子求交很快)
  • 如何知道一条光线会碰到哪一个盒子?最简单的想法,如果光线向右上方打,那么下一个经过的格子一定在右边或上边,只需要验证这两个格子即可,三维情况下也类似(如何光栅化一条线)

  • 不同的格子数加速效果不同,既不能太密也不能太稀疏,经实际应用 #cells = C * #objs,其中C在三维中约为27时加速效果最好
  • 使用格子的划分方法在大量均匀分布的物体上比较有效 Grids work well on large collections of objects
    that are distributed evenly in size and space

  • 该方法在物体分布不均匀、有大量空旷区域的场景中表现不好,会出现”体育场中找茶壶“的问题 “Teapot in a stadium” problem

6.2 其他不同的空间划分方法 Spatial Partitioning Examples

  • 八叉树 Oct-Tree
    • 先把三维空间切成八份(二维的话如图是四份),对于每一个子节点,再切一遍,以此类推
    • 制定停止规则时一般规定划分到有多少格子内为空的或有足够少的物体时停止
    • 人们并不喜欢用八叉树,因为在二维这种划分方法是四叉树,三维是八叉树,维度更高,就是2的n次方叉树,维度更高会越来越复杂
  • KD树 KD-Tree
    • KD树空间的划分复杂程度和维度无关
    • KD树和八叉树的划分方法几乎完全相同,只不过它每次沿着某一个轴砍开,并且只砍一刀
    • 空间被划分成类似二叉树的结果,每次节点的划分底下都只有两个子节点
    • 划分时为了均匀起见,xyz依次划分
  • BSP树 BSP-Tree
    • BSP树是一种对空间二分的划分方法,它每次选一个方向砍开,它和KD树的区别是它不是横平竖直的划分,而且它会有越高维越不好计算的问题(砍开二维用线,砍开三维用面,维度越高越复杂)

6.2.1 KD树 KD-Tree

  • KD树的预处理 KD-Tree Pre-Processing
    • 处理之后所有物体都分布在叶子节点上

  • 设计适合KD树的数据结构 Data Structure for KD-Trees
    • 中间节点(Internal nodes)储存:沿哪一个轴划分(split axis: x-, y-, or z-axis)、划分平面的位置(split position: coordinate of split plane along axis)、两个子节点的指针(children: pointers to child nodes)
    • 叶子节点(Leaf nodes)储存:物体(list of objects)
  • KD树的遍历过程 Traversing a KD-Tree

  • KD树的问题
    • 给出一个包围盒很难判定它和哪些三角形有交集,也是这个原因最近10年渐渐不用KD树的方法了

    • 一个物体和很多包围盒都有交集的情况下,会存在多个叶子节点内

6.3 物体划分和BVH结构 Object Partitions & Bounding Volume Hierarchy (BVH)

  • 从物体划分可以避免空间划分的一些问题,所以现在普遍采用BVH结构(BVH不涉及三角形和包围盒求交,避免了一个物体存在多个叶子节点内,但是BVH的划分并没有划分开,包围盒可能会相交,不过做到尽可能重叠少就好,这也是当今一个研究的方向)
  • BVH的预处理 Pre-Processing
    • 找到场景包围盒 Find bounding box
    • 递归地将对象集拆分为两个子集 Recursively split set of objects in two subsets
    • 重新计算子集的边界框 Recompute the bounding box of the subsets
    • 根据一定条件停止(如分到每个子集中不多于5个三角形) Stop when necessary
    • 在每个叶子节点中存储物体 Store objects in each leaf node

  • 划分节点的方法 How to subdivide a node?
    • 选一个维度进行划分,如选择最长的轴划分 Choose a dimension to split, Always choose the longest axis in node
    • 取中间的物体(第n/2个三角形,可以使用快速选择算法) Split node at location of median object
  • 设计适合BVH结构的数据结构 Data Structure for BVHs
    • 中间节点(Internal nodes)储存:包围盒(Bounding box)、叶子节点的指针(Children: pointers to child nodes)
    • 叶子节点(Leaf nodes)储存:包围盒(Bounding box)、物体(List of objects)
  • BVH的遍历过程(类似于KD树的方法) BVH Traversal

Intersect(Ray ray, BVH node) {              
	if (ray misses node.bbox) return;       //光线和整个BVH节点不相交,什么也不发生
	 
	if (node is a leaf node)                //如果光线与BVH的叶子节点相交
		test intersection with all objs;      //和节点内所有物体都求交
		return closest intersection;          //返回最近的交点
		
		hit1 = Intersect(ray, node.child1);   //如果相交的不是叶子节点,那光线与两个子节点都可能相交
		hit2 = Intersect(ray, node.child2);   //分别与两个子节点求交

		return the closer of hit1, hit2;      //返回最近的交点
}

6.4 以KD树为例的空间划分 VS 以BVH为例的物体划分 Spatial vs Object Partitions

  • 以KD树为例的空间划分 Spatial partition (e.g.KD-tree)
    • 空间与空间之间没有交集 Partition space into non-overlapping regions
    • 一个对象可以包含在多个区域中 An object can be contained in multiple regions
  • 以BVH为例的物体划分 Object partition (e.g. BVH)
    • 将对象集划分为不相交的子集 Partition set of objects into disjoint subsets
    • 每个集合的边界框可能在空间上重叠 Bounding boxes for each set may overlap in space