✠OpenGL-3-数学基础

目录
3D坐标系统

矩阵(单位矩阵、转置矩阵、逆矩阵;矩阵加法和乘法)
变换矩阵(*移、缩放、旋转、投影[透视&推导/正射]、LookAt)
旋转矩阵的数学推导
向量及操作
点积和叉积的应用
局部和世界空间——模型矩阵M
证明:一个变换矩阵就能完成不同坐标系下的坐标变换
视觉空间和合成相机——模型-视图矩阵MV
三维物体的显示过程
我们如何变换来计算出P~W~在相机空间中的位置P~C~?
为什么是[负]相机位置/旋转角?
用纯GLSL函数构建变换矩阵
[组合矩阵变换] & NDC & 归一化处理与透视除法 & 齐次坐标下的裁剪 & 视口变换
补充说明
3D坐标系统
3D图形学中几乎每个方面、每种效果——移动、缩放、透视、纹理、光照、阴影等都在很大程度上以数学方式实现。
在OpenGL中的坐标系大体是右手坐标系。

 


3D空间中的点可以通过使用形如(2, 8, -3)的符号,列出X、Y、Z的值来表示。不过,如果用齐次坐标来表示点会更有用。在每个点的齐次坐标有4个值。即(X, Y, Z, W),其中W总是非零值,通常为1。因此我们会将之前的点表示为(2, 8, -3, 1)。齐次坐标将会使我们的图形学计算更高效。

用来存储齐次3D坐标的GLSL数据类型是vec4。GLM库包含适合在C++/OpenGL应用中创建和存储3元和4元(齐次)点的类,分别叫作vec3和vec4。

矩阵(单位矩阵、转置矩阵、逆矩阵;矩阵加法和乘法)
[ A 00 A 01 A 02 A 03 A 10 A 11 A 12 A 13 A 20 A 21 A 22 A 23 A 30 A 31 A 32 A 33 ] \left[
A00A10A20A30A01A11A21A31A02A12A22A32A03A13A23A33
A00A01A02A03A10A11A12A13A20A21A22A23A30A31A32A33
\right]






A
00


A
10


A
20


A
30




A
01


A
11


A
21


A
31




A
02


A
12


A
22


A
32




A
03


A
13


A
23


A
33









GLSL语言中的mat4数据类型用来存储4×4矩阵。同样,GLM库中有mat4类用以实例化并存储4×4矩阵。

①单位矩阵:
一条对角线的值为1,其余值全为0。
[ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] \left[
1000010000100001
1000010000100001
\right]






1
0
0
0


0
1
0
0


0
0
1
0


0
0
0
1







任何值乘以单位矩阵都不会改变。

单位矩阵也可以简单地记为一个对角线矩阵:En = diag(1,1,…,1)
根据矩阵乘法的定义,单位矩阵的重要性质为:AEn = A;EnA = A

在GLM中,调用构造函数glm::mat4 m(1.0f);以在变量m中生成单位矩阵。

②转置矩阵:
矩阵转置的计算是通过交换矩阵的行与列完成的。转置,就是旋转置换的意思;给定一个mxn的矩阵,则A的转置就是nxm的矩阵,用AT表示。例如:


转置矩阵特性:
1、(Aᵀ)ᵀ = A
2、(A+B)ᵀ = Aᵀ + Bᵀ
3、对任意数r,(rA)ᵀ = rAᵀ
4、(AB)ᵀ = BᵀAᵀ

GLSL库和GLM库都有转置函数,分别是transpose(mat4)和glm::transpose(mat4)。

➂逆矩阵
一个4×4矩阵的逆矩阵是另一个4×4矩阵,用M-1表示。

在矩阵乘法中有如下性质:
M×M-1 = M-1×M = 单位矩阵 E
若 AB = BA = E,则 A 是可逆的,B 是 A 的逆矩阵(B=A-1)。
若A可逆,则A-1亦可逆,且:(A-1)-1 = A
若A可逆,则AT亦可逆,且:(AT)-1=(A-1)T
若A、B为同阶方阵且均可逆,则AB亦可逆,且:(AB)-1=B-1A-1

GLSL和GLM都提供了mat4.inverse()函数完成矩阵的逆矩阵运算。

➊矩阵加法:

GLSL 和 GLM 都支持使用重载后的加法“+”运算符,进行矩阵加法。

➋矩阵乘法:


矩阵相乘也经常叫作合并。
我们需要经常将相同的一系列矩阵变换应用到场景中的每个点上。通过预先一次计算好这些矩阵的合并,就可以成倍减少总的矩阵运算量。

GLSL 和 GLM 都支持使用重载后的乘法“*”运算符,进行矩阵乘法。

点与矩阵相乘,得到新点:

我们用齐次坐标将点(X, Y, Z)表示为列数为1的矩阵。
注意,矩阵乘法不满足【交换律】。(很容易证明交换律不成立)

矩阵乘法满足【结合律】:(很容易证明结合律成立)
Now Point = M1×(M2×(M3×Point)) == (M1×M2×M3)×Point( = M123合并×Point)

变换矩阵(*移、缩放、旋转、投影[透视&推导/正射]、LookAt)
变换矩阵的重要特性之一就是它们都是 4× 4 矩阵。这是因为我们决定使用齐次坐标系。否则,各变换矩阵可能会有不同的维度并且无法相乘。正如我们所见,确保变换矩阵大小相同并不只是为了方便,同时让它们可以任意组合,进行预先计算变换矩阵以提升性能。

➊*移矩阵
*移矩阵用于将物体从一个位置移至另一位置。

下面展示了*移矩阵和它与齐次坐标点相乘的效果——*移矩阵变换:(若非齐次坐标,无法进行)
( X + T x Y + T y Z + T z 1 ) = [ 1 0 0 T x 0 1 0 T y 0 0 1 T z 0 0 0 1 ] × ( X Y Z 1 ) \left(
X+TxY+TyZ+Tz1
X+TxY+TyZ+Tz1
\right) = \left[
100001000010TxTyTz1
100Tx010Ty001Tz0001
\right] \times \left(
XYZ1
XYZ1
\right)






X+T
x


Y+T
y


Z+T
z


1







=






1
0
0
0


0
1
0
0


0
0
1
0


T
x


T
y


T
z


1







×






X
Y
Z
1







()是代表向量vector,[]是代表矩阵matrix

*移矩阵变换的结果:
点(X, Y, Z)*移到了位置(X+Tx, Y+Ty, Z+Tz)

