您的位置:首页 > 运维架构

OpenGL取景变换(视图变换)矩阵推导

2017-02-13 15:11 399 查看

OpenGL取景变换(视图变换)矩阵推导

标签(空格分隔): OpenGL VR 游戏开发

前言

关于取景变换(视图变换)矩阵的推导本人查过许多资料, 包过关于openGL的和数学方面, 数学方面的资料很严谨, 推导过程环环相扣, 但是数书知识毕竟是理论, 怎么将理论变成实现的代码, 数学知识是不涉及的. 代码原理方面的知识只能查找openGL或图形学相关的资料, 但是这些资料有个缺点就是涉及数学原理的地方不会写的很清楚, 很多东西都是一笔带过, 没有那种环环相扣的严谨性, 所以到最后就发现查了很多资料都发现推导过程是脱节的, 代码不是从数学知识平滑过渡过来的.

本文旨在结合数学知识一步一步的推导出取景变换的变换矩阵, 从最基本的向量相乘到最终的代码实现.

使用线性变换推导基本坐标变换公式

先从简单的情况入手, 让两个坐标系的原点相同, 基向量不同, 设世界坐标系W为 {O;i,j,k}, 观察坐标系V为 {O;u,v,n}, 其中观察坐标系的基向量分别为 u⃗ =(xu,yu,zu), v⃗ =(xv,yv,zv), n⃗ =(xn,yn,zn), 从而存在:

u=xui+yuj+zukv=xvi+yvj+zvkn=xni+ynj+znk

即:

(u,v,n)=(i,j,k)⎛⎝⎜⎜⎜xuyuzuxvyvzvxnynzn⎞⎠⎟⎟⎟

令:

A=⎛⎝⎜⎜⎜xuyvznxvyvznxnyvzn⎞⎠⎟⎟⎟

则有 V=WA, 由线性代数知识得知, A 称为从 W 到 V 的过渡矩阵. 由上式可以得出 W=VA−1, 即:

(i,j,k)=(u,v,n)A−1

称之为坐标转换公式.

设世界坐标系中W的一点X0坐标为(x0,y0,z0), 现在求这个点在观察坐标系V中的坐标,

由:

WX0=(i,j,k)⎛⎝⎜⎜⎜x0y0z0⎞⎠⎟⎟⎟=(u,v,n)A−1⎛⎝⎜⎜⎜x0y0z0⎞⎠⎟⎟⎟

得出X0在观察坐标系的坐标为A−1⎛⎝⎜⎜⎜x0y0z0⎞⎠⎟⎟⎟, 即:

⎛⎝⎜⎜⎜xuyuzuxvyvzvxnynzn⎞⎠⎟⎟⎟−1⎛⎝⎜⎜⎜x0y0z0⎞⎠⎟⎟⎟

由于A 是正交矩阵(1), 所以有 A−1=AT, 设 B=AT, 则有 W=VB, 其中

B=⎛⎝⎜⎜⎜xuxvxnyuyvynzuzvzn⎞⎠⎟⎟⎟

从而得出X0在V中的坐标为 B⎛⎝⎜⎜⎜x0y0z0⎞⎠⎟⎟⎟, 即:

X0′=⎛⎝⎜⎜⎜xuxvxnyuyvynzuzvzn⎞⎠⎟⎟⎟⎛⎝⎜⎜⎜x0y0z0⎞⎠⎟⎟⎟

引入齐次坐标推导最终矩阵

上面只涉及了线性变换部分, 由于平移不能用线性变换来表示, 所以要引入仿射变换, 首先要做的是将上述坐标转换为齐次坐标, 同时将变换矩阵改升级为4x4的齐次矩阵, X0在V中的坐标可以等价的表示为:

X0′=⎛⎝⎜⎜⎜⎜xuxvxn0yuyvyn0zuzvzn00001⎞⎠⎟⎟⎟⎟⎛⎝⎜⎜⎜⎜x0y0z01⎞⎠⎟⎟⎟⎟

而原来的变换矩阵就变成了:

T1=⎛⎝⎜⎜⎜⎜xuxvxn0yuyvyn0zuzvzn00001⎞⎠⎟⎟⎟⎟

有这么一条理论: 在发生坐标系转换时, 如果已知一个点在老坐标系的坐标, 需要求这个点在新坐标系的坐标, 只需要对这个点做 新坐标系发生过的变换 的逆过程 (2).

这个逆过程就是将观察坐标系变换回世界坐标系的过程, 这个过程相对应的矩阵就是取景变换矩阵. 对于任意观察坐标系, 都可以通过先平移再旋转, 从而变换回世界坐标系.

设世界坐标系中的一点 P=(dx,dy,dz) 为观察坐标系的原点, 先将观察坐标系的原点移至世界坐标的原点, 变换矩阵为:

T2=⎛⎝⎜⎜⎜⎜100001000010−dx−dy−dz1⎞⎠⎟⎟⎟⎟

原点重合后, 再应用上面推导过的线性变换就可以与世界坐标系完全重合了, 于是左乘之前的线性变换矩阵 T1, 就得到最终的变换矩阵 T=T1T2, 即:

T=⎛⎝⎜⎜⎜⎜xuxvxn0yuyvyn0zuzvzn00001⎞⎠⎟⎟⎟⎟⎛⎝⎜⎜⎜⎜100001000010−dx−dy−dz1⎞⎠⎟⎟⎟⎟

结果是:

T=⎛⎝⎜⎜⎜⎜xuxvxn0yuyvyn0zuzvzn0−(xudx+yudy+zudz)−(xvdx+yvdy+zvdz)−(xndx+yndy+zndz)1⎞⎠⎟⎟⎟⎟

即:

T=⎛⎝⎜⎜⎜⎜xuxvxn0yuyvyn0zuzvzn0−u⋅P−v⋅P−n⋅P1⎞⎠⎟⎟⎟⎟

OpenGL取景变换矩阵源代码解析

首先简单介绍一下观察坐标系三个基向量的计算, 在代码中一般会传入三个信息:

- 观察点: 就是相机的位置.

- 观察中心: 就是观测的中心.

- up向量: 相机向上的大致方向, 是个向量.

利用观察点和观察中心就可以算出观察平面的法向量n(与观测方向相反), 法向量与up向量叉乘就可以得出第三个向量u.

(由于传入的up向量不一定是严格的向上, 可能是斜的, 所以还要用n叉乘u得出矫正后的up向量v)



以下是iOS平台在xcode中看到的OpenGL求取景变换矩阵的代码:

GLK_INLINE GLKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ)
{
GLKVector3 ev = { eyeX, eyeY, eyeZ };   // 观察点
GLKVector3 cv = { centerX, centerY, centerZ };  // 观测中心
GLKVector3 uv = { upX, upY, upZ };  // up向量, 可以与观测方向不垂直, 但是不能平行

// 被观察平面的法向量, 与观测方向相反, n对应于OpenGL的z轴
GLKVector3 n = GLKVector3Normalize(GLKVector3Add(ev, GLKVector3Negate(cv)));
// 叉乘得到第一根轴, u对应于OpenGL的x轴
GLKVector3 u = GLKVector3Normalize(GLKVector3CrossProduct(uv, n));
// 再次叉乘得到矫正后的up向量, v对应于OpenGL的y轴
GLKVector3 v = GLKVector3CrossProduct(n, u);

// 采用了列主序储存(用一个一维数组依次储存了第一列, 第二列, ...),
GLKMatrix4 m = { u.v[0], v.v[0], n.v[0], 0.0f,
u.v[1], v.v[1], n.v[1], 0.0f,
u.v[2], v.v[2], n.v[2], 0.0f,
GLKVector3DotProduct(GLKVector3Negate(u), ev),
GLKVector3DotProduct(GLKVector3Negate(v), ev),
GLKVector3DotProduct(GLKVector3Negate(n), ev),
1.0f };

return m;
}


代码注释应该已经很清晰了, 这里再额外解释几个点:

1. GLKVector3Normalize: 功能是把向量归一化, 就是转换成单位向量.

2. GLKVector3DotProduct: 求向量的点乘.

3. GLKVector3Negate: 将向量转成负的.

4. 外部传入的up向量(upX, upY, upZ)不一定与观测方向垂直, 所以要通过叉乘算出.

结语

虽然这里只证明了取景变换的推导过程, 但是这个原理可以推广到OpenGL中所有的坐标变换中去.

在OpenGL里, 任何变换都可以用一个齐次矩阵表示, 因为所以的变换都可以分解成多个基本变换(旋转, 缩放, 平移), 每个基本变换都能用一个齐次矩阵表示, 这些矩阵相乘就得到了最终的变换矩阵.

附录:

(1) 正交矩阵: 正交矩阵的定义就是A−1=AT, 其充要条件是其内部的向量全部为单位向量, 且两两相互垂直.

(2) 逆向变换理论: 其实这条理论在高中学二维坐标系转换就学过了, 只是时间久了就忘了, 但是很容易理解. 空间中有一个坐标系I, 对I做一系列的变换T1,T2,⋯,Tn变成II, I和II中分别有一点A和B的坐标为都为(a,b), A经过一系列变换T1,T2,⋯,Tn变成I中一点C, 那么C就会与B重合, 因为B跟随II做了相同的变换. 相反的, 对B做这一系列变换的逆过程, 即做变换T−1n,⋯,T−12,T−11, 得到的II中的一点D会与A完全重合. 所以, 求I中一点A在II中的坐标就相当于求D的坐标, 而D坐标只需用B做逆向变换就可以, 又因为B坐标的数值与A的相等, 所以只要用A的坐标做逆向变换就可以了.

这样说可能很绕, 其实可以简单点理解, 想象坐标转换只是复制了一个立方体, 然后旋转平移到另一个地方, 原来立方体里的所有点都在新立方体里有一个替身, 欲求老立方体中的一个点在新立法体坐标, 只要将它的替身做相逆的变换, 他俩就重合了, 这时替身的坐标就是欲求的.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  opengl 游戏开发 VR