|
2022/7/15/
计算机图形学主题描述了如何使用计算机创造和操作图形。 这本书介绍了可以创造所有类型图形的算法和数学工具—— 逼真的视觉效果,丰富的技术插图 或者漂亮的计算机动画。图形有2D和3D;图形可以完全是合成的或者由照片加工生成。这本书主要介绍基础的算法和数学实现,尤其是用于合成3维物体场景图像的算法。
实际上做图形学相关内容需要了解具体的硬件,文件格式和通用的一个到两个图形API。计算机图形学是快速发展的领域,具体的知识也在发展。 因此在这本书上避免使用任何具体的硬件或者API。读者可以用它们的软硬件环境相关的文档作为这本书的学习补充。幸运的是,图形学的文化有充足的标准术语和概念,所以本书讨论的内容可以很好的覆盖不同的环境(图形学概念比较通用)。
这一章定义了一些基础的术语,并提供一些关于图形学的历史背景和信息源。
1.1 图形学研究领域
在任何领域上进行分类都是有风险的,但大多数参与者认为图形学主要是一下几个领域
Modeling建模
建模是指使用具体的数学描述,以特定的方式来处理 形状和外观,从而将模型存储在计算机上。比如,一个马克杯可以用一系列的3D点来描述,使用一些插值的规则来建立点之间的联系。使用反射模型来描述光线如何与马克杯交互。
Rendering渲染
渲染是从美术中继承的一个术语,指的是从三维的计算机模型中 处理 着色图象。
Animation动画
动画指的是通过多张图片构成的图片序列,创造一个运动的影像。 动画用到了建模和渲染,同时补充了随时间运动过程中的关键问题,这些问题在基本的建模和渲染中不会遇到。
<hr/>其他一些领域也包括了图形学,至于这些领域是不是图形学核心问题,有很多不同的观点。这本书还会谈及下面的一些领域:
User Interaction用户接口 用户接口指的是处理输入设备和应用的接口,反馈给用户图像的,场景化的反馈。从历史发展来看,这一个领域和图形学有关很大原因是因为早期的图形研究人员使用的一些输入输出设备(手写板,显示器)如今已经无处不在。 UI设计,GUI图形界面目前也是一个偏向设计的应用领域。
Virtual Reality虚拟现实 虚拟现实的目标是让用户沉浸在一个3D的虚拟空间中,这要求硬件设备能够检测到玩家的头部运动,对于真实的虚拟现实,也应该提供声音和力的反馈。这个领域要求卓越的3D图形和卓越的显示技术,因而与图形学有很大关联
Visualization 可视化 可视化是将复杂的信息通过视觉展现的方式呈现给用户,旨在给用户留下深刻印象, 有些图形学的问题其实也是可视化的问题。
Image Processing图像处理处理2D的图像,在图形学和视觉方向都有广泛应用。
Three-dimensional scanning 3维扫描 使用测距技术(range-finding technology)来建立准确的3D模型。这些模型可以创造丰富的可视图像,并且这些模型的处理通常要求图形学算法。
Computational photography计算影像学 计算影像学涉及计算机图形学,计算机视觉,图像处理方法的应用,使用这些新的方法来摄影捕捉物理,场景和环境。
1.2 图形学应用领域
几乎任何行业都可以用到计算机图形学,但是图形学技术主要的应用领域是在一下几个方面:
Video games电子游戏
Cartoons卡通形象
Visual effects特效
Animated films动画电影
CAD/CAM计算机辅助设计/计算机辅助操作 指的是使用计算机技术来设计产品,比如很多机械零件是在计算机软件上设计好,然后再使用自动化设备进行加工
Simulation 仿真 仿真是要建立逼真的场景,比如,飞行器仿真就要模拟3D的图形在天空飞行的真实环境。 再比如现在的自动驾驶的模拟仿真环境,场景的训练环境等等。
Medical Imaging 医学影像 通过扫描病人的数据创造有意义的图像。 比如CT影像就是打的3D三角形场景
Information visualization 信息可视化 创造一个数据的图像,这些数据没有“自然的”可视化表述。比如股票的价格变化趋势等等,但是有效的图形技术可以帮助人观测数据的模式。(一个典型的例子是通过数据的可视化散点图可以大致认为数据呈现高斯分布)
1.3 图形学 APIs (交互接口)
使用图形库的一个关键部分是处理图形API。 API(application program interface)应用程序接口是一系列函数构成的, 它们可以执行一些列的操作而不用关心这些操作是如何实现的。 图形学API就指的是一系列可以执行基本的图形操作的函数, 比如绘制图像,将3D的图像在屏幕上的窗口中。
每个图形程序都需要两套API:一套是图形API用来可视输出,另一套是用户接口API用来从用户获取输入。
目前的图形API和用户接口API有两个主要的范式:
一套是Java实例化的整体方法:图形API和用户接口API整合到可移植的包中,作为语言支持的组件。(不是很懂)
第二个是以Direct3D和OpenGL为代表的,绘图命令是软件库的一部分,绑定在编程语言比如C++中, 而交互软件是独立的实体,根据系统的变化而变化。
对于第二种方法,编写可移植的代码是有问题的。 尽管对于简单的程序,可以使用可移植库来封装系统特定的用户接口代码(UI-API)
不论选择哪种API, 基本的图形学上的实现大致相同,这些会在这本书中介绍。
1.4 图形学管线
如今的每一台桌面电脑都有强大的3D graphics pipeline3D图形学管线 , 这是一个具体的软/硬件子系统,用来高效绘制特定视角下的 3D基本元素(primitives)。通常这些系统被专门优化过,用来处理具有共同顶点的三角片。这一管线的基本操作是将3D的顶点定位到2D的屏幕位置上,并且对这些三角形着色。从而让他们都能看起来很逼真,并且有合理的从前到后的(景深)层次顺序。
尽管有效的从前到后绘制三角形是曾经图形学最重要的研究问题,而如今通过Z-buffer的方法基本解决了这一问题。 Z-buffer使用特殊的内存缓存和暴力求解的思路来解决这一问题。
经过证明得到,在图形学管线中用到的所有几何操作(旋转,平移,缩放等变换)全部都可以通过4维坐标空间来实现,这一4维的坐标空间是由3维的几何坐标和一个第4维的齐次坐标构成。同时4维坐标系也可以用于实现透视变换。 4维的坐标包括4*4的矩阵和4维的向量。因此图形学管线包含许多高效处理这些矩阵和向量的机制,4D坐标系统是计算机中一个最微妙和漂亮的构造,它确实是在学习图形学过程中最智慧的跨越。大部分图形学的书第一部分都会介绍这一齐次坐标。
图形生成的速度非常依赖于绘制的三角片的数目。由于在许多场景下可交互要比显示的质量更加的重要(游戏的实时交互要求实时渲染),所以最小化表征模型的三角片数量是有价值的。另外,如果模型是从远处观察,我们需要的三角片数量比从近处观察需要的三角片数量要少很多,(远处的物体可以更模糊)。 这表明使用不同的细节层级表征一个模型是有效的(LOD level of details )
1.5 数学表示问题(IEEE标准)
许多的图形学程序只是3维的数字编码,在这样的程序中,数字表示的问题是至关重要的。在过去使用一种鲁棒(Robust)的并且可移植的方式来处理这样的问题是很困难的,因为机器内部有不同的数字表示方式,更糟糕的是,这些数字使用不同的和不兼容的处理方式。幸运的是, 所有的现代计算机遵循了IEEE floating-point标准,(IEEE Standards Association,1985),这让编程者能够方便的假定具体的数学条件应该如何处理。
尽管IEEE floating-point 有许多的特点在编码数字算法时是有效的,但在大多数我们面对的图形学情境中,只有一少部分是我们需要的。 第一步是了解在IEEE floating-point中指代真实数字的 表示:
- Infinity无穷大/正无穷
- Minus infinity 负无穷
- Not a number 无效数字 NaN 输入无效
IEEE float-point 的设计者做了几个方便的规定,许多规定和具体值的处理有关(除了除以0)。 如下标注了特例的处理,在大多数情境下,特例可以忽略。
对于一个正实数 a, 犹有如下规定:
其他一些包含无穷的操作,结果规定如下:
包含无穷值的布尔逻辑表达式按如下规则规定:
所有的值都小于正无穷
所有的值都大于负无穷
负无穷小于正无穷
包含NaN的表达式处理简单:
任何包含NaN的算数表达式结果是NaN
任何包含NaN的布尔逻辑表达式结果是false
实数除以0的规定如下:
使用IEEE float-point标准,很多的数学运算都变得很简单,比如说和计算电阻,计算透镜参数有关的表达式:
在这一表达式中,如果除以0会导致程序崩溃,那么我们需要增加两个判断条件if 来判断是否b等于0,是否c等于0。 但是,在IEEE标准中,我们会得到分母的值为无穷,从而得到最终a的值为0,程序可以有效的运行,并且最后a的结果是有效的。 另一个好处是,如果表达式不合理,最后输出为NaN,在经过判断条件时,也不会引起程序崩溃,比如如下的程序:
a = f(x)
if ( a>0 ) then
do something无论最后的f(x) 传递给a的值是 无穷还是NaN,都不会影响后续判断条件的正确执行,这显然是有效的。这一IEEE标准让程序更轻量,更鲁棒,更高效。
1.6 高效性
There are no magic rules for making code more efficient 。
代码的高效性没有明确规则,高效性是通过慎重的tradeoff (权衡)实现的,不同的体系结构下有不同的tradeoff。然而,在可预见的将来,一个好的启发式是编程人员更关注内存的访问模式而不是操作数。这是两个世纪之前最好的启发式的对立(关注操作数而不是内存访问)。 这一转变是由于内存的访问速度没有赶得上处理器的处理速度。由于这一趋势仍在持续, 有限和连续内存的优化应该增加。
一个合理的加速代码的方式如下,只需要执行需要的步骤:
1.尽可能以最直接的方式写代码,按照需求计算结果,而不是存储这些结果。
2.使用优化模式编译。
3.利用任何的现有分析(profiling tools)工具发现关键瓶颈。
4.检查数据的结构来寻求本地的提升。尽可能地让数据单元匹配目标体系下的缓存的大小。
5.如果分析发现瓶颈是在数值计算上,检查一下编译器生成的相似代码寻找忽略的效率,重写源码来解决发现的问题
在这些优化的方式中,最重要的是第一步的方式。
大多数的优化会让代码变得不易读,并且速度也没有提升。 同时,花费在优化代码的前期时间如果花费在处理bug和添加特征上,会取得更好的效果。并且我们要警惕从旧书籍中挖掘的启发,一些经典的技巧可能不在适用:比如使用整数来替代实数可能不会取得较大的速度提升。因为现代的CPU通常在浮点操作上也能表现得和处理整数一样好。不管在什么情况下,我们都需要分析来确定对于特定机器和编译器进行优化的价值。
1.7 设计和编写图形学程序
确定的通用策略通常在图形学编程中是有效的。在这一章节,我们提供了一些建议, 这些建议对于你学习本书,实现其中的方法是有帮助的。
1.7.1 Class Design 类的设计
任何图形程序的关键部分都是有好的实体类或者处理流程用于图形实体,比如向量和矩阵,也有一些图形实体比如RGB颜色和图片。这些流程应该被设置的尽可能简洁高效。
一个普遍的设计问题是是否位置和位移应该被分成不同类,因为他们有不同的操作。 比如: 一个位置乘以1.5没有几何含义,而一个位移乘以1.5则有另一个几何含义。 这一问题很难统一,会在图形学研究者之间引发几个小时的激烈争论,但是为了以身作则,我们假定我们不做位置和位移的区分。
这里展示了一些基本的类:
vector2
2维向量 2维向量类包含两个存储构成 x 和 y 。 它使用一个长度为2的数组来存储这一组成,所以可以支持索引操作( index operator)。 同时,它应该支持向量加,向量减,点乘,叉乘,标量乘法,标量除法。
vector3
3维向量,包含三个存储构成x,y,z 。 和二维向量类似
hvector
齐次向量,齐次坐标系下的向量,有四个组成元素(x,y,z,w)(详见第八章)
rgb
rgb颜色存储了3个构成(R,G,B)通道,类中也要包括RGB的操作: RGB加,RGB减,RGB乘,RGB标量乘法,RGB标量除法。
transform
变换,用一个4*4的矩阵来表示变换。齐次坐标系下的基本变换都可以用4*4矩阵表示。 包含的操作应该有:矩阵乘法和作用于位置,作用于方向,作用于法向量的成员函数。(详见第七章,这些都是不同的)
image
由(R,G,B)像素点组成的2维数组,有一个图像输出的操作。
<hr/>其它的的,你可能会想或者不会想着为区间,正交基,坐标系,增加类,也可能想着为单位向量增加类。这些带来的负面效果是大于它们的价值的。
1.7.2浮点数和双精度浮点数
现代的体系结构表明,让内存向下使用,保持内存访问的一致性是高效的关键。这建议我们只使用single-precision data (float浮点数) 。 然而,为了避免数值问题,又建议我们合理使用 double-prefcision arithmetic 双精度算法(double)。tradeoff依赖于具体程序,但在自己的类定义中最好有一个默认选择。
备注: 不同大佬的看法。
I suggest using doubles for geometric computation and floats for color computation. For data that occupies a lot of memory, such as triangle meshes, I suggest storing float data, but converting to double when data are accessed through member functions. —P.S.
I advocate doing all computations with floats until you find evidence that double precision is needed in a particular part of the code. —S.M.
1.7.3 Debugging 调试图形程序
如果你询问周围,你可能会发现随着程序员变得更有经验,他们越来越少的使用传统调试工具(debuggers)。 一个原因是使用这样的调试器在面对更复杂的程序时略显尴尬(没啥用)。 另一个原因是更困难的错误是概念上的错误,这些错误是在编程中已经实现了,使用调试器很可能会花费大量的时间一步一步的检查变量,但是并没有发现这些错误。我们发现了几个在图形学中非常有用的调试策略。
The Scientific Method 科学的方法
在图形程序中,有一种替代传统调试器的非常有效的替代方案。
这一方案和计算机程序员在职业早期被教导的不要做的事有些相似(即观察输出的结果,猜猜哪里出了问题。)所以如果你这样做的话你会感到有点调皮”naughty“:创建一张图片,观察它哪里出错了。 然后我们提出一个假想关于是什么导致这一问题并且验证我们的假想。
(例子是依靠图片结果和理论分析,确定程序的错误,最好学完光追再理解)
比如说,在一个光线追踪的程序中,我们也许有某几个看起来很黑的像素点。这是一个大部分人在写光线追踪器时遇到的经典的阴影痤疮“shadow acne”问题。 传统的调试方法基本没有用(只是某几个输出值错误),然而,我们必须意识到,阴影的光线碰撞到被着色的表面上留下了阴影。 我们可能会注意到黑色点的颜色构建为透明颜色,所以是直接的光照缺失了。直接光照可能在阴影中被关闭了, 所以你可能会猜想这些点在他们不在阴影中时被不正确的标记了。为了验证我们的建设,我们可以关闭阴影检测并且重新编译。这表明这些是错误的阴影测试,我们可以再观察我们的结果。
这一实践往往有效,是因为我们不用再盯着错误的值或者检查我们的概念错误。相反,我们不断划分我们出错的地方在哪个部分。通常只需要一点点的尝试,我们就能确定错误出在哪个部分,这一debug过程也很有趣。
Images as Coded Debugging Output 图像作为编码调试输出
在许多案例中,许多早期的图形程序的debugging输出通道信息是图像的本身。如果你观察到一些和局部输出结果有关的像素点变量的值,你只需要把他们输出到图像通道中,并且跳过正常运行时后续的计算部分。
(例子时法向量着色和值输出到颜色通道,这部分讲的很模糊,主要意思就是通过输出到图像来debug)
比如说,如果你怀疑一个和表面法线有关的问题引起了着色的错误,你可以直接把法线向量输出到图像通道中, 得到实际用于计算的向量颜色编码的解释。或者,你怀疑一个值脱离了它的有效范围,它让你的程序在发生这一情况的地方写成红色像素。
其他的常用技巧包括用一个明显的颜色画出表面的背面(这些应该是看不到的),使用不同序号物体着色图像,或者根据计算的工作量大小着色像素点。
Using a Debugger使用调试器
也有一些案例,尤其是当科学方法似乎引发矛盾时, 比如当没有什么替代方案来比较观察发生了什么时。 困难在于图形程序通常包含许多许多相同代码的执行(比如对每一个像素,对每一个三角形)。这使得从起点一步一步的使用调试器不太实际。并且难以处理的bug通常也只在复杂输入的情况下发生。
<hr/>一个有效的策略是为bug”设置陷阱“。 第一步,确保你的程序是确定性的 ——将他运行在一个线程内,并且确保所有的随机数都来自固定的随机种子。第二步,发现哪一个像素或者三角形引发了bug,在你怀疑引发了bug的代码前增加一个声明,使得它只会在你怀疑的案例上执行。比如,如果你发现像素(126,247)引起了bug,则补充代码:
if x = 126 and y = 247 then
print &#34;blarg&#34;
在这个输出声明的地方设置一个断点,那么你的调试器就会进入着色这一像素的点的断点处,有一些调试器的“conditional breakpoint” 由相同的功能,我们不需要额外修改代码。
<hr/>在程序崩溃的时候,使用传统的调试器对定位崩溃位置是有效的。然后你应该回溯程序,使用断言和重新编译(asserts and recompiles,注意理解什么是断言), 查找程序哪里出错了。这些断言应该留在程序中来处理你未来可能引起的潜在bug。这也意味着传统的步进式处理要被避免,因为它不会为你的程序增加有价值的断言。
使用数据可视化方式来调试
通常,我们很难理解程序在做什么,因为在最后得到错误结果之前,它计算了许多中间的结果。这一情况和测量许多数据的科学实验是相似的。有一个解决方法是相同的:制作好的图表并且向自己解释,来理解这些数据的含义。比如,在一个光线追踪其中,你可能编码可视化 ray-trees ,于是你可以看到哪条路径对一个像素点有共线。 又或者在一个图像重采样的过程中你可以通过作图来显示输入中所有被采样的点。花费在可视化程序中间状态上的代码也能在花时间优化程序时更好的理解程序的执行行为。
I like to format debugging print statements so that the output happens to be a MATLAB or Gnuplot script that makes a helpful plot. —S.M
Notes后记
这一章对于软件工程的讨论受到了 Effective C++ 系列 (Meyers, 1995, 1997)的影响,也受到了 Extreme Programming movement (Beck & Andres, 2004)和 The Practice of Programming (Kernighan & Pike, 1999)的影响。 关于经验性调试的讨论也是基于和Steve Parker的讨论。
每年有很多和图形学相关的会议。包括
图形学相关会议 | ACM SIGGRAPH | SIGGRAPH Asia | Graphics Interface | the Game Developers Conference (GDC) | Eurographics | Pacific Graphics | High Performance Graphics | the Eurographics Symposium on Rendering | IEEE VisWeek |
|
|