例如,将一组点向上沿Y轴正方向移动5个单位,可以通过给一个单位矩阵的Ty位置放入5来构建*移矩阵。之后只需将想要移动的点与矩阵相乘就可以了。

GLM中有一些函数是用于构建与点相乘的*移矩阵的。相关操作有:

mat4 glm::translate(mat4 const& m, vec3 const& v) 通过3元素向量,构建4×4*移变换矩阵;
mat4 × vec4 // 4×4矩阵 × 4元素向量 => 新4元素向量。
一般是用单位矩阵来使3元素*移向量生成*移变换矩阵:
glm::mat4 m = glm::translate(glm::mat4(1.0f), glm::vec3(x,y,z));
结果如下:
1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1 (刚好满足*移变换矩阵的格式)
注:对于vec3 类型,要用它可以与乘以的 4× 4 矩阵兼容,需要将其转换为 vec4 类型,这个转换是用 vec4(vec3,1.0)完成的。

➋缩放矩阵
缩放矩阵用于改变物体的大小或者将点向原点相反方向移动。虽然缩放点这个操作乍一看有点奇怪,不过 OpenGL 中的物体都是用一组或多组的点定义的。因此,缩放物体涉及缩放它的点的集合。

下面展示了缩放矩阵的形式和当它与齐次坐标点相乘的效果——经过缩放值修改后的新点。
( X ∗ S x Y ∗ S y Z ∗ S z 1 ) = [ S x 0 0 0 0 S y 0 0 0 0 S z 0 0 0 0 1 ] × ( X Y Z 1 ) \left(
X∗SxY∗SyZ∗Sz1
X∗SxY∗SyZ∗Sz1
\right) = \left[
Sx0000Sy0000Sz00001
Sx0000Sy0000Sz00001
\right] \times \left(
XYZ1
XYZ1
\right)






X∗S
x


Y∗S
y


Z∗S
z


1







=






S
x


0
0
0


0
S
y


0
0


0
0
S
z


0


0
0
0
1







×






X
Y
Z
1







GLM中有一些函数是用于构建与点相乘的缩放矩阵的。相关操作有:

glm::scale(x, y, z) 构建缩放(x, y, z)的矩阵;
mat4 × vec4
缩放还可以用来 切换坐标系:
例如,可以用缩放来在给定右手坐标系的情况下确定左手坐标。

从上图可以看到通过反转Z坐标就可以从右手坐标切换为左手坐标。因此,用来切换坐标系的缩放矩阵变换是:
[ 1 0 0 0 0 1 0 0 0 0 − 1 0 0 0 0 1 ] \left[
1000010000−100001
1000010000−100001
\right]






1
0
0
0


0
1
0
0


0
0
−1
0


0
0
0
1







➌旋转矩阵
旋转会稍微复杂一些,因为在 3D 空间中旋转物体需要指定旋转轴和旋转的角度或弧度。

数学家莱昂哈德· 欧拉表明,围绕任何轴的旋转都可以表示为绕 X、Y、Z 轴旋转的组合。围绕这 3 个轴的旋转角度被称为欧拉角。 这个被称为欧拉定理的发现,对我们很有用,因为对于每个坐标轴的旋转可以用矩阵变换来表示。

下面展示了3种旋转变换,即绕X、Y和Z轴旋转。

ⓐ绕X轴旋转θ度:
( X ′ Y ′ Z ′ 1 ) = [ 1 0 0 0 0 c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 ] × ( X Y Z 1 ) \left(
X′Y′Z′1
X′Y′Z′1
\right) = \left[
10000cosθsinθ00−sinθcosθ00001
10000cosθ−sinθ00sinθcosθ00001
\right] \times \left(
XYZ1
XYZ1
\right)






X


Y


Z


1







=






1
0
0
0


0
cosθ
sinθ
0


0
−sinθ
cosθ
0


0
0
0
1







×






X
Y
Z
1







ⓑ绕Y轴旋转θ度:
( X ′ Y ′ Z ′ 1 ) = [ c o s θ 0 s i n θ 0 0 1 0 0 − s i n θ 0 c o s θ 0 0 0 0 1 ] × ( X Y Z 1 ) \left(
X′Y′Z′1
X′Y′Z′1
\right) = \left[
cosθ0−sinθ00100sinθ0cosθ00001
cosθ0sinθ00100−sinθ0cosθ00001
\right] \times \left(
XYZ1
XYZ1
\right)






X


Y


Z


1







=






cosθ
0
−sinθ
0


0
1
0
0


sinθ
0
cosθ
0


0
0
0
1







×






X
Y
Z
1







ⓒ绕Z轴旋转θ度:
( X ′ Y ′ Z ′ 1 ) = [ c o s θ − s i n θ 0 0 s i n θ c o s θ 0 0 0 0 1 0 0 0 0 1 ] × ( X Y Z 1 ) \left(
X′Y′Z′1
X′Y′Z′1
\right) = \left[
cosθsinθ00−sinθcosθ0000100001
cosθ−sinθ00sinθcosθ0000100001
\right] \times \left(
XYZ1
XYZ1
\right)






X


Y


Z


1







=






cosθ
sinθ
0
0


−sinθ
cosθ
0
0


0
0
1
0


0
0
0
1







×






X
Y
Z
1







沿任意轴旋转:
对于任意轴旋转,一个简单的办法是拆分成xyz三轴分解旋转,即先沿x轴旋转,再沿y轴旋转,最后沿z轴旋转。但是,将一个旋转分解成三次,很容易出现万向节死锁。万向节死锁简单说就是当子旋转转到特定角度后转动,一旦转动父旋转,就会使子旋转丢失旋转角度,从而使某些角度永远也不能旋转到。关于万向节死锁的动画演示可以看这个视频欧拉旋转。
一个比较好的方法是有一个旋转矩阵可以对任意轴进行旋转,而不是将一个旋转分解成x y z三轴。这样的矩阵是存在的,但却非常麻烦。具体公式如下,其中(Rx,Ry,Rz)就是这个任意轴向量。

需要注意的是,这个旋转矩阵虽然能够极大的避免万向节死锁的出现,但并不能完全避免。想要彻底避免万向节死锁问题,真正的解决方案是使用四元数(Quaternion),它不仅更安全,而且计算会更有效率。

旋转矩阵的数学推导
完整版,见:三维旋转矩阵的推导过程

二维*面旋转:

三维空间旋转——绕z轴旋转:

