计算机图形学基础-介绍
本文最后更新于:2025年4月17日 下午
图形领域(Graphics Areas)
- 建模(Modeling)
- 渲染(Rendering)
- 动画(Animation)
- 用户交互(User interaction)
- 虚拟现实(Virtual reality)
- 可视化(Visualization)
- 图像处理(Image processing)
- 三维扫描(Three-dimensional scanning)
- 计算摄影(Computational photography)
主要应用(Major Applications)
- 电子游戏(Video games)
- 动画(Cartoons)
- 视觉效果(Visual effects)
- 动画电影(Animated films)
- CAD/CAM(Computer-aided design/Computer-aided manufacturing)
- 仿真(Simulation)
- 医学成像(Medical imaging)
- 信息可视化(Information visualization)
图形API(Graphics APIs)
每个图形程序都需要能够使用两个相关的API:一个用于视觉输出的图形API和一个用于从用户那里获得输入的用户界面API
- 第一种是集成的方法,以Java为例,其中图形和用户界面工具箱时集成的、可移植的软件包,是完全标准化的并作为语言的一部分得到支持。
- 第二种是以Direct3D和OpenGL为代表的,其中的绘图命令是与语言相联系的软件库的一部分,而用户界面软件则是一个独立的实体。
图形管线(Graphics Pipeline)
将三维顶点位置映射到二维屏幕位置,并对三角形着色,使它们看起来很真实,并以适当的前后顺序出现。现在几乎都是用 Z-buffer 来解决绘制三角形的前后顺序问题。几何操作几乎完全在4D的齐次坐标空间中完成。
数值问题(Numerical Issues)
实数有三个特殊值
- 无限大($\infty$) 有效数字,比其他所有有效数字都大
- 负无穷大($-\infty$) 有效数字,比其他所有有效数字都小
- 不是一个数字($NaN$) 无效的数字,是由具有未定义结果的操作引起的,如零除以零。
对于任何正实数 a:
$+a/(+\infty) = +0$
$-a/(+\infty ) = -0$
$+a/(-\infty ) = -0$
$-a/(-\infty ) = +0$
$\infty+\infty = +\infty$
$\infty-\infty = NaN$
$\infty\times\infty = \infty$
$\infty/\infty = NaN$
$\infty/a = \infty$
$\infty/0 = \infty$
$0/0 = NaN$
涉及无限值的布尔表达式规则如下:
- 所有有限的有效数字都小于 $\infty$
- 所有有限的有效数字都大于 $-\infty$
- $-\infty$ 小于 $\infty$
涉及有 $NaN$ 值的表达式的规则很简单:
- 任何包括 $NaN$ 的算术表达式的结果都是 $NaN$
- 任何涉及 $NaN$ 的布尔表达式都是假的
对于任何正实数 a,以下涉及除以零值的规则都成立:
$+a/+0 = +\infty$
$-a/+0 = -\infty$
效率(Efficiency)
- 尽可能以最直接的方式编写代码。根据需要即时计算中间结果,而不是存储它们。
- 在优化模式下进行编译
- 使用现有的任何分析工具寻找关键的效率瓶颈问题
- 检查数据结构,寻找提高定位数据的方法。尽量使数据单元大小和目标架构上的缓存/页面大小相匹配。
- 如果分析发现了数字计算的瓶颈,检查编译器生成的汇编代码是否有遗漏的效率问题。重写源代码来解决发现的任何问题。
设计和编码图形程序(Designing and Coding Graphics Programs)
类的设计
应该为几何实体(如向量和矩阵)以及图形实体(如RGB颜色和图像)编写尽可能干净又高效的类或程序。
- vector2 一个存储 x 和 y 分量的二维向量类。应该将分量存储在一个二维数组中,这样就可以支持索引操作。还应编写向量加法、向量减法、点积、叉积、标量乘法和标量除法的操作程序。
- vector3 类似于 vector2 的三维向量类。
- hvector 具有四个分量的齐次向量。
- rgb 一个 RGB 颜色存储三个分量。还应编写 RGB 加法、RGB 减法、RGB 乘法,标量乘法和标量除法的操作程序。
- transform 一个用于变换的 4 $\times$ 4 矩阵。还应编写一个矩阵乘法和成员函数,以应用于位置、方向和表面法向量的变换。
- image 一个具有输出操作的 RGB 像素的二维阵列。
Float VS Double
现代架构表明,减少内存的使用和保持连贯的内存访问是效率的关键。所以建议使用单精度数据,然而,为了避免数字问题(Numerical Issues),建议使用双精度算术。这种权衡取决于程序,但在你的类中有一个默认的定义。
调试图形程序
科学方法
创建一个图像并观察它的问题所在。然后,我们对导致问题的原因提出一个假设,并对其进行测试。我们有时会觉得这种方法很好用,因为不需要发现一个错误的值或者真正确定概念性错误。我们只是通过实验轻松定位到问题的位置。
将图像作为调试的输出
在许多情况下,从图形程序中获得调试信息的最简单渠道是输出图像本身。如果你想知道某个变量在每个像素上的部分计算值,你可以临时修改你的程序,把这个值直接复制到输出图像上,而跳过通常要进行的其他计算。例如,如果你怀疑某个特定的值有时超出了它的有效范围,让你的程序在发生这种情况的地方写上鲜红色的像素。其他常见的技巧包括用明显的颜色画出表面的背面(当它们不应该是可见的),用物体的ID号给图像着色,或者用像素的计算量来着色。
使用调试器
仍然有一些情况,特别是当科学方法似乎已经导致了矛盾的时候,这时没有什么可以替代观察到底发生了什么。麻烦的是,图形程序往往涉及同一代码的许多许多执行(例如,每个像素一次,或每个三角形一次),这使得从一开始就在调试器中步步深入完全不现实。而且最困难的错误通常只发生在复杂的输入上。
一个有用的方法是为这个错误 “设置一个陷阱(trap)”。首先,确保你的程序是确定性的–在一个单线程中运行,并确保所有的随机数都是由固定的种子计算出来的。然后,找出表现出错误的像素或三角形,并在你怀疑不正确的代码前添加一条语句,只对可疑情况执行。
如果你设置一个断点,你就可以在你感兴趣的像素被计算出来之前进入调试器。有些调试器有一个 “条件断点 “的功能,可以在不修改代码的情况下达到同样的效果。
在程序崩溃的情况下,传统的调试器对于确定崩溃的地方很有用。然后,你应该在程序中开始回溯,使用断言(asserts)和重新编译,以找到程序出错的地方。这些断言(asserts)应该留在程序中,以备将来可能添加的错误。这又意味着避免了传统的一步到位的过程,因为那不会在你的程序中加入有价值的断言(asserts)。
为调试进行数据可视化
通常情况下,我们很难理解程序在做什么,因为它在最后出错之前计算了很多中间结果。这种情况类似于测量大量数据的科学实验,有一个解决办法是相同的:为自己制作好的图表和插图来理解数据的含义。例如,在光线追踪器中,你可能会写代码将光线树可视化,这样你就可以看到哪些路径对一个像素的贡献,或者在一个图像重采样程序中,你可能会做一些图来显示所有从输入中提取样本的点。花费时间编写代码来可视化你的程序的内部状态,在优化它的时候,也可以通过更好地理解它的行为来得到回报。