这一章节讲述的主要是和模型有关的变换,我们需要先学习几何对象的一些基本性质,了解OpenGL中对几何对象建模的支持,然后学习几何对象的缩放/平移/旋转变换,所有的这些变换最终都可以归结为模型变换矩阵。
几何对象实体
几何对象主要指的是三维空间对象,包含点、线、面、体,图形学就是利用计算机对几何对象进行操作和显示的技术。
OpenGL中,基本的几何要素包含:
- 点
- 标量
- 向量
线性代数在计算机图形学中占有重要一席地位,需要回顾的线代概念有: - 向量空间:线性空间,处理向量的数学系统,包含向量和标量
- 点积/内积和叉积
- 线性相关/无关
- 维数/基
从向量空间触发,图形学引申出了仿射空间这一重要概念,仿射空间是向量空间的扩展,加入了点对象。
直线
直线可以通过点和向量联合表示,其参数形式为:P(α) = P0 + αd,表示所有经过P0点,与P0连线平行于向量d的点。
上面是采用单点定义的直线,直线也可以通过两点定义,参数形式为:
上面参数方程移项就可以得到:
这就是仿射加法。
几何对象表示
向量坐标系
由一组线性无关的基张成,向量可以表示为:
标架
上面向量坐标系,没有表示位置信息,引入标架可以解决。所谓标架,其实就是在原来向量坐标系的基础上,加入了原点,引入了点位置信息。
这么一来,点和向量就没法区分了。例如,(1,1,1)究竟表示的是一个点,还是一个向量?为了解决这个问题,我们引入齐次坐标。(但要知道齐次坐标的作用不仅在于区分点和向量,后几章会说明,等我写完了放个超链接上来)
所谓齐次坐标,其实就是在原坐标表示上加入第4维度,第4维若为1则表示点,为0则表示向量。
这里做一个形式化定义:
三维空间的四维齐次坐标的一般形式为:
齐次坐标的用途:
- 所有标准变换(旋转、平移、放缩)都可以应用4×4阶矩阵的乘法实现
- 硬件流水线体系可以应用四维表示
- 对于正交投影,可以通过w = 0保证向量,w = 1保证点
- 对于透视投影,需要进行特别的处理:透视除法
向量坐标系的变换
而
则
可得
又
得
标架的变化
矩阵形式的仿射变换
OpenGL中的标架
OpenGL管线中有6个固定的标架:
- 模型坐标系
- 世界坐标系
- 相机坐标系
- 裁剪坐标系
- 规范化的设备坐标系
- 屏幕坐标系
不管是什么坐标系,标架中的坐标都是采用齐次坐标表示,标架之间的变换都是通过仿射变换矩阵实现。
说了这么多,我们终于要进入最后的也是最重要的内容了,那就是如何利用仿射变换对图形进行变换。
仿射变换
- 变换:把点(或向量)映射到另一个点(或向量)
- 作用:
- 生成多个相同或者近似几何对象
- 从不同的视角观察场景
- 计算机动画
模型变换
平移
平移由一个向量就可以确定了。
可以用在齐次坐标中一个4×4的平移变换𝑻表示平移: 𝒑’=𝑻𝒑
旋转
一般旋转都是绕某轴旋转,这个时候旋转面和轴交点可以看成是旋转原点。
齐次坐标表示为 𝒑’=𝑹𝒛(𝜃)𝒑
如果绕其他轴也同理:
为什么我们这里这么强调是饶坐标轴旋转呢?因为简单。如果是一般情况的话,情况复杂,需要考虑旋转不动点、任意的旋转轴、旋转角度等。所以我们一般把任意的旋转进行分解,
平移和旋转变换合称为刚体变换,因为只改变物体的位置和定向,非刚体变换如缩放、错切、反射。
缩放
缩放矩阵太简单了,当然,前提是在缩放中心在原点的情况下:
一般来说,sx,sy,sz都是正数,如果是负数,则会发生镜面对称,其实就是反射。
错切变换(了解)
变换级联
多个变换可以级联在一起,级联矩阵中,越往后的矩阵越先作用在物体上。为了得到正确的模型变换效果,一般先将模型放置在原点,按照缩放-旋转-平移的顺序进行变换。
𝑴=𝑪𝑩𝑨, 𝒒=𝑴𝒑
关于级联矩阵举几个例子简单说明。
- 绕原点的一般旋转:
绕原点的任意旋转根据选择的顺序不同可不唯一地分解为绕x轴、y轴、z轴的三个旋转的级联,绕哪个轴先旋转,关系倒是不大。其级联矩阵为:R=RxRyRz - 沿着z轴,绕物体中心𝑷𝑓旋转
先将模型移动至原点,旋转之后再移回去。
注意这里不可直接Rz(θ),细想一下,这样会破坏模型旋转的不动点。
OpenGL中的变换矩阵
从概念上说,当前变换矩阵(CTM)就是一个4x4阶的齐次坐标矩阵,它是状态的一部分,被应用到经过流水线中的所有顶点。
在OpenGL的流水线中有一个模型视图矩阵和一个投影矩阵,这两个矩阵复合在一起构成CTM
可以选择在CPU变换之后传导到GPU,也可以直接在GPU中进行计算,也就是下面的两种方式:
- 构建新的模型视图矩阵,变换顶点位置,重新送入顶点坐标到GPU
mat4 ctm = RotateX(theta[0])*RotateY(theta[1])*RotateZ(theta[2]);
point4 new_points[36]; //======更新顶点位置
for(int i=0; i<36; i++)
{
new_points[i] = ctm*points[i];
}
……
//======将顶点坐标送入GPU重绘
loc = glGetAttribLocation(program, "vPosition");
……
glutSwapBuffers();
- 将新的模型视图矩阵送入着色器
void main() // 顶点着色器
{
gl_Position = rotation*vPosition;
color = vColor;
}