在实践中,当3D空间中旋转轴不穿过原点时,物体使用欧拉角进行旋转需要几个额外的步骤。一般有:
(1)*移旋转轴以使它经过原点;
(2)绕X、Y、Z轴旋转适当的欧拉角;
(3)复原步骤(1)中的*移。

3个旋转变换都有自己有趣的特性,即:[ 反向旋转的矩阵 ] 恰等于 [ 其转置矩阵 ]。这个特性在后面会用到。
(通过观察上述矩阵公式,因为总有cos(-θ)=cos(θ)和sin(-θ)=-sinθ,上述特性得以验证。)

欧拉角在某些 3D 图形应用中会导致一些瑕疵。因此,通常在计算旋转时推荐使用四元数。欧拉角足以满足我们的大部分需求。

➍投影矩阵(透视投影矩阵、正射投影矩阵)

这幅画明显强烈地使用了透视。这种画法让人产生了深度感知和画中有 3D 空间的错觉。这个过程中,在现实中*行的线在画中并不*行。同样,在前景中的人物比在背景中的人物要大。

Ⓐ透视矩阵

我们通过使用变换矩阵将*行线变为恰当的不*行线来实现这个效果。这个矩阵叫作透视矩阵。

通过定义4个参数来构建视体(view volume):
(a) 纵横比;(b) 视场;(c) 投影*面或*剪裁*面;(d)远剪裁*面。

只有在远*剪裁*面间的物体才会被渲染。

*剪裁*面(near plane)同时也是物体所投影到的*面,通常放在离眼睛或相机较*的位置。
会在第 4 章中讨论如何为远剪裁*面(far plane)选择合适的值。
纵横比(aspect ratio):远*剪裁*面的宽高比。
视场(field of view):可视空间的纵向角度。

通过这些元素所形成的形状叫作视锥(frustum)。


glm::perspective(<fieldOfView>, <aspectRatio>, <nearPlane>, <farPlane>);
<fieldOfView>=FOV角,<aspectRatio>=W/H,
<nearPlane>=Znear,<farPlane>=Zfar

构建透视矩阵:
[ A 0 0 0 0 q 0 0 0 0 B C 0 0 − 1 0 ] \left[
A0000q0000B−100C0
A0000q0000BC00−10
\right]






A
0
0
0


0
q
0
0


0
0
B
−1


0
0
C
0







其中,
q = 1 tan ⁡ ( f i e l d O f V i e w 2 ) A = q a s p e c t R a t i o ▫ B = Z n e a r + Z f a r Z n e a r − Z f a r C = 2 ∗ ( Z n e a r ∗ Z f a r ) Z n e a r − Z f a r q = \frac { 1 } { \operatorname { tan } ( \frac { fieldOfView } { 2 } ) }\qquad A = \frac { q } { aspectRatio }\qquad \\ \\▫ \\ B = \frac {Z_{near} + Z_{far}} {Z_{near} - Z_{far}}\qquad C = \frac {2*(Z_{near}*Z_{far})} {Z_{near} - Z_{far}}
q=
tan(
2
fieldOfView

)
1

A=
aspectRatio
q



B=
Z
near

−Z
far


Z
near

+Z
far



C=
Z
near

−Z
far


2∗(Z
near

∗Z
far

)


理解并推导透视投影矩阵:

计算机显示器是二维表面。由OpenGL渲染的3D场景必须作为2D图像投影到计算机屏幕上。投影矩阵用于此投影变换。首先,它将所有顶点数据从眼睛坐标转换为剪裁坐标。然后,通过与剪裁坐标的w分量相除,这些剪裁坐标也被转换为归一化设备坐标(NDC)。

剪裁坐标:眼睛坐标现在与投影矩阵相乘,成为剪裁坐标。该投影矩阵定义了视锥体——顶点数据投影到屏幕上的方式(透视或正交)。之所以称为剪裁坐标,是因为变换后的顶点(x,y,z)是通过与±wclip进行比较来剪裁的。

视锥体剔除(裁剪)操作是在剪裁坐标中执行的,正好在除以wclip之前。通过与wclip的比较,测试了剪裁坐标xclip、yclip和zclip。如果任何剪裁坐标小于-wclip或大于wclip,则顶点将被丢弃(剪裁掉)。

因此,我们必须记住,裁剪(视锥体剔除)和NDC变换都集成到投影矩阵中。以下介绍如何从6个参数构建投影矩阵:left、right、bottom、top、near和far边界值。

请注意,视锥体剔除(剪裁)是在剪裁坐标中执行的,正好在除以wc之前。通过与wc的比较,测试了剪裁坐标xc、yc和zc。如果任何剪裁坐标小于-wc或大于wc,则顶点将被丢弃。
然后,OpenGL将重建发生剪裁的多边形的边(如下图中的两条红线)。
下图中的灰色区域即是保留而未被丢弃的点,满足:xc,yc,zc∈(-wc,wc)

下图展示了透视视锥体和归一化设备坐标(NDC):

在透视投影中,视锥体的锥台(眼睛坐标)中的3D点被映射到立方体(NDC);x坐标从[l,r]到[-1,1],y坐标从[b,t]到[-1,1],z坐标从[-n,-f]到[-1,1]。

void glFrustum(
// 指定左右垂直剪裁*面的坐标。
GLdouble left, GLdouble right,
// 指定底部和顶部水*剪裁*面的坐标。
GLdouble bottom, GLdouble top,
// 指定到*深度剪裁*面和远深度剪裁*面的距离。两个距离都必须为正。
GLdouble nearVal, GLdouble farVal
);

请注意,眼睛坐标是在右手坐标系中定义的,但NDC使用左手坐标系。也就是说,原点处的相机在眼睛空间中沿-Z轴观看,但在NDC中沿+Z轴观看。由于glFrustum()只接受near和far的正值,因此我们需要在构造投影矩阵时对它们求反。

在OpenGL中,眼睛空间中的一个3D点被投影到**面(投影*面)上。下图显示了眼睛空间中的一个点(xe,ye,ze)如何投影到**面上的(xp,yp,zp)。

从视锥体的[俯视图],即眼睛空间的x坐标,xe被映射到xp,xp是通过使用相似三角形的比率来计算的;

从视锥体的侧面来看,yp也以类似的方式计算;

注意xp和yp都依赖于ze;它们与-ze成反比。换句话说,它们都被-ze除。这是构造投影矩阵的第一条线索。通过乘以投影矩阵变换眼睛坐标后,剪裁坐标仍然是齐次坐标。它最终成为标准化设备坐标(NDC),除以剪裁坐标的w分量。(更多细节,见OpenGL_Transformation)

