又开了一个新坑,这次聊一聊计算机图形学。
虽然这门课我十分喜欢,上课时也听得津津有味,但无奈知识点实在太多,有点难以记全,所以仔细思索后还是通过笔记的形式记录下来。这次的笔记就不像之前的XML与数据结构那么系统化了,想到啥就写啥吧。
接下来的内容会涉及很多 线性代数 的内容,大家务必带上线性代数的基本功再一同上车~
图形学中比较重要的一个概念就是变换了,不论是模型变换、视图变换还是投影变换,其基本思路都是矩阵运算的变换,所以接下来的内容都以矩阵变换为主,较为简单的我便一笔带过,偶尔几个比较麻烦的会稍微进行解释。
模型变换
在聊模型变换之前,我们先讲一讲坐标的表示,一般情况下我们是用三维的齐次坐标来表示一个点,如
[xyz]T
但在这里,我们需要将其扩展为四维的齐次坐标,变成 [xyz1]T 或 [xyz0]T 。其中,第四维为1代表其为点,0则代表向量,至于为什么要这样做,看到后面就会明白了。
平移矩阵
要对向量 p
进行平移,我们需要一个平移矩阵 T
,使得 p'=Tp
。其中:
T(dx,dy,dz)=⎣⎢⎢⎡100001000010dxdydz1⎦⎥⎥⎤
去和四维齐次坐标相乘,就知道为什么要这样做了。
缩放矩阵
S(sx,sy,sz)=⎣⎢⎢⎡sx0000sy0000sz00001⎦⎥⎥⎤
旋转矩阵
二维旋转矩阵
假设在 xOy
平面上进行绕z轴旋转 θ 度,那么很容易得知:
⎩⎪⎨⎪⎧x′=xcosθ−ysinθy′=xsinθ+ycosθz′=z
所以,绕z轴的旋转矩阵
Rz(θ)=⎣⎢⎢⎡cosθsinθ00−sinθcosθ0000100001⎦⎥⎥⎤
同理:
Rx(θ)=⎣⎢⎢⎡10000cosθsinθ00−sinθcosθ00001⎦⎥⎥⎤
Ry(θ)=⎣⎢⎢⎡cosθ0−sinθ00100sinθ0cosθ00001⎦⎥⎥⎤
绕任意轴旋转
绕任意轴旋转看似麻烦,但我们可以将其分解为几个小步骤:
- 将物体中心 p0 移动到原点: T(−p0)
- 将旋转轴与z轴对齐,该步骤只需要分别经过绕x轴的旋转与绕y轴的旋转即可实现,具体原理不赘述,应该能理解
- 好了,现在物体中心在原点,而旋转轴就是z轴,此时只需进行一次 Rz(θ) 变换即可
- 先将物体绕x轴与y轴旋转回去,再将物体移动回原本的位置: T(p0)
具体计算较为复杂,感兴趣的朋友可以自己推算,此处只给出一个思路:
M=T(p0)Rx(−θx)Ry(−θy)Rz(θ)Ry(θy)Rx(θx)T(−p0)
视图变换
相机变换
在介绍相机变换之前,我们肯定都能想到与相机变换息息相关的函数:
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
函数的相关参数就不再过多解释,但是我们需要从这些参数中提取出几个重要信息
- 相机位置
e=(eyex, eyey, eyez)
- 视线方向
g=(centerx-eyex, centery-eyey, centerz-eyez)
- 上方向量
t=(upx, upy, upz)
但是这些向量都是由用户给出,不能保证规范性,所以我们要先将其进行归一化:
w=−∣g∣g
此处的w就是相机坐标系的z轴,而x轴,则只需通过t与w叉乘得到,然后将其归一化来保证规范性:
u=∣t×w∣t×w
最后由这两个轴计算出y轴:
v=w×u
此时就可获得相机坐标系 (u, v, w)
,而坐标系变换矩阵,也很容易得出:
Mcam=[u0v0w0e1]−1=⎣⎢⎢⎡xuxvxw0yuyvyw0zuzvzw00001⎦⎥⎥⎤⎣⎢⎢⎡100001000010−xe−ye−ze1⎦⎥⎥⎤
投影变换
正交投影
glOrtho(left, right, bottom, top, near, far);
此处需要注意一下,near与far参数是相对于相机而言,所以-near需要映射到-1,-far需要映射到1。
Morth=S(right−left2,top−bottom2,far−near−2)T(−2left+right,−2bottom+top,2near+far)=⎣⎢⎢⎢⎡right−left20000top−bottom20000far−near−20−right−leftright+left−top−bottomtop+bottom−far−nearfar+near1⎦⎥⎥⎥⎤
透视投影
glFrustum(left, right, bottom, top, near, far);
Mfrus=⎣⎢⎢⎢⎡right−left2near0000top−bottom2near00right−leftright+lefttop−bottomtop+bottom−far−nearfar+near−100−far−near2far∗near0⎦⎥⎥⎥⎤
了解就行,记不下来的。
视口变换
即把规范化视景体中的内容填入窗口中: glViewport(Ox, Oy, width, height);
由于规范化视景体坐标 (x, y, z)
的取值范围都在区间 [-1, 1]
中,而我们要将其填入 [Ox, Ox+width]
宽, [Oy, Oy+height]
高的窗口中,所以需要进行坐标的映射:
⎩⎪⎨⎪⎧WinX=Ox+2x+1∗widthWinY=Oy+2y+1∗heightWinZ=2z+1
稍作整理:
⎩⎪⎨⎪⎧WinX=(0.5∗width)∗x+(Ox+0.5∗width)WinY=(0,5∗height)∗y+(Oy+0.5∗height)WinZ=(0.5∗z)+(0.5)
这样一来,我们就可以将该变换视为 缩放+平移 两步操作,对应的矩阵也就很容易计算出:
Mview=T(Ox+0.5∗width,Oy+0.5∗height,0.5)S(0.5∗width,0.5∗height,0.5)=⎣⎢⎢⎡0.5∗width00000.5∗height00000.50Ox+0.5∗widthOy+0.5∗height0.51⎦⎥⎥⎤