3D数学和图像图形的关系
对于学习OpenGL 有一个误区,就是大家认为如果不能精通那些3D图形数学知识,会让我们寸步难⾏,其实不不然。就像我们不需要懂得任何关于汽⻋车结构和内燃机方⾯面的 知识也能每天开车。但是,我们最好能对汽车有足够的了解,以便我们意识到什么时候需要更换机油、定期加油、汽车常规保养工作。
同样要成为一名可靠和有能力的OpenGL程序员,⾄少需要理解这些基础知识,才知 能作什么?以及那些工具适合我们要做的⼯工作。
初学者,经过一段时间的实践,就会渐理解矩阵和向量。并且培养出一种更为直观的能⼒,能够在实践中充分利用所学的内容。
在开发过程我们涉及到的图形变换,就会涉及到矩阵/向量的计算.例如大家在使用 CAnimation 实现仿射变换,就使用了了OpenGL渲染技术.
向量
在数学中,向量(也称为欧几里得向量、几何向量、矢量),指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小。
在 3D 笛卡尔坐标系, 基本上. 一个顶点 就是XYZ 坐标空间上的一个位置. 而在空间中给定的一个位置 恰恰是由一个单独的 XYZ 定义的. 而这这样的 XYZ 就是向量;
image.png
单位向量
单位向量是指模等于1的向量。由于是非[零向量],单位向量具有确定的方向。单位向量有无数个。
如果一个向量为X,Y,Z,则X²+Y²+Z² = 1,说明(x,y,z)为单位向量.
如果向量X²+Y²+Z² != 1 则这个向量不是单位向量,我们把它缩放到1这个过程叫做标准化,将一个向量进行标准化就是将它缩为1也叫做单位化向量
(x,y,z)经过单位化向量后就是(x/√x²+y²+z²,y/√x²+y²+z²,z/√x²+y²+z²)
OpenGL中的向量
math3D中有2个数据类型能够表示一个3维或者4维向量
3维向量
typedef float M3DVector3f[3]; //(x,y,z)x,y,z各数据类型为float
typedef double M3DVector3d[3]; //(x,y,z)x,y,z各数据类型为double
4维向量
typedef float M3DVector4f[4]; //(x,y,z,w)x,y,z各数据类型为float
typedef double M3DVector4d[4]; //(x,y,z,w)x,y,z各数据类型为double
在典型情况下,w坐标设为1.0。x,y,z值通过除以w,来进⾏行行缩放。而除以1.0则本质上不改 变x,y,z值。
gltMakeSphere(sphereBatch, 3.0, 10, 20);
M3DVector3f verctor3f = {1,0,1};
M3DVector4f verctor4f = {1,0,0,1};
M3DVector3f verctor3f3[3] = {
1,0,0,
0,1,0,
0,0,1
};
向量点乘
2个单元向量 之间进⾏点乘运算将得到一个标量(不是三维向量量,是一个标量量).它表示两个向量之间的夹角的余弦值[-1,1];
如果向量不是单位向量,我们可以通过单位化向量。
单位向量1(x1,y1,z1)点乘单位向量2(x2,y2,z2)等于x1x2+y1y2+z1*z2
如图单位向量v1点乘单位向量v2等于angle的余弦值.
math3d关于点乘的api
inline double m3dDotProduct3(const M3DVector3d u, const M3DVector3d v)
{ return u[0]*v[0] + u[1]*v[1] + u[2]*v[2]; }
结果是u和v都是单位向量,得到是u和v夹角的余弦值.
inline double m3dGetAngleBetweenVectors3(const M3DVector3d u, const M3DVector3d v)
{
double dTemp = m3dDotProduct3(u, v);
return acos(dTemp);
}
结果是U和V的夹角。
向量叉乘
两个向量叉乘就可以得到另外一个向量,新的向量会与原来2个向量定义的平面垂直。(叉乘不要求两个向量为单位向量)
新的向量与原来两个向量垂直,我们可以得到两个方向的新向量,显然得到两个向量是不对的,在这里要用右手法则判断
image.png
math3d关于叉乘的api
inline void m3dCrossProduct3(M3DVector3d result, const M3DVector3d u, const M3DVector3d v)
{
result[0] = u[1]*v[2] - v[1]*u[2];
result[1] = -u[0]*v[2] + v[0]*u[2];
result[2] = u[0]*v[1] - v[0]*u[1];
}
result为u叉乘v的结果
矩阵
在空间有一个点.使⽤ xyz 描述它的位置. 此时让其围绕任意位置旋转一定⻆角度
后. 我们需要知道这个点的新的位置. 此时需要通过矩阵进行计算;矩阵只有一行或者一列都是合理的. 只有一⾏或者一列数字可以称为向量. 也可以称为矩阵;
math3d的定义矩阵
typedef float M3DMatrix33f[9];
typedef double M3DMatrix33d[9];
两种定义方式只是精度不同,都为三维矩阵
typedef float M3DMatrix44f[16];
typedef double M3DMatrix44d[16];
精度不同的四维矩阵
image.png在其他编程标准中,许多矩阵库定义一个矩阵使用二维数组,OpenGL的约定里,更多倾向使用一维数组,这样做的原因是: OpenGL 使用的是 Column-Major(以列为主)矩阵排序的约定
单位矩阵
GLfloat m[] = {
1,0,0,0, //x轴
0,1,0,0,//y轴
0,0,1,0, //Z轴
0,0,0,1 //Translation
};
M3DMatrix44f matrix44f = {
1,0,0,0, //x轴
0,1,0,0,//y轴
0,0,1,0, //Z轴
0,0,0,1 //Translation
};
单元矩阵初始化
void m3dLoadIdentity33(M3DMatrix33f m);
void m3dLoadIdentity33(M3DMatrix33d m);
void m3dLoadIdentity44(M3DMatrix44f m);
void m3dLoadIdentity44(M3DMatrix44d m);
将⼀个 向量乘以单元矩阵 ,就相当于一个向量乘以1. 不会发生任何改变。
UnitMatrix.png
单元矩阵[线性代数]
在线性代数学的维度,为了了便于书写. 所以坐标计算. 都是从左往右顺序,进⾏算. 如下列公式
变换后顶点向量量 = V_local * M_model * M_view * M_pro
变换后顶点向量量 = 顶点 ✖ 模型矩阵 ✖ 观察矩阵 ✖ 投影矩阵;
image.png
单元矩阵[OpenGL]角度
变换顶点向量量 = M_pro * M_view * M_model * V_local
变换顶点向量量 = 投影矩阵 ✖ 视图变换矩阵 ✖ 模型矩阵 ✖ 顶点
image.png
矩阵不是不满足交换律吗?为什么单元矩阵在线性代数和OpenGL不同呢
A*B = Bᵀ*Aᵀ
A = (1,2,3,4)
B = {
1,2,0,0,
2,3,0,1
0,0,1,1,
0,0,1,1
}
A*B = {
5,8,7,9
}
Bᵀ = {
1,2,0,0
2,3,0,0,
0,0,1,1
0,1,1,1
}
Aᵀ = {
1,
2,
3,
4
},
Bᵀ*Aᵀ = {
5,
8,
7,
9
}
A 到Aᵀ就是从行矩阵转化成列矩阵的过程,这个过程叫矩阵的转置
所以在openGL中运用的是列矩阵。
顶点着色器中的运用
image.png
矩阵在OpenGL里的变化
变换 | 解释 |
---|---|
视图变换 | 指定观察者 |
模型变化 | 在场景中移动物体 |
模型视图 | 描述视图/模型变换的⼆二元性(2种看到模型转 换的⽅方式) |
投影 | 改变视景体⼤大⼩小和设置它的投影⽅方式 |
视口 | 伪变化,对窗⼝口上最终输出进⾏行行缩放 |
视图变换
视图变换是应用到场景中的第一种变换, 它⽤来确定场景中的有利位置,在默认情况下, 透视投影中位于原点(0,0,0),并沿着 z 轴负方向进行观察 (向显示器内部”看过去”).当观察者点位于原点(0,0,0) 时,就像在透视投影中一样
视图变换将观察者放在你希望的任何位置.并允许在任何方向上观察场景, 确定视图变换就像 在场景中放置观察者并让它指向某一个方向;
从大局上考虑, 在应用任何其他模型变换之前, 必须先应用视图变换. 这样做是因为, 对于 视觉坐标系⽽言, 视图变换移动了当前的工作的坐标系; 后续的变化都会基于新调整的坐标系进⾏.
模型变换
模型变换: ⽤于操纵模型与其中某特定变换. 这些变换将对象移动到需要的位置. 通过旋转,缩放,平移.
image.png
image.png
模型变换不符合交换律
image.png两种看待模型变换
image.pngmath3d中模型变换
模型平移变换
void m3dTranslationMatrix44(M3DMatrix44f m, float x, float y, float z)
m:设置平移后的矩阵
X:X轴方向上平移的距离
Y:Y轴方向上平移的距离
Z:Z轴方向上平移的距离
image.png
模型旋转变换
void m3dRotationMatrix44(M3DMatrix44d m, double angle, double x, double y, double z);
m:设置旋转后的矩阵
angle:设置旋转的角度
X:如果为1,沿X轴旋转 ,如果为0,X轴不旋转
Y:如果为1,沿Y轴旋转,如果为0,Y轴不旋转
Z:如果为1,沿Z轴旋转, 如果为0,Z轴不旋转
image.png
模型缩放变换
void m3dScaleMatrix44(M3DMatrix44f m, float xScale, float yScale, float zScale)
m:设置旋转后的矩阵
xScale:x轴的坐标缩放xScale,如果为-1,x轴翻转
yScale:y轴的坐标缩放yScale,如果为-1,y轴翻转
Z xScale:z轴的坐标缩放zScale,如果为-1,z轴翻转
image.png
模型综合变换
void m3dMatrixMultiply44(M3DMatrix44d product, const M3DMatrix44d a, const M3DMatrix44d b);
product:两种模型变化后的结果
a:先按照a矩阵变化
b:后按照b矩阵变化.
** 其实我们经过打断点再手动计算可知,是bxa,,为什么呢?原因是OpenGL默认是列矩阵,我们手动设置的值还是按照数学规律设置的,因为转置,所以相等**
网友评论