因此,我们可以将剪裁坐标的w分量设置为-ze。投影矩阵的第4行变成(0,0,-1,0)。

接下来,我们用线性关系将xp和yp映射到NDC的xn和yn;[l,r]⇒ [-1,1]和[b,t]⇒ [-1,1]。

然后,我们将xp和yp代入上述方程。

注意,对于透视除法(xc/wc,yc/wc),我们使每个方程的两项都可以被-ze整除。我们在前面将wc设置为-ze,括号内的术语变成了剪裁坐标的xc和yc。

从这些方程中,我们可以找到投影矩阵的第1行和第2行。

现在,我们只需要解第3行投影矩阵。发现zn与其他的有点不同,因为眼睛空间中的ze总是投射到**面上的-n。但是我们需要唯一的z值来进行裁剪和深度测试。此外,我们应该能够取消投影(逆变换)。因为我们知道z不依赖于x或y值,所以我们借用w分量来寻找zn和ze之间的关系。因此,我们可以这样指定投影矩阵的第3行。

在眼睛空间中,we等于1。因此,方程变为:

为了求系数A和B,我们使用(ze,zn)关系:(-n,-1)和(-f,1),并将它们放入上述方程中,解出A、B。

此时,ze与zn的关系变为:

最后,我们找到了投影矩阵的所有条目。完整的投影矩阵是:

该投影矩阵适用于一般视锥体。如果视体是对称的,即r=-l, t=-b,则可以简化为:

在我们继续之前,请再次看看ze和zn之间的关系,等式(3)。你注意到这是一个有理函数,ze和zn之间是非线性关系(如下图)。这意味着在**面的精度非常高,但在远*面的精度非常低。如果射程[-n,-f]越来越大,则会导致深度精度问题(z-fighting);远*面附*ze的微小变化不影响zn值。n和f之间的距离应尽可能短,以最小化深度缓冲精度问题。

使用FOV指定的透视投影:


h = 2 × near × tan(θ/2)
w = h × aspect;
对应上述透视投影矩阵中:
r = w/2;t = h/2
所以:
n/r = (2×n)/w
= (2×n)/(h × aspect)
= (2×n)/(2 × n × tan(θ/2) × aspect)
= cot(θ/2)/aspect
同理求得,n/t = cot(θ/2)
则得到透视投影矩阵为:


Ⓑ正射投影矩阵
在正射投影中,*行线仍然是*行的,即不使用透视。正射与透视相反,在视体中的物体不因其距相机距离做任何调整,而直接进行投影。

正射投影是一种*行投影,其中所有的投影都与投影*面垂直。
正射矩阵通过如下参数构建:
(a) 从相机到投影*面的距离 Znear;
(b) 从相机到远剪裁*面的距离 Zfar;
(c) L、R、T、B 的值,其中 L 和 R 分别是投影*面左右边界的 X 坐标, T 和 B 分别是投影*面上下边界的 Y 坐标。


void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal)

构建正射投影矩阵:
[ 2 R − L 0 0 − R + L R − L 0 2 T − B 0 − T + B T − B 0 0 1 Z f a r − Z n e a r − Z n e a r Z f a r − Z n e a r 0 0 0 1 ] \left[
2R−L00002T−B00001Zfar−Znear0−R+LR−L−T+BT−B−ZnearZfar−Znear1
2R−L00−R+LR−L02T−B0−T+BT−B001Zfar−Znear−ZnearZfar−Znear0001
\right]












R−L
2


0
0
0


0
T−B
2


0
0


0
0
Z
far

−Z
near


1


0



R−L
R+L



T−B
T+B



Z
far

−Z
near


Z
near




1














正交投影矩阵与透视图相同或更多,只要它们通过将3D图像投影到2D观察*面上即可工作。它们和透视图之间的唯一区别是*行线保持*行,换句话说,(x,y,near)和(x,y,far)在正投影中理想地投影到屏幕上的相同位置。

*行投影与我们眼睛所见到的真实世界不同。但是它们在很多情况下都有其用处,比如投射阴影、进行 3D 剪裁以及 CAD(计算机辅助设计)中——用在 CAD 中是因为无论物体如何摆放,其尺寸都不变。无论如何,绝大多数例子使用透视投影。

正射投影矩阵推导过程,见:OpenGL Projection Matrix——Orthographic Projection

➎LookAt矩阵
当你想要把相机放在某处并看向一个特定的位置时,就需要用到它了。

当然,用我们已经学到的方法也可以做到,但是这个操作非常频繁,因此为它专门构建一个矩阵通常比较有用。

LookAt变换依然由相机旋转决定。通常,可以通过一系列叉积获得相机旋转的正面(fwd)、侧面(side)、上面(up)。

通过:(a)相机位置(眼睛);(b)目标位置;(c)初始向上向量up,构建LookAt矩阵。
f w d ⃗ = n o r m a l i z e ( e y e ⃗ − t a r g e t ⃗ ) s i d e ⃗ = n o r m a l i z e ( − f w d ⃗ × u p ⃗ ) u p ⃗ = n o r m a l i z e ( s i d e ⃗ × ( − f w d ) ⃗ ) — — — — — — — — — — — — — — — — — — — — [ s i d e x ⃗ s i d e y ⃗ s i d e z ⃗ − ( s i d e ⋅ e y e ) ⃗ ⃗ u p x ⃗ u p y ⃗ u p z ⃗ − ( u p ⋅ e y e ) ⃗ ⃗ − f w d x ⃗ − f w d y ⃗ − f w d z ⃗ − ( − f w d ⋅ e y e ) ⃗ ⃗ 0 0 0 1 ] \vec{fwd} = normalize(\vec{eye} - \vec{target})\\ \vec{side} = normalize(\vec{-fwd} \times \vec{up})\\ \vec{up} = normalize(\vec{side} \times \vec{(-fwd)})\\ \\ ———————————————————— \\ \left[
sidex→upx→−fwdx→0sidey→upy→−fwdy→0sidez→upz→−fwdz→0−(side⋅eye)→→−(up⋅eye)→→−(−fwd⋅eye)→→1
sidex→sidey→sidez→−(side⋅eye)→→upx→upy→upz→−(up⋅eye)→→−fwdx→−fwdy→−fwdz→−(−fwd⋅eye)→→0001
\right]
fwd

=normalize(
eye


target

)
side
=normalize(
−fwd

×
up

)
up

=normalize(
side
×
(−fwd)

)
————————————————————








side
x




up
x




−fwd
x




0


side
y




up
y




−fwd
y




0


side
z




up
z




−fwd
z




0


−(side⋅
eye)




−(up⋅
eye)




−(−fwd⋅
eye)




1









在GLM中已经有一个用来构建LookAt矩阵的函数glm::lookAt(eye, center, up)。


template<typename T, qualifier Q>
GLM_FUNC_DECL mat<4,4,T,Q> lookAt(
const vec<3,T,Q>& eye, // eye position in worldspace
const vec<3,T,Q>& center, // the point where we look at
const vec<3,T,Q>& up // the vector of upwords(your head is up)
)
——————————————————————————————————————
作用:基于默认的左右手法则构建一个LookAt矩阵。
参数:
eye 相机位置
center 相机注视的位置(即目标位置)
up 归一化的向上失量,指明相机的方位
模板参数:
T 一个浮点标量类型
Q 一个限定符,值是枚举常量值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
理解参数——up向量:
把相机想象成自己的脑袋,那么:
eye - 脑袋的位置;
center - 脑袋看的方向;
up - 脑袋看物体的姿态(因为可以歪着头看同一个物体)。

该函数定义一个视图矩阵,其中:
eye - 相机在世界坐标的位置;
center - 相机镜头对准的物体在世界坐标的位置;
up - 指明世界坐标中相机向上的方位(并不是说此时相机的向上方位!)。

此向量基本上是一个定义世界“向上”方向的向量。
它不得与从视点到参考点的视线*行。
在几乎所有正常情况下,这将是向量(0, 1, 0),即朝向正Y。

在第8章生成阴影时会用到这个函数。

向量及操作
向量表示大小和方向。它们没有特定位置。
在 3D 图形学中,向量一般用空间中的单个点表示,向量的大小是原点到该点的距离,方向则是原点到该点的方向。
在GLSL和GLM中,它们所提供的vec3 / vec4类型既能用来存储点,又能用来存储向量。


向量操作:
假设有向量 A(u, v, w) 和 B(x, y, z):
❶归一化:
A ⃗ = A ⃗ ∣ A ∣ = A ⃗ u 2 + v 2 + w 2 \vec{A} = \frac{\vec{A}}{|A|} = \frac{\vec{A}}{\sqrt{u^2+v^2+w^2}}
A
=
∣A∣
A


=
u
2
+v
2
+w
2



A


glm/GLSL:normalize(vec3 or vec4)

❷加减法:
A ⃗ ± B ⃗ = ( u ± x , v ± y , w ± z ) \vec{A}\pm\vec{B} = (u\pm{x}, v\pm{y}, w\pm{z})
A
±
B
=(u±x,v±y,w±z)

向量A-B的几何表示如下图:
C ⃗ = A ⃗ − B ⃗ = ( O P ⃗ − O Q ⃗ ) = Q P ⃗ \vec{C} = \vec{A} - \vec{B} = (\vec{OP}-\vec{OQ}) = \vec{QP}
C
=
A

B
=(
OP

OQ

)=
QP


glm/GLSL:vec3 ± vec3
❸相乘:
A ⃗ ∗ B ⃗ = ( u x , v y , w z ) \vec{A}*\vec{B} = (u{x}, v{y}, w{z})
A

B
=(ux,vy,wz)

glm/GLSL:vec3 * vec3
❹点积:
A ⃗ ⋅ B ⃗ = u x + v y + w z \vec{A}\cdot{\vec{B}} = ux + vy + wz
A

B
=ux+vy+wz

glm/GLSL:dot(vec3, vec3) or dot(vec4, vec4)

➎叉积:
A ⃗ × B ⃗ = ( v z − w y , w x − u z , u y − v x ) \vec{A}\times{\vec{B}} = (vz-wy, wx-uz, uy-vx)
A
×
B
=(vz−wy,wx−uz,uy−vx)

glm/GLSL:cross(vec3, vec3)

其他有用的向量函数如magnitude(在GLSL/GLM中是length())、reflection和refraction(在GLSL/GLM中都有)。

点积和叉积的应用
㊀点积:
点积最重要也最基本的应用是求解两向量夹角。

V ⃗ ⋅ W ⃗ = ∣ V ∣ ∣ W ∣ c o s θ \vec{V}\cdot{\vec{W}} = |V||W|cosθ
V

W
=∣V∣∣W∣cosθ

如果V和W是归一化向量,则有:(“^”是标记归一化)
c o s θ = V ^ ⋅ W ^ θ = a r c c o s ( V ^ ⋅ W ^ ) cosθ = \hat{V}\cdot{\hat{W}}\\ θ = arccos(\hat{V}\cdot{\hat{W}})
cosθ=
V
^

W
^

θ=arccos(
V
^

W
^
)

有趣的是,我们后面会看到通常用到的是 cos(θ),而非 θ。因此,这两个推导出的公式都很有用。

点积同时还有许多其他用途:
点乘,也叫向量的内积、数量积。
点乘的几何意义:一条边向另一条边的投影乘以另一条边的长度。

一般*面的表示法为:a(x-x0) + b(y-y0) + c(z-z0) = 0

对于*面的一般方程 ax + by + cz + d = 0,可表示为*面S(a, b, c, d)。
*面S(a, b, c, d)的法向量公式:
n ⃗ = ( a , b , c ) \vec{n} = (a, b, c)
n
=(a,b,c)

点P(xp, yp, zp)到*面S(a, b, c, d)的距离公式:
D p = ∣ a x p + b y p + c z p + d ∣ a 2 + b 2 + c 2 D_p = \frac{|ax_p+by_p+cz_p+d|}{\sqrt{a^2+b^2+c^2}}
D
p

=
a
2
+b
2
+c
2



∣ax
p

+by
p

+cz
p

+d∣

从而推出,*面S单位法向量、原点到*面S的有符号距离分别为:
n ^ = ( a a 2 + b 2 + c 2 , b a 2 + b 2 + c 2 , c a 2 + b 2 + c 2 ) D 0 = d a 2 + b 2 + c 2 \hat{n} = (\frac{a}{\sqrt{a^2+b^2+c^2}}, \frac{b}{\sqrt{a^2+b^2+c^2}}, \frac{c}{\sqrt{a^2+b^2+c^2}})\\ D_0 = \frac{d}{\sqrt{a^2+b^2+c^2}}
n
^
=(
a
2
+b
2
+c
2



a

,
a
2
+b
2
+c
2



b

,
a
2
+b
2
+c
2



c

)
D
0

=
a
2
+b
2
+c
2



d

推出,点P到*面S的有符号距离,可以用点积(方便程序函数运算)表示为:
D p s = n ^ ⋅ P ⃗ + D 0 D_{ps} = \hat{n}\cdot{\vec{P}} + D_0
D
ps

=
n
^

P
+D
0

我们可以得知*面方程的如下特性:
对于*面一般方程:ax + by + cz + d = 0,有:➊(a,b,c)是*面的法向量; ➋|d|是原点到*面的距离。

㊁叉积:
向量积,数学中又称外积、叉积,物理中称矢积、叉乘,是一种在向量空间中向量的二元运算。叉积方向遵循右手定则。

通过叉积来获得法向量的能力对我们后面要学习的光照部分非常重要。为了确定光照效果,我们需要知道所渲染模型的外向法向量。使用叉积计算来获得其中一面的外向法向量。


局部和世界空间——模型矩阵M
当构建物体的 3D 模型时,我们通常以最方便的定位方式描述模型。如果模型是个球形,那么我们很可能将球心定位于原点(0, 0, 0)并赋予它一个方便的半径,比如 1。模型定义的空间叫作局部空间(local space)或模型空间(model space)。 OpenGL 文档使用的术语是物体空间(object space)。

使用同样的方式, 通过设定物体在模拟世界中的朝向和大小,将物体放在模拟这个世界的空间中,这个空间叫作世界空间。将对象[定位]及[定向]在【世界空间】的矩阵称为模型矩阵或 M(model)。


证明:一个变换矩阵就能完成不同坐标系下的坐标变换

 


其中变换矩阵R由三列构成:第一列为坐标系B的x轴相对于坐标系A的描述,第二列为坐标系B的y轴相对于坐标系A的描述,第三列为坐标系B的z轴相对于坐标系A的描述。

要想计算一个在原坐标系上的点P在切换到到新坐标系后对应的坐标,只需知道新坐标系从原坐标系变换过去的变换矩阵,然后把同样的变换矩阵应用到点P,就可以得到点P在新坐标系下的坐标位置。

视觉空间和合成相机——模型-视图矩阵MV
正如我们在现实世界通过眼睛从一点观察一样,我们也必须找到一点并确立观察方向作为我们观察虚拟世界的窗口。这个点叫作“视图”或“视觉”空间,或“合成相机”。所谓“相机空间”就是以相机所在的世界空间中的位置(包括定向)为【坐标系原点】的三维坐标系空间。

如下图所示,观察 3D 世界需要:
(a) 将相机放入世界的某个位置;
(b) 调整相机的角度,通常需要一套它自己的直角坐标轴 U/V/N;
(c) 定义一个视体(view volume);
(d) 将视体内的对象投影到投影*面(projection plane)上。


OpenGL 有一个固定在原点(0,0,0) 并向下 看向 Z 轴负方向的相机,如下图:

为了应用 OpenGL 相机,我们需要做的是将相机模拟移动到适合的位置和方向。

我们需要先找出在世界中的物体与[ 我们期望的相机位置 ]的【相对位置】(如物体应该在由上图所示相机U、V、N轴定义的“相机空间”中的何处)。

给定世界空间中的点PW,我们需要通过变换将它转换成相应相机空间中的点,从而让它看起来好像是我们从期望的相机位置CW进行观看的。

W:world;C:camera
Pw:点P世界空间中的点;Pc:点P在相机空间中的点;Cw:相机在世界空间中的点

三维物体的显示过程

◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
已知OpenGL相机 [位置] 永远固定在点(0, 0, 0),[方向] 为看向Z轴负方向。

我们如何变换来计算出PW在相机空间中的位置PC?
◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
需要做的变换如下:
(1) 将PW*移,*移向量为 [负的] 期望相机位置。
(2) 将PW旋转,旋转角度为 [负的] 期望相机旋转的欧拉角。
因为相机本身是不需要缩放的,所以对相机是没有缩放操作的。

我们可以构建一个单一变换矩阵以完成旋转和*移,这个矩阵叫作视图变换(viewing transform)矩阵V(view)。
通过合并矩阵 T(translation)(包含负相机期望位置的*移矩阵)和 R(rotate)(包含负相机期望旋转角的旋转矩阵)得到矩阵 V 。

在本例中,从右向左,我们先*移世界空间中的点PW,然后旋转:

PC = R ( T * Pw)

通过结合律:

PC = (R * T) Pw

合并 R * T 存入矩阵 V:

PC = V * Pw

构建视图变换矩阵:
PC=V * PW=(R * T) PW=R ( T * Pw)

 

注:在OpenGL中,矩阵运算的顺序是相反的,是从右往左运算,我们需要从右往左阅读矩阵的乘法。
如:A * B * C == A * (B * C)

通常,视图变换矩阵 V 与模型矩阵 M 合并成一个模型-视图(model-view, MV)矩阵:

MV = V * M
V:将世界空间中的点PW变换到相机空间中的点PC。
M:将模型空间中的点PM变换到世界空间中的点PW。

通过如下一个步骤就可以从[模型空间]直接转换至[相机空间]:

PC = MV * PM
逆向分析:
从右往左结合,PC = V * M *PM
过程为:PM左乘 M 变换成PW,再左乘 V 变成PC
根据结合律,可以先计算 V * M = MV矩阵。

在复杂场景中,当我们需要对每个顶点,而非只是一个点做这个变换的时候,这种方法的好处就很明显了。通过预先计算 MV,对于空间中每个点的变换只需要我们进行一次矩阵乘法计算。之后,我们将会看到,我们可以将这个过程延伸到与计算更多的合并矩阵,以大量减少每个顶点的计算量。

为什么是[负]相机位置/旋转角?
我们上述已知,对于点(X, Y, Z)应用*移矩阵变为(X+Tx, Y+Ty, Z+Tz),如下:
( X + T x Y + T y Z + T z 1 ) = [ 1 0 0 T x 0 1 0 T y 0 0 1 T z 0 0 0 1 ] × ( X Y Z 1 ) \left(
X+TxY+TyZ+Tz1
X+TxY+TyZ+Tz1
\right) = \left[
100001000010TxTyTz1
100Tx010Ty001Tz0001
\right] \times \left(
XYZ1
XYZ1
\right)






X+T
x


Y+T
y


Z+T
z


1







=






1
0
0
0


0
1
0
0


0
0
1
0


T
x


T
y


T
z


1







×






X
Y
Z
1







对于PW应用*移矩阵 T(包含负相机期望位置的*移矩阵),如下:
( P W X − C x P W Y − C y P W Z − C z 1 ) = [ 1 0 0 − C x 0 1 0 − C y 0 0 1 − C z 0 0 0 1 ] × ( P W X P W Y P W Z 1 ) \left(
PWX−CxPWY−CyPWZ−Cz1
PWX−CxPWY−CyPWZ−Cz1
\right) = \left[
100001000010−Cx−Cy−Cz1
100−Cx010−Cy001−Cz0001
\right] \times \left(
PWXPWYPWZ1
PWXPWYPWZ1
\right)






P
W
X



−C
x


P
W
Y



−C
y


P
W
Z



−C
z


1







=






1
0
0
0


0
1
0
0


0
0
1
0


−C
x


−C
y


−C
z


1







×






P
W
X




P
W
Y




P
W
Z




1







只讨论X轴方向上的*移,向X轴正方向*移距离Tx,则X变成X+Tx。
由于以相机为坐标系空间,因为PWx是要在相机空间中作变换,所以PWx的*移是相对相机而言的。
如下图,世界坐标系是黑色,相机坐标系是绿色。(在此只讨论X轴,其他轴类似)
在世界坐标系中,虽然相机位置已经通过变换定位和定向了,但要通过相机位置及方位得到负相机*移(和旋转)矩阵(即视图矩阵),点P应用*移(和旋转及缩放)变换(即模型矩阵)从原点在X轴移动距离PWx,那么如果以相机空间即绿色坐标系为当前坐标系的话,因为点PWx与点CWx的距离为 |PWx - CWx|,而点PWx在Xc轴负方向上,而如图PWx - CWx刚好是负值,所以在相机空间中,点P的横坐标值为PWx - CWx。(也可以这样理解:在世界空间中的CWx为相机空间中的0,所以其他任何点从世界空间转换到相机空间就总有一个偏移量,这个偏移量就是CWx。)其实用相对的概念就一目了然了,下图中CWx向右移,要保持相对位置不变,就等效于PWx向左移,仅此而已。这就解释了为什么*移矩阵中Tx为-Cx了(也即是应用视图变换矩阵后的结果),对于Y、Z轴性质是一样的,也就说明了,对世界空间中点P的*移量是[负]的相机位置。
如下图,相机绕Z轴旋转α角、点P绕Z轴旋转β角,△=β-α这个相对空间偏角是固定的,如果此时以相机为空间坐标系,则α=0°;由于△不变,那么此时β=β-α=β+(-α),符合[负]相机旋转角。
推而广之,对于在X/Y/Z轴上*移和绕轴旋转都是[负]的相机*移量/旋转角。

 

用纯GLSL函数构建变换矩阵
GLSL 只包含了基础的矩阵运算,如加法、合并等。因此,有时我们需要自己为GLSL 写一些实用函数来构建 3D 变换矩阵,以在着色器中进行特定 3D 运算。用于存储这些矩阵的 GLSL 数据类型是 mat4。

GLSL 中用于初始化 mat4 矩阵的语法以【列】为单位读入值。前 4 个参数会放入第一列,接下来 4 个参数放入下一列,直到第四列,如下所示:

// 构建一个*移矩阵
mat4 translationMatrix = mat4(
1.0, 0.0, 0.0, 0.0, // 这是最左列,而非第一行!
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
tx, ty, tz, 1.0);
1
2
3
4
5
6
下面的代码包含了5个用于构建4×4*移、旋转和缩放矩阵的GLSL函数:

// 构建并返回*移矩阵
mat4 buildTranslate(float x, float y, float z) {
mat4 trans = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
x, y, z, 1.0);
return trans;
}

// 构建并返回绕X轴的旋转矩阵
mat4 buildRotateX(float rad) {
mat4 xrot = mat4(1.0, 0.0, 0.0, 0.0,
0.0, cos(rad), -sin(rad), 0.0,
0.0, sin(rad), cos(rad), 0.0,
0.0, 0.0, 0.0, 1.0);
return xrot;
}
// 构建并返回绕Y轴的旋转矩阵
mat4 buildRotateY(float rad) {
mat4 yrot = mat4(cos(rad), 0.0, sin(rad), 0.0,
0.0, 1.0, 0.0, 0.0,
-sin(rad), 0.0, cos(rad), 0.0,
0.0, 0.0, 0.0, 1.0);
return yrot;
}
// 构建并返回绕Z轴的旋转矩阵
mat4 buildRotateZ(float rad) {
mat4 zrot = mat4(cos(rad), -sin(rad), 0.0, 0.0,
sin(rad), cos(rad), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
return zrot;
}

// 构建并返回缩放矩阵
mat4 buildScale(float x, float y, float z) {
mat4 scale = mat4(x, 0.0, 0.0, 0.0,
0.0, y, 0.0, 0.0,
0.0, 0.0, z, 0.0,
0.0, 0.0, 0.0, 1.0);
return scale;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[组合矩阵变换] & NDC & 归一化处理与透视除法 & 齐次坐标下的裁剪 & 视口变换
组合矩阵变换:
一个顶点坐标将会根据以下过程被变换到裁剪坐标:

OpenGL矩阵运算的顺序是相反的(需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position。

NDC:
OpenGL将会自动进行透视除法和裁剪,目的是:将它们变换到标准化设备坐标NDC(Normalized device coordinates)。
OpenGL工作在一个叫做NDC(Normalized Device Coordinates)的坐标系统下,在这个坐标系统中,x、y和z的值全部都坐落在[-1, +1]范围内,超出这个范围的点会被OpenGL忽略。如果我们直接使用OpenGL的NDC坐标系统,那么定义的所有顶点坐标值都应用在-1到+1的范围区间。

归一化处理与透视除法:
给出点的齐次表达式[X Y H],就可求得其二维笛卡尔坐标,即
[X Y H]→[X/H Y/H H/H] = [x y 1], 这个过程称为归一化处理。

一旦所有顶点被变换到裁剪空间,最终的操作——透视除法(Perspective Division)将会执行,在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;透视除法是将4D裁剪空间坐标变换为3D标准化设备坐标的过程。这一步会在每一个顶点着色器运行的最后被[自动]执行。
vec3 ndc = gl_Position.xyz / gl_Position.w;
在光栅化阶段会做透视除法(或透视分割,即除以第四个分量)。

在3D计算机图形学中,透视是通过投影矩阵变换,改变每一个向量中w分量的值来实现透视的。
透视除法只是将齐次坐标中的w分量转换为1的专用名词。
注意,对于attribute类型的属性量。OpenGL会用默认的值替换属性中未指定的分量,前三个分量会被设定为0,最后一个分量w会被设定为1。

对剪裁坐标的点的x、y、z坐标除以它的w分量,除以w的坐标叫做归一化设备坐标NDC。如果w分量大,除以w后的点就接*(0,0,0),在三维空间中,距离我们较远的坐标如果它的w分量较大,进行透视除法后,就距离原点越*,原点作为远处物体的消失点,就有三维场景的效果。

齐次坐标下的裁剪:
在进行坐标和向量计算中,为了不至于混淆点和向量,另外,在进行几何变换时,为了加快运算速度,简化计算,往往使用矩阵,而在使用矩阵运算时,矩阵的乘积只能表示旋转、比例和剪切等等变换,而不能表示*移变换。因此为统一计算(使用齐次坐标在数学中的意义还要广),引入了第四个分量w,这使得原本二维坐标变成三维坐标,同理三维坐标变为四维坐标,而w称为比例因子,当w不为0时(一般设1),表示一个坐标,一个三维坐标的三个分量x,y,z用齐次坐标表示为变为x,y,z,w的四维空间,变换成三维坐标的方式是x/w,y/w,z/w,当w为0时,在数学上代表无穷远点,即并非一个具体的坐标位置,而是一个具有大小和方向的向量。从而,通过w我们就可以用同一系统表示两种不同的量(点和向量)。

在OpenGL中,作为坐标点时,w参数为1,否则为0(比如向量);如此一来,所有的几何变换和向量运算都可以用相同的矩阵乘积进行运算和变换,当一个向量和一个矩阵相乘时所得的结果也是向量。

齐次坐标指由(x,y,z,w)构成的四维坐标,其一般由世界坐标系下的顶点坐标经过model,view,perspective变换得到,在此基础之上再进行一步透视除法 /w,那么原视锥体所对应的空间就会变为 [-1,1]×[-1,1]×[-1,1] 的标准立方体。

显然,此时的6个*面分别为y=1,y=-1,x=1,x=-1,z=1,z=-1,可以得到被标准立方体裁剪过后的内侧的顶点集合,完成裁剪过程。

假设一个点P(x,y,z,w)是属于裁剪空间内部的,那么经过透视除法之后,应该满足如下几个不等式:
-1 <= x/w <=1
-1 <= y/w <=1
-1 <= z/w <=1
站在gl_Position的角度来说,[-w,w]之间的坐标点才是可见的,否则都是不可见会被剪裁掉。在做投影变换的时候我们说,在视景体内的物体有效,视景体外的会被剪裁,实际上是对应的,剪裁就是发生在图元装配阶段判断所有的坐标是否在[-w,w]之间。

视口变换:
OpenGL会使用glViewPort内部的参数来将标准化设备坐标NDC映射到【屏幕坐标】,每个坐标都关联了一个屏幕上的点(比如800x600的屏幕),这个过程称为视口变换。

void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
x, y:指定矩形视口的左下角坐标,单位是像素,初始值是(0,0)。
width, height:指定视口的宽、高。当一个GL上下文首次附加到window时,width和height设置为window的尺寸。

描述:
glViewport指定x和y从标准化设备坐标到【窗口坐标】的仿射变换。设xnd, ynd为标准化设备坐标。然后,窗口坐标xw, yw的计算如下:(其实也很好理解这个转换公式)
x w = ( x n d + 1 ) × ⁢ w i d t h 2 + x y w = ( y n d + 1 ) × ⁢ h e i g h t 2 + y x_{w} = (x_{nd}+1)×⁢\frac{width}{2} + x\\ y_{w} = (y_{nd}+1)×⁢\frac{height}{2} + y
x
w

=(x
nd

+1)×⁢
2
width

+x
y
w

=(y
nd

+1)×⁢
2
height

+y

由于xnd、ynd∊[-1, 1],所以xnd+1、ynd+1∊[0, 2],x,y取初始值(0,0),则xw∊[0, width]、yw∊[0, height]。这样就完成了(xnd,ynd)到(xw,yw)的“仿射”。

视口的宽度和高度会自动钳制到一个范围,该范围取决于实现。

错误:GL_INVALID_VALUE:如果宽或高是负数。

关联的Get方法:
glGet with argument GL_VIEWPORT
glGet with argument GL_MAX_VIEWPORT_DIMS

补充说明
我们看到了使用矩阵对点进行变换的例子。之后,我们会将同样的变换应用于向量。要对向量 Q 使用变换矩阵 M 来进行与点相同的变换,一般需要计算 M 的逆转置矩阵,记为(M-1)T,并用所得矩阵乘以 Q。在某些情况下, M=(M-1)T,在这些情况下只要用 M 就可以了。例如,本章中我们所见到的基础旋转矩阵与它们的逆转置矩阵相等,我们可以直接将他们应用于向量(同样也可以应用于点)。因此有时候使用(M-1)T对向量进行变换,有时候仅使用 M。

为什么对点应用变换矩阵与对向量应用变换矩阵的计算方式会不一样?
点是三维空间中的某个坐标,是绝对的,它的值是参照原点的;而向量用于表示具有方向和大小的量。虽然他们都具有三个分量,但对于向量,任何位置的*移操作,都不会改变其性质,它的值是相对于基准点的。
我们是使用齐次坐标矩阵进行变换的,1.当第四个分量w不为0时(一般设1),表示一个坐标,一个三维坐标的三个分量x,y,z用齐次坐标表示为变为x,y,z,w的四维空间;2.当w为0时,在数学上代表无穷远点,即并非一个具体的坐标位置,而是一个具有大小和方向的向量。从而,通过w我们就可以用同一系统表示两种不同的量。当一个向量和一个矩阵相乘时所得的结果也是向量。
由于齐次坐标表示坐标点与向量的原理上有以上数学意义上的区别,所以在数学意义上对它们应用矩阵变换也肯定要在计算上区别对待的。

本章中仍未讨论的一个技术是在空间中*滑地移动相机。这是一种很有用的技术,在制作游戏和 CGI 电影时更加明显,同时也适用于可视化、虚拟现实和 3D 建模。
————————————————
原文链接:https://blog.csdn.net/itzyjr/article/details/118284963

 

posted @   imxiangzi  阅读(100